Sprzęganie. Najlepsze praktyki

11

Kontynuując ten wątek, zacząłem

Wzór Singletona

Sprawiło, że pomyślałem o tym, jak sprzężone są moje klasy i jak najlepiej osiągnąć luźne połączenie. Proszę pamiętać, że jestem nowym programistą (4 miesiące do mojej pierwszej pracy) i to jest naprawdę pierwsza uwaga, którą poświęciłem temu i bardzo chętnie rozumiem tę koncepcję.

Czym więc właściwie jest luźne sprzężenie z ciężkim sprzężeniem? W moim bieżącym (i pierwszym projekcie) pracuję nad projektem ac # winforms, w którym sekcja GUI tworzy obiekty i subskrybuje ich zdarzenia, gdy są one wyzwalane, GUI tworzy inny obiekt (w tym przykładzie przeglądanie danych (klasa który utworzyłem, który otacza standardowy widok danych i dodaje dodatkową funkcjonalność) i dołącza go do GUI. Czy to złe połączenie, czy dobre?

Naprawdę nie chcę wpaść w złe nawyki i zacząć źle kodować, dlatego byłbym wdzięczny za twoje odpowiedzi.

Darren Young
źródło

Odpowiedzi:

11

Aby luźno powiązać kod, należy pamiętać o kilku prostych sprawach:

Część 1:

Technicznie znany jako „Separation of Concern”. Każda klasa ma określoną rolę, powinna obsługiwać logikę biznesową lub logikę aplikacji. Staraj się omijać klasę, która łączy oba obowiązki. tj. klasa zarządzająca (szerokim terminem) danymi jest logiką aplikacji, podczas gdy klasa, która wykorzystuje dane, jest logiką biznesową.

Osobiście nazywam to (w moim własnym małym świecie) jako create it or use it. Klasa powinna utworzyć obiekt lub użyć obiektu, którego nigdy nie powinna robić jednocześnie.

Część 2:

Jak wdrożyć rozdział dotyczący obaw.
Punktem wyjścia są dwie proste techniki:

Uwaga: Wzory projektowe nie są absolutne.
Mają być dostosowane do sytuacji, ale mają motyw przewodni podobny do wszystkich aplikacji. Więc nie patrz na poniższe przykłady i nie mów, że muszę ściśle to przestrzegać; są to tylko przykłady (i nieco wymyślone).

Wstrzykiwanie zależności :

Tutaj przekazujesz obiekt używany przez klasę. Obiekt przekazywany na podstawie interfejsu, dzięki czemu klasa wie, co z nim zrobić, ale nie musi znać faktycznej implementacji.

class Tokenizer
{
    public:
        Tokenizer(std::istream& s)
            : stream(s)
        {}
        std::string nextToken() { std::string token; stream >> token;return token;}
    private:
        std::istream& stream;
};

Tutaj wstrzykujemy strumień do Tokenizera. Tokenizer nie wie, jakiego typu jest strumień, o ile implementuje interfejs std :: istream.

Wzorzec lokalizatora usług :

Wzorzec lokalizatora usług jest niewielką odmianą wstrzykiwania zależności. Zamiast dać obiekt, którego może użyć, przekazujesz mu obiekt, który wie, jak zlokalizować (utworzyć) obiekt, którego chcesz użyć.

class Application
{
     public:
         Application(Persister& p)
             : persistor(p)
         {}

         void save()
         {
             std::auto_ptr<SaveDialog> saveDialog = persistor.getSaveDialog();
             saveDialog.DoSaveAction();
         }

         void load()
         {
             std::auto_ptr<LoadDialog> loadDialog = persistor.getLoadDialog();
             loadDialog.DoLoadAction();
         }
    private:
        Persister& persistor;
};

Tutaj przekazujemy obiekt aplikacji jako obiekt persistor. Podczas wykonywania operacji zapisu / wczytywania wykorzystuje on persistor do utworzenia obiektu, który faktycznie wie, jak wykonać tę akcję. Uwaga: Znowu persistor jest interfejsem i można zapewnić różne implementacje w zależności od sytuacji.

Jest to przydatne, gdy za potentiallykażdym razem, gdy tworzona jest akcja, wymagany jest unikalny obiekt.

Osobiście uważam, że jest to szczególnie przydatne podczas pisania testów jednostkowych.

Uwaga wzorów:

