Ответ 1
Добро пожаловать в мир Double Dynamic Dispatch.
AFAIK, вы не можете сделать это легко на Java. Вы можете сделать это двумя способами: "quick'n'dirty" и "Visitor way":
Quick'n'dirty
Вам нужно задать тип объекта, поэтому вам понадобится метод стирки на Fruit, который перенаправит вызов на нужную функцию в соответствии с его типом:
public void wash(Fruit f)
{
if(f instanceof Apple)
{
wash((Apple) f) ;
}
else if(f instanceof Peach)
{
wash((Peach) f) ;
}
else
{
// handle the error, usually through an exception
}
}
Проблема с quick'n'dirty заключается в том, что компилятор не скажет вам, что существует новый действительный Fruit (например, Orange), который в настоящее время не обрабатывается методом стирки.
для посетителей
Вы можете использовать шаблон посетителя для Fruit:
public abstract class Fruit
{
// etc.
public abstract void accept(FruitVisitor v) ;
}
public class Apple extends Fruit
{
// etc.
public void accept(FruitVisitor v)
{
v.visit(this) ;
}
}
public class Peach extends Fruit
{
// etc.
public void accept(FruitVisitor v)
{
v.visit(this) ;
}
}
И определите посетителя как:
public interface class FruitVisitor
{
// etc.
// Note that there are no visit method for Fruit
// this is not an error
public void visit(Apple a) ;
public void visit(Peach p) ;
}
И затем, посетитель для вашего случая стирки:
public class FruitVisitorWasher : implements FruitVisitor
{
// etc.
// Note that there are no visit method for Fruit
// this is not an error
// Note, too, that you must provide a wash method in
// FruitVisitorWasher (or use an anonymous class, as
// in the example of the second edit to access the
// wash method of the outer class)
public void visit(Apple a)
{
wash(a) ;
}
public void visit(Peach p)
{
wash(p) ;
}
}
В конце концов, код может быть
FruitVisitorWasher fvw = new FruitVisitorWasher() ;
for( Fruit f: arguments)
{
f.accept(fvw) ;
}
Et voilà...
У шаблона посетителя есть то преимущество, что компилятор скажет вам, добавили ли вы еще один Fruit (например, Orange), в котором вы закодировали метод accept, и если вы забыли обновить шаблон FruitVisitor для его поддержки.
И тогда шаблон посетителя расширяем: у вас может быть FruitVisitorWasher, FruitVisitorEater, FruitVisitor, независимо от того, добавив их без необходимости изменять Fruit, Apple, Peach и т.д.
Одна ловушка, однако, вы должны вручную записать в каждом классе Fruit метод accept (который является действием copy/paste), потому что именно этот метод выполняет всю работу "зная" правильный тип Fruit.
Изменить
Если вы займетесь решением Quick'n'dirty, решение Samuel Parsonage может быть даже лучше моего:
Как перебрать этот общий список с помощью подстановочных знаков?
который использует отражение Java (я - С++-кодер, поэтому отражение не приходит как естественное решение... Мне плохо на это...). Я нахожу его решение довольно изящным, даже если он каким-то образом пахнет (все проверки будут выполняться во время выполнения, поэтому вам лучше убедиться, что все в порядке... Опять же, на фоне С++: если что-то может быть сделано или ошибка может быть обнаружено во время компиляции, поэтому следует избегать его перемещения во время выполнения)
Изменить 2
Джон Асимптот прокомментировал:Шаблон посетителя, который вы пишете, не является вариантом, так как я не могу добавить метод стирки в Fruit.
Итак, я предлагаю встроенный код для подтверждения стирания(), как ожидается, не будет внутри Fruit to work.
(я изменил FruitVisitor из абстрактного класса на интерфейс, что лучше)
Представьте, что цикл for находится внутри метода bar класса Foo, который имеет свой метод стирки:
public class Foo
{
public wash(Apple a) { /* etc. */ }
public wash(Peach p) { /* etc. */ }
public bar(List<? extends Fruit> arguments)
{
for( Fruit f: arguments)
{
wash(f) ; // we wand the right wash method called.
}
}
}
Вы хотите, чтобы вызвал правильный метод очистки, поэтому приведенный выше код не будет работать правильно.
Повторите использование шаблона FruitVisitor, чтобы исправить этот код. Мы будем использовать анонимный класс внутри метода bar:
public class Foo
{
public void wash(Apple a) { System.out.println("Apple") ; }
public void wash(Peach p) { System.out.println("Peach") ; }
public void bar(List<? extends Fruit> arguments)
{
FruitVisitor fv = new FruitVisitor()
{
public void visit(Apple a)
{
wash(a) ; // will call the wash method
// of the outer class (Foo)
}
public void visit(Peach p)
{
wash(p) ; // will call the wash method
// of the outer class (Foo)
}
} ;
for(Fruit f: arguments)
{
f.accept(fv) ;
}
}
}
Теперь это работает, и нет метода стирки в Fruits.
Обратите внимание, что этот код был протестирован против 1,6 JVM, поэтому я могу предоставить полный код при необходимости.