The easiest way is to learn concepts such as SOLID, DRY, FIT, DDD, TDD, MVC, etc. As you look up these acronyms it will lead you down many other rabbit holes and once you are done with your reading you should have a good understanding of what better object-oriented programming is!
And yes, rolling up your sleeves and coding is always a good idea. Make a small project to the best of your current abilities. Then read an article from above. Then refactor your code to meet the needs of what you just read. Repeat until you have refactored the hell out of your code. At the end you should not only know what OO is all about but you should be able to explain why it is important and how to get their the first time. Learning how to refactor is a key to good code too. What is right now is not right tomorrow.
Those small simple rules will make you a better OO programmer. Follow the rules religiously as you code and you will find your code is better than it would otherwise be.
I would say focus less on the OO programming and focus more on the OO design. Grab a paper and a pencil (or maybe a UML modelling tool), and get away from the screen.
By practicing how to design a system, you'll start to get a natural feel for object relationships. Code is just a by-product of design. Draw diagrams and model your application in a purely non-code form. What are the relationships? How do your models interact? Don't even think about the code.
Once you've spent time designing... then translate it to code. You'll be surprised at just how quickly the code can be written from a good OO design.
After a lot of design practice, you'll start seeing common areas that can be modularized or abstracted out, and you'll see an improvement in both your designs and your code.
Learn a different language! Most developers using only Java (just as an example) have only a limited understanding of OO because they cannot separate language features and concepts. If you don't know it yet, have a look at python. If you know python, learn Ruby. Or choose one of the functional languages.
Have you read the chapter on OO from the first edition of Scott Meyers "Effective C++" book? It didn't make it to later editions, but it was a great explanation. The title was basically "say what you mean, mean what you say" about suitable conventions.
Actually, you might like to see my answer to a similar question over here.
If you're lost as to how to design object-oriented systems, start with the data. Figure out what stuff you need to keep track of and what information naturally goes together (for example, all of the specs of a model of car group together nicely).
Each of these kinds of thing you decide to track becomes a class.
Then when you need to be able to execute particular actions (for example, marking a model of car as decommissioned) or ask particular questions (for example, asking how many of a given model of car were sold in a given year), you load that functionality onto the class it interacts with most heavily.
In general, there should always be a pretty natural place for a given bit of code to live in your class structure. If there isn't, that signals that there's a place where the structure needs to be built out.
In many fields there's a "eureka" moment where everything kind of comes together.
I remember feeling frustrated in high school geometry. I didn't know which theorem to apply on each step of the proof. But I kept at it. I learned each theorem in detail, and studied how they were applied in different example proofs. As I understood not only the definition of each theorem, but how to use it, I built up a "toolbox" of familiar techniques that I could pull out as needed.
I think it's the same in programming. That's why algorithms, data structures, and design patterns are studied and analyzed. It's not enough to read a book and get the abstract definition of a technique. You have to see it in action too.
So try reading more code, in addition to practicing writing it yourself. That's one beauty of open source, you can download lots of code to study. Not all of that code is good, but studying bad code can be just as educational as studying good code.
My suggestion would be to learn something different.
Learn functional programming, and apply what you learn from that to OOP. If you know C++, play around with generic programming.
Learn non-object-oriented languages.
Not just because you should use all these things as well (you should), or because they should completely replace OOP (they probably shouldn't), but because you can apply lessons from these to OOP as well.
The secret to OOP is that it doesn't always make sense to use it. Not everything is a class. Not every relationship or piece of behavior should be modeled as a class.
Blindly trying to apply OOP, or striving to write the best OOP code possible tends to lead to huge overengineered messes with far too many levels of abstraction and indirection and very little flexibility.
Don't try to write good OOP code. Try to write good code. And use OOP when it contributes to that goal.
OOP skills comes over time. Reading 1, 2 ...10 books doesn't cut it. Practice writing some code. If you are working in a programming enviornment...that can be helpful. If not try getting into one. Offer to develop some application(s) for free. You have to get your hands dirty. Remember...no application is perfect from the ground up.That's why there is re-factoring.
Also...don't get carried away with the OOP too much...it somes over time. Worry about developing fully functional applications.
Try some programming in Self, one of the most pure OO languages around. So pure, in fact, that it doesn't even have classes, only objects. It also doesn't have variables, fields, statics, attributes, only methods. Also interesting is the fact that every object in the system is also an object on the screen and vice-versa.
This works for pretty much any concept, actually, at least for me: find the language which most purely embodies the concept you want to learn about and just use it.
OOP is not a thing you can master by reading thousands of books. Rather you have to feel the inner concepts. Read anything but try to feel what you read. Build a concept in the back of your mind and try to match those concepts when you face a new scenario. Verify and Update your concepts as you explore new things.
The more code you write, the more you will notice the pitfalls of certain programming practices. After enough time, and enough code, you will be able to identify the warning signs of these pitfalls and be able to avoid them. Sometimes when I write code, I will get this itch in the back of my mind telling me that there may be a better way to do this, even though it does what I need it to. One of my greatest programming weaknesses is "over-analyzing" things so much that it starts to dramatically slow down development time. I am trying to prevent these "itches" by spending a little more time on design, which usually results in a lot less time writing code.
...secretly I look at some of the things my colleagues do with envy. A lot of them seem to have some inner OO instinct that I don't have - no matter how hard I try...
I think you have answered your own question here. Reading good code is a good start, and understanding good code is even better, but understanding the steps to get to that good code is the best. When you see some code that you are envious of, perhaps you could ask the author how he/she arrived at that solution. This is entirely dependent on your work environment as well as the relationships with your colleagues. In any event, if anyone asks me the thought process behind any code I write, I don't hesitate to tell them because I know I would want them to do the same for me.
Language designers have interpreted "Object Oriented Programming" in different ways. For instance, see how Alan Kay, the man who first used the term OOP, defined it:
OOP to me means only messaging, local
retention and protection and hiding of
state-process, and extreme
late-binding of all things. It can be
done in Smalltalk and in LISP. There
are possibly other systems in which
this is possible, but I'm not aware of
them.
It might seem strange that he don't consider Java and C++ OOP languages! But as the designer of one of the first and best OOP languages (Smalltalk) he has his own valid reasons for that. Why did Alan Kay consider Lisp an Object Oriented language but not Java? That question demands serious consideration by anyone who claims to understand OOP.
Erlang has an altogether different implemntation of OOP, Scheme has another.
It is worth considering all these alternative views. If possible learn all these languages! That will give you a broader outlook, put some new and powerful tools in your hand and make you a better programmer.
I have summarized my experiments with implementing an OOP language, based on ideas borrowed from Smalltalk, Scheme and Erlang in this article.
OO finally clicked for me after I tried to program a bank-like program that handled transactions, calculated interest, and kept track of it all. I did it while I was learning Java. I would suggest just trying it, completing it, and then when you're done go look at a GOOD solution and see what you could've done better.
I also think OOP skills strenghten mostly with practice. Consider changing your company, if you've been there for more than 3 years. Certainly, this is not valid for all jobs, but often a man gets used to the projects and practices at a company and stops advancing as time passes.
There's too much information about objects. The most important thing is to master the basics and everything falls into place more easily.
Here's a way to think about objects. Think about data structures in procedural languages. They are a group of fields without behaviour. Think about functions that receive pointers to those data structures and manipulate the latter. Now, instead of having them separate, define the functions inside the definition of the the structures and assume the functions usually receive a pointer to the data structure to manipulate. That pointer is called this. In sum, think about objects as the combination of status (data) and behaviour (methods - the fancy name for functions in OOP).
This is the absolute basic. There are three more concepts you must absolutely master:
Inheritance - This is all about code reuse.
Encapsulation - This is all about hiding the implementation from the interface. Simply put, everything ought to be private until proven otherwise.
Polymorphism - It doesn't matter the type of the reference variable, but the type of the actual instance to know which behaviour (method) is called. Java doesn't make it easy to have this concept very visible because by definition everything is polymorphic. .Net makes it easier to understand as you decide what is polymorphic and what is not, hence noticing the difference in behaviour. This is achieved by the combination of virtual and override.
If these concepts are very well understood, you'll be fine.
One last final tip: You mention the best books. Have you read "Thinking in Java" by Bruce Eckel? I recommend this book even to people who are beginning in .Net, as the OOP concepts are clearly laid out.
Too many people think of coding first, objects, last.
You can read all the books you want but that's not going to teach you how to think in an object-oriented fashion--that takes practice and a certain methodology.
Here are a few methods that have
helped me: When you're away from
work and open-minded you can
practice by looking at everything as an object. Don't look at these
objects and wonder how you're going
to program them, look at them as
properties and functions only and
how they relate or inherit from each
other. For example, when you see a
person, they are an object and
therefore would represent a class.
They have properties like hair
color, skin tone, height, etc. They
do certain functions as well. They
walk, talk, sleep, etc. Some of the
functions these people do returns
results. For example, their working
function returns a dollar amount.
You can do this with everything you
see because everything is an object.
Bicycle, car, star, etc.
Before coding a project, design it by
using post-it notes and a dry-erase
board. This will make good practice
until you get the hang of this.
Think of your specific
object/function/property. Each of
those items will have its own
post-it note. Place them as a
hierarchy on the dry-erase board. In
this regard, function/properties
will be placed under the object. If
you have another object, do the same
for that one. Then ask yourself, do
any of these post it notes
(objects/functions/properties)
relate to each other. If two objects
use the same function, create a
parent object (post-it note) and put
it above the others with the
reusable function under the new
note. Draw a line using the
dry-erase marker from the two child
objects to the parent.
When all this is done, then worry
about the internals of how the class
works.
beer helps. seriously. lie out on a couch with an A3 sized scribble pad, a pen and a beer. Lock the dog, cat and wife outside. And think about the problem while relaxed. Don't even dare draw an API on it!
Flowcharts, Responsibity cards (CRC) and beer (but not too much) go a long way.
Easiest way to refactor code is to not have to in the first place.
public void MasteryOfOOP()
{
while(true)
/* My suggestion is: */
DO: find a lot of well-written object oriented code and read it. Then
try to use the insights from it on your own coding. Then do it again. Then
have a colleague who is a good OOP look at it and comment. Maybe post a chunk
of your code on SO and ask for how it could be improved.
Then read some more of those books. Maybe they make a little more
sense now...?
Now go back to the top of this post, and do it again.
Repeat Forever.
}
}