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 potentially
każ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.
managing the data
mam 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.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.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.
źródło
źródło
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.
źródło
Według Wikipedii:
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:
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.
źródło
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
źródło
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.
źródło