ABC
ABC

Reputation: 335

What is the appropriate design pattern for this situation? (instance of)

I've to use a library and need to check (if this instance of that) a lot. For example:

if(myInterface instanceof Foo) {
  Foo foo = (Foo) myInterface;
  if(foo.name.equals("Alex")) {
     // use function that can handle Foo with name "Alex"
     handleAlexFooLogic(foo);
  }
  if(foo.name.equals("Michael") {
     // use function that can handle Foo with name "Michael"
     handleMichaleFooLogic(foo);
  }
}
else if(myInterface instanceof Bar) {
  Bar bar = (Bar) myInterface;
  if(bar.name.equals("Smith")) {
     // use function that can handle Bar with name "Smith"
     handleSmithBarLogic(bar);
  }
  // ...
} else if(....) {
  // ...
}

I was first thinking about the Factory pattern then I'm just stuck because something is so contradicting, that I cannot explaining.

I also want to separate the if(foo.name.equals("Name")) part to a different class, to avoid too much unpredict nested if-else

So what is the appropriate design pattern for this situation? Thank you!

Upvotes: 0

Views: 323

Answers (3)

Nowhere Man
Nowhere Man

Reputation: 19545

A map-based solution can be used to get a Consumer by String key:

Map<String, Consumer<Foo>> fooActions = Map.of(
    "Alex", MyClass::handleAlexFooLogic,
    "Michael", MyClass::handleMichaelFooLogic
);

Map<String, Consumer<Bar>> barActions = Map.of(
    "Smith", MyClass::handleSmithBarLogic
);

which can be used along with Optional

if(myInterface instanceof Foo) {
    Foo foo = (Foo) myInterface;
    Optional.ofNullable(fooActions.get(foo.name()))
            .ifPresent(action -> action.accept(foo));
}
else if(myInterface instanceof Bar) {
    Bar bar = (Bar) myInterface;
    Optional.ofNullable(barActions.get(bar.name()))
            .ifPresent(action -> action.accept(bar));

Similar approach would be to use enum which either provides implementations handleXxxLogic or stores references to appropriate methods:

enum FooActions {
    Alex(MyClass::handleAlexFooLogic),
    Michael(MyClass::handleMichaelFooLogic);
    
    private Consumer<Foo> action;
    
    private FooActions(Consumer<Foo> action) {
        this.action = action;
    }
    
    public Consumer<Foo> getAction() {
        return this.action;
    }

    // use this instead of valueOf which may throw exceptions
    public static FooActions byName(String name) {
        try {
            for (FooActions val : FooActions.values()) {
                if (val.name().equalsIgnoreCase(name)) {
                    return val;
                }
            }
        } catch (Exception ex) {}

        return null;
    }
}

Then it can be used as follows:

if(myInterface instanceof Foo) {
    Foo foo = (Foo) myInterface;
    Optional.ofNullable(FooActions.byName(foo.name()))
            .map(FooActions::getAction)
            .ifPresentOrElse(
                action -> action.accept(foo),
                ()-> System.out.println(foo.name() + " incorrect!")
            );
}

Upvotes: 1

Alphaharrius
Alphaharrius

Reputation: 222

I would suggest using an interface in this case, if you have multiple methods for handling different things (Given that Bars / Foos with different names does have huge differences in their handler methods).

public interface HandlerInterface {
  public void handleLogic();
}

Define Classes for each of the Bars or Foos.

public class AlexBar extends Bar implements HandlerInterface {
  @Override
  public void handleLogic() { ... }
}

Then just call it directly without having to check them.

myInterface.handleLogic();

Just a suggestion, if there are only little differences between Bars of different names, this approach might not be the best. This approach also creates abstractions and reduces readability (In my opinion), but kept the code base neat.

It will be even better if you can implement the interface to Bar / Foo.

Upvotes: 1

Anonymike
Anonymike

Reputation: 341

When I see a lot of instance specific checks like this I tend to ask myself if I need to improve my base interfaces so that my logic can be more consistent. These checks only occur because you have specific logic for each type that is not encapsulated by your interfaces or your composition.

Is there a common operation you are trying to achieve in these blocks of code that could be better encapsulated in an interface, abstract class, common composition, or factory object that wraps this logic?

Is there a base interface / contract that all of the types you are checking for could implement, solving the problem with inheritance OR even better in many cases if it serves your architecture, a common property to use, solving the problem through composition?

Will the number of these checks continue to grow? If so, I would lean more towards composition or inheritance than a factory or service class.

While your question lacks enough detail for me to say use "X" pattern, that is still a very subjective thing. I find it better to be able to thoroughly describe each trade off you make in your design pattern choice and why you favored one side over the other. Then you will both truly understand your choices and how they play out for your team as well as debate them in a constructive manner.

Patterns are just labels on common solutions that should be used with thorough reasoning rather than religiously adhered to in a limited vocabulary. I hope this helps you with your decision nonetheless.

Upvotes: 3

Related Questions