【问题标题】:Recursive Generic and Fluent Interface递归通用和流畅的接口
【发布时间】:2014-12-05 22:11:36
【问题描述】:

tl;dr

尝试实现一个层次流畅的接口,以便我可以组合 nodes 子类,同时也可以独立使用该类,但获取类型参数不在其绑定错误范围内。

详情

我正在尝试实现一个解决方案,以便我可以创建一些东西,以便我可以执行以下操作:

farm
    .animal()
        .cat()
            .meow()
            .findsHuman()
                .saysHello()
                .done()
            .done()
        .dog()
            .bark()
            .chacesCar()
            .findsHuman()
                .saysHello()
                .done()
            .done()
        .done()
    .human()
        .saysHello()
        .done();

同时也能做到:

Human human = new Human()
    .saysHello()

我已经使用各种策略接近了,但无法获得所描述的灵活性。

我目前的尝试使用以下类:

abstract class Base<T extends Base<T>>{

    private T parent;

    Base(){

    }

    Base( T parent ){
        this.parent = parent;
    }

    public T done() throws NullPointerException{
        if ( parent != null ){
            return (T) parent;
        }

        throw new NullPointerException();
    }   
}

class Farm<T extends Base<T>> extends Base{

    private Animal<Farm<T>> animal;
    private Human<Farm<T>> human;

    public Farm(){
        super();
        this.animal = new Animal( this );
        this.human = new Human( this );
    }

    public Animal<Farm> animal(){
        return this.animal;
    }

    public Human<Farm<T>> human(){
        return this.human;
    }
}

class Animal <T extends Base<T>> extends Base{

    private Cat<Animal<T>> cat;
    private Dog<Animal<T>> dog;

    public Animal(){
        super();
        init();
    }

    public Animal( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.cat = new Cat(this);
        this.dog = new Dog(this);
    }

    public Cat<Animal<T>> cat(){
        return cat;
    }

    public Dog<Animal<T>> dog(){
        return dog;
    }
}

class Human<T extends Base<T>> extends Base{

    public Human<T> saysHello(){
        System.out.println("human says hi");
        return this;
    }
}

class Cat <T extends Base<T>> extends Base{

    private Human<Cat> human;

    public Cat(){
        super();
        init();
    }

    public Cat( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.human = new Human();
    }

    public Cat<T> meow(){
        System.out.println("cat says meow");
        return this;
    }

    public Human<Cat<T>> findsHuman(){
        return this.human;
    }
}


class Dog <T extends Base<T>> extends Base{

    private Human<Dog> human;

    public Dog(){
        super();
        init();
    }

    public Dog( T parent ){
        super( parent );
        init();
    }

    private void init(){
        this.human = new Human();
    }


    public Dog<T> bark(){
        System.out.println("dog says woof");
        return this;
    }

    public Dog<T> chacesCar(){
        System.out.println("cat drinks milk");
        return this;
    }

    public Human<Dog<T>> findsHuman(){
        return this.human;
    }

}

我看到的错误通常是:

Animal.java:4:类型参数Animal不在其绑定的私有Cat猫内; Animal.java:5:类型参数Animal不在其绑定的私有Dog狗内;

适用于所有类似的参考文献,也适用于我的示例所需案例:

找不到符号 符号:方法狗() 位置:类 Base.dog()

我尝试使用以下似乎可以解决类似问题的解决方案,但无济于事,因此欢迎任何和所有支持。

参考文献

【问题讨论】:

  • 恕我直言,您正在使用 java 泛型来实现它们的非设计。这在 C++ 中可以正常工作,但 java 泛型 相同。 泛型是有害的,这就是一个例子。当然,还有其他软件工程解决方案可以满足您的需求,而不是您正在粘贴的编程艺术。当事情像这里显示的答案那么复杂时,可能是出了点问题。
  • 我想我找到了解决方案。我相应地更新了我的答案。
  • @AlfonsoNishikawa 我同意它可能会突破这种模式的极限,并且超出了我最初的一些理解,所以我想问问社区的想法。
  • @morpheus05 感谢您的更新,我将查看所有答案,看看是否能找到最能解决我问题的答案。

标签: java generics fluent-interface nested-generics


【解决方案1】:

这是我们在一个项目中所做的:

public abstract class Parent<T extends Parent<T>> {

    /**
     * Get {@code this} casted to its subclass.
     */
    @SuppressWarnings("unchecked")
    protected final T self() {
        return (T) this;
    }

    public T foo() {
        // ... some logic
        return self();
    }

    // ... other parent methods

}

public class Child extends Parent<Child> {

    public Child bar() {
        // ... some logic
        return self();
    }

    // ... other child methods

}

允许孩子拥有自己的子类是:

public class Child<T extends Child<T>> extends Parent<T> {

    public T bar() {
        // ... some logic
        return self();
    }

}

【讨论】:

  • self 的实现应该是最终的,所以没有人会搞砸。
  • 这不适用于OtherChild extends Parent&lt;Child&gt; - self() 中的(T) this 会失败。
【解决方案2】:

在这一行:

class Farm<T extends Base<T>>

编译器将第二个类型参数视为具体类。例如,假设您将那行替换为:

class Farm<T extends Base<Double>>

'Double' 是一个具体的类。当编译器扫描它时,它无法区分 T 和 Double 之间的区别,并且将它们都视为具体类,而不是类型参数。让编译器知道 T 是类型参数的唯一方法是:

class Farm<T extends Base<T>, T>

我希望这能回答(或至少是相关的)您的问题。

编辑 我在打字时编辑了帖子,所以我想这个答案不再相关了。

【讨论】:

    【解决方案3】:

    没有“安全”的方法可以做到这一点,但这应该可以编译:

    class Dog extends Base{
    
     <T extends Dog> T bark(){
        return (T) this;
     } 
    
    }
    

    【讨论】:

      【解决方案4】:

      我想到的最好的事情是:

      new Animal()
          .cat()
            .meow()
            .findsHuman()
              .<Cat>done()
            .<Animal>done()
          .dog()
            .bark()
              .findHuman()
                .<Dog>done()
            .done();
      

      具有以下基类:

      public abstract class Base<T extends Base<T>>{
      
        private Base<?> backRef;
      
        public Base() {}
      
        public Base(Base<?> backRef) {
          this.backRef = backRef;
       }
      
        @SuppressWarnings("unchecked")
          protected T self() {
          return (T)this;
        }
      
        @SuppressWarnings("unchecked")
        public <U extends Base<U>> U done() {
          return (U)backRef;
        }
      }
      

      如果您将 backRef 声明为 T 类型,则不允许使用其他类,因为它们不是彼此的子类,因此您必须指定不同的类型,但由于此类型是上下文相关的(有时是它的 Cat,一次是狗)我没有看到其他方法来传递提示。

      我找到了解决办法:

      new Animal()
          .cat()
            .meow()
            .findsHuman()
              .done()
            .done()
          .dog()
            .bark()
              .findHuman()
                .done()
          .done();
      
      
      
      public abstract class Base<T extends Base<T,P>, P>{
      
        private P backRef;
        public Base() {}
      
        public Base(P backRef) {
          this.backRef = backRef;
        }
      
        @SuppressWarnings("unchecked")
        protected T self() {
          return (T)this;
        }
      
        public P done() {
          return backRef;
       }
      }
      

      就像有人建议的那样,我们为父级添加了一个额外的类型。

      现在是基类:

      public final class Cat extends Base<Cat, Animal>{
      
        public Cat() {}
      
        public Cat(Animal backRef) {
          super(backRef);
        }
      
        public Cat meow() {
          System.out.println("Meeeoooww");
          return self();
        }
      
        public Human<Cat> findsHuman() {
          return new Human<Cat>(this);
        }
      }
      

      如您所见,Cat 清楚地指定了它应该使用的基本类型。现在是人类,它可以根据上下文改变类型:

      public final class Human<P> extends Base<Human<P>, P> {
      
        public Human() {}
      
        public Human(P backRef) {
          super(backRef);
        }
      

      }

      Human 指定了调用者(Cat、Dog)在其 findHuman() 方法中指定的附加泛型。

      【讨论】:

      • 类型提示的使用非常有趣(尽管它的类型安全性不是很好)。 +1
      • 恭喜。我希望我永远不需要这样的东西;)但是干得好。就个人而言,我永远无法解决这个问题。
      【解决方案5】:

      你也可以玩接口,这样你就可以伪造多重继承。有点冗长,但没有危险的强制转换,我觉得很好理解。


      定义可用的方法:

      public interface AnimalIn {
          AnimalOut animal();
      }
      
      public interface CatIn {
          CatOut cat();
      }
      
      public interface MeowIn {
          CatOut meow();
      }
      
      public interface DogIn {
          DogOut dog();
      }
      
      public interface BarkIn {
          DogOut bark();
      }
      
      public interface ChacesCarIn {
          DogOut chacesCar();
      }
      
      public interface FindsHumanIn<T> {
          HumanOut<T> findsHuman();
      }
      
      public interface HumanIn {
          HumanOut<FarmOut> human();
      }
      
      public interface SaysHelloIn<T> {
          HumanOut<T> saysHello();
      }
      
      public interface DoneIn<T> {
          T done();
      }
      

      你可能需要在一个接口中有多个方法,但我还没有满足这个需求。例如,如果您不得不使用 meows 的种类:

      public interface MeowIn {
          CatOut meowForFood();
          CatOut meowForMilk();
          CatOut meowForStrokes();
      }
      

      定义输出类型:

      Farm 提供AnimalHuman

      public interface FarmOut extends AnimalIn, HumanIn {
          // no specific methods
      }
      

      Animal 提供CatDogDone

      public interface AnimalOut extends CatIn, DogIn, DoneIn<FarmOut> {
          // no specific methods
      }
      

      Cat 提供MeowFindsHumanDone

      public interface CatOut extends MeowIn, FindsHumanIn<CatOut>, DoneIn<AnimalOut> {
          // no specific methods
      }
      

      Dog 提供BarkChacesCarFindsHumanDone

      public interface DogOut extends BarkIn, ChacesCarIn, FindsHumanIn<DogOut>, DoneIn<AnimalOut> {
          // no specific methods
      }
      

      Human 提供SayHelloDone

      public interface HumanOut<T> extends SaysHelloIn<T>, DoneIn<T> {
          // no specific methods
      }
      

      只需实现 *Out 接口:

      public class Farm implements FarmOut {
      
          @Override
          public AnimalOut animal() {
              return new Animal(this);
          }
      
          @Override
          public HumanOut<FarmOut> human() {
              return new Human<FarmOut>(this);
          }
      
      }
      
      public class Animal implements AnimalOut {
      
          private FarmOut chain;
      
          public Animal(FarmOut chain) {
              this.chain = chain;
          }
      
          @Override
          public CatOut cat() {
              return new Cat(this);
          }
      
          @Override
          public DogOut dog() {
              return new Dog(this);
          }
      
          @Override
          public FarmOut done() {
              return chain;
          }
      
      }
      
      public class Dog implements DogOut {
      
          private AnimalOut chain;
      
          public Dog(AnimalOut chain) {
              this.chain = chain;
          }
      
          @Override
          public DogOut bark() {
              System.out.println("bark");
              return this;
          }
      
          @Override
          public DogOut chacesCar() {
              System.out.println("chaces car");
              return this;
          }
      
          @Override
          public HumanOut<DogOut> findsHuman() {
              return new Human<DogOut>(this);
          }
      
          @Override
          public AnimalOut done() {
              return chain;
          }
      
      }
      
      public class Cat implements CatOut {
      
          private AnimalOut chain;
      
          public Cat(AnimalOut chain) {
              this.chain = chain;
          }
      
          @Override
          public CatOut meow() {
              System.out.println("meow");
              return this;
          }
      
          @Override
          public HumanOut<CatOut> findsHuman() {
              return new Human<CatOut>(this);
          }
      
          @Override
          public AnimalOut done() {
              return chain;
          }
      
      }
      
      public class Human<T> implements HumanOut<T> {
      
          private T chain;
      
          public Human(T chain) {
              this.chain = chain;
          }
      
          @Override
          public HumanOut<T> saysHello() {
              System.out.println("hello");
              return this;
          }
      
          @Override
          public T done() {
              return chain;
          }
      
      }
      

      这些实现在没有接口的情况下也可以工作:删除implements *Out@Overrides,并将任何*Out 替换为*(例如AnimalOut 替换为Animal)。也就是说,使用接口更容易维护:只需更新它们并修复编译错误。找到带有接口的 DSL 解决方案也更容易(如您所见),而且它们有时是必需的。


      演示:

      new Farm()
      .animal()
          .cat()
              .meow()
              .findsHuman()
                  .saysHello()
                  .done()
              .done()
          .dog()
              .bark()
              .chacesCar()
              .findsHuman()
                  .saysHello()
                  .done()
              .done()
          .done()
      .human()
          .saysHello()
          .done();
      

      打印:

      meow
      hello
      bark
      chaces car
      hello
      hello
      

      【讨论】:

        【解决方案6】:

        您的问题是完成的方法应该返回父级,但父级不一定是T,而只是一个Base。另一个问题是无论类是什么,done 方法应该总是返回同一个类。

        但这里是您提议的课程的轻微变化。首先是 Base 声明它的具体类和它的具体父类:

        abstract class Base<T extends Base<T, P>, P>{
        
            private P parent;
        
            Base(){
        
            }
        
            Base( P parent ){
                this.parent = parent;
            }
        
            public P done() throws NullPointerException{
                if ( parent != null ){
                    return parent;
                }
        
                throw new NullPointerException();
            }   
        }
        

        这样,派生的具体类就变成了:

        class Farm extends Base<Farm, Object>{
        
            private Animal animal;
            private Human human;
        
            public Farm(){
                super();
                this.animal = new Animal( this );
                this.human = new Human( this );
            }
        
            public Animal animal(){
                return this.animal;
            }
        
            public Human human(){
                return this.human;
            }
        }
        
        class Animal extends Base<Animal, Farm>{
        
            private Cat cat;
            private Dog dog;
        
            public Animal(){
                super();
                init();
            }
        
            public Animal( Farm parent ){
                super( parent );
                init();
            }
        
            private void init(){
                this.cat = new Cat(this);
                this.dog = new Dog(this);
            }
        
            public Cat cat(){
                return cat;
            }
        
            public Dog dog(){
                return dog;
            }
        }
        
        class Human extends Base<Human, Farm>{
        
            public Human() {
        
            }
        
            public Human(Farm farm) {
                super(farm);
            }
        
            public Human saysHello(){
                System.out.println("human says hi");
                return this;
            }
        
        }
        
        class CatOrDog extends Base<Cat, Animal>{
        
            protected Human human;
        
            public CatOrDog(){
                super();
                init(null);
            }
        
            public CatOrDog( Animal parent ){
                super( parent );
                init(parent);
            }
        
            private void init(Animal parent){
                Animal parent = done();
                Farm farm = (parent == null) ? null : parent.done();
                this.human = new Human(farm);
            }
        
            public Human findsHuman(){
                return this.human;
            }
        }
        
        
        class Cat extends CatOrDog{
        
            public Cat(){
                super();
            }
        
            public Cat( Animal parent ){
                super( parent );
            }
        
            public Cat meow(){
                System.out.println("cat says meow");
                return this;
            }
        }
        
        
        class Dog extends CatOrDog {
        
            public Dog(){
                super();
            }
        
            public Dog( Animal parent ){
                super( parent );
            }
        
            public Dog bark(){
                System.out.println("dog says woof");
                return this;
            }
        
            public Dog chacesCar(){
                System.out.println("cat drinks milk");
                return this;
            }
        }
        

        这样,我就可以毫无错误或警告地写了:

        Farm farm = new Farm();
        farm.animal()
            .cat()
                .meow()
                .findsHuman()
                    .saysHello()
                    .done()
                .animal()
            .dog()
                .bark()
                .chacesCar()
                .findsHuman()
                    .saysHello()
                    .done()
                .animal()
            .done()
        .human()
            .saysHello()
            .done();
        

        但请注意,我必须将 done 调用替换为 animals 调用。

        编辑:

        我添加了一个新类CatOrDog 来分解Human 处理。由于Human 的父级是Farm,因此我使用正确的父级(如果存在)初始化新的human。这样,不仅上述源代码编译时没有错误或警告,而且运行时也没有任何问题并打印:

        cat says meow
        human says hi
        dog says woof
        cat drinks milk
        human says hi
        human says hi
        

        【讨论】:

          【解决方案7】:

          下面的代码似乎工作正常,不需要任何@SuppressWarnings。要掌握的关键概念是您的T 参数实际上是对象父级的类,但T 的父级可以是任何东西。所以你想要T extends Base&lt;?&gt;而不是T extends Base&lt;T&gt;

          输出是:

          cat says meow
          human says hi
          dog says woof
          cat drinks milk
          human says hi
          human says hi
          

          ...我认为这是正确的,尽管您可能想要更改您的 Dog.chacesCar() 方法,使其不输出 cat drinks milk!也应该是chases 而不是chaces

          希望这会有所帮助!

          abstract class Base<T extends Base<?>> {
          
              private final T parent;
          
              Base() {
                  this.parent = null;
              }
          
              Base(T parent) {
                  this.parent = parent;
              }
          
              public T done() throws NullPointerException {
                  if (parent != null) {
                      return parent;
                  }
          
                  throw new NullPointerException();
              }
          }
          
          class Farm<T extends Base<?>> extends Base<T> {
          
              private final Animal<Farm<T>> animal;
              private final Human<Farm<T>> human;
          
              public Farm() {
                  super();
                  this.animal = new Animal<>(this);
                  this.human = new Human<>(this);
              }
          
              public Animal<Farm<T>> animal() {
                  return this.animal;
              }
          
              public Human<Farm<T>> human() {
                  return this.human;
              }
          }
          
          class Animal<T extends Base<?>> extends Base<T> {
          
              private Cat<Animal<T>> cat;
              private Dog<Animal<T>> dog;
          
              public Animal() {
                  super();
                  init();
              }
          
              public Animal(T parent) {
                  super(parent);
                  init();
              }
          
              private void init() {
                  this.cat = new Cat<>(this);
                  this.dog = new Dog<>(this);
              }
          
              public Cat<Animal<T>> cat() {
                  return cat;
              }
          
              public Dog<Animal<T>> dog() {
                  return dog;
              }
          }
          
          class Human<T extends Base<?>> extends Base<T> {
              public Human() {
                  super();
              }
          
              public Human(T parent) {
                  super(parent);
              }
          
              public Human<T> saysHello() {
                  System.out.println("human says hi");
                  return this;
              }
          }
          
          class Cat<T extends Base<?>> extends Base<T> {
          
              private Human<Cat<T>> human;
          
              public Cat() {
                  super();
                  init();
              }
          
              public Cat(T parent) {
                  super(parent);
                  init();
              }
          
              private void init() {
                  this.human = new Human<>(this);
              }
          
              public Cat<T> meow() {
                  System.out.println("cat says meow");
                  return this;
              }
          
              public Human<Cat<T>> findsHuman() {
                  return this.human;
              }
          }
          
          class Dog<T extends Base<?>> extends Base<T> {
          
              private Human<Dog<T>> human;
          
              public Dog() {
                  super();
                  init();
              }
          
              public Dog(T parent) {
                  super(parent);
                  init();
              }
          
              private void init() {
                  this.human = new Human<>(this);
              }
          
              public Dog<T> bark() {
                  System.out.println("dog says woof");
                  return this;
              }
          
              public Dog<T> chacesCar() {
                  System.out.println("cat drinks milk");
                  return this;
              }
          
              public Human<Dog<T>> findsHuman() {
                  return this.human;
              }
          
          }
          

          测试代码:

          public static void main(String[] args) {
              Farm<?> farm = new Farm<>();
              farm
                  .animal()
                      .cat()
                          .meow()
                          .findsHuman()
                              .saysHello()
                              .done()
                          .done()
                      .dog()
                          .bark()
                          .chacesCar()
                          .findsHuman()
                              .saysHello()
                              .done()
                          .done()
                      .done()
                  .human()
                      .saysHello()
                      .done();
          
              Human human = new Human()
                      .saysHello();
          }
          

          【讨论】:

          • 能否请您分享您的主要方法,在尝试时我遇到了找不到符号错误
          • 我已将其添加到答案中(这几乎是您发布的确切测试代码)。
          • 找不到什么符号?
          • 谢谢,看来我的问题是我在没有 > 的情况下运行 Farm,这导致 done 返回 Base。这似乎解决了我的问题并为我提供了所需的方向。
          • 为了更清楚 - 您已将泛型参数的语义从 “作为叶子子类型” 更改为 “作为拥有的构建器类型” i>(这就是我所说的从其他方式接近的意思)。
          猜你喜欢
          • 1970-01-01
          • 2019-05-10
          • 1970-01-01
          • 1970-01-01
          • 2010-09-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多