Wzory projektowe są dla siebie ogromnym tematem. Nie jest to w żadnym wypadku ekskluzywna lista wzorów, których można użyć, aby pomóc w luźnym sprzężeniu; to tylko wspólny punkt wyjścia.

Z doświadczeniem zdasz sobie sprawę, że już używasz tych wzorów, po prostu nie użyłeś ich formalnych nazw. Dzięki ujednoliceniu ich nazw (i zachęceniu wszystkich do ich nauki) stwierdzamy, że przekazywanie pomysłów jest łatwiejsze i szybsze.

Martin York
źródło
3
@Darren Young: Dziękujemy za akceptację. Ale twoje pytanie ma zaledwie trzy godziny. Chciałbym wrócić za jakiś dzień i sprawdzić, czy inni nie udzielili lepszych odpowiedzi.
Martin York,
Dzięki za doskonałą odpowiedź. Jeśli chodzi o oddzielne zaniepokojenie ... Mam klasy, które wymagają pewnych danych do ich użycia, a następnie zrobić coś z tymi danymi. Tak w przeszłości projektowałem zajęcia. Czy byłoby zatem lepiej stworzyć klasę, która pozyskuje dane i dostosowuje je do właściwej formy, a następnie inną klasę do faktycznego wykorzystania danych?
Darren Young,
@Darren Young: Podobnie jak w przypadku wszystkich programów, linia jest szara i niewyraźna, niewyraźna. Trudno jest podać dokładną odpowiedź bez czytania kodu. Ale kiedy mówię o, managing the datamam na myśli zmienne (a nie rzeczywiste dane). Takimi rzeczami, jak wskaźniki, należy zarządzać, aby nie wyciekły. Ale dane można wstrzykiwać, a sposób ich pobierania można wyodrębnić (dzięki czemu klasa może być ponownie wykorzystana przy użyciu różnych metod pobierania danych). Przepraszam, nie mogę być bardziej precyzyjny.
Martin York,
1
@Darren Young: Jak zauważył @StuperUser w swojej odpowiedzi. Nie idź za burtę (AKA minutae of loose coupling(uwielbiam to słowo minutae)). Sekret programowania polega na tym, aby dowiedzieć się, kiedy stosować techniki. Nadużywanie może prowadzić do plątaniny kodu.
Martin York,
@Martin - dziękuję za radę. Myślę, że o to teraz walczę ... Mam tendencję do ciągłego martwienia się o architekturę mojego kodu, a także próbuję nauczyć się określonego języka używam C #. Myślę, że przyjdzie to z doświadczeniem i moim pragnieniem uczenia się tego. Doceniam twoje komentarze.
Darren Young
6

Jestem programistą ASP.NET, więc niewiele wiem o sprzężeniu WinForms, ale wiem trochę o aplikacjach internetowych N-Tier, zakładając 3-warstwową architekturę aplikacji z interfejsem użytkownika, domeną i warstwą dostępu do danych (DAL).

Luźne sprzężenie dotyczy abstrakcji.

Jak stwierdza @MKO, czy można zastąpić zespół innym (np. Nowym projektem interfejsu użytkownika korzystającym z projektu domeny, nowym DAL, który zapisuje się w arkuszu kalkulacyjnym zamiast w bazie danych), wówczas występuje luźne połączenie. Jeśli twoja domena i DAL zależą od projektów w dalszej części łańcucha, połączenie może być luźniejsze.

Jednym z aspektów luźnego powiązania jest to, czy można zastąpić obiekt innym, który implementuje ten sam interfejs. Nie zależy od rzeczywistego obiektu, ale abstrakcyjny opis tego, co robi (jego interfejs).
Luźne sprzężenie, interfejsy i wtryskiwacze zależności (DI) i inwersja sterowania (IoC) są przydatne w aspekcie izolacji projektowania dla testowalności.

Np. Obiekt w projekcie interfejsu użytkownika wywołuje obiekt repozytorium w projekcie domeny.
Można stworzyć fałszywy obiekt, który implementuje ten sam interfejs jako repozytorium kodu pod zastosowań testowych, a następnie napisać specjalny zachowanie dla testów ( odcinki zapobiegania kod produkcyjny, który zapisuje / usuwa / dostaje miano i mocks które działają jako odcinki i śledzić stanu fałszywego obiektu do celów testowych).
Oznacza to, że jedyny wywoływany kod produkcyjny znajduje się teraz tylko w obiekcie interfejsu użytkownika, test będzie dotyczył tylko tej metody, a wszelkie niepowodzenia testu spowodują izolację defektu do tej metody.

