views:

351

answers:

10

I'm a relative newbie to thinking in OOP terms, and haven't yet found my ‘gut instinct’ as to the right way to do it. As an exercise I'm trying to figure out where you'd create the line between different types of objects, using the drinks on my desk as an example.

Assuming I create an object Drink, that has attributes like volume and temperature, and methods like pour() and drink(), I'm struggling to see where specific drink 'types' come in.

Say I have a drink types of Tea, Coffee or Juice, my first instinct is to sub-class Drink as they have attributes and methods in common.

The problem then becomes both Tea and Coffee have attributes like sugars and milk, but Juice doesn't, whereas all three have a variant (Earl Grey, decaff and orange respectively).

Similarly, Tea and Coffee have an addSugar() method, whereas that makes no sense for a Juice object.

So does that mean the super-class should have those attributes and methods, even if all the sub-classes don't need them, or do I define them on the sub-classes, especially for attributes like variant, where each sub-class has it's own list of valid values?

But then I end up with two addSugar() methods, on the Tea and Coffee sub-classes.

Or given that I then end up putting all the attributes and methods on the super-class, as most are shared between at least a couple of the drink types, I wonder what was the point in sub-classing at all?

I fear I am just trying to abstract too much, but don't want to back myself in to a corner should I want to add a new type, such as Water—with variant still or sparkling—down the road.

+11  A: 

There are two solutions to the sugart problem. The first is to add a subclass like HotDrinks whcih contains the addSugar and addMilk like:

                 Drink
                  /  \
          HotDrink    Juice
           /   \
        Tea     Coffee

The problem is that this gets messy if there are a lot of these.

The other solution is to add interfaces. Each class can implement zero or more interfaces. So you have the ISweetened and ICowPowerEnabled interfaces. Where both Tea and Coffee implement the ISweetened and the ICowPowerEnabled interface.

Gamecat
+1 for ICowPowerEnabled. Lol.
RCIX
+13  A: 

Object oriented design is not done in isolation. The question you have to ask is what does my specific application need to do (if anything) with a Drink class? The answer, and therefore the design, will differ from application to application. The number 1 way to fail at OO is to try to construct perfect Platonic hierarchies of classes - such things only exist in perfect Platonic worlds and are not useful in the real world we inhabit.

anon
Great answer. +1
jalf
+1 for invoking Plato. If you'd mentioned cave walls as well I would have gone nuts. 8)
duffymo
@duffymo Well, this is of course just a shadow of my real answer...
anon
+4  A: 

I know this is a very RTFM type answer but this pdf might help you a lot and it fits prefectly with your example:

The Decorator Pattern

Nathan W
@Nathan W, I edited the link so that people know what you're actually recommending (I was going to recommend the same thing). I think you're likely to get more up votes now :)
Ionuț G. Stan
Cool, I was worried I was going to get down votes for the "Here just read this" type answer
Nathan W
A: 

If you are a newbie I wouldn't worry about not getting your design pattern perfect . Infact many would argue that there is no perfect design pattern :P

As for your example , I would probably create an abstract class for drinks like tea and coffee (because they share similar attributes). And try to make the drink class have very few attributes and methods. Water and Juice could subclass from the abstract drink class.

Rick J
looks like gamecat beat me to it :P
Rick J
+3  A: 

Two things that might be relevant:

1) Neil Butterworth brings up very important distinction between a Model of Real World in some context versus Real World itself. Object Oriented Design sometimes also referred to as Object Oriented Modelling for a reason. A model is only concerned with the "interesting" aspects of the real-world. What is interesting depends on the context of your application.

2) Favour composition over inheritance. A drink has properties of volume and temperature (which in your modelling context maybe relative to human perception, i.e. extra-cold, cold, warm, hot) and composed of ingredients (water, tea, coffee, milk, sugar, flavourings). Please note, it doesn't inherit from ingredients, it's made of them. As far as good ways of composing ingredients into drinks concerned try looking up decorator pattern.

Totophil
+9  A: 

