The huge win for me is inheritance, or making an object that behaves almost exactly like another but with a few differences. Here's a real-world example from my office:
We needed code to process TIFF files that a customer sent to us, convert them to a standard format, insert some information about the file into a database, then send a result email. I wrote this in a set of classes (in Python, but the idea is the same). The "fetcher" class got emails from a POP3 mailbox and handed them to a "container" class which knew how to read attachments from an email. That class handed each image off to a "file object" class that did the necessary processing.
Well, one day we got a customer who wanted to send PDF files. I subclassed the "TIFF file object" class and rewrote the "normalize" function to take a PDF as input instead, but left every other bit of code untouched. It worked the first time and I was pretty pleased.
A change to our mailserver meant that I needed to fetch emails via IMAP. Again, I subclassed the "POP3 fetcher" so that it could speak IMAP. Problem solved.
Another customer wanted to mail us CDs, so I subclassed the "email container" class with a "filesystem directory" class. Voila - done.
In each of those cases, the new code was 95% similar to the old code. For example, the "TIFF file object" class has about 15 methods. The "PDF file object" class defines exactly one: the method that converts files in a specific format into our standard. All others it gets from its parent class.
Now, you can definitely do the same kind of stuff procedurally, such as by writing:
if fileobjecttype == 'TIFF':
data = <snip 30 lines of code to read and convert a TIFF file>
elif fileobjecttype == 'PDF':
data = <another 45 lines to read a PDF>
elif fileobjecttype == 'PNG':
data = <yep, another one>
The biggest difference is that I believe you can make OOP look much cleaner and more organized. My PDF class looks like:
class PDFReader(GenericImageReader):
def normalize(self):
data = <45 lines to read a PDF>
and that's it. You can tell at a glance that it only does one thing differently than the class it inherits from. It also forces you - or at least strongly encourages you - to make clean interfaces between the layers of your application. In my example, the PDFReader has no idea and doesn't care whether its image came from a POP3 mailbox or a CD-ROM. The POP3 fetcher knows absolutely nothing about attachments, since its job is merely getting emails and passing them along. In practice, this has allowed us to do some pretty amazing things with the absolute minimum amount of coding or redesign.
OOP isn't magic, but it's a pretty fantastic way to keep your code organized. Even if you don't use it everywhere, it's still a skill that you really should develop.