2

Going through CS OOP classes a couple years ago, the examples they always used for OO class examples were the standard car/tire/engine and shape examples.

I sat down with the book 'Agile Principles, Patterns, and Practices in C#' and the Coffee Maker example attempted to change my view of OO class design. The book said that classes should NOT be physical objects like a car or tire, but should represent some behavior.

The (bad) example he gave was an intermediate programmer's view of how to design classes related to a Coffee Maker (and exactly how I would have designed it). He eventually went on to create fewer classes such as CoffeeMakerUI, HotWaterSource, and ContainmentVessel (none of which represent any physical part of an actual coffee maker).

Now I'm stuck wondering if I have been thinking about OO design incorrectly all these years and how I can think more abstractly when I design my classes. Should behavior drive how I design my classes?

keelerjr12
  • 1,199
  • 1
  • 9
  • 22
  • see [Why do 'some examples' and 'list of things' questions get closed?](https://softwareengineering.meta.stackexchange.com/a/7538/31260) – gnat Nov 07 '17 at 15:57
  • 4
    All three of those things do absolutely represent physical parts of a coffee maker, just named differently. – RibaldEddie Nov 07 '17 at 16:06
  • 1
    What the author was trying to get you to see was that you don't need to model the _entire_ physical coffee maker into classes or code. So you don't need a Screw class of which there are 300 objects instantiated to represent every screw in the machine, or a Hose class to represent the hoses that go from the water jug to the boiler and then to the brew head, etc. Instead the point of OOP is to create _useful abstractions_ of the coffee maker so that the _details_ of the machine are _encapsulated_... – RibaldEddie Nov 07 '17 at 20:13
  • 1
    ... but the coffee maker does have a physical user interface that you use, you press the buttons to start the brewing or fill the hopper with beans. You also put the water in and it makes sure that hot water is included in the process, and of course the place where the coffee is brewed is a vessel. But these parts do exist, they're just made up of smaller physical parts that aren't part of the code model because _they don't need to be_. – RibaldEddie Nov 07 '17 at 20:15

3 Answers3

7

There is no consensus on what exactly distinguishes object-oriented programming.

Historically, OOP was first “discovered” in the context of simulations, where objects literally represented physical objects and methods on these objects represented external events that the objects can react to. The general consensus is that this is not a good way to write software, but that the underlying principles can be very valuable.

One problem with modelling physical objects is that this misleads the design to include irrelevant relationships between objects, and irrelevant properties of the objects. Also, it is often more helpful to model abstract concepts instead of physical objects.

A more modern view does not see OOP as a technique to translate models into code, but sees object-orientation as a way to solve various problems through polymorphism, primarily interface inheritance.

This is usually a bottom up approach: I have different things that behave differently, but I want to use them as if they were the same type of thing. Without OOP, I have to look at each thing to find out what it is, then execute the correct behaviour.

Instead, I can define an interface that describes how I want to use them. Then I make my existing things conform to this interface, either if they inherit the interface or if I create an adapter between the interface and a specific thing. Now I can tell the things what to do, and they can perform their individual behaviour without me knowing their real type, because they conform to the interface.

It turns out that OOP is an excellent way to achieve modularity and extensibility. By depending only on interfaces instead of on concrete types, I can swap out implementations without further changes to the system (often through a “dependency injection” mechanism). One consequence is that object-oriented systems are easier to test in isolation than procedural systems, because I can easily replace the real dependencies with stubs or mock objects.

Not every software system needs this kind of flexibility. Many problems are a very good fit for non-polymorphic models. Quite often, a class is not used because we want to do OOP, but because that's the only mechanism in the language to implement record types. Most method calls instance.Method() are not polymorphic, but just a convenient abbreviation for what is effectively a static function Class.Method(instance).

In addition to “OOP models the real world” and “OOP uses polymorphic techniques” there are also a number of other OOP interpretations:

  • OOP is defined by Inheritance and Encapsulation (often taught in introductions to programming, but misses the point)
  • Objects = Data + Behaviour
  • Objects use open recursion (B. Pierce)
  • Object-oriented systems are SOLID (Robert C. Martin)
  • Objects communicate through message-passing (Alan Kay)
amon
  • 132,749
  • 27
  • 279
  • 375
  • Interfaces: do you mean as in Java? Those are language specific, not really object oriented. There are other languages that use other things for the same concept. SOLID has nothing to do with object oriented programming. – Frank Hileman Nov 07 '17 at 22:17
  • @FrankHileman I do mean interfaces as in Java/C#/Go but also traits in Rust or existentials in Haskell. With duck typing (JS/Python/Ruby/Perl), the concept of interfaces is never reified but still exists as a convention. The important part about interfaces isn't any language construct, but that they enable dynamic dispatch, without introducing (problematic) subclassing/inheritance. I agree that SOLID neither requires nor ensures OO, but “Uncle Bob”'s writing on this matter has (for better or worse) been so influential that it shouldn't go unmentioned as one perspective. – amon Nov 07 '17 at 22:31
4

First of all, think of concrete domain you try to model. If you're modeling a domain of Coffee-maker factory, you have to know all its internals and chances are that your design would map 1:1 on physical design of a coffee-maker.

If your domain about having some coffee, than why do you have to be aware of all its internals? All you should focus on is just a process of making coffee. So what is the design story of your domain? Say, it could the following:

Every day I go to work. I start my day with having some coffee. To do that, I sometimes need to clean a dispenser. When there is no water, I need to pour it in. If there is no coffee -- I need to fill my coffee maker with it as well. Than I need to put a cup, choose my beverage and push the button.

So by having this design story you decide what you model and what you leave behind. Chances are that it contains some basic responsibilities. Probably you could start with them, probably you could start with identifying candidate objects.

Starting with responsibilities, you could have:

  • ability to fill coffee maker with water (right now I don't think of whose responsibility it is: a human's or coffee-maker's)
  • ability to fill it with coffee
  • ability to choose a beverage
  • ability to pour it in a cup

Ok, one by one. Who's gonna fill a coffee-maker with coffee? First point: by this time, there is no explicit responsibilities of a human. So chances are we don't need it. Second point: object thinking implies objects being a smart and self-contained systems, who do things to themselves. So the candidate object for filling a coffee-maker with coffee is not some mythical CoffeeService, it's a Coffee-maker itself. The same goes to beverage choosing and to filling it with water. But now I see that there are way too much responsibilities assign to a Coffee-maker. So look closer at it. Who exactly takes part in coffee supplying? It's a coffee vessel. Great, coffee-maker's one responsibility less. Following the same principle we can get a Dispenser object, ContainmentVessel and UI. And the funniest part is that we don't need a Coffee-maker object. Probably as a structurer only.

You could get the same result starting from candidate objects -- just see what parts really provide some service. Providing some service is primary responsibility if any class.

There are some more ways to think about decomposing your domain. But the best advice I allow myself to give is to think about a domain first.

Vadim Samokhin
  • 2,158
  • 1
  • 12
  • 17
0

The focus should be on responsibilities also remember you're not trying to model a 'Coffee maker' you're creating a software model which could include more abstract classes such as factories or strategy classes that have nothing to do with the domain of 'coffee maker' but are necessary for maintainable code.

Rob
  • 1