Как называется следующий (анти) шаблон? Каковы его преимущества и недостатки?

28

За последние несколько месяцев я несколько раз спотыкался о следующей технике / схеме. Тем не менее, я не могу найти конкретное имя, и я не уверен на 100% во всех его преимуществах и недостатках.

Шаблон идет следующим образом:

В интерфейсе Java набор общих методов определяется как обычно. Однако при использовании внутреннего класса экземпляр по умолчанию пропускается через интерфейс.

public interface Vehicle {
    public void accelerate();
    public void decelerate();

    public static class Default {
         public static Vehicle getInstance() {
             return new Car(); // or use Spring to retrieve an instance
         }
    }
 }

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

 Vehicle someVehicle = Vehicle.Default.getInstance();
 someVehicle.accelerate();

Кроме того, я видел, как этот метод используется вместе с Spring для динамического предоставления экземпляров в зависимости от конфигурации. В связи с этим, похоже, что это может помочь с модульностью.

Тем не менее, я не могу избавиться от ощущения, что это неправильное использование интерфейса, поскольку он связывает интерфейс с одной из его реализаций. (Принцип инверсии зависимостей и т. Д.). Может ли кто-нибудь объяснить мне, как называется этот метод, а также его достоинства и недостатки?

Обновить:

После некоторого времени для рассмотрения я перепроверил и заметил, что следующая одноэлементная версия шаблона использовалась гораздо чаще. В этой версии общедоступный статический экземпляр предоставляется через интерфейс, который инициализируется только один раз (из-за того, что поле является окончательным). Кроме того, экземпляр почти всегда извлекается с использованием Spring или универсальной фабрики, которая отделяет интерфейс от реализации.

public interface Vehicle {
      public void accelerate();
      public void decelerate();

      public static class Default {
           public static final Vehicle INSTANCE = getInstance();

           private static Vehicle getInstance() {
                return new Car(); // or use Spring/factory here
           }
      }
 }

 // Which allows to retrieve a singleton instance using...
 Vehicle someVehicle = Vehicle.Default.INSTANCE;

В двух словах: кажется, что это пользовательский шаблон синглтона / фабрики, который в основном позволяет представить экземпляр или синглтон через его интерфейс. Что касается недостатков, некоторые из них были названы в ответах и ​​комментариях ниже. Пока что преимущество заключается в его удобстве.

Жером
источник
какой-то синглтон?
Брайан Чен
Я думаю, что вы правы, что интерфейс не должен «знать» о своих реализациях. Вероятно, Vehicle.Defaultследует перенести в пространство имен пакета как фабричный класс, например VehicleFactory.
Авгурар
7
Кстати,Vehicle.Default.getInstance() != Vehicle.Default.getInstance()
Конрад Моравский
20
Антипаттерном здесь является использование антипаттерна автомобильной аналогии, тесно связанного с антипаттерном аналога животного. У большинства программ нет колес или ног.
Питер Б
@PieterB: :-)))) Вы сделали мой день!
Док Браун

Ответы:

24

Default.getInstanceИМХО - это просто очень специфическая форма фабричного метода , смешанного с соглашением об именах, взятым из шаблона синглтона (но не являющегося реализацией последнего). В текущей форме это является нарушением « принципа единой ответственности », потому что интерфейс (который уже служит для объявления API класса) берет на себя дополнительную ответственность за предоставление экземпляра по умолчанию, который был бы намного лучше помещен в отдельныйVehicleFactory учебный класс.

Основная проблема, вызванная этой конструкцией, заключается в том, что она вызывает циклическую зависимость между Vehicleи Car. Например, в текущей форме не удастся разместить Vehicleв одной библиотеке, а Carв другой. Использование отдельного класса фабрики и размещение там Default.getInstanceметода решит эту проблему. Также может быть хорошей идеей дать этому методу другое имя, чтобы избежать путаницы, связанной с одиночками. Таким образом, результат может быть что-то вроде

VehicleFactory.createDefaultInstance()

Док Браун
источник
Спасибо за Ваш ответ! Я согласен, что этот конкретный пример является нарушением ПСП. Тем не менее, я не уверен, является ли это все еще нарушением в случае, если реализация извлекается динамически. Например, используя Spring (или аналогичную структуру) для получения конкретного экземпляра, определенного, например, файлом конфигурации.
Жером
1
@ Jérôme: ИМХО именованный не вложенный класс VehicleFactoryявляется более традиционным, чем вложенная конструкция Vehicle.Default, особенно с вводящим в заблуждение методом getInstance. Хотя с помощью Spring можно обойти циклическую зависимость, я лично предпочел бы первый вариант только из-за здравого смысла. И, тем не менее, у вас остается возможность перенести фабрику на другой компонент, затем изменить реализацию для работы без Spring и т. Д.
Док Браун
Часто причина использования интерфейса, а не класса, состоит в том, чтобы позволить классам, которые имеют другие цели, быть пригодными для использования кодом, который требует реализации интерфейса. Если класс реализации по умолчанию может удовлетворить все потребности кода, который просто нуждается в чем-то, что реализует интерфейс «обычным» способом, то определение способа создания экземпляра может показаться полезным для интерфейса.
суперкат
Если Car - это очень общая, стандартная реализация Vehicle, это не является нарушением SRP. Автомобиль все еще имеет только одну причину для изменения, например, когда Google добавляет autodrive()метод. Ваш очень универсальный Автомобиль должен затем измениться, чтобы реализовать этот метод, например, с помощью исключения NotImplementedException, но это не дает дополнительной причины для изменения в Транспортном средстве.
user949300
@ user949300: SRP не является «математически доказуемой» концепцией. И решения по разработке программного обеспечения не всегда "черно-белые". Оригинальный код не так уж плох, его нельзя было использовать в производственном коде, конечно, и могут быть случаи, когда удобство перевешивает циклическую зависимость, как отметил сам ОП в своем «обновлении». Поэтому, пожалуйста, прочитайте мой ответ как «подумайте, не поможет ли вам использование отдельного фабричного класса лучше». Обратите внимание, что автор немного изменил свой первоначальный вопрос после того, как я дал свой ответ.
Док Браун
3

Это похоже на нулевой объект шаблон для меня.

Но это сильно зависит от того, как Carреализован класс. Если он реализован «нейтрально», то это определенно Нулевой Объект. Это означает, что если вы можете удалить все нулевые проверки на интерфейсе и поместить экземпляр этого класса вместо каждого вхожденияnull , то код все равно должен работать правильно.

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

Euphoric
источник
2
Привет и спасибо за ответ. Хотя я вполне уверен, что в моем случае это не использовалось для реализации шаблона «Null Object», это интересное объяснение.
Жером