I have two classes, Bar and Baz. Both subclass the abstract class Foo which has a method doThing(). Bar implements a functional interface Now which contains method doThingNow() whereas Baz implements a functional interface Later which contains method doThingLater(). It's likely that I will have more classes down the line that subclass Foo but I expect all of them to either implement Now or Later. I'm debating between two potential designs but I'm hoping somebody can help me understand why one might be preferable to the other.

Design 1: I make doThing() abstract and force every subclass to implement it. The implementation for each class will simply call the interface method.

Design 2: I implement doThing as follows:

public void doThing() {
    if (this instanceof Now) {
        ((Now) this).doThingNow();
    } else if (this instanceof Later) {
        ((Later) this).doThingLater();
    } else {
        throw new RuntimeException("Foo subclass must implement Now or Later!");
    }
}

Design one seems to require more duplicate code across subclasses and has no guarantee that the subclasses will implement Now or Later. Design two templates the behavior that I want, but to be honest it just seems wrong to me because of the casting. I don't feel particularly good about either approach which makes me feel like there's a more fundamental design flaw with what I'm doing. Are one of these patterns glaringly less flexible/maintainable and if so, why? If you would scrap both patterns in favor of a different alternative, I'd be happy to hear suggestions and the motivation behind them.

  • 5
    Sounds like an XY problem. Describe the problem you want to solve not in senseless terms like "Foo", "Bar", "Baz" or "doThing", but in terms where the intended semantics becomes clear. Then we may be able to suggest a design to your problem (which is probably different from both of your approaches.) – Doc Brown Nov 21 at 7:23
  • 1
    It's not clear to me why the subclasses need the Now / Later on the name. Please explain why they can't just implement doThing(). I don't see any Promise / Future behavior in your question. – user949300 Nov 21 at 7:30
  • 2
    #2 is a doubtful solution to any problem. But #1 is hard to say how good or evil is if you don't provide us with a real problem. – Laiv Nov 21 at 7:32

The reason this is giving you so much trouble is that it is a very bad idea already. You really should look at refactoring this completely so that you don't have to make the decision.

The issue is that the semantics of doThing() seem to be very ill defined. One subclass makes it do one thing (an operation "now") and one subclass defines it to do another thing (queuing an even to be done "later"). The reason you're struggling with the best way to tie these two together in the Foo class is that they shouldn't be tied together.

If you must do one of these designs, I recommend Design 1 unless you have a strong reason to pick Design 2. Design 2 is typically seen as an anti-pattern. If you see code like that, it says "The writers of Foo knew every way Foo would be used and all conditions where it would be used. They knew all subclasses and how they should operate." In general, this is against the spirit of OO programming.

Of course, there are exceptions to every rule. One case study is trees. Sometimes you have a structure like a scene graph or an abstract syntax tree where you know ahead of time all of the possible nodes that could ever appear. In cases like this, it can be convenient to implement functions like bool isNow() or Later asLater() rather than doing the more formal OO approach using visitors. But this should be treated as a special case. If you aren't positive you're in this situation, you shouldn't go down that path. Thus Design 1 functions better than Design 2. But you really should be warry of having one abstract function doThing() doing two rather different operations.

If your function is nothing more than doThing() or execute(), you might get away with this. But if what you really wrote was something like doConnect(), where Now immediately makes a connection to a server while Later does an async call to make the connection later, then you'll likely get in trouble by getting confused as to what is really happening when you call that function.

If we're talking about a synchronous action or an asynchronous one, you can coerce an asynchronous action to be a synchronous one fairly easily. I don't know what language you're using, you didn't specify, but most languages provide a means to make one thread wait for another (terminology differs, but usually it is like await/join). Just be mindful of deadlock problems (waiting on a thread which is waiting for your thread to finish isn't ideal).

Since this is possible, assuming your doThing method is performing the same action in either case, the solution is to have an asynchronous call to doThing by default, and let the caller synchrononize as desired. If you prefer, you can make Foo have a doThingAsync and a doThingSync, in which doThingSync in Foo simply calls doThingAsync and awaits for it to finish.

But suppose for a minute that this wasn't your case and that doThingNow and doThingLater do separate things. The mistake is designing it in such a way as to let Foo know anything about how it is implemented.

If you wanted different actions to be taken when doThing is called, every implementation simply needs to handle it differently. Therefore any calls to doThing of Foo call the appropriate method each and every time, no interfaces are required.

This doesn't sound like a difference that should be expressed via subtypes. If the action we're talking about is not central to the purpose of the class, i.e. it's okay to do it either synchronously or asynchronously without touching the core functionality, then it looks more like a configuration setting that instances of the class could have - "lazyInitialization = true or something like that. Then you'd need just the method init() and different instances would do different things as configured without having to write multiple code paths for it.

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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