I want to write a "Coffee" class containing "Enum of enums" if that is possible.
That is, on the client side, the user will input at the main menu, either value 1 or 2 to indicate coffee type.
1 is Cappucino, 2 is Regular.
Is there a way to instantiate a "Cappucino" object (of the Coffee class type) which inturn instantiates 3 other containing ingredient objects, such as "Cream", "Sugar" and "Coffeepowder" enum types?
So also, if I instantiate "Regular" object (also of the type Coffee), it has to instantiate its own unique ingredient objects via enum.
How to write such a "Coffee" class? Any ideas?
One good way to avoid that is by writing lots of test code to try out your ideas. The more complicated designs will tend to be more difficult to test and you'll soon grow tired of them. This leaves you with the simpler, more straightforward designs that already have tests. How convenient would that be, right?
A simpler design might be for a CupOfCoffee to have a list of Ingredients. You might also have a Recipe that lists Ingredients. There might be a Strength enum for None, Light, Regular, Strong, and VeryStrong that can apply in different ways to Ingredient items. ... Well, now I'm letting my imagination run wild on me.
See how that goes?
So, just write some tests first to see what you can reasonably code out, then slowly build it (both your tests and production code) out with more features/capabilities.
Again, you have to try to keep things simple.
The Decorator design pattern is often explained by way of a coffee making example but I personally don't like that example much and it doesn't resonate well with me. Your mileage may vary (YMMV).
There are only three possible elements for such an enum:-The other elements you suggested do not exist.
Junilu Lacar wrote:. . . There might be a Strength enum for None, Light, Regular, Strong, and VeryStrong that can apply in different ways to Ingredient items. ... Well, now I'm letting my imagination run wild on me. . . .
Thank you very much for your replies!
Right now, I have an enum class only for the ingredient part. It is coded as follows:
In reality, I am forced to test everytime on the client code, whether it is "Cappucino" or "Regular" that a customer is purchasing, such as:
Now, this above check is repeated in several places on the client code and makes the code somewhat ugly and messy, as too much processing logic is exposed all over.
That is why I asked the question, if there is a way to code in the following way, that will be superb, no?
Coffee mycoffee = new Coffee(coffeetype); // And bravo! Based on the integer value of 'coffeetype' variable, mycoffee is instantiated with 'Cappucino' type with its own list of unique ingredients or 'Regular' with its unique ingredients.
So that is why my question!
I thank you both for the replies!
So first we would instantiate a coffeeRecipe class as in:
Then, the user input string would be used to fetch the value object (of CoffeeRecipe type) from menu map.
Then, we would instantiate a Coffee object passing in the fetched CoffeeRecipe object.
Then, the CoffeeRecipe object would obtain a reference of its containing Coffee object and add its internal list of ingredients to it!
I guess you have given the Beantype choice to indicate a finer grain of control, to be able to choose which coffee powder subtype, within my general coffeepowder Item type.
The advantage of this solution is that you transparently add the recipes due for each coffeetype elsewhere in the code, as opposed to explicitly stating each ingredient every time!
That is a really good piece of (tough) code, thank you very much for the novel solution!
PS: I am just re-iterating the steps for more clarity to myself and to convey my understanding to you.
Ravi Desigan wrote:
So first we would instantiate a coffeeRecipe class as in:
Almost but not quite what I had in mind. I actually already have this thing coded up. The CoffeeRecipe just starts with a name. Then you'd add ingredients and quantities to it:
Of course, you'd do that in some kind of loop and probably read/parse the values from a file or get it from a database. I just created an array of String and parsed it out in a loop to build up a test menu.
Edit: that should actually be
Ravi Desigan wrote:Then, the CoffeeRecipe object would obtain a reference of its containing Coffee object and add its internal list of ingredients to it!
No, that's not quite accurate. The Coffee does not contain the recipe. The Coffee.make() method is just passed a CoffeeRecipe object which will provide the list of ingredients and quantities needed to make the cup of coffee.
First, you'd pass a CoffeeRecipe object to a Coffee object's make() method.
I was still trying to decide if that name was good enough. I've actually refactored to this now:
which I think reads a little better. However, this circuitous route, called a double dispatch, can be a code smell because it makes the code a little complex and difficult/confusing to follow.
I'm now re-considering whether or not the double dispatch is necessary at all. By that I mean it may be worth eliminating the extra hop through Coffee.makeWith() to call recipe.addIngredientsTo(this);
Seems like I've just complicated the design unnecessarily in which case the double dispatch is indeed a code smell. If I just do this:
the code still makes sense and I've eliminated the double dispatch and the smell of unnecessary complexity.
Thanks! I am actually happy to have learnt about this design pattern called 'Double Dispatch'!
Yes, I found the design quite complex, but all the same, the way the CoffeeRecipe encapsulates its ingredients and the coffee is 'made' by adding ingredients in the coffee recipe to the coffee object transparently simulate the real life scenario better.
I am currently looking for ways to incorporate this design in my code.
I have toyed around with the design some more and now I have this:
So now, the CoffeeRecipe.makeACup(CupSize) method becomes a Factory method and looks something like this:
You might notice that I changed the Coffee constructor, adding a CupSize parameter to it. The BeanType is now encapsulated in a CoffeeRecipe because it made more sense to me after coming up with more recipes to put on the menu like "Robusta Negra," "Arabica Negra," and "House Blend, black."
I'm trying to find who best to make responsible for adjusting the quantity of each ingredient based on the cup size chosen by the customer. I'm thinking of making the Quantity class do that. It makes sense to me that a Quantity object knows how to adjust itself based on a CupSize. Normally, I'd be careful about creating too many collaborations but this one seems natural and makes sense.
I wrote:I'm thinking of making the Quantity class do that. It makes sense to me that a Quantity object knows how to adjust itself based on a CupSize. Normally, I'd be careful about creating too many collaborations but this one seems natural and makes sense.
Doing that actually worked out nicely for the CoffeeRecipe.addIngredientsTo() method:
(more) - the Quantity.proportionFor() method looks like this:
I have made my Quantity class immutable so calling proportionFor() gives back a different Quantity instance if it's a different cup size, otherwise, I just return the same Quantity. Be careful about doing something similar to this (returning a this reference when there's no difference) with a mutable class because it can lead to surprises and problems.
The caveat again is that this is still a design in progress. I may change this again later. I'm just trying to show you the different options that can be explored while testing out the API for these classes.
I hope that makes sense.
Design is a very creative process so if you like doing it, then knowing how to look critically at designs and finding ways to improve them becomes both challenging and fun. One thing you might want to read up on is refactoring, and in particular, Martin Fowler's Refactoring - Improving the Design of Existing Code, the classic book on refactoring. The book also lists out twenty or so "code smells" that you should learn how to recognize; code smells tell you that something in your design needs to be changed. Another book I like is Joshua Kerievsky's Refactoring to Patterns book.