Pull to refresh

Comments 27

В приведенном прмере идет чистый key - value. Такое-то животное, ест такой-то корм. Поэтому оптимально использовать Dictionary. Если нужна возможность расширить список кормов, то валуем нужно сделать лист<>. Куда добавлять корма. А если в валуй загнать обьект класса, то можно реализовать любую логику.

Пишу на C#. Про java знаю что он идентичен. Писал на java/fx давно чуть-чуть, перешел на c#/wpf. Поэтому не эксперт.

Полиморфизм это супер, тоже стараюсь использовать его, когда это не оверхед

В этом случае для каждого животного создадим отдельного сотрудника, который будет кормить только его

Смешивание архитектуры и бизнес-логики. Зоопарк может не захотеть нанимать дополнительных работников только потому, что программисты так отрефакторили код. При этом классы, которые отвечают за реально существующих работников всё же могут появиться и будет полная каша из названий.

Вот, да! А ещё не понятно почему все указанные параметры это не прерогатива животных их определять?

Связывание типа животного с относящейся к нему бизнес-логикой в рамках енам-класса - это уже нарушение SRP и смешение уровней абстракции.

Кроме того, финальный вариант из статьи вовсе не финальный. Что если с каждым животным потребуется связать не только тип работника, но и, например, тип вольера, тип поставщика кормов, режим дня? А что если работник нужен не в одном экземпляре (с мышом справится один человек, а на бегемота нужны двое-трое)? Енам будет обрастать новыми private final полями и дополнительной бизнес-логикой, а проблема нарушения SRP будет только усугубляться.

Я б использовал абстрактную фабрику.

Hidden text

Как это часто бывает, GOF спешит на помощь даже спустя 30 лет

AnimalFactory factory = animalFactory.findFactory(animal)
List<Workers> workers = factory.getWorkers()
List<FoodItem> food = factory.getFood()
...
AnimalFactory findFactory(Animal animal) {
	switch(animal)
  	case HIPPO -> new HippoFactory()
    ...
}

Такой подход максимально развязывает руки. Например, работники могут браться из пула, а не быть строго назначенными на каждый тип животного.

Короче, лучше вернутся к исчерпывающим свитчам из 14 джавы, чем накручивать енам левой логикой. Разрастающийся свитч будет локализован внутри фабрики. Ну а что потребуется дописывать код - тут уж извините, работа у нас такая. Плюс не забываем о TDD. Кейс добавления нового типа животного должен сразу появляться в тестах, что также минимизирует вероятность что-то забыть.

Если животное обрастает зависимостями, как в вашем примере - можно связать его [животное] с конкретной фабрикой, и тогда и не придется нагружать енам логикой, и избавимся от всех перечисленных в статье проблем в методе findFactory().

Естественно, расширять енам кучей полей/логики - плохая идея. Но в тех случаях, когда это оправдано - почему нет? Приведенный зоопарк - лишь наглядный пример. Также, я не согласен, что примеры нарушают SRP: енам имеет единственную причину для изменений - добавление нового животного.

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

Мне кажется, тут лучше стратегия подойдет, а не фабрика.

Совершенно верно, у рабоника может быть список пермишнов (орнитолог не может чесать пузико киту) поэтому этим должна заниматься фабрика.

По-моему термин Worker внутри термина Animal не совсем уместен и вносит путаницу. Перечисление Animal должно содержать информацию о животном в соответствии с так любимой буквой S из магического слова SOLID. И тогда вместо Worker в Animal можно поместить информацию о рационе животного, да и то достаточно условно т.к. можно еще накрутить, что детеныши и взрослые особи питаются по-разному и т.п.(погружаться можно до бесконечности).

А далее, если речь о зоопарке. Есть список работников-ухаживающих за животными, есть понимание кто из работников на рабочем месте (не заболел, не в отпуске), кто умеет кормить тот или иной животного и назначать дневную нагрузку на рабочего, а рабочий лезет в перечисление Animal, получает рацион, берет со склада корм и кормит животного.

В разработке о правильности и неправильности абстракций спорить можно бесконечно. И мое предложение вполне вероятно не идеал. Но мыслями поделиться хотелось с вами и с публикой.

Что обидно, на низком уровне вы все равно никуда не денетесь от GOTO. Весь этот сахар нужен только чтобы набирать код было удобнее, а мест, где человек может ошибиться, меньше. Но и только.

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

Не низком уровне не goto, а электроны. Избавиться от них не выйдет, но какая разница? Код пишут для людей, а не только для машин (если это не личный проект для самого себя).

Как уже выше писали, делаем список обьектов и к нему придефайненый енум таким образом, что бы его числовое значение было индексом соответствующего объекта в списке. И никаких if-case-switch-goto.

Ну а далее уже в зависимости от задачи. Или список-енум статически линкованный, или инициализируемый при старте, или динамически изменяемый.

System.out.printf("Hippo eat: %d %s", , foodQuantity, foodName);

Две запятые? Я программировал на java, но не помню такого? Это опечатка?
Иначе хотелось бы понять, что за магия?

UFO just landed and posted this here
import lombok.Getter;

import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;

class Scratch {

    public enum Animal {
        HIPPO("Grass", BaseWorker::feedHippo),
        PENGUIN("Fish", BaseWorker::feedPenguin),
        MONKEY("Banana", BaseWorker::feedMonkey),
        OWL("Mouse", BaseWorker::feedOwl);

