I've kind of wondered what the best approach to learning OOP is, I've read some suggestions that learning about code "design patterns" is a good way to get into the right frame of mind for understanding OOP issues. Another way to think about it that can help you avoid dumb newbie problems is if you understand relational databases. Design your OO system as if it really was going to be implemented as a set of database tables and you can avoid doing a number of idiotic design decisions. e.g. some of the traditional "learning examples" kind of suck. e.g. one of the canonical examples is this:
- make a "person" type, with the right data and functions (name, age, address fields etc).
- "student" derives from "person", they gain extra fields such as "major", and "course_start_date"
- "teacher" also derives from "person", they gain extra fields such as "lesson_taught" and "salary"
Sounds great, as long as things stay simple. But what happens in this database (and it is in fact a hardcoded database) when a teacher asks to enrol in a class taught by a different teacher? Or if a student can also tutor other students, thus earns a salary? A teacher is the teacher type, they cannot be a subclass of student and vice versa. In fact, this is the exact same problem you get when dealing with relational databases and the solution is the same. You can understand that classes are conceptually similar to database tables and objects are the rows of the table.
And the solution to the above problem is exactly the same as you do in a database. In a database, you'd make an "enrolments" table, linking the person table to the classes table, and another "teacher_assignment" table, also linking the person table to the classes table, whereas in OOP, you'd make an "enrolments" object, and each enrolment object references one person object and one class object, and you'd make a "teaching_assignment" object that links a person to a class, as the teacher. And this way you didn't need to split your "table" of people into separate tables for teachers and students in the first place. ... so a lot of the common sense from database design can in fact help you do better OOP.
So avoid inheritance-based splits whenever possible. Splitting on derived types is effectively like if you had a "widgets" table in your database, then you had different colored widgets so you made separate tables for every color, the "blue widgets" database table, the "red widgets" database table, and so on. You would'nt do that in a database, you'd have a field for color, maybe referencing a "colors" table. And in OOP, you can do that too, you can make "color" objects, and every widget object gets a reference to one color object. Whenever possible, don't use inheritance to define subtypes, create property objects and put them inside the generic object.