Jak uniknąć szalonej liczby interfejsów w interfejsie użytkownika z wstrzykiwaniem zależności?

8

Problem
Ostatnio dużo czytałem o tym, że Singletony są złe i jak lepsze jest wstrzykiwanie zależności (które rozumiem jako „używanie interfejsów”). Kiedy zaimplementowałem część tego z wywołaniami zwrotnymi / interfejsami / DI i przestrzegając zasady segregacji interfejsu, skończyło się to dość bałaganem.

Zależności rodzica interfejsu użytkownika były w zasadzie połączone ze wszystkimi jego elementami potomnymi, więc im wyżej w hierarchii był element interfejsu użytkownika, tym bardziej rozdęty był jego konstruktor.

Na szczycie hierarchii interfejsu użytkownika znajdowała się klasa aplikacji, zawierająca informacje o bieżącym wyborze i odniesienie do modelu 3d, który musi odzwierciedlać zmiany. Klasa aplikacji wdrożyła 8 interfejsów, a to dopiero około jedna piąta nadchodzących produktów (/ interfejsów)!

Obecnie pracuję z singletonem zawierającym bieżący wybór i elementy interfejsu mające funkcję aktualizacji. Ta funkcja spływa po drzewie interfejsu użytkownika i elementach interfejsu użytkownika, a następnie w razie potrzeby uzyskuje dostęp do bieżącego singletonu wyboru. W ten sposób kod wydaje mi się czystszy.

Pytanie
Czy singleton może być odpowiedni dla tego projektu?
Jeśli nie, to czy jest jakaś podstawowa wada w moim myśleniu i / lub implementacji DI, co czyni ją tak uciążliwą?

Dodatkowe informacje o projekcie
Typ: Koszyk na zakupy do mieszkań, z dzwonkami i gwizdkami
Rozmiar: 2 osobo-miesiące na kod i interfejs użytkownika
Utrzymanie: Brak bieżących aktualizacji, ale może później „wersja 2.0”
Środowisko: Używanie C # w Unity, który używa encji System elementów

W prawie wszystkich przypadkach interakcja użytkownika wyzwala kilka działań. Na przykład, gdy użytkownik wybierze element

  • część interfejsu użytkownika pokazująca ten element i jego opis musi zostać zaktualizowana. W tym celu musi również uzyskać informacje z modelu 3d w celu obliczenia ceny.
  • w dalszej części interfejsu należy zaktualizować ogólną cenę całkowitą
  • należy wywołać odpowiednią funkcję w klasie na modelu 3d, aby wyświetlić tam zmiany
R. Schmitz
źródło
DI nie polega tylko na korzystaniu z interfejsów, ale także na wymianie konkrecji, co jest szczególnie przydatne w sferze testów jednostkowych ...
Robbie Dee,
1
Ponadto nie widziałem jeszcze problemu, w którym singleton jest preferowanym rozwiązaniem. Kanoniczny przykład, który ludzie zawsze twierdzą, to piszący dzienniki, ale nawet tutaj możesz chcieć pisać do wielu różnych dzienników (błąd, debugowanie itp.).
Robbie Dee,
@RobbieDee Tak, DI dotyczy więcej, ale nie widzę sytuacji, w której używanie interfejsu nie jest DI. A jeśli singleton nie jest preferowanym rozwiązaniem - co również zakładam - to dlaczego preferowane rozwiązanie jest tak niechlujne? To jest moje główne pytanie, ale chciałem zachować je otwarte, ponieważ czasami ogólna zasada nie ma zastosowania.
R. Schmitz
Układ interfejsu-konkrecji jest również cechą szydzących ram. Byli także w językach OO od dobrych kilku dziesięcioleci ...
Robbie Dee,
Nie rozumiem o co ci chodzi.
R. Schmitz

Odpowiedzi:

4

Myślę, że pytanie jest objawem, a nie rozwiązaniem.

Ostatnio dużo czytałem o tym, że Singletony są złe i jak lepsze jest wstrzykiwanie zależności (które rozumiem jako „używanie interfejsów”). Kiedy zaimplementowałem część tego z wywołaniami zwrotnymi / interfejsami / DI i przestrzegając zasady segregacji interfejsu, skończyło się to dość bałaganem.

