質問

I'm trying to design an async framework and wanted to know what people think are the pros/cons of the callback pattern vs the observer pattern.

Callback pattern:

//example callback
public interface Callback{
    public void notify(MethodResult result);
}

//example method
public class Worker{
  public void doAsyncWork(Callback callback){
     //do work
     callback.notify(result);
  }
}

//example observer pattern
public interface EventListener{
   public void notify(MethodResult result);

}

public class Worker{
  private EventListener listener;
  public registerEventListener(EventListener listener){
   this.listener=listener;
  }
  public void doAsyncWork(){
     //do work
     listener.notify(result);
  }
}

I'm working with a framework which seems to use both of these patterns. The EventListener pattern is not the typical pattern as it doesn't have a list of listeners. This can easily be implemented though by creating a CompositeListener which has its own semantics on the priority of listeners and how to handle the distribution of events to each listener e.g. spawning a new thread for each listener vs serial notifications. (I actually think this is a good idea as its a good separation of concerns and is an improvement on the standard observer/listener pattern).

Any thoughts on when you should use each?

Thxs.

役に立ちましたか?

解決

Both patterns are great and which one to choose depends on what are you going to build and how your framework will be used.

If you are trying to build some kind of publish-subscribe system with following typical flow of work:

  • client starts async task and forgets about it
  • multiple handlers receives notifications when task is completed

then Observer pattern is a natural choice for you. As you are doing a framework you should also consider using EventBus pattern to achieve loose coupling.

If you need nothing more than a simple asynchronous execution and a typical flow using of your framework is:

  • start async task
  • do something when it is completed

or

  • start async task
  • do something
  • wait till it is completed and do something

then you should go with simple Callback.

But in order to achieve more usable and clean API I'd recommend you to get rid of Callback abstraction and design your worker code to return a some kind of Future.

public interface Worker<T> {

    Future<T> doAsync();

}

And Worker can be used following way:

Future<Integer> future = worker.doAsync();

// some work here

Integer result = future.get(); // waits till async work is done

Future could be a standard java Future. But I'd suggest you to use ListenableFuture from guava library.

他のヒント

Command, callback and observer patterns have different semantics:

  • callback - notifies a single caller that some operation finished with some result
  • observer - notifies zero to n interested parties that some event (for example a finished operation) happened
  • command - encapsulates a operation call in an object thus making it transferable over a wire or persist-able

In your example you could combine both callback and observer patterns to achieve greater API flexibility:

  1. Use the callback pattern to trigger operations and notify the caller asynchronously that the triggered operation finished.
  2. Use the event/observer pattern to give some other components (that did not trigger the operation) the chance to be notified when an operation finishes.

I'd argue that the callback pattern is better as it is simpler which means it'll be more predictable and less likely to have bugs due to it's own mutating state. An example of this in operation would be the way GWT handles browser / server communication.

You might want to use generics though:

//example callback
public interface Callback<T> {
    public void notify(T result);
}

//example method
public class Worker{
  public void doAsyncWork(Callback<SomeTypeOrOther> callback){
     //do work
     callback.notify(result);
  }
}

Lets consider the example of Lamp and Switch to see the difference between observer and command pattern.

Observer Pattern

  • Switch is a Subject and a list of lamps are Observers on which ON/OFF actions can be applied.
  • It's a one to many relationship.
  • Actions ON/OFF are actually functions in its simplest form.
  • Does not provide encapsulation to commands in its simplest form.

Command Pattern

  • On the other hand in command pattern Actions ON/OFF becomes Command class.

  • Command class contains receiver Lamp on which action can be applied.

  • Command pattern is often provides one to one relationship however can be scaled to provide one to many as well.

  • Command pattern provides proper encapsulation to the command.

Both patterns share few common intentions except,

Observable pattern can notify multiple listeners. On the other hand, command pattern will be best suited, where you need single callback handler.

In command pattern, it's easy to implement the undo operation.

Cheers!

  • Callback: executable code passed as an argument to a function which is called when a specific event happens. It may be implemented in different forms in different programming languages, such as function pointers, anonymous functions and listeners/observers (object-oriented paradigm). It often refers to a single executable code, but that is not required. A counterexample: the SurfaceHolder interface in Android SDK (API level 29) allows the registration of multiple callbacks using the addCallback() method.
  • Listener/observer: in object-oriented paradigm, it is an object with a method which is passed as an argument to a function which is called when a specific event happens. It is one possible implementation of multiple callbacks.
  • Observer pattern: an object oriented software design pattern which recommends using observer/listeners in order to decouple the observer class from the observed class.

In your specific code, the only differences between Callback and EventListener are:

  • you allow registering only one Callback, but allow multiple EventListeners
  • Callback registration is synchronized with the execution of the async task, EventListener registration is not.

The former is simpler, the latter is more flexible. If you are creating a reusable framework, the latter makes more sense.

The Command design pattern is the encapsulation of all information needed to perform an action in an object and it is has nothing to do with the mechanism used to notify events.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top