I have an abstraction that defines something like a command pattern,

interface Participant {
    void proceed();
}

Participants are grouped in a collection and are called all together. But each Participant in the group doesn't have to participate in a certain call due to certain conditions.

I wanted to add an allowance method to my abstraction. The idea is that the caller can ask the Participant if it should be called or not, before calling proceed().

Is this the right approach? Maybe that determining the conditions for the allowance could require very expensive calculations, that later would needed to be performed again in proceed(). If the implementation has to be stateless, it would not be possible to save the calculation between the two calls, and the performance would be impacted.

By the way, if this is an acceptable approach, I would need a clear name for the allowance function. I thought about canProceed() but I wanted to avoid repeating the name of the action, and a simple can() seemed vague to me. Are there usual naming conventions for this kind of function?

有帮助吗?

解决方案

The problem

Lets look at the analogy with the Command pattern:

  • A Client creates objects of classes that that implement the Participant (similar to Command). For convenience, let's call one of these classes ConcreteParticipant.
  • The ConcreteParticipant object knows all the context, such as receiver to perform its duties and proceed() (similar to execute() in the command pattern). This context may be provided at construction or later, but before any invocation.
  • The invoker object is a little more complex, since it has a collection of Participant, This may be let's take for example ConcreteParticipant have all the necessary context (e.g. receiver in the command pattern) to perform the calculation of the conditions and proceed().
  • The conditions that say if the Participant must proceed() is not calculated by the invoker, but by the Participant itself.

Your initial approach

Your first approach is to expose the Participant condition determination and let the invoker do the filtering:

//PSEUDO CODE:  I'm not Java fluent...

interface Participant {
    void proceed();
    boolean isApplicable();  // but why?
}

// in the invoker:  

for (Participant p: participants) {
    if (p.isApplicable()) 
       p.proceed();
}

What's the benefit of such approach? If the Participant determines itself if the condition applies (and perhaps some are even unconditional) it makes no sense to let another class deciede.

Keep it simple!

According to the principle of least knowledge and sound separation of concerns, and unless there are reasons you forget to tell about, you should not expose the condition.

Instead, you should trust the participant to work only if necessary:

interface Participant {
    void proceed();
}
class ConcreteParticipant implements Participant {
    private bolean isApplicable() { ... }  // no longer explosed in the interface
    public void proceed() { 
        if (isApplicable()) {
            ...
        }
    }
}  
class ConcreteUnconditionalParticipant implements Participant {
    public void proceed() { 
        doItAnyway(); 
    }
}  

With this approach you no longer have any performance issue of redoing twice the same time-consuming operation: you may refactor the code without changing the interface:

class OtherConcreteParticipant implements Participant {
    private ComplexResult prepareCondition() { ...} 
    private bolean isApplicable (ComplexResult r) { ... }  // stateless :-) 
    public void proceed() { 
        ComplexResult r = prepareCondition(); 
        if (isApplicable(r)) {
            ... // here you may reuse r.  
        }
    }
}  

And you have no longer a naming issue either ;-)

Need tighter control?

If the invoker must do some bookkeeping, for example counting the number of participants that contributed, you could change slightly your interface:

interface TalkativeParticipant {
    boolean proceed();  // return true if it was active, and false if not applicable.
}

Note that this approach would also allow you more elaborate decisions on invoking participants:

  • you could easily stop the calls after the first participant did work.
  • or stop the calls after the first that did not work

Remark: The first result could also be achieved using a chain of responsibility pattern. But chain of responsibility would require each Participant to know its successor. THis is a very different situation than what you described. The collection of participants seems easier to manage.

许可以下: CC-BY-SA归因
scroll top