Ponadto w menu Analiza w VS (w zależności od posiadanej wersji) znajdują się narzędzia do obliczania metryk kodu dla twojego projektu, z których jednym jest Sprzężenie klas, więcej informacji na ten temat znajdziesz w dokumentacji MSDN.

Nie zrozumcie TOO ugrzęznąć w minutae luźnego sprzężenia jednak, jeśli nie ma szans, że sprawy mają się ponownie wykorzystywane (np projekt domeny z więcej niż jednego interfejsu) i życia produktu jest niewielka, wówczas luźne sprzężenie staje się mniej priorytetowe (nadal będzie brane pod uwagę), ale nadal będzie odpowiedzialnością architektów / liderów technicznych, którzy będą sprawdzać Twój kod.

StuperUser
źródło
3

Łączenie odnosi się do stopnia bezpośredniej wiedzy jednej klasy o drugiej . Nie należy tego interpretować jako enkapsulacja vs. brak enkapsulacji. Nie odnosi się do wiedzy jednej klasy o atrybutach lub implementacji innej klasy, ale raczej o wiedzy o samej innej klasie. Silne sprzężenie występuje, gdy klasa zależna zawiera wskaźnik bezpośrednio do konkretnej klasy, która zapewnia wymagane zachowanie. Zależności nie można zastąpić ani zmienić jej „sygnatury” bez konieczności zmiany klasy zależnej. Luźne sprzężenie występuje, gdy klasa zależna zawiera wskaźnik tylko do interfejsu, który może być następnie zaimplementowany przez jedną lub wiele konkretnych klas.Zależność klasy zależnej polega na „kontrakcie” określonym przez interfejs; zdefiniowana lista metod i / lub właściwości, które muszą zapewnić klasy implementujące. Każda klasa implementująca interfejs może zatem spełnić zależność klasy zależnej bez konieczności zmiany klasy. Pozwala to na rozszerzalność w projektowaniu oprogramowania; nowa klasa implementująca interfejs może zostać napisana w celu zastąpienia bieżącej zależności w niektórych lub wszystkich sytuacjach, bez konieczności zmiany klasy zależnej; nowe i stare klasy można dowolnie wymieniać. Silne połączenie nie pozwala na to. Link referencyjny.

Amir Rezaei
źródło
3

Spójrz na 5 zasad SOLID . Przestrzegając SRP, ISP i DIP będą znacznie niższe sprzężenie, przy czym DIP jest zdecydowanie najsilniejszy. Jest to podstawowa zasada pod wspomnianym już DI .

Również GRASP Warto przyjrzeniu się. To dziwna mieszanka abstrakcyjnych pojęć (na początku trudno będzie je wdrożyć) i konkretnych wzorów (które mogą być naprawdę pomocne), ale piękno jest obecnie prawdopodobnie najmniejszą z twoich obaw.

I na koniec, ta sekcja dotycząca IoC może okazać się bardzo przydatna jako punkt wejścia do popularnych technik.

W rzeczywistości znalazłem pytanie dotyczące przepełnienia stosu , gdzie demonstruję zastosowanie SOLID w konkretnym problemie. To może być ciekawa lektura.

back2dos
źródło
1

Według Wikipedii:

W informatyce i projektowaniu systemów luźno sprzężonym systemem jest taki, w którym każdy z jego elementów ma lub korzysta z niewielkiej lub żadnej wiedzy na temat definicji innych oddzielnych komponentów.

Problem z ciasnym sprzężeniem utrudnia wprowadzanie zmian. (Wielu autorów wydaje się sugerować, że powoduje to przede wszystkim problemy podczas konserwacji, ale z mojego doświadczenia wynika, że ​​ma to również znaczenie podczas początkowego rozwoju.) To, co zwykle dzieje się w ściśle powiązanych systemach, polega na tym, że zmiana jednego modułu w systemie wymaga dodatkowych zmian w modułach, z którymi jest sprzężony. Najczęściej wymaga to więcej zmian w innych modułach i tak dalej.

Natomiast w systemie luźno sprzężonym zmiany są względnie izolowane. Są zatem mniej kosztowne i można je wykonać z większą pewnością.