Part of the problem is that real-world objects aren't neatly arranged in a hierarchy. Every object has many different parents. A glass of juice is certainly a drink, but it is also a source of nutrition -- but not every drink is. There's not much nutrition in a glass of water. Likewise, there are many sources of nutrition which are not drinks. But most languages will only let you inherit from one class. A cup of tea is technically a source of energy (it is hot, it contains thermal energy), but so is a battery. Do they have a common base class?

And of course, the bonus question, what's to prevent me from putting sugar in my juice if I want to? Physically, that is possible. But it is not conventionally done. Which do you wish to model?

Ultimately, don't try to model "the world". Model your problem instead. How do you want your application to deal with a cup of tea? What is the role of a cup of tea in your application? Which, if any, of the many possible parents makes sense in your case?

If your application needs to distinguish between "drinks you may add sugar to" and "drinks you can only consume untouched", then you would probably have those be two different base classes. Do you even need the common "drink" class? Perhaps, perhaps not. A bar might not care that it is a drink, that it is drinkable. it is a product that can be sold, and in one case, you must ask the customer if he wants sugar in it, and in the other case that is not necessary. But the "drink" base class might not be necessary.

But for the person drinking the drink, you probably want to have a "Drink" base class with a Consume() method, because that is the important aspect of it.

Ultimately, remember that your goal is to write a functioning program, and not to write the perfect OOP class diagram. The question is not "how can I represent different types of drinks in OOP", but "how can I enable my program to deal with different types of drinks". The answer might be to arrange them in a class hierarchy with a common Drink base class. But it might also be something else entirely. It might be "treat all drinks the same, and just switch the name", in which case one class is plenty. Or it might be to treat drinks as just another kind of consumable, or just another kind of liquid, without actually modelling their "drinkable" property.

If you're writing a physics simulator, then perhaps a battery and a cup of tea should derive from the same base class, since the property that is relevant to you is that both are able to store energy. And now juice and tea suddenly have nothing in common. They might both be drinks in another world, but in your application? They're completely distinct. (And please, no nitpicking about how juice contains energy too. I'd have said a glass of water, but then someone would probably bring up fusion power or something ;))

Never lose sight of your objective. Why do you need to model drinks in your program?

jalf
It's going to take some time to digest this, and the other answers to my original question, plus do some background reading on the issues raised, so I can't say you've all resolved the issue, but I can answer your last question.I'm planning an app to take drinks orders for an office, building site, etc., so you'd go around asking people what they wanted and ultimately end up with a list of who wanted what. So your order might look like:Tea, milk and 2 sugars for Alice and Bob.Tea, milk, no sugar for Charlie.Coffee, black, one sugar for DeanJuice, orange for Edward and Fiona.
Andy W
My last question was for your own benefit, not for mine. :) The point is that you need to decide how to model these concepts based on your own needs, and not on some universal concept of what a drink "is". Like Neil said, you can't make some kind of perfect "platonic" class hierarchy. All you can do is make a model that behaves the way you need it to.
jalf
@Andy SO comments are no place for complex answers, so can I simply suggest that you don't seem to need a drinks hierarchy at all. You need a list of people and the a string describing the drink they want. This is because there is an almost infinite variety of possible classes of drinks.
anon
A: 

Use Decorator pattern.

this. __curious_geek
+6  A: 

To add to other answers, interfaces tend to give you greater flexibility with decoupling the code.

You must be careful not to relate a feature to a base class, only because it is common to all derived classes. Think rather if this feature can be extracted and applied to other, unrelated objects - you will find that many methods in your "real world" base class actually might belong to different interfaces.

E.g., if you are "adding sugar" to a drink today, you might decide to add it to a pancake later. And then you might end up with your code asking for a Drink at many places, using AddSugar and various other unrelated methods - it can be difficult to refactor.

If your class knows how to do something, then it can implement the interface, regardless of what the class actually is. For example:

interface IAcceptSugar
{
    void AddSugar();
}

public class Tea : IAcceptSugar { ... }
public class Coffee : IAcceptSugar { ... }
public class Pancake : IAcceptSugar { ... }
public class SugarAddict : IAcceptSugar { ... }

Using an interface to request a specific feature from the object that actually implements it helps you make highly independent code.

