1

I am practicing design patterns and OO concepts such as inheritance in java and I'm writing an application that represents a vending machine.

I have two questions focused on ideal structure and design patterns:

  1. What is an alternative way to decide which treat from the vending machine to manufacture based on the passed name parameter of the treat object(see below there are three treat type classes derived from a treat superclass). I am currently using switch statements and conditional statements based on treat name.
  2. What is the best way to represent a list of potential variables for "brand name" of the treat. The brand names can be used by a salty treat or a sweet treat, but not by the healthy treat.

So I have a vending machine class that has a "treat" class that represents a superclass for all treats held within the machine.

The treat class has three types of objects that inherit from treat: "salty", "sweet", "healthy."

Each of these types of objects will then have specific methods (like getIngredients()) and member variables (name, price) that are unique to those objects.

But I'd like to have a class that is merely a list of the brand names that can be a member variable of the salty and sweet treat objects. Healthy is different because it won't have a brand -- it will just be fruit.

For example, the Nabisco brand could be a member variable of two of the three treat types: "Oreos" (sweet), "Wheat thins" (salty).

Should I maybe use a command pattern to determine which treat to generate based on the name? I'd just like to avoid switch/conditionals if possible and use the best design patterns for each task. I've watched videos and have been reading tutorials on how to use patterns but I'm still unsure in these two cases which is best to use. I also can't find a good example on stack exchange that deals with my issue there.

Should I try to use a strategy pattern to store this list of brands that has no other function but is simply an attribute stored within an object? Something like BrandStrategy --> assignBrand() --should brand even be stored in it's own class? It seems like it should since two subclasses would use it.

Diagram of Program:

                                 VendingMachine
                                        |
                                 FactoryMethod (Creates treat based on treat "name")
                                        |
                                      Treat    (Abstract class)
                                   /    |    \

                              Sweet   Salty   Healthy (Concrete classes: inherit from treat)
                                ^       ^
                                |       |       
                               Brand class (member variable of sweet & salty)

I have been reading about design patterns and best practices and I've been trying to avoid switch statements and conditional statements to generate specific objects and I've been reading that poor use of enums can create brittle code. I originally had "brand" stored as an enum but I'm realizing now that is a bad way to do it since that value is likely to change (basing this statement off of this post I researched).

Here are some of the stack exchange posts and tutorials I have read:

  1. https://stackoverflow.com/questions/61024826/what-is-the-best-way-to-structure-a-group-of-variables-that-have-a-group-of-vari
  2. Refactoring Switch Statements and is there any real use for Switch Statements at all?
  3. https://www.planetgeek.ch/2009/07/01/enums-are-evil/
  4. https://stackoverflow.com/questions/4417070/switch-statements-are-bad
  5. https://www.javatpoint.com/design-patterns-in-java
  6. https://stackoverflow.com/questions/38855909/alternative-to-enum-in-design-pattern
13
  • 3
    This will be closed as "too broad", but here are some general comments: - Design patterns are not ingredients that you add to a system in order to fulfill a recipe. Asking yourself or others "Which design pattern should I use here?" is nearly always misguided. The point of a design pattern is to help you recognize the recurring pattern in your requirements, and then implement the recurring solution without considering another pattern. Aug 2, 2021 at 14:24
  • 3
    Also, if you really must create objects of different types depending on textual input to your system, then ultimately you have to use if, switch or equivalent constructs. You can hide them, but you can't get rid of them. Wrapping them into a Factory is a good idea, but that doesn't change the fact that you need an if. Aug 2, 2021 at 14:26
  • 1
    Implementing different brands as an enum is indeed a bad idea, but implementing them as sister classes is almost as bad. You really should try to find a solution that doesn't require recompiling when a brand is added. Depending on how you factor things, the brand might be nothing more than a string-typed label, while the business-relevant aspects between brands are represented by classes that implement that aspect, not a brand that happens to have it. Aug 2, 2021 at 14:29
  • 1
    I'm not sure I understand your diagram. What is brand, exactly? A property or a class? Why do you need Salty and Sweet classes? What about things that are Sweet and Salty? I used to buy something out of a vending machine that was literally labelled "sweet & salty"
    – JimmyJames
    Aug 2, 2021 at 15:20
  • 1
    "I was concerned with using switch/conditional encapsulated within factory" - that's the right thing to do; it confines the switch to a single place. What you don't want is switching over basically the same thing all over the code, in a bunch of scattered places that you have to hunt down whenever you change anything that relates to the switching logic. Aug 2, 2021 at 20:59

