2017-03-16 57 views
1

假設我們有以下的,我們不能改變類:爪哇 - 從顧客返回的值

interface Base { 
    void accept(Visitor visitor); 
} 

class Foo implements Base { 
    short getShortValue() { 
     return 1; 
    } 

    @Override 
    public void accept(Visitor visitor) { 
     visitor.visit(this); 
    } 
} 

class Bar implements Base { 
    int getIntValue() { 
     return 2; 
    } 

    @Override 
    public void accept(Visitor visitor) { 
     visitor.visit(this); 
    } 
} 

interface Visitor { 
    void visit(Foo foo); 

    void visit(Bar bar); 
} 

,我們需要實現的方法:

int getValue(Base base) 

有很多可能性去做與訪客使用一些存儲對象:

int useArray(Base base) { 
    int[] result = new int[1]; 

    base.accept(new Visitor() { 
     @Override 
     public void visit(Foo foo) { 
      result[0] = foo.getShortValue(); 
     } 

     @Override 
     public void visit(Bar bar) { 
      result[0] = bar.getIntValue(); 
     } 
    }); 

    return result[0]; 
} 

int useAtomic(Base base) { 
    AtomicInteger result = new AtomicInteger(); 

    base.accept(new Visitor() { 
     @Override 
     public void visit(Foo foo) { 
      result.set(foo.getShortValue()); 
     } 

     @Override 
     public void visit(Bar bar) { 
      result.set(bar.getIntValue()); 
     } 
    }); 

    return result.intValue(); 
} 

int useMutable(Base base) { 
    MutableInteger result = new MutableInteger(0); 

    base.accept(new Visitor() { 
     @Override 
     public void visit(Foo foo) { 
      result.setValue(foo.getShortValue()); 
     } 

     @Override 
     public void visit(Bar bar) { 
      result.setValue(bar.getIntValue()); 
     } 
    }); 

    return result.getValue(); 
} 

或者變態的東西:

int useException(Base base) { 
    class GotResult extends RuntimeException { 
     private final int value; 

     public GotResult(int value) { 
      this.value = value; 
     } 
    } 

    try { 
     base.accept(new Visitor() { 
      @Override 
      public void visit(Foo foo) { 
       throw new GotResult(foo.getShortValue()); 
      } 

      @Override 
      public void visit(Bar bar) { 
       throw new GotResult(bar.getIntValue()); 
      } 
     }); 
    } catch (GotResult result) { 
     return result.value; 
    } 

    throw new IllegalStateException(); 
} 

或者不使用訪客都:

int useCast(Base base) { 
    if (base instanceof Foo) { 
     return ((Foo) base).getShortValue(); 
    } 
    if (base instanceof Bar) { 
     return ((Bar) base).getIntValue(); 
    } 
    throw new IllegalStateException(); 
} 

現在這些是我們唯一的選擇嗎?我們有Java 8(很快就會有9個),並且仍然在寫這些容易出錯的代碼。 :)

回答

3

我同意,從安全使用的角度來看,不能從訪問者返回值是可怕的。

爲了避免上面演示的體操負擔,最好的選擇是創建一個包裝類型,揭示一個理智的API(基於corrected visitor pattern),以便骯髒的工作只做一次:當轉換Base值到那個包裝類型。

這裏是你如何能做到這一點:

interface BaseW { 

    interface Cases<X> { 
     X foo(Foo foo); 
     X bar(Bar bar); 
    } 

    <X> X match(Cases<X> cases); 
    //or alternatively, use a church encoding: 
    //<X> X match(Function<Foo, X> foo, Function<Bar, X> bar); 

    default Base asBase() { 
     return match(new Cases<Base>() { 
      @Override 
      public Base foo(Foo foo) { 
       return foo; 
      } 

      @Override 
      public Base bar(Bar bar) { 
       return bar; 
      } 
     }); 
    } 

    static BaseW fromBase(Base base) { 
     return new Visitor() { 
      BaseW baseW; 
      { 
       base.accept(this); 
      } 
      @Override 
      public void visit(Foo foo) { 
       baseW = new BaseW() { 
        @Override 
        public <X> X match(Cases<X> cases) { 
         return cases.foo(foo); 
        } 
       }; 
      } 

      @Override 
      public void visit(Bar bar) { 
       baseW = new BaseW() { 
        @Override 
        public <X> X match(Cases<X> cases) { 
         return cases.bar(bar); 
        } 
       }; 
      } 
     }.baseW; 
    } 

    static int useCorrectedVisitor(Base base) { 
     return fromBase(base).match(new Cases<Integer>() { 
      @Override 
      public Integer foo(Foo foo) { 
       return (int) foo.getShortValue(); 
      } 

      @Override 
      public Integer bar(Bar bar) { 
       return bar.getIntValue(); 
      } 
     }); 
     // or, if church encoding was used: 
     // return fromBase(base).match(
     //   foo -> (int) foo.getShortValue(), 
     //   bar -> bar.getIntValue() 
     //); 
    } 
} 

現在(無恥插頭),如果你不介意使用derive4j(JSR一269碼發生器)以上可以簡化公平一點,因爲以及改進語法:

@org.derive4j.Data // <- generate an BaseWs classe that allows handling 
        // of the interface as an algebraic data type. 
interface BaseW { 

    interface Cases<X> { 
     X foo(Foo foo); 
     X bar(Bar bar); 
    } 

    <X> X match(Cases<X> cases); 

    default Base asBase() { 
     return match(BaseWs.cases(f -> f, b -> b)); 
    } 

    static BaseW fromBase(Base base) { 
     return new Visitor() { 
      BaseW baseW; 
      { 
       base.accept(this); 
      } 
      @Override 
      public void visit(Foo foo) { 
       baseW = BaseWs.foo(foo); 
      } 

      @Override 
      public void visit(Bar bar) { 
       baseW = BaseWs.bar(bar); 
      } 
     }.baseW; 
    } 

    static int useStructuralPatternMatching(Base base) { 
     return BaseWs.caseOf(fromBase(base)) 
      .foo(foo -> (int) foo.getShortValue()) 
      .bar(Bar::getIntValue); 
    } 
}