I've read a lot about object-oriented design. I can make an OO calculator! Where I usually run into trouble is six months after implementing my OO design when the requests to make changes to the existing functionality and add new capabilities start piling up. I've not found my designs don't stand the test of time that well. Is there something I should be thinking about at the start of the project beyond trying to create independent objects with clearly defined roles? My feeling is I am getting the abstractions not quite right. I've seen an example, that went something like this- create an abstract bird class. Define a method fly(). All the birds described in the requirements can fly. Six months later, they want to add another bird, an Emu, but they don't want it to fly. Or I have a Dog class, with a bark() method and it is decided that how loud a dog barks depends on how much an Emu weighs. That seems like I am introducing a dependency between the classes, and I am not sure how to resolve it.
What should I be thinking about when I start a design to be ready for the dog bark depending on the Emu weight?
A lot boils down to cost of the systems and how much making more detailed designs is really worth it to you. Remember also that the more detailed the design, the more complicated it can be to implement and maintain. As for the examples you bring up regarding the birds and the dogs, I am not sure that this is exactly what you are looking for, but you can use a hierarchy of birds. When you ask people to describe the most common behavior of birds almost everyone would say 'flight". However, not all birds fly. So you would have to decide if you want to create a Bird class that doesn't include a "flight" method. Perhaps you would need 2 classes, FlyingBirds and FlightLessBirds that inherits from the Bird class. There really is no right or wrong way to handle the design - it just makes for interesting discussions. Not all dogs can bark either so you can create a Dog class without the bark behavior and then use inheritance and create a BarkingDog and a BarklessDog. This approach might model the system correctly, but it is more complex. The one thing that I would not do, for example, is create a Bird class, include a Fly method, and create a no-op for some birds (basically have the fly method that won't fly but generates an error message etc). I like to use the example of a Penguin who sees that there is a fly method and then leaps off the cliff - only to receive an error message that indicates that Penguins can't fly. By that time it is too late - basically the system is not realistic.
posted 7 years ago
Thanks for the reply. That was a good comment about deciding how much a detailed design is really worth it. I think I have been thinking ones design is supposed to cover all cases, current and future and creating problems instead of solving them.
You also got me thinking about some real cases I have dealt with over the past year. Here is a really short version history-
Version 1. I needed a container and I needed items to put in the container. The containers had common properties, such as volume. The items had common properties as well, that are a bit technical, but all the items had them. So I could create a container and an item and put the item in the container. Straight-forward.
Version 2. The original containers were made from a mold. So the volume was fixed. This version needed to include containers that were formed around the item that was going in them. So the container had no volume until what was being added to it was defined. I changed my container to have a method to set the method for calculating the volume, which I never got implemented cleanly, as one type of container has to be told its volume, the other does not. The user is not required to specify things in any order, which was part of the trouble, I was trying to enter information about the container before I knew which type of container it was.
Version 3. The items had gone directly in the container. Now a new object was added, that acted like all the other items in the container for the technical calculations, but also had those items added to it (producing a third definition of volume). The items could be added directly to the container, or put inside this new object which was added to the container. I changed my item to have the property of being contained in this new object and changed my method that got the items volume to report the volume of this special container if that property was set. That volume method would be used by my container volume method, if it was a container whose volume depended on the contents.
I feel like I try to encapsulate things and then future requirements start creating "leakage" between my encapsulated objects. I think this is closest to the earlier problem I mentioned where I had a bird class and a dog class and later it was decided that how loud the dog barked depended on how much the bird weighed.
Is it common that requirements simply defy good encapsulation and are there some strategies I can work on to limit the complexity that results? Or am I missing something from the start that makes it harder to adapt to future changes?