2 Answers 2

3

Note: I am not answering the exact question asked. Consider this a 'frame challenge' answer.

I think Erik Eidt hit the nail on the head but you probably need some elaboration on that. Based on a few things you've said in the question and the comments, I think you are making a common error in OO. I'd call it a beginner error but I've seen many people continue to make it after years of working in an OO language. It's not your fault, there's just a lot of bad advice and explanations about this floating around. Let's focus on this for a second:

For salty/sweet classes, my original idea was to have a getNutrition method based on food type, so you'd be most concerned about sugar content in sweet items and have a "getSugar() method for that class, and you'd be more concerned about sodium content in salty items so that class would have a "getSodium() method.

Here's a simplified version of what you are describing here:

interface Treat 
{
  // nothing here (for now)
}

interface Sweet
{
   double getSugar();
}

interface Salty
{
   double getSodium();
}

OK. Now let's say you want to get information about salt content in the items:

List<Treat> items = vend.getItems();

Treat saltiest = null;
double highestSalt = 0d;

for (Treat treat: in items) {
  double sodium = treat.getSodium(); // does not compile!!!
  if sodium > highestSalt {
    highestSalt = sodium;
    saltiest = treat;
  }
}

See the issue? The point of OO is not to create as many incompatible classes as possible. Every time you subclass and add methods, you are creating a distinction from the other implementations of the superclass that cannot be used with them.

The proper idea here is to create common interfaces with varying implementations. To solve the issue above we simply move the methods to the top level:

interface Treat 
{
  double getSugar();

  double getSodium();
}

If a particular food item has no salt, you can simply return 0 from it's getSodium() method. At this point, you no longer need Sweet or Salty classes. It also means that you don't have to choose between them for sweet and salty items.

It's actually ideal (from an OO perspective) to have every attribute that you need to ever worry about when dealing with Treats defined in the Treat interface. Brand is a more interesting example. So let's assume fruits don't have brands (they can, but let's ignore that.) What does that look like for Treat? One option is that you declare 'brand' as a property of Treat and you just set it to null or some "N/A" for fruits. That might be a completely reasonable thing to do. You could also create a new subclass called BrandedTreat but you will run into the same issues as above unless you plan ahead. There are ways to deal with this without messy instanceof checks but I would advise you avoid that at this point. You should "consider the abstractions and operations/behaviors being offered the consuming client" as Erik has stated which I take to mean, design your interface based on how it will be used.

One way to turn this around is to start writing the code the other way. That is, create an empty interface, and start writing the code that will use it. Add the methods that the consuming code needs to the interface. Then, starting creating classes that derive from that interface.

1
  • 1
    Thank you very much--this is very helpful. I think this is an especially good point: "The point of OO is not to create as many incompatible classes as possible. Every time you subclass and add methods, you are creating a distinction from the other implementations of the superclass that cannot be used with them."
    – Bob Jones
    Aug 2, 2021 at 17:13
1

Trading off an if/switch/enum-based design vs. an inheritance-based one

A general remark, not connected to your example.

People who favor the object-oriented, inheritance-based design style often argue that it is good because of the independence of the subclasses. I usually find that this is not the case, because the subclasses need to follow a coherent joint semantics and therefore are logically dependent even if they are technically independent.

As the true strengths of the two styles, I perceive these:

  • Holism: When an abstract superclass dictates which methods you have to implement, you are unlikely to forget one. This is a strength of the inheritance-based style.
    In contrast, in a switch-based style, it happens easily that, when introducing the, say, fifth discrimination case, you only modify 11 of the 12 switch statements where it needs to be added.
  • Locality: Conversely, the switch-based style makes it easier to maintain that coherent semantics.
    When you add this fifth case, you have the other four cases right at hand for comparison everywhere you add new code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.