Як називається такий (анти) візерунок? Які його переваги та недоліки?


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: :-)))) Ти зробив мій день!
Doc Brown

Відповіді:


24

Default.getInstanceIMHO - це лише дуже специфічна форма фабричного методу , змішана з умовою іменування, взятою з одинарного шаблону (але без здійснення останньої). У нинішній формі це є порушенням " принципу єдиної відповідальності ", оскільки інтерфейс (який вже служить для оголошення API класу) бере на себе додаткову відповідальність за надання екземпляра за замовчуванням, який набагато краще розміститься в окремому VehicleFactoryклас.

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

VehicleFactory.createDefaultInstance()


Дякую за вашу відповідь! Я згоден, що саме цей приклад є порушенням СРП. Однак я не впевнений, чи все-таки це порушення у випадку, якщо реалізація буде динамічно отримана. Наприклад, використовуючи Spring (або подібний фреймворк) для отримання конкретного екземпляра, визначеного, наприклад, конфігураційним файлом.
Jérôme

1
@ Jérôme: IMHO названий не вкладений клас VehicleFactoryє більш звичайним, ніж вкладена конструкція Vehicle.Default, особливо з оманливим методом "getInstance". Хоча це можливо обійти циклічною залежністю за допомогою Spring, я особисто вважаю за краще перший варіант саме через здоровий глузд. І все-таки це залишає вам можливість перевести завод на інший компонент, змінивши реалізацію згодом на роботу без весни тощо.
Doc Brown

Часто причиною використання інтерфейсу, а не класу, є те, що дозволяють класи, які мають інші цілі, використовуватись за кодом, який потребує реалізації інтерфейсу. Якщо клас реалізації за замовчуванням може задовольнити всі потреби коду, який просто потребує чогось, що реалізує інтерфейс "нормальним" способом, то вказівка ​​засобів створення екземпляра може здатися корисною для інтерфейсу.
supercat

Якщо автомобіль - це дуже загальна, стандартна реалізація Транспортного засобу, це не є порушенням SRP. У транспортного засобу є лише одна причина для зміни, наприклад, коли Google додає autodrive()метод. Тоді ваш дуже загальний автомобіль повинен змінитись, щоб застосувати цей метод, наприклад, кинувши NotImplementedException, але це не дає додаткової причини для зміни транспортного засобу.
user949300

@ user949300: SRP не є поняттям "математичний доказ". І рішення програмного забезпечення не завжди є «чорно-білими». Оригінальний код не настільки поганий, що його не можна використовувати у виробничому коді, звичайно, і можуть бути випадки, коли зручність переважає циклічну залежність, як зазначив сам ОП у своєму "оновленні". Тому, будь ласка, прочитайте мою відповідь як "врахуйте, якщо використання окремого фабричного класу не служить вам краще". Зауважте, автор трохи змінив своє початкове запитання після того, як я дав свою відповідь.
Док Браун

3

Це схоже на зразок Null Object для мене.

Але це сильно залежить від того, як Carреалізується клас. Якщо він реалізований "нейтральним" способом, то це, безумовно, Нульовий об'єкт. Це означає, що якщо ви можете видалити всі нульові перевірки інтерфейсу та поставити екземпляр цього класу замість кожного виникнення null, тоді код все одно повинен працювати правильно.

Крім того, якщо це така закономірність, то не виникає проблем у створенні щільної зв'язку між самим інтерфейсом та реалізацією нульового об'єкта. Оскільки нульовий об'єкт може бути скрізь, де використовується зазначений інтерфейс.


2
Привіт і дякую за відповідь. Хоча я майже впевнений, що в моєму випадку це не було використано для реалізації шаблону "Нульовий об'єкт", це цікаве пояснення.
Jérôme
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.