开发者

Chain style method design

I wrote a simple class to demonstrate a chain st开发者_运维知识库yle method design:

public class Cal {

    private Cal(){}

    private boolean isCheckArguments = false;

    public static Cal useAbs() {
        return new Cal(){   
          @Override int check(int i) {
                return Math.abs(i);
            }};
    }

    public static Cal useNormal() {
        return new Cal();
    }   

    public Cal checkArguments() {
        isCheckArguments =true;
        return this;
    }

     int check(int i){ return i;}

     public int plus(int i, int j) {    
        if(isCheckArguments && i<j){
            throw new IllegalArgumentException("i<j!");
        }
        return check(i+j);
    }
}

So the client code can be:

Cal cal = Cal.useAbs().checkArguments();
int sum = cal.plus(100,2);//IllegalArgumentException occurs

Cal cal2 = Cal.useAbs();
int sum2 = cal.plus(-100,2);//98

Cal cal3 = Cal.useNormal();
int sum3 = cal.plus(-100,2);//-98

My question is: Is it a reasonable design? comparing to: int plus(int a, int b, boolean useAbs, boolean checkArguments). Thanks!


Sounds like you want a fluent interface to build a service class. Guava does similar things. You'd do something like this:

public interface Cal {
   int plus(int a, int b);
}

public class CalBuilder {
    class InternalCal implements Cal {
       boolean useAbs;
       boolean checkArgs;
       public int plus(int a, int b) {
          if(checkArgs) {
             // blah, blah blah
          }
          if(useAbs) {
             // doodle bug, doodle darn
          }
          return a+b; // whatevs
       }
    }
    boolean absSet=false;
    InternalCal holder=new InternalCal();
    public CalBuilder useNormal() {
        if(absSet) { throw new IllegalArgumentException(); } // already called
        holder.useAbs=false;    
        absSet=true;
        return this;
    }

    public CalBuilder useAbs() {
        if(absSet) { throw new IllegalArgumentException(); } // already called
        holder.useAbs=false;    
        absSet=true;
        return this;
    }

    public CalBuilder checkArguments() {
       if(holder.checkArgs) { throw new IllegalArgumentException(); }
       holder.checkArgs=true;
       return this;
    }

    public Cal build() {
       return holder;
    }
}

Usage would look like this:

Cal cal=new CalBuilder().useAbs().checkArguments().build();
int sum=cal.plus(1,2);


This is called a fluent interface, and looks pretty reasonable to me.

One thing I might suggest changing is the name of the class: Calc is more obviously a calculator than Cal (which could be a calendar instead of a calculator).


The best design is a simple design. What you are doing is obfuscating the code. It's more difficult to read and fix errors than the "plus(a, b, useAbs, checkArguments)". Also, in useNormal you are returning the same object than "new Cal ()" because you are overrididing the check(int) method and returning the parent's implementation with super.check(int)


IMHO, this "chaining" approach is not that commonly used in Java, except in the case of Builders or things that are essentially builders.

What you're describing here is actually a calculator builder.

You should probably have a CalculatorBuilder class, which you instantiate, set multiple things (e.g., useAbs, checkArguments, etc.), and eventually call "build". Build will return a calculator that knows nothing about how it was built except for the state it was initialized with.

Also, I don't personally like a design that mixes builder-style logic (e.g., "useAbs"), and things that affect the state of the underlying object (such as checkArguments). I'd say pick one. Either generate a default calculator and set everything later, or have a builder that sets everything and then creates instances whose functionality and behavior cannot be changed.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