Software Engineering
java design-patterns inheritance enum switch-statement
Updated Thu, 07 Jul 2022 07:40:13 GMT

Concept/Design question: Alternatives to switch/conditional statements and Enums

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:

                                 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:

  2. Refactoring Switch Statements and is there any real use for Switch Statements at all?
  4. /opiyqx/switch-statements-are-bad


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.

Comments (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." — Aug 02, 2021 at 17:13