Comments 35
Ну вообще этой теме уже много лет. Мне кажется, Mario Fusco ее полностью рассмотрел уже года три назад. Даже странно что тут так мало было написано.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Duck extends ArrayList<Runnable> implements Runnable{
@Override
public void run() {
this.stream().forEach(r-> r.run());
}
public static void main(String[] args) {
final Duck mallardDuck = new Duck();
mallardDuck.add(() -> System.out.println("Я кряква"));
mallardDuck.add(() -> System.out.println("Я летаю"));
mallardDuck.add(() -> System.out.println("Кря-кря-кря"));
final Duck rubberDuck = new Duck();
rubberDuck.add(() -> System.out.println("Я резиновая утка"));
rubberDuck.add(() -> System.out.println("Кря-кря-кря"));
final Duck exclusiveDuck = new Duck();
exclusiveDuck.add(() -> System.out.println("Я эксклюзивная утка"));
exclusiveDuck.add(() -> System.out.println("Я изрыгаю пламя"));
List<Duck> ducks = Arrays.asList(mallardDuck,rubberDuck,exclusiveDuck);
ducks.forEach(duck -> {System.out.println("####"); duck.run();} );
}
}
####
Я кряква
Я летаю
Кря-кря-кря
####
Я резиновая утка
Кря-кря-кря
####
Я эксклюзивная утка
Я изрыгаю пламя
ладно, самый примитивный способ заставить только крякнуть это
Map<String, Integer> actMap,
public void act(String action){
this.get(actMap.get(action)).run();
}
можно в Java 8 написать намного короче и чище:
public class Duck extends ArrayList<Runnable> implements Runnable{
Можно ли утку, рождённую от ArrayList
, назвать чистой?
У меня сомнения на этот счёт.
в архитектуре с кучей лямбд потом будет очень сложно разобраться, хотя я не против лямбд, но для статического поведения и улучшения читаемости лучше определять явные интерфейсы, именно с явным именем IFlying, IMoving, и давать их подклассам… а репозиторий если уж динамически модифицируемый нужен, он не существует сам по себе, гдето юзер в UI / или api если это серв, — будет вызывать ваши методы, ему надо показать ваши объекты, что там у каждого объекта за список behaviour-ов, и что он не может делать, и это в коде также не составит труда определить явно через тот же список behaviours в каждом объекте и реверсного списка объектов которые имплементируют конкретный интерфейс (возможно по типу интерфейса или enum)
разделяем уток на интерфейсы FlyingDuck и NonFlyingDuck, и т.п.
В яве будет несколько неудобно потому что нет двойного наследования.
Это в статической типизации.
Можно просто вообще иметь один(!) класс Duck менять методы в рантайме имея Map<Key,Function> dict наподобии более высоких языков ака JS или Python.
Можно просто вообще иметь один(!) класс Duck менять методы в рантайме имея Map<Key,Function> dict наподобии более высоких языков ака JS или Python.
Так в статье и сделано
не думайте что это автоматом удобно всегда. написать 1 раз и только автору понимать — да удобней. но поддерживать даже автору, а тем более новому человеку такой код — ад.
к тому же у вас разные поведения с разным числом аргументов могут быть. это уже Func<T,T2,T3> а гдето Func<T,T2> и т.п
если точно также все объединить под максимальное число аргументов (типо Func<T,T2,T3>) тогда у вас будут неиспользуемые аргументы в реализациях которым они не нужны — что тоже запутывает (типо читаешь код и думаешь — а зачем это суда передается), а если более явно определять где-то будет список Func, гдето список Func<T,T2> гдето 3х аргументов и т.п. то это уже не 1 Map а 3 штуки! И поиск надо будет делать динамически во всех Map, и добавлять только в конкретную. И т.п.
Далее, конкретно по вашему заявлению об «BroilerDuck расширяет класс MallardDuck… У BroilerDuck будут свои подклассы и все они, вместе с BroilerDuck, летать не будут никогда.» это неправильный подход, не надо их наследовать.
Вы объявляете класс BroilerDuck implements IFlying, и никого от нее не наследуете. Если вы хотите иметь общую реализацию в каких-то классах, делайте через компонентное наследование.
«Как избавляться от принудительно унаследованных ненужных интерфейсов?» ну не наследоваться просто так.
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class Duck {
private final String name;
public Duck(String name) {
this.name = name;
}
private Map<Language, Consumer<Duck>> greetings = new HashMap<>();
public void addGreeting(Language language, Consumer<Duck> greeting) {
greetings.put(language, greeting);
}
public void greet(Language language) {
greetings.get(language).accept(this);
}
public String getName() {
return name;
}
public static void main(String[] args) {
Duck donald = new Duck("Donald");
donald.addGreeting(Language.RUS, duck -> System.out.println("Привет, я " + duck.getName()));
donald.addGreeting(Language.ENG, duck -> System.out.println("Hello, I'm " + duck.getName()));
donald.greet(Language.RUS);
donald.greet(Language.ENG);
}
public enum Language {
RUS,
ENG
}
}
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public abstract class Duck<T extends Duck> {
private final String name;
public Duck(String name) {
this.name = name;
}
abstract public T getDuck();
private Map<Language, Consumer<T>> greetings = new HashMap<>();
public void addGreeting(Language language, Consumer<T> greeting) {
greetings.put(language, greeting);
}
public void greet(Language language) {
greetings.get(language).accept(getDuck());
}
public String getName() {
return name;
}
public static void main(String[] args) {
DuckWithAge donald = new DuckWithAge("Donald", 18);
donald.addGreeting(Language.RUS, duck -> System.out.println("Привет, я " + duck.getName() + " мне " + duck.getAge()));
donald.addGreeting(Language.ENG, duck -> System.out.println("Hello, I'm " + duck.getName() + " I'm " + duck.getAge()));
donald.greet(Language.RUS);
donald.greet(Language.ENG);
}
public enum Language {
RUS,
ENG
}
public static class DuckWithAge extends Duck<DuckWithAge> {
private final int age;
public DuckWithAge(String name, int age) {
super(name);
this.age = age;
}
public int getAge() {
return age;
}
@Override
public DuckWithAge getDuck() {
return this;
}
}
}
Наверное, есть и более элегантные варианты, в Kotlin я бы просто extension сделал бы:
fun <T: Duck> T.add(language: Language, greeting:(T) -> Unit) {}
… а теперь давайте попробуем сделать так, чтобы в конкретный метод ("пруд") можно было передать только уток, у которых есть поведение "плавать", и это ограничение проверялось статически.
… а у вас и есть "классическая имплементация паттерна Стратегия":
The strategy pattern
- defines a family of algorithms,
- encapsulates each algorithm, and
- makes the algorithms interchangeable within that family.
Strategy lets the algorithm vary independently from clients that use it
Тот факт, что она сделана на делегатах вместо классов, всего лишь следствие того, что у вас в языке есть легко доступные делегаты (и, заметим, у такой реализации тоже есть недостатки).
а репозиторий если уж динамически модифицируемый нужен, он не существует сам по себе, гдето юзер в UI / или api если это серв, — будет вызывать ваши методы, ему надо показать ваши объекты, что там у каждого объекта за список behaviour-ов, и что он не может делать, и это в коде также не составит труда определить явно через тот же список behaviours в каждом объекте и реверсного списка объектов которые имплементируют конкретный интерфейс (возможно по типу интерфейса или enum)
т.е. вот есть юзер, у него есть UI, в нем вам надо показать список уток которые этот интерфейс имплементируют, через реверс-маппинг т.е List и туда заносить только могущить плавать.
Другое дело что иногда если есть редактор, скажем для игры, юзер может сам настраивать свойства уток, возможность их делать или не делать что-то, то тут лучше пойдет компонентный подход, как в том же unity3d, хотя и там не идеально сделано.
fly(City to, float speed);
sleep(int hours);
Почему у BehaviorRegistry публичный внутренний map? Инкапсуляция не нужна?
Зачем вызывать add через sendTo? Чтобы выпендриться?
В целом получился Publisher-Subscriber, ну ок.
А ещё забыли реализовать пруд из условия и абсолютно всю часть взаимодействия с ним.
Java 8 и паттерн Стратегия