Sure, my 3rd and 4th year projects didn't immediately map into objects either. It might have been a useful learning exercise if I'd been on the design teams, but I would have slowed things down. Taking a requirements specification and turning out a list of objects and how they'll interact was magic so far as I could tell.
I didn't learn Perl OO until I had to look for work. It had always seemed too hard before then. It took me 30 minutes to read all the relevant stuff from Damian's book. It looked easy. It was.
After doing some simple introductory projects at my new job, I was set loose on "The Project". Well coded... well mostly well coded but it had 1 object and about 1000 functions full of if/else statements. If my type is a subnet do this. Else if my type is a host do that. Else if my type is a domain do something else...
Even I could see that there should be either a heap more specialized functions or a lot more specific objects. So I asked if we could change it, and of course was told "no". It worked as it was. So I "maintained" The Project and secretly started to modularize it a bit better. Then another developer, slightly higher in the hierarchy than I, found that a feature request was impossible to provide in the system as it stood. We started to write more specific objects (subnet, host, domain etc), all inheriting from the initial monster... Hooray!
After working on this project I suddenly understood both Perl OO and general OOD much better. I got a good feel for what kind of functions should be generic and which should be specific. I started thinking more in terms of OO. I started proposing OO solutions to things, even here because they seemed to be better solutions than otherwise.
Then BUU asked a question and I found myself responding "Do consider OO where it might be useful". But what does that mean? If someone had said that to me before I worked on The Project I doubt I would have had a good idea of where OO would be useful at all.
Computer Science classes had taught me the platitudes:
- Use OO for data encapsulation
- Use OO whenever you have things that belong together
- Use OO to provide a consistent interface even when the underlying implementation changes
So I begin to think that it might be helpful to look at WHY I thought an OO solution to vaevictus's problem was a good idea. Firstly it seemed obvious to me that a solution involving an array of hashes would work. So I created one to play with. The requested push function took my array and did stuff to it, likewise my pop function.
Then I looked at it again and thought, this function is very specific to this data structure. Nothing else could be passed to this function and have a sensible result. It seemed that it would be nice to save the programmer from assuming that these just replaced push and pop. And I was passing the array around, how could I stop the programmer from messing up it's order and then wondering why the pop function didn't give the next event? Could I make it so this code could be useful to other programs too?
It seemed like this event handling was a really good thing to modularize. When a lot of OO Perl programmers think of modules they immediately think of objects. Not me. I usually think of libraries. Then that second last concern up there started niggling at me. How could I hide the internals of the event queue from the programmer? OO of course. I'd even stopped thinking of it as "an array" and started thinking of it as "an event queue". It wanted to be an object. So it was.
Of course the use of a single object in a Perl project doesn't automatically require that the rest of your code be broken up around objects. Many of us work with CGI in an OO sense every day yet write largely imperative scripts. Findy Services and B. Jacobs don't have it all wrong when they criticize OO. It's not a magical solution. But it can be very, very useful.
So here are my guidelines for indications that you might want to use objects, even in otherwise imperative programs.
- Create an object when you have a number of functions that all work exclusively on the same data type and would return errors or meaningless results on anything else.
- Create an object when your data type must not have its internals messed about with by the object user.
- Create an object when it's easier to think of a collection of data as a unit instead of thinking of its structure.
- Create a number of objects when you find yourself writing many different subroutines with the same basic purpose to work on slightly different data (eg. read_data1, read_data2, read_data3, process_data1, process_data2, process_data3), or huge subroutines with big cascades of if-then-elses. Polymorphism works great here.
What other suggestions do people have?