Czy abstrakcyjne konstruktory mogłyby mieć sens?

Odpowiedz Nowy wątek
2019-05-09 19:24
0

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 zwraca nulle - 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 i List<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() mamy new impl§Calendar() - mam na myśli to że jest new, 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.


char mander; bool basaur;
Zaawansowana biblioteka T-Regx do wyrażeń regularnych w PHP
edytowany 3x, ostatnio: TomRiddle, 2019-05-09 19:27

Pozostało 580 znaków

2019-05-09 22:56
0
jarekr000000 napisał(a):

Zasadniczo mocno się mylisz, że żaden język nie ma. Bo jak najbardziej ma.

W trochę koślawy sposób dochodzisz do koncepcji typeclass.

Mam IMO lepszy przykład w postaci interfejsu JSONSerializable.
Każdy umi zrobić:

interface JSONSerializable {
  JSON toJSON();
}

i zaimplementować,
i jest polimorfizm,
i spoko.

Ale napiszmy teraz interfejs FromJSON.
Tak żeby była metoda, fromJSON -> T -= gdzie T to nasz typ.

Ta co szukamy to w istocie konstuktor - polimorficzny. I w takich językach jak Scala czy Haskell rzecz zupełnie normalna. Do uzyskania dzięki tzw. Typeclass.
(W Scali może lekko zabawnie, ale istnieje przyjęta konwencja jak to się robi).

Ale wiesz że ta implementacja miałaby być wybierana w runtime'ie, nie? O to się cały problem rozchodzi.


char mander; bool basaur;
Zaawansowana biblioteka T-Regx do wyrażeń regularnych w PHP

Pozostało 580 znaków

2019-05-09 22:56
0

Nie rozumiem tylko celu tego. Interface definiuje zachowanie, a nie sposób w jaki to ma być "wykonane"


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ

Pozostało 580 znaków

2019-05-09 22:59
0
danek napisał(a):

Nie rozumiem tylko celu tego. Interface definiuje zachowanie, a nie sposób w jaki to ma być "wykonane"

Matko boska SPECJALNIE PO TO NAPISAŁEM WSTĘP na początku postu:

TomRiddle napisał(a):

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 zwraca nulle - 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.

char mander; bool basaur;
Zaawansowana biblioteka T-Regx do wyrażeń regularnych w PHP
No i z ego nie wynika dlaczego niby w Runtime. Co by to Ci miało dać, i co to za różnica? - jarekr000000 2019-05-10 07:47
Wybór w runtime dałby pewnie możliwość hot-deploymentu. - yarel 2019-05-10 09:14

Pozostało 580 znaków

2019-05-10 00:57
2

No ale własnie pola i konstruktor to nie są elementy interface tylko "interface" konkretnej klasy. Czemu chcesz narzucać jakiejś implementacji co dokładnie ma przyjmować w konstruktorze? Może wynika to z tego, że nie do końca rozumiem co chcesz zrobić? Jaka ma być wartość dodana tej możliwości?


Spring? Ja tam wole mieć kontrole nad kodem ᕙ(ꔢ)ᕗ

Pozostało 580 znaków

2019-05-10 09:09
0
danek napisał(a):

No ale własnie pola i konstruktor to nie są elementy interface tylko "interface" konkretnej klasy. Czemu chcesz narzucać jakiejś implementacji co dokładnie ma przyjmować w konstruktorze? Może wynika to z tego, że nie do końca rozumiem co chcesz zrobić? Jaka ma być wartość dodana tej możliwości?

Na prawdę chcesz się kłócić o nazewnictwo...?

SPECJALNIE PO TO NAPISAŁEM WSTĘP W PIERWSZYM POŚCIE.

Dobrze, specjalnie dla Ciebie zmienię słownictwo.

  • Interfejs - Twoje użycie słowa interface - we wstępie jako "interfejs"
  • Maciek - Twoje użycie słowa "interface" - we wstępie jako" interfejs abstrakcyjny"

Mówię to już trzeci raz. Wartością dodaną miałaby być Dedykowana składania do wyboru implementacji Maćka przy instancjonowaniu.

Dokładnie tak samo jak -> jest już dedykowaną skladnią do instancjonowania interfejsów abstrakcyjnych. Nie wierzę że muszę to programistom mówić trzeci raz.


char mander; bool basaur;
Zaawansowana biblioteka T-Regx do wyrażeń regularnych w PHP
edytowany 3x, ostatnio: TomRiddle, 2019-05-10 09:11

Pozostało 580 znaków

2019-05-10 09:20
1
TomRiddle napisał(a):

Dokładnie tak samo jak -> jest już dedykowaną skladnią do instancjonowania interfejsów abstrakcyjnych. Nie wierzę że muszę to programistom mówić trzeci raz.

