3

Let me explain the problem I am dealing with. I have a custom (jar) API that I use in my production code. This API contains an ENUM that looks as follows:

public enum Cars {
   AUDI("BERLIN", "FRANKFURT", "HAMBURG", "MUNICH")
   VW("BERLIN", "FRANKFURT"),
   MERCEDES("HAMBURG", "MUNICH")

   private final String[] cityNames;

   Cars(String... cityNames) {
      this.cityNames = cityNames;
   }

   public isCityNameAvailable(String cityName) {
      for (String city : cityNames) {
         if (cityName.equals(city)) {
            return true;
         }
      }
      return false;
   }
}

This simple ENUM contains cars and cities where they are available. So far our production code logic works as follows:

  1. Receive a city name (Berlin).
  2. Go through all cars from ENUM Cars.values() one by one.
  3. Build a list if cars which are available for provided city (Berlin)
  4. Return this list of cars (AUDI, VW)

The new requirement states that we need to add a new constraint dealer to the available cars. This means, additional to the city, a filter has to be applied based on the dealer where the car is available (can be purchased).

HOWEVER the dealer constraint can be applied for one or more cars. For example, only AUDI is suppose to be filtered based on city name and dealer. All other cars are always available (based on city) whether or not dealer is available or not.

Simple solution would have been; updating the enum as follows:

public enum Cars {
   AUDI("COOL AUTOS", "BERLIN", "FRANKFURT", "HAMBURG", "MUNICH")
   VW("ALL", "BERLIN", "FRANKFURT"),
   MERCEDES("ALL", "HAMBURG", "MUNICH")

   private String dealerName;
   private final String[] cityNames;

   Cars(String dealerName, String... cityNames)  {
      this.dealerName = dealerName;
      this.cityNames = cityNames;
   }
   // rest 
}

The code flow becomes:

  1. Receive city name and dealer name. (Berlin, JUMBO CARS)
  2. Go through all cars, get a list of cars based on city name (Berlin).
  3. Out of the two cars available in Berlin (AUDI, VW), AUDI is only available at "COOL AUTOS" which does not matches the constraint "JUMBO CARS"
  4. Remove it from the list.
  5. VW is available at ALL dealers; dealer name is therefore irrelevant.
  6. Provide VW back.

Another example:

  1. Receive city name and dealer name. (Berlin, COOL AUTOS)
  2. Go through all cars, get a list of cars based on city name (Berlin).
  3. Out of the two cars available in Berlin, AUDI is only available at "COOL AUTOS" which matches the constraint.
  4. VW is available all ALL dealers.
  5. Provide Audi and VW back.

Problem:

I don't like the solution. It is not flexible, today a filter needs to be build based on Distributor, City and Car relationship. Tomorrow it could be something else. I simply added a tight coupling in ENUM based on my new requirement.

Question

Is there are design pattern that I can use to enhance the functionality of ENUM so that I can add as many filters as i like without building logic into ENUM itself?

Enhancing the functionality also means the relationship between Cars, cities and (new) constraints?

EDIT

  1. Updated the description and code to clarify that dealer != manufacturer.
  2. This is a hypothetical example. Scenarios where one car becomes available in another city, or more cars become part of ENUM is not the issue of this question.
2
  • 3
    Why are you using an enum in the first place? Any minor change (like VW moving from Berlin to Hamburg) requires a new software release. Commented Nov 21, 2019 at 15:32
  • @BartvanIngenSchenau This is just an example to illustrate the issue. We actually have nothing to do with car business. In reality, the enum contains a set of constants that have always been the same. Basically what I mean is, in reality, we never have the use case where VW becomes available in Hamburg. Additional constraints might limit the number of result. Without any given constraint, the list returned always remains the same. Commented Nov 21, 2019 at 15:49

1 Answer 1

6

I don't know whether there's a specific pattern for this but there are various approaches to handling this in other ways that are more flexible. First off, this seems very rigid and brittle. I don't know enough about the context here to say for sure but the names of car manufacturers doesn't seem like something you'd want hard coded. Enums are about as hard-coded as hard-coding can get so they might not be the best solution at all here.

That said, one of the features of enums is that they work in switch statements. So you could simply do something like this (assumes using Java 12/13):

boolean match = switch(brand) {
  case Audi -> distributorName == "COOL AUTOS";
  default -> true;
}

With this new switch syntax you can use this as a Predicate lambda.

Another option is create a Map keyed by the distributor names and the manufacturers they support. This is more conducive to a solution where you store that information in a database or other data source instead of hardcoding.

Based on your comment, it sounds like you are moving towards more of simple rules engine. For the requirements you have given this is fairly straight forward.

A basic structure might be something like this:

private Map<Cars, List<Rules>> rules = new HashMap<>();

void addRule(Cars car, Rule rule) {
  List<Rules> list = rules.get(car);
  if (list == null) {
    list = new ArrayList<>();
    rules.put(car, list);
  }
  list.add(rule);
}

boolean canBuy(Cars car, Location location, Distributor distributor) {
    for (Rule rule : rules.get(car)) {
      if (rule.isRestricted(location, distributor)) return false;
    }

    return true;
}

Where Rule is defined like so:

public interface Rule {
   boolean isRestricted(Location location, Distributor distributor);
}

Then you can populate your rules map like so:

addRule(AUDI, (loc, dist) -> loc == BERLIN && dist == COOLAUTOS);

I'm ignoring the Person constraint for simplicity and assuming that the location and distributors are also independent Enums.

Again though, the use of Enums here is questionable. Enums work well for things that are never expect to change or would only change due to other things changing in the code. Things like brands, locations, and distributors are likely to change, especially if the business using this system is successful. You can use any object as a key in a map and regular classes to implement things like locations and distributors. This approach I have outlined here is not dependent on Enums.

3
  • Exactly, I agree with your statement 100%; hard coding Enums is not the solution I am looking for. I actually look for a way to extend the filtering (functionality of Cars) outside the Enum. The issue at hand is relationship between an Enum value and new constraint. Imagine tomorrow a new constraint arrive that a Person XY is not allowed to buy AUDI; in the city of Berlin, from a dealer COOL AUTOS. However he can buy other cars available in Berlin, or AUDI in Frankfurt or in Berlin but not from COOL AUTOS. Commented Nov 21, 2019 at 16:14
  • 1
    Simply Brilliant! I see now, your approach is the way forward. When you say not to use Enum for changing requirements, I interpret it (specifically to this case and generally) that use of enums for location and distributor should rather be classes. Please do comment my interpretation is correct. I am going to accept the answer. Commented Nov 21, 2019 at 18:25
  • I fixed an error in the example. I had skipped the step of creating the list before.
    – JimmyJames
    Commented Nov 21, 2019 at 22:52

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.