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.
źródło
Vehicle.Default
powinien zostać przeniesiony do przestrzeni nazw pakietów jako klasa fabryczna, npVehicleFactory
.Vehicle.Default.getInstance() != Vehicle.Default.getInstance()
Odpowiedzi:
Default.getInstance
jest 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 osobnymVehicleFactory
klasa.Główny problem spowodowany przez ten konstrukt polega na tym, że indukuje on cykliczną zależność między
Vehicle
iCar
. Na przykład w bieżącej formie nie będzie możliwe umieszczenieVehicle
w jednej bibliotece, aCar
w innej. Zastosowanie oddzielnej klasy fabrycznej i umieszczenieDefault.getInstance
metody 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ś takiegoVehicleFactory.createDefaultInstance()
źródło
VehicleFactory
jest bardziej konwencjonalna niż konstrukcja zagnieżdżonaVehicle.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.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.Wygląda mi to na wzór Null Object .
Ale to w dużej mierze zależy od sposobu
Car
implementacji 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ąpienianull
, 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.
źródło