W twoim konkretnym przykładzie obsługa zdarzeń zapewnia pewne oddzielenie GUI od danych bazowych. Jednak wydaje się, że istnieją inne obszary separacji, które można zbadać. Bez szczegółowych informacji na temat konkretnej sytuacji trudno jest być konkretnym. Możesz jednak zacząć od architektury trójwarstwowej, która oddziela:

  • Logika przechowywania i pobierania danych
  • Logika biznesowa
  • Logika wymagana do uruchomienia interfejsu użytkownika

Należy wziąć pod uwagę, że w przypadku małych, jednorazowych aplikacji z jednym deweloperem korzyści z egzekwowania luźnego łączenia na każdym poziomie mogą nie być warte wysiłku. Z drugiej strony, większe, bardziej złożone aplikacje z wieloma programistami są koniecznością. Początkowo nakładanie abstrakcji i edukowanie programistów niezaznajomionych z kodem dotyczącym jego architektury wiąże się z pewnymi kosztami. Jednak w dłuższej perspektywie luźne połączenie oferuje zalety, które znacznie przewyższają koszty.

Jeśli poważnie myślisz o projektowaniu luźno powiązanych systemów, przeczytaj zasady SOLID i wzorce projektowe.

Należy jednak pamiętać, że te wzorce i zasady są właśnie takie - wzorce i zasady. To nie są zasady. Oznacza to, że muszą być stosowane pragmatycznie i inteligentnie

Jeśli chodzi o wzorce, ważne jest, aby zrozumieć, że nie ma jednej „poprawnej” implementacji któregokolwiek z wzorców. Nie są to również szablony do usuwania ciasteczek do projektowania własnej implementacji. Są po to, aby powiedzieć ci, jaki kształt może mieć dobre rozwiązanie, i zapewnić wspólny język do komunikowania decyzji projektowych z innymi programistami.

Wszystkiego najlepszego.

Kramii
źródło
1

Użyj zastrzyku zależności, wzorców strategii i zdarzeń. Ogólnie: poczytaj o wzorcach projektowych, wszystkie dotyczą luźnego sprzężenia i zmniejszenia zależności. Powiedziałbym, że wydarzenia są tak luźno powiązane, jak to możliwe, podczas gdy zastrzyk zależności i wzorce strategii wymagają pewnych interfejsów.

Dobrą sztuczką jest umieszczenie klas w różnych bibliotekach / zestawach i sprawienie, by były one zależne od jak najmniejszej liczby innych bibliotek, co zmusi cię do refaktoryzacji do używania mniejszych zależności

Homde
źródło
4
Czy prezerwatywa jest jakimś abstrakcyjnym terminem IT, czy po prostu nie dostaję kalambury?
Darren Young
3
To inna nazwa kontenera wstrzykiwania zależności.
Mchl
2
Świetny sposób na zapobieganie rozprzestrzenianiu się wirusów. Czy mówimy o tym samym?
sova
1
Ciężkie sprzęganie często odbywa się na
backendie
1
Ustawienie nagłówka raczej nie nadużywa formatowania
Homde
0

Pozwól, że przedstawię alternatywny widok. Po prostu myślę o tym, jeśli każda klasa jest dobrym API. Kolejność wywoływania metod jest oczywista. To, co robią, jest oczywiste. Zmniejszyłeś liczbę metod do niezbędnego minimum. Dla przykładów,

init, otwórz, zamknij

przeciw

setTheFoo, setBar, initX, getConnection, close

Pierwszy jest oczywisty i wygląda na niezły API. Drugi może powodować błędy, jeśli zostanie wywołany w niewłaściwej kolejności.

Nie przejmuję się zbytnio koniecznością modyfikacji i ponownej kompilacji dzwoniących. Mam dużo kodu, niektóre z nich są nowe, a jakieś 15 lat. Zwykle chcę błędów kompilatora, gdy wprowadzam zmiany. Czasami celowo zepsuję API z tego powodu. Daje mi to możliwość rozważenia konsekwencji każdego dzwoniącego. Nie jestem wielkim fanem wstrzykiwania zależności, ponieważ chcę mieć możliwość wizualnego śledzenia mojego kodu bez czarnych skrzynek i chcę, aby kompilator wyłapał jak najwięcej błędów.

Sean McEligot
źródło