Rozwiązanie szukające problemu; to i nieporozumienie prawdopodobnie psuje projekt. Czy przeczytałeś to SO pytanie o DI vs Singleton, różne koncepcje czy nie? Przeczytałem, że to po prostu opakowanie singletonu, aby klient nie musiał sobie radzić z singletonem. It.is.just.good.old.encapsulation. Myślę, że to jest na miejscu.


im wyżej w hierarchii był element interfejsu użytkownika, tym bardziej rozdęty był jego konstruktor.

Najpierw zbuduj mniejsze bity, a następnie przekaż je konstruktorowi rzeczy, do której należą, i przekaż tę większą rzecz konstruktorowi kolejnej większej rzeczy ...

Jeśli masz skomplikowane problemy konstrukcyjne, użyj wzoru fabrycznego lub konstruktora. Najważniejsze jest to, że złożona konstrukcja jest wciągnięta we własną klasę, aby inne klasy były schludne, czyste, zrozumiałe itp.


Klasa aplikacji wdrożyła 8 interfejsów, a to dopiero około jedna piąta nadchodzących produktów (/ interfejsów)!

To brzmi jak brak możliwości rozszerzenia. Brak projektu rdzenia i wszystko jest wbijane od góry. Powinna istnieć większa oddolna konstrukcja, skład i dziedziczenie.

Zastanawiam się, czy twój projekt jest „top heavy”. Wygląda na to, że staramy się, aby jedna klasa była wszystkim lub czymkolwiek innym, czym mogłaby być. A fakt, że jest to klasa interfejsu użytkownika, a nie klasa domeny biznesowej, naprawdę sprawia, że ​​zastanawiam się nad oddzieleniem problemów.

Zrewiduj swój projekt od samego początku i upewnij się, że istnieje solidna, podstawowa abstrakcja produktu, na której można zbudować bardziej złożone lub różnej kategorii produkcje. W takim razie lepiej mieć niestandardowe kolekcje tych rzeczy, aby mieć gdzieś umieścić funkcjonalność „na poziomie kolekcji” - jak wspomniany „trzeci model”.


... model 3d, który musi odzwierciedlać zmiany.

Wiele z nich może mieścić się w niestandardowych klasach kolekcji. Może być również niezależną strukturą klasową ze względu na głębię i złożoność. Te dwie rzeczy nie wykluczają się wzajemnie.

Przeczytaj o strukturze odwiedzin. Jest to pomysł, aby całe uchwyty funkcjonalności były podłączone abstrakcyjnie do różnych typów.


Projektowanie i DI

90% wszystkich zastrzyków zależności, jakie kiedykolwiek wykonasz, to przekazanie parametrów konstruktora. Tak mówi quy, który napisał książkę . Zaprojektuj dobrze swoje klasy i unikaj zanieczyszczenia tego procesu myślowego z niejasnym pojęciem o potrzebie użycia kontenera DI. Jeśli potrzebujesz, Twój projekt zasugeruje ci to, że tak powiem.


Skoncentruj się na modelowaniu domeny zakupów mieszkań.

Unikaj podejścia Jessiki Simpson do projektowania : „Zupełnie nie wiem, co to znaczy, ale chcę tego”.

Następujące są po prostu złe:

  • Mam używać interfejsów
  • Nie powinienem używać singletona
  • Potrzebuję DI (cokolwiek to jest)
  • Powinienem używać kompozycji, a nie dziedziczenia
  • Powinienem unikać dziedziczenia
  • Potrzebuję użyć wzorów
