Jak nazywa się następujący (anty) wzór? Jakie są jego zalety i wady?

28

W ciągu ostatnich kilku miesięcy potknąłem się kilka razy o następującą technikę / schemat. Nie mogę jednak znaleźć konkretnej nazwy, ani nie jestem w 100% pewien wszystkich jej zalet i wad.

Wzór wygląda następująco:

W interfejsie Java zestaw typowych metod jest definiowany jak zwykle. Jednak przy użyciu klasy wewnętrznej domyślna instancja przecieka przez interfejs.

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
         }
    }
 }

Wydaje mi się, że największą zaletą jest to, że programista musi wiedzieć tylko o interfejsie, a nie o jego implementacjach, np. Na wypadek, gdyby szybko chciał utworzyć instancję.

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

Ponadto widziałem tę technikę stosowaną razem ze Springem w celu dynamicznego dostarczania instancji w zależności od konfiguracji. Pod tym względem wygląda również na to, że może to pomóc w modularyzacji.

Niemniej jednak nie mogę pozbyć się wrażenia, że ​​jest to niewłaściwe użycie interfejsu, ponieważ łączy on interfejs z jedną z jego implementacji. (Zasada inwersji zależności itp.) Czy ktoś mógłby mi wyjaśnić, jak nazywa się ta technika, a także jej zalety i wady?

Aktualizacja:

Po pewnym czasie zastanowiłem się ponownie i zauważyłem, że następująca pojedyncza wersja wzorca była używana znacznie częściej. W tej wersji publiczna instancja statyczna jest udostępniana przez interfejs, który jest inicjowany tylko raz (ze względu na to, że pole jest ostateczne). Ponadto instancja jest prawie zawsze pobierana za pomocą Spring lub ogólnej fabryki, która oddziela interfejs od implementacji.

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;

W skrócie: wydaje się, że jest to niestandardowy wzorzec singletonu / fabryki, który w zasadzie pozwala ujawnić instancję lub singleton poprzez interfejs. Jeśli chodzi o wady, kilka zostało wymienionych w odpowiedziach i komentarzach poniżej. Jak dotąd korzyść wydaje się polegać na wygodzie.

Jérôme
źródło
jakiś wzór singletonu?
Bryan Chen
Myślę, że masz rację, że interfejs nie powinien „wiedzieć” o swoich implementacjach. Prawdopodobnie Vehicle.Defaultpowinien zostać przeniesiony do przestrzeni nazw pakietów jako klasa fabryczna, np VehicleFactory.
sierpnia
7
Nawiasem mówiąc,Vehicle.Default.getInstance() != Vehicle.Default.getInstance()
Konrad Morawski
20
Anty-wzorzec tutaj polega na użyciu anty-wzoru analogii samochodowej, ściśle związanego z analogicznym wzorem zwierzęcym. Większość programów nie ma kół ani nóg.
Pieter B
@PieterB: :-)))) Sprawiłeś, że mój dzień!
Doc Brown,

Odpowiedzi:

24

Default.getInstancejest IMHO tylko bardzo specyficzną formą metody fabrycznej , pomieszanej z konwencją nazewnictwa zaczerpniętą ze wzorca singletonu (ale nie będącą implementacją tego ostatniego). W obecnej formie jest to naruszenie „ zasady pojedynczej odpowiedzialności ”, ponieważ interfejs (który już służy do deklarowania interfejsu API klasy) bierze dodatkową odpowiedzialność za zapewnienie domyślnej instancji, która byłaby znacznie lepiej umieszczona w osobnym VehicleFactoryklasa.

Główny problem spowodowany przez ten konstrukt polega na tym, że indukuje on cykliczną zależność między Vehiclei Car. Na przykład w bieżącej formie nie będzie możliwe umieszczenie Vehiclew jednej bibliotece, a Carw innej. Zastosowanie oddzielnej klasy fabrycznej i umieszczenie Default.getInstancemetody rozwiązałoby ten problem. Dobrym pomysłem może być także nadanie tej metodzie innej nazwy, aby uniknąć nieporozumień związanych z singletonami. Rezultatem może być coś takiego

