Zanim zacznę pytanie zaznaczę może co ja rozumiem przez określenia "Interfejs" oraz "Interfejs abstrakcyjny".
- Interfejs to po prostu sposób korzystania z klasy: metody, pola, konstruktor,
throws
w metodzie, to czy zwraca/nie zwracanull
e - wszystko to ma związek z korzystaniem z klasy - a więc jest to interfejs. - Interfejs abstrakcyjny to dla mnie ta część interfejsu struktur klas które są wspólne (np
List.add()
,List.remove()
to części interfejsu abstrakcyjnego, bo niezależnie od implementacji list, dostęp do nich jest taki sam). Od razu widać też że konstruktor nie jest częścią interfejsu abstrakcyjnego, ponieważ różne implementacje mają różne konstruktory.
Wyobraźcie sobie kod - dosyć OOP:
class Application {
User user;
List<Events> events;
Date day(int offset) {
return new Calendar(user, events).nextDay(offset).getDate();
}
}
I teraz chcemy mieć dwie implementacje Calendar
: GregorianCalendar
i JulianCalendar
(oczywiście sytuacja abstrakcyjna). Co zrobi programista:
- Fanatyk Clean Java Wujek Bob code - wyniesie
new Calendar(
do fabryki abstrakcyjnych (GregorianCalendarFactory
,JulianCalendarFactory
)
class Application {
CalendarFactory factory;
User user;
List<Events> events;
Date day(int offset) {
return factory.createCalendar(user, events).nextDay(offset).getDate();
}
}
- Bootcampowiec powie że parametry w konstruktorze to zło, że zależnościami mogą być tylko inne serwisy i trzymanie jakiegokolwiek stanu (nawet immutable) jest be (kocha też kontenery DI) i wyrzuci
User
iList<Event>
z parametrów konstruktora, i pewnie jeszcze dorzuci@Autowired
żeby Spring mu to wstrzyknął.
class Application {
Calendar calendar;
User user;
List<Events> events;
Date day(int offset) {
return calendar.nextDay(user, events, offset).getDate();
}
}
I ja się zacząłem zastanawiać. Czemu żaden język jeszcze nie ma dedykowanej składni do takich przypadków? Np przy użyciu jakiegoś losowego znaku, niech będzie że np §
. Można by zrobić tak, że konstruktor też mógłby być abstrakcyjny (pełniłby funkcję fabryki abstrakcyjnej, tylko że byłby częścią klasy). Mogłoby to wyglądać tak
interface Calendar {
§Calendar(User user, List<Events>); // zaznaczenie że konstruktor też ma być abstrakcyjny
// klasy implementujące go musiałyby mieć konstruktor z taką sygnaturą
Date nextDay(int offset); // zwykła metoda
}
A użycie faktyczne, wyglądałoby tak
class Application {
§Calendar calendar; // to nie jest fabryka ani instancja Calendar - to tylko informacja którą implementację przy konstruowaniu wybrać
User user;
List<Events> events;
Date day(int offset) {
// instancjonowanie jednej z implementacji Calendar - tej implementacji która siedzi w `calendar`
return new calendar§Calendar(user, events).getNext(offset).getDate();
}
}
a chcąc zdecydować o implementacji można by tak
new Application(§JulianCalendar, user, events); // zamiast `new JulianCalendarFactory()`
new Application(§GregorianCalendar, user, events); // ewentualnie GregorianCalendar.class
Podsumowanie
Więc, to rozwiązanie ma kilka wad i zalet:
Wady:
- Ma mniejsze możliwości niż zwykła fabryka (np nie da się zrobić cache'owania/współdzielenia instancji)
- Ciężko zrobić różne implementacje które mają zupełnie różne parametry
- Jasne że use-case'ów dla tego jest dużo mniej niż dla zwykłej fabryki.
Zalety:
- Nie ma szumu z plikami fabryki - czasem mam problem z rozróżnieniem po co są fabryki, bo w projektach w których pracowałem około 50% fabryk jest po to żeby wybrać implementację, a pozostałe 50% po to żeby stworzyć obiekt który wymaga wiele set-up'u. Z
§
byłoby to czytelniejsze - od razu byłoby widać że chodzi o wybranie implementacji. - Kod dla niektórych jest czytelniejszy, bo zamiast
obiekt.createInstance()
mamynew impl§Calendar()
- mam na myśli to że jestnew
, jest konstruktor i podane parametry. Innymi słowy nieco ciaśniejsze przywiązanie do siebie implementacji tego samego interfejsu. - Dzięki temu można mieć jednocześnie instancjonowanie w miejscu instancji oraz parametry w obiektach.
Jak uważacie pomysł za popier****y to mówcie.