radarbob
źródło
Próbowałem pozbyć się wszystkich singletonów, z których korzystałem, a niektóre z rzeczy, które powiedziałeś, wydarzyły się mniej więcej automatycznie. Reszta też ma sens i posłucham twojej rady, przeczytam o schemacie odwiedzin itp. W sumie dobrze napisana odpowiedź, dziękuję!
R. Schmitz
Tylko jedna kwestia i nie staram się tutaj być wampirem pomocniczym, ale była to swego rodzaju część pierwotnego pytania: ogólny konsensus (i opiera się na ważnych powodach) wydaje się, że tak naprawdę nie należy użyj singletona. Piszesz: „Nie powinienem używać singletonu” jest niewłaściwy ”- w jakiej sytuacji wtedy byłoby stosownym?
R. Schmitz
Mówię tylko: „to zależy”. Zbyt często widzę maksymy przyjęte dosłownie.
radarbob
3

„Hierarchia klas” jest trochę czerwoną flagą. Hipotetyczny: strona internetowa z 5 widżetami. Strona nie jest przodkiem żadnego z widżetów. Może zawierać odniesienie do tych widżetów. Ale to nie jest przodek. Zamiast dziedzicznej klasy, rozważ użycie kompozycji. Każdy z 5 widżetów można zbudować osobno, bez odniesienia do innych widżetów lub strony głównej. Strona główna jest następnie konstruowana z wystarczającą ilością informacji, aby zbudować stronę podstawową i rozmieścić przekazane do niej obiekty widżetów (Kolekcja). Strona odpowiada za układ itp., Ale nie za konstrukcję i logikę widżetów.

Po użyciu kompozycji DI jest twoim najlepszym przyjacielem. DI pozwala zamienić każdy widżet na dowolną wersję lub typ widżetu zdefiniowany w DI. Budowa widżetów jest przechwytywana w DI i jest oddzielna od strony głównej. Być może nawet skład Kolekcji można zdefiniować w DI. Strona główna wykonuje układ w oparciu o przekazane do niej widżety, tak jak powinna. Nie są wymagane żadne zmiany w konstruktorze strony głównej. Strona główna potrzebuje tylko logiki do wykonania układu widżetów i przekazania informacji do / z widżetu w oparciu o zdefiniowane interfejsy.

Zamiast przepuszczać słuchaczy w górę iw dół łańcucha kompozycji, wstrzyknij kolekcję słuchaczy do tych widżetów, które tego potrzebują. Wstrzyknij kolekcję do wydawcy, aby mógł opublikować w kolekcji. Wstrzyknij kolekcję do słuchaczy, aby mogli dodać się do kolekcji. Kolekcja obejmuje wszystkie obiekty w łańcuchu kompozycji.

scorpdaddy
źródło
Niestety, „hierarchia klas” była w tym miejscu niepoprawna; w rzeczywistości nie była to hierarchia klas, ale raczej hierarchia interfejsu użytkownika. „Widżety” nie są podklasami klasy aplikacji, ale „strona” zawiera odniesienia do nich w celu opublikowania aktualizacji interfejsu użytkownika. Z tą strukturą było to bardzo testowalne, ale było też sporo narzutów, szczególnie w konstruktorach, po prostu przekazując wielu słuchaczy dla ich dzieci (UI).
R. Schmitz
@ R. Schmitz: Czy koszty ogólne miały znaczenie? Czy interfejs użytkownika był powolny i czy winny był projekt, który opisałeś?
Robert Harvey
@ R. Schchitz Czy możesz rozwinąć kwestię „przekazywania wielu słuchaczy”? Być może moje doświadczenie z DI w C # jest niskie, ale coś mi mówi, że nie byłoby konieczne (przynajmniej w konstruktorze) z odpowiednim projektem.
Katana314
@RobertHarvey Brak utraty wydajności, chodzi tylko o czytelność.
R. Schmitz
@ Katana314 Na przykład, gdy element jest dodawany, model 3dmodel musi zostać zaktualizowany i nie należy do żadnej kategorii produktów, więc jest przywoływany w klasie aplikacji, która nasłuchuje dodawanych / usuwanych / zmienianych elementów. W końcu byłoby prawie tak samo dla każdej kategorii produktów. Inne części interfejsu użytkownika również reagują (i nasłuchują), ale wiadomość musi dotrzeć na górę, ponieważ tam znajduje się odwołanie do modelu 3d i rzeczywiste dane (które następnie są również aktualizowane).
R. Schmitz