VehicleFactory.createDefaultInstance()

Doktor Brown
źródło
Dzięki za odpowiedź! Zgadzam się, że ten konkretny przykład stanowi naruszenie SRP. Nie jestem jednak pewien, czy nadal stanowi naruszenie w przypadku dynamicznego pobierania implementacji. Np. Za pomocą Springa (lub podobnego frameworka) w celu pobrania określonej instancji zdefiniowanej na przykład przez plik konfiguracyjny.
Jérôme,
1
@ Jérôme: IMHO nazwana nie zagnieżdżona klasa VehicleFactoryjest bardziej konwencjonalna niż konstrukcja zagnieżdżona Vehicle.Default, szczególnie z wprowadzającą w błąd metodą „getInstance”. Chociaż możliwe jest obejście zależności cyklicznej za pomocą Springa, osobiście wolałbym pierwszy wariant ze względu na zdrowy rozsądek. I wciąż pozostawia Ci możliwość przeniesienia fabryki do innego komponentu, zmiany implementacji do pracy bez wiosny itp.
Doc Brown
Często powodem używania interfejsu zamiast klasy jest umożliwienie wykorzystania klas, które mają inne cele, za pomocą kodu wymagającego implementacji interfejsu. Jeśli domyślna klasa implementująca może zaspokoić wszystkie potrzeby kodu, który po prostu potrzebuje czegoś, co implementuje interfejs w „normalny” sposób, określenie sposobu utworzenia instancji wydawałoby się przydatne dla interfejsu.
supercat
Jeśli Car jest bardzo ogólną, standardową implementacją Pojazdu, nie stanowi to naruszenia SRP. Pojazd wciąż ma tylko jeden powód do zmiany, np. Kiedy Google dodaje autodrive()metodę. Twój bardzo ogólny samochód musi się zmienić, aby wdrożyć tę metodę, np. Poprzez zgłoszenie wyjątku NotImplementedException, ale nie stanowi to dodatkowego powodu do zmiany pojazdu.
user949300,
@ user949300: SRP nie jest koncepcją „matematycznie dającą się udowodnić”. A decyzje dotyczące projektowania oprogramowania nie zawsze są „czarno-białe”. Oryginalny kod nie jest tak zły, że nie można go oczywiście użyć w kodzie produkcyjnym, i mogą zdarzyć się przypadki, w których wygoda przeważa nad cykliczną zależnością, jak zauważył OP w swojej „aktualizacji”. Więc proszę przeczytaj moją odpowiedź jako „rozważ, czy zastosowanie oddzielnej klasy fabrycznej nie służy ci lepiej”. Zauważ też, że autor zmienił swoje pierwotne pytanie nieco po udzieleniu odpowiedzi.
Doc Brown
3

Wygląda mi to na wzór Null Object .

Ale to w dużej mierze zależy od sposobu Carimplementacji klasy. Jeśli jest implementowany w sposób „neutralny”, to z pewnością jest to Obiekt zerowy. Oznacza to, że jeśli możesz usunąć wszystkie kontrole zerowe interfejsu i umieścić instancję tej klasy zamiast każdego wystąpienia null, kod nadal musi działać poprawnie.

Również jeśli jest to ten wzorzec, nie ma problemu z utworzeniem ścisłego sprzężenia między samym interfejsem a implementacją obiektu zerowego. Ponieważ obiekt zerowy może znajdować się wszędzie tam, gdzie używany jest ten interfejs.

Euforyk
źródło
2
Witam i dziękuję za odpowiedź. Chociaż jestem całkiem pewien, że w moim przypadku nie zostało to wykorzystane do zaimplementowania wzorca „Null Object”, jest to interesujące wyjaśnienie.
Jérôme,