Zachowujesz się jak meNADŻER. Przyjmij, że jeżeli musisz ileś razy powtarzać, to może po prostu nie piszesz jasno. Jest taka możliwość, potencjalnie....
Nie wiem czy nie lepiej by było jakbyś jeszcze raz opisał bardziej rzeczywisty przypadek - z kontekstem. Mam podejrzenie XY.

Bo jeśli

Wartością dodaną miałaby być Dedykowana składania do wyboru implementacji Maćka przy instancjonowaniu.

To ewidentnie takie coś juz istnieje i nazywa sie new - daje możliwośc wyboru implementacji przy instancjonowaniu.
Tytlko nadal nmyśle, że nie o to chodzi...

A wracając do pierwszego postu również Templaty w C++ jak najbardziej twój przypadek obsługują.


Bardzo lubie Singletony, dlatego robię po kilka instancji każdego.
edytowany 3x, ostatnio: jarekr000000, 2019-05-10 09:24
Widzę że nie przeczytałeś dokładnie mojego pierwszego posta, skoro uważasz że to co sugeruję to jest new :| - TomRiddle 2019-05-10 10:10
Nie jestem/staram się nie zachowywać jak menadżer, bo nie na tym polega moja praca. Staram się posługiwać raczej tylko argumentami merytorycznymi, bo zazwyczaj tylko takie działają na kolegów po fachu. - TomRiddle 2019-05-10 10:11
Ale zgadzam się że gdzieś w naszym kanale komunikacyjnym jest utrudnienie, co do porozumiewania się ze zrozumieniem, i nie koniecznie jest to server (iykwim). - TomRiddle 2019-05-10 10:12

Pozostało 580 znaków

2019-05-10 09:36
3

@TomRiddle
Cały pomysł z abstrakcyjnym konstruktorem generalnie przeczy zasadzie polimorfizmu, bo klasa (czy interfejs) nadrzędna musi wiedzieć jakie zależności są potrzebne klasom dziedziczącym. W ogólnym rozrachunku takie coś nie powinno mieć miejsca.

Dedykowana zwięzła składnia do skracania kodu hierarchi klas z takimi samymi konstruktorami prowadziłaby do tego, że ludzie na siłę by takie rzeczy tworzyli.

Zamiast Calendar mógłbym mieć np UserRepository i podklasy InMemoryUserRepository oraz DbUserRepository. DbUserRepository musi przyjmować pulę połączeń do bazki, a dla InMemoryUserRepository taka pula jest niepotrzebna. Gdybym chciał wstawić taki abstrakcyjny konstruktor jak ty chcesz to wstawiłbym do niego pulę, a w InMemoryUserRepository bym go zignorował.

interface UserRepository {
  constructor UserRepository(DbConnectionPool dbConnPool);
}
class DbUserRepository implements UserRepository {
  DbUserRepository(DbConnectionPool dbConnPool) {
    // tutaj zapisuję dbConnPool
  }
}
class InMemoryUserRepository implements UserRepository {
  InMemoryUserRepository(DbConnectionPool ignored) {
    // tutaj ignoruję referencję do puli połączeń bazodanowych
  }
}

Taki abstrakcyjny konstruktor w praktyce to byłby raczej antywzorzec, moim zdaniem.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit, 2019-05-10 09:37

Pozostało 580 znaków

2019-05-10 10:08
0
Wibowit napisał(a):

@TomRiddle
Cały pomysł z abstrakcyjnym konstruktorem generalnie przeczy zasadzie polimorfizmu, bo klasa (czy interfejs) nadrzędna musi wiedzieć jakie zależności są potrzebne klasom dziedziczącym. W ogólnym rozrachunku takie coś nie powinno mieć miejsca.

A jak to się ma to tego że korzystając z fabryki, do instancjonowania konkretnych implementacji korzysta się z jednej metody calendarFactory.create(user, events);. Tutaj też nie ma informacji o zależnościach (zapewne wszystkie zależności ma Factory).

Czemu przypadek z dedykowaną składnią też nie mógłby brać zależności "z góry"?


char mander; bool basaur;
Zaawansowana biblioteka T-Regx do wyrażeń regularnych w PHP

Pozostało 580 znaków

2019-05-10 10:20
1

1) Fabryki to zupełnie inny kod niż tworzone instancje. Fabryki zwykle tworzy się w projekcie, w którym znasz wszystkie tworzone podklasy.
2) Fabryki też mogą mieć swoje zależności, czyli teoretycznie możesz mieć:

class DbUserRepositoryFactor implements UserRepositoryFactory {
  DbUserRepositoryFactory(DbConnectionPoolFactory aaa) { ... }
  UserRepository makeUserRepository() { ... }
}

Fabryki czy ogólnie DI możesz zrobić na milion sposobów, zamiast wspawać to w jeden sztywny sposób do klasy z logiką biznesową.


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 1, gości: 0, botów: 0

Użytkownik: Kamil Żabiński