        @Getter
        private final String foodName;
        @Getter
        private final BiConsumer<BaseWorker, Integer> foodCalculation;

        Animal(String foodName, BiConsumer<BaseWorker, Integer> foodCalculation) {
            this.foodName = foodName;
            this.foodCalculation = foodCalculation;
        }
    }

    //@Component - воркер может быть под управлением DI контейнера
    static class BaseWorker {
        //@Autowired - с инжектом внешних зависимостей.
        MenuRepository menuRepository;

        public void feedHippo(int animalCount) {
            //Сложная логика
            int foodQuantity = (int) Math.pow(animalCount, 2);
            System.out.printf("Hippo eat: %d %s", foodQuantity, menuRepository.findMenuFor("Hippo"));
        }

        void feedPenguin(int animalCount) {
            //Сложная логика
            int foodQuantity = (int) (Math.pow(animalCount, 3) / 2);
            System.out.printf("Penguin eat: %d %s", foodQuantity, menuRepository.findMenuFor("Penguin"));
        }

        void feedMonkey(int animalCount) {
            //Сложная логика
            int foodQuantity = animalCount * 10;
            System.out.printf("Penguin eat: %d %s", foodQuantity, menuRepository.findMenuFor("Monkey"));
        }

        void feedOwl(int animalCount) {
            //Сложная логика
            int foodQuantity = animalCount * 3;
            System.out.printf("Penguin eat: %d %s", foodQuantity, menuRepository.findMenuFor("Owl"));
        }
    }
}

Мне кажется, можно чутка дальше подход развить...
Вариант более лаконичный в плане создания классов, позволяет инжектить зависимости и менее хардкордный в плане воркеров.

Чето как-то не очень? А если кол-во наименований животных будет постоянно увеличиваться?

Идея интересная, однако не стоит так делать повсеместно. Здесь вы инкапсулируете в класс животного свойства, которые относятся больше к компетенции ZooWorker-а, и не имеют большого смысла в отрыве от него. Например вот у нас в зоопарке открылся ветеринарный отдел, и теперь снова будем нагружать бедных животных новыми свойствами и всей логикой, относящейся к ветеринарной службе? В итоге когда предметная область вырастет, ваши животные превратятся в пухлых монстров.

Идея интересная, но на SOLID тут забили огромный болт.

Кажется, что тут уместнее абстрактный класс Animal и его имплементации с реализацией нужных методов / свойств.
А перечисления я привык использовать как удобную замену констант, которая исчезает при компиляции и не несет оверхеда.

Хороший велосипед ты изобрел. Перейдем к читабельности/ поддерживаемости кода в рамках не микросервиса(да даже в нем) и монолита.

Я только учус. Имхо, автор вообще не понимает, как работает jvm и что скрывается за реализацей енум. Спасибо за мнение, придете в команду, не забудьте навязать! Отпишитесь - похохочем!

Что красивого в том, чтобы захардкодить этот зоопарк? И при любом пополнении или смерти животного переписывать текст программы? Может, я кощунственные слова скажу, но я б загнал зоопарк – людей, зверей, пищу, зарплатные ведомости – в простенькую БД. И ни в одной строке программы не будут упомянуты "Monkey" или "Сторож дядя Петя".

От ООП что-то совсем мало осталось.

В самом общем случае мы имеем 3 класса: ZooAnimal, ZooKeeper, Food.

  • В ZooAnimal у нас инкапсулируется метод eat(Food), который внутри себя решает, ест животное это или выбрасывает исключение, что животное сдохло. Чтобы понять, ест ли это животное или нет, можно при создании животного передать список того, что же оно ест. И есть метод - дай мне доступную еду.

  • В ZooKeeper у нас инкапсулирует метод feed(List<ZooAnimal> animals, List<Food> food), который просто перебирает животных и к каждому подбирает первую попавшуюся еду из списка доступных у животного. Метод поиска еды можно усложить(он в ZooKeeper) сколько угодно - прикрутить расчет и все такое. Но в таком виде он примитивный - дай мне первую попавшуюся еду, которая соответствует еде из списка доступных.

  • Дальше просто создаем список животных, еду и передаем это все ZooKeeper.

  • Можно усложнять и добавлять классы, но откуда в этой задаче switch? ООПе же.

В примере животные представляют собой не стандартные объекты, которые создаются/изменяются/уничтожаются в процессе работы программы. Это заранее определенный перечень уникальных в приложении животных. Это енам, и так задумано для примера. Вместо них можно взять дни недели, или список планет в солнечной системе, или любой другой ограниченный список объектов, который если и изменяется, то крайне редко. И зачастую, если подумать, может оказаться, что логика, зашитая в свиче, принадлежит конкретному объекту в перечислении.

Я могу привести другой пример: математические операции.

public enum Operation {
        PLUS,
        MINUS,
        MULTIPLY,
        DIVIDE;
}

Я надеюсь, никто не скажет, что они могут умирать, или может оказаться, что у них появится вторая функция? Так вот, возьмем абстрактный метод int apply(int a, int b, Operation operation). Как бы вы реализовали его - запихнули бы внутри свич кейс, или реализовали бы для каждой операции отдельный класс? А может логичнее в енам добавить поле ToIntBiFunction<Integer, Integer> func, и вызывать его для произведения операции? Енам - это такой же класс, как и любой другой, почему же мы не можем завезти логику для объектов, перечень которых ограничен?

Sign up to leave a comment.

Articles