public void Sweeten(List<IAcceptSugar> items)
{
   if (this.MustGetRidOfAllThisSugar)
   {
       // I want to get rid of my sugar, and
       // frankly, I don't care what you guys
       // do with it

       foreach(IAcceptSugar item in items)
          item.AddSugar();
   }
}

Your code also becomes easier to test. The simpler the interface is, the easier you will test the consumers of that interface.

[Edit]

Maybe I wasn't not completely clear, so I will clarify: if AddSugar() does the same thing for a group of derived objects, you will of course implement it in their base class. But in this example, we said that Drink doesn't always have to implement IAcceptSugar.

So we might end up with a common class:

 public class DrinkWithSugar : Drink, IAcceptSugar { ... }

which would implement AddSugar() the same way for a bunch of drinks.

But if you one day realize that's this is not the best place for your method, you will not have to change other parts of code, if they are only using the method through the interface.

The moral is: your hierarchy may change, as long as your interfaces remain the same.

Groo
+1 "consumers of that interface" is funny in this context.
duffymo
You will need to implement AddSugar method for each drink that implements the interface when all those drink should have specific base AddSugar defination. This in practice will force you to write lot of repititive code..
this. __curious_geek
You never have to duplicate code. Only your base class needs to implement AddSugar, as a part of the interface. But it can turn out that one of the derived classes fits perfectly with all methods except this one. Then you can easily create a new (intermediate) base class, move some methods there, and leave your interface unmodified for those who know how to implement it.
Groo
This was so close to 'accepted answer' that I feel somewhat guilty not marking it as such. It's certainly given me a lot to think about, but once I'd moved away from the 'platonic ideal', this seemed overkill for the real-world problem. But I really appreciate this, and everyone else's answers.
Andy W
That's what I like about refactoring, it allows you to fix design mistakes as you go! I must however say, after reading your comments on the answer, I agree with Neil that you may not need a hierarchy at all. If a drink is only defined by its name, price, supplier (etc.) to end up in a "list of drinks", then you only need a *single* class with a bunch of *properties* - not much to override in child classes. A chance to use OOP might be in some totally different parts of your code (e.g. abstract OrderPrinter could be a base for TextOrderPrinter, ExcelOrderPrinter or PdfOrderPrinter).
Groo
+1  A: 

One common mistake of new OO programmers is to not recognize that inheritance is used for two different purposes, polymorphism and reuse. In single inheritance languages typically you're going to have to sacrifice the reuse part and just do polymorphism.

Another mistake is to over use inheritance. While it may seem like a good idea to start out laying out a beautiful inheritance hierarchy, it's better to wait until a polymorphic problem comes around and then create the inheritance.

If you're given a problem to create classes for 4 different types of drinks, make 4 different types with no inheritance. If you're given a problem that says "Ok, now we want to give any of these drinks to a Person and print out what they drank", that's a perfect use of polymorphism. Add an interface or abstract base class with a function drink (). Push compile, notice that all 5 classes say the function is not implemented, implement all 5 functions, push compile and you're done.

To answer your question your question about adding milk or sugar the problem I'm assuming you're running in to is that you want to pass an object Drinkable to a Person but Drinkable doesn't have addMilk or addSugar. This problem can be solved in two or more ways, one is a very bad way to do it.

The bad way: Pass a Drinkable to a Person and do a type inspection and cast on the object to get back a Tea or Juice.

void prepare (Drinkable drink) { if (drink is Juice) { Juice juice_drink = (Juick) drink; juice_drink.thing(); } }

One way: One way is to pass a drink to Person through concrete classes and then later pass it to drink.

class Person { void prepare_juice (Juice drink) { drink.thing1(); } void prepare_coffee (Coffee drink) { drink.addSugar (); drink.addCream (); } }

After you have asked the person to prepare the drink, you then ask them to drink it.

A: 

It all depends on circumstances - and that really means - start with the simplest design and refactor it when you see the need. Maybe it would not look very shiny OOP - but I would start with just one class and a property 'CanAddSugar' and 'SugarAmount' (later you could generalize it to a predicate CanAddIngredient(IngredientName) so that people could add whiskey to their drinks :)

There is a deep question in all of that - what difference between two objects is enough to require a separate class for them? I haven't seen any good answer to that.

zby