2014-10-10 2 views
8

TL; дрРекурсивного Generic и свободный интерфейс

Попытка реализовать иерархический свободно интерфейс таким образом, что я могу объединить узлов дочерних классов, а также класс автономным, но получить параметр типа не входит в его связанных ошибках ,

Подробности

Я пытаюсь добиться решения, так что я могу создать что-то такое, что я могу сделать что-то вроде:

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: параметр животных типа не входят в его связанном частном Cat Кот; Animal.java:5: параметр типа Animal не находится в пределах своей связанной собаки Dog Dog;

Применяют для всех подобных ссылок, а также относящиеся к моему примеру желаемому случай:

не может найти символ символ: метод собаки() местоположение: класс Base.dog()

Я пробовал использовать следующие решения, которые, казалось, решали аналогичные проблемы, но безуспешно, поэтому всякая поддержка приветствуется.

Ссылки

+1

ИМХО вы используете _java_ дженерики для того, что они не предназначены. Это будет отлично работать на C++, но java generics - не то же самое. _Generics вредны, и это один из примеров. Разумеется, существуют и другие * решения для разработки программного обеспечения * для ваших нужд, а не в * программном искусстве *, которое вы вставляете. Когда все так сложно, как показано здесь, возможно, что-то не так. –

+0

Думаю, я нашел решение. Я обновил свой ответ соответственно. – morpheus05

+1

@AlfonsoNishikawa Я согласен с тем, что это может подтолкнуть пределы этого шаблона и было за пределами моего первоначального понимания, поэтому я подумал, что попрошу сообщества подумать. – SS44

ответ

3

ниже код, кажется, работает нормально и не требует каких-либо @SuppressWarnings. Ключевой концепцией, которую нужно понять, является то, что ваш параметр T фактически является классом родителя вашего объекта, но родителем T может быть что угодно. Итак, вместо T extends Base<T> вы хотите T extends Base<?>.

Выход:

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(); 
} 
+0

Не могли бы вы поделиться своим основным методом, когда я пытаюсь запустить приложение, не могу найти ошибки символов – SS44

+0

Я добавил его к ответу (это был довольно точный тестовый код, который вы опубликовали). – Constantinos

+0

Какой символ это не найти? – Constantinos

2

Это то, что мы сделали на один наш проект:

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(); 
    } 

} 
+1

реализация 'self' должна, вероятно, быть окончательной, чтобы никто больше не общался с этим. – SpaceTrucker

+0

Это не сработает для 'OtherChild extends Parent ' '' '' (T) this' в 'self()' будет терпеть неудачу. –

1

В этой строке:

class Farm<T extends Base<T>> 

Компилятор обрабатывает второй параметр типа в качестве конкретного класса , Например, скажем, замените ли вы эту строку следующим образом:

class Farm<T extends Base<Double>> 

«Двойной» - это конкретный класс. Когда компилятор сканирует это, он не может определить разницу между вашим T и Double, и такой подход рассматривает их как конкретный класс, так и не тип параметров. Единственный способ заставить компилятор знать T является параметром типа этот путь:

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

Я надеюсь, что это ответы (или, по крайней мере применимо) к вашему вопросу.

Редактировать Сообщение было отредактировано, пока я печатал, поэтому, я думаю, этот ответ больше не имеет отношения к делу.

0

Там нет «безопасный» способ сделать это, но это должно составить:

class Dog extends Base{ 

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

} 
3

Самое лучшее, что я придумал следующий:

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 определяет дополнительный родовой которой абонент (кошки, собаки) указывает на их findHuman() метод.

+0

Это довольно интересное использование типов подсказок (хотя безопасность этого не так уж и хороша). +1 –

+0

Поздравляем. Надеюсь, мне никогда не понадобится что-то подобное;) Но хорошая работа. Лично я никогда не мог войти в решение. –

0

Вы также можете играть с интерфейсами, чтобы вы могли подделывать множественное наследование. Немного подробный, но нет опасного кастинга, и я считаю это вполне понятным.


Определить доступные методы:

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(); 
} 

Вам может потребоваться несколько методов в интерфейсе, но я не встретил эту потребность еще. Например, если бы вы были виды meow с:

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

Определить типы выходных:

Farm обеспечивает Animal или Human:

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

Animal обеспечивает Cat , Dog или Done:

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

Cat обеспечивает Meow, FindsHuman или Done:

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

Dog обеспечивает Bark, ChacesCar, FindsHuman или Done:

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

Human обеспечивает SayHello или Done:

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, в @Override s и замените любой *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 
1

Ваша проблема в том, что метод сделано должен вернуть родителя, но родитель не обязательно T, но это просто База. Другая проблема заключается в том, что независимо от класса, метод 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.

Edit:

Я добавил новый класс CatOrDog факторизовать Human обработки. Поскольку родительский элемент Human является Farm, я инициализирую новый human с правильным родителем, если он существует. Таким образом, не только вышеупомянутые источники компилируется без ошибок или предупреждений, но он также работает без каких-либо проблем, и она печатает:

cat says meow 
human says hi 
dog says woof 
cat drinks milk 
human says hi 
human says hi 
Смежные вопросы