Jestem dość pragmatykiem, ale moim głównym problemem jest to, że możesz pozwolić, ConfigBlock
aby zdominowało to twoje projekty interfejsów w możliwie zły sposób. Gdy masz coś takiego:
explicit MyGreatClass(const ConfigBlock& config);
... bardziej odpowiedni interfejs może wyglądać tak:
MyGreatClass(int foo, float bar, const string& baz);
... w przeciwieństwie do zwykłego zbierania tych foo/bar/baz
pól z ogromnych rozmiarów ConfigBlock
.
Leniwy interfejs
Z drugiej strony, ten rodzaj projektu ułatwia zaprojektowanie stabilnego interfejsu dla twojego konstruktora, np. Jeśli w końcu potrzebujesz czegoś nowego, możesz po prostu załadować to do ConfigBlock
(prawdopodobnie bez żadnych zmian kodu), a następnie wybieraj dowolne nowe rzeczy, których potrzebujesz, bez jakiejkolwiek zmiany interfejsu, tylko zmiana implementacji MyGreatClass
.
Jest to więc w pewnym sensie plus i minus, który uwalnia cię od zaprojektowania bardziej przemyślanego interfejsu, który akceptuje tylko te dane wejściowe, których faktycznie potrzebuje. Stosuje sposób myślenia: „Po prostu daj mi tę ogromną kroplę danych, wybiorę z niej to, czego potrzebuję” w przeciwieństwie do czegoś bardziej podobnego: „Te precyzyjne parametry są tym, co ten interfejs musi działać”.
Są więc na pewno niektórzy profesjonaliści, ale ich zalety mogą być znacznie większe niż ich.
Sprzęganie
W tym scenariuszu wszystkie takie klasy tworzone z ConfigBlock
instancji mają swoje zależności wyglądające tak:
Może to być na przykład PITA, jeśli chcesz przeprowadzić test jednostkowy Class2
na tym schemacie w izolacji. Być może będziesz musiał powierzchownie zasymulować różne ConfigBlock
dane wejściowe zawierające odpowiednie pola, które Class2
są zainteresowane, aby móc je przetestować w różnych warunkach.
W każdym nowym kontekście (test jednostkowy lub cały nowy projekt) każda taka klasa może stać się większym obciążeniem dla (ponownego) użytkowania, ponieważ ostatecznie musimy zawsze zabrać ConfigBlock
ze sobą na przejażdżkę i skonfigurować ją odpowiednio.
Możliwość ponownego użycia / wdrażania / testowalności
Zamiast tego, jeśli odpowiednio zaprojektujesz te interfejsy, możemy oddzielić je od siebie ConfigBlock
i otrzymać coś takiego:
Jeśli zauważysz na powyższym schemacie, wszystkie klasy stają się niezależne (ich połączenia aferentne / wychodzące zmniejszają się o 1).
Prowadzi to do znacznie większej liczby niezależnych klas (przynajmniej niezależnych ConfigBlock
), co może być o wiele łatwiejsze do (ponownego) użycia / przetestowania w nowych scenariuszach / projektach.
Teraz ten Client
kod jest tym, który musi polegać na wszystkim i złożyć wszystko razem. Ostatecznie obciążenie jest przenoszone do tego kodu klienta, aby odczytać odpowiednie pola z ConfigBlock
i przekazać je do odpowiednich klas jako parametry. Jednak taki kod klienta jest generalnie wąsko zaprojektowany dla konkretnego kontekstu, a jego potencjał do ponownego użycia zwykle będzie zilch lub zamknięty (może to być main
funkcja punktu wejścia aplikacji lub coś w tym rodzaju).
Z punktu widzenia możliwości ponownego użycia i testowania może to uczynić te klasy bardziej niezależnymi. Z punktu widzenia interfejsu dla tych, którzy używają twoich klas, może również pomóc w jawnym określeniu, jakich parametrów potrzebują, a nie tylko jednego, ConfigBlock
który modeluje cały wszechświat pól danych wymaganych do wszystkiego.
Wniosek
Ogólnie rzecz biorąc, tego rodzaju projektowanie klasowe, które zależy od monolitu, który ma wszystko, co potrzebne, ma zwykle takie cechy. Ich zastosowanie, możliwość zastosowania, możliwość ponownego użycia, testowalność itp. Mogą w rezultacie ulec znacznej degradacji. Mogą jednak uprościć projekt interfejsu, jeśli spróbujemy go pozytywnie zakręcić. Od Ciebie zależy, czy zmierzysz zalety i wady i zdecydujesz, czy kompromisy są tego warte. Zazwyczaj bezpieczniej jest pomylić się z tego rodzaju projektami, w których wybieramy monolit z klas, które ogólnie mają na celu modelowanie bardziej ogólnego i szeroko stosowanego projektu.
Nie mniej ważny:
extern CodingBlock MyCodingBlock;
... jest to potencjalnie jeszcze gorsze (bardziej wypaczone?) pod względem cech opisanych powyżej niż podejście polegające na wstrzykiwaniu zależności, ponieważ ostatecznie łączy swoje klasy nie tylko ConfigBlocks
, ale bezpośrednio z konkretnym jego wystąpieniem . To dodatkowo obniża możliwości zastosowania / wdrażania / testowalności.
Moja ogólna rada byłaby błędna przy projektowaniu interfejsów, które nie zależą od tego rodzaju monolitów w celu zapewnienia ich parametrów, przynajmniej dla najbardziej ogólnych klas, które projektujesz. I unikaj globalnego podejścia bez wstrzykiwania zależności, jeśli możesz, chyba że naprawdę masz bardzo silny i pewny powód, aby go nie unikać.
switch
instrukcji lubif
instrukcji testujących wartość odczytaną z plików konfiguracyjnych.Tak. Lepiej jest scentralizować stałe i wartości środowiska wykonawczego oraz kod do ich odczytania.
To źle: większość twoich konstruktorów nie będzie potrzebować większości wartości. Zamiast tego utwórz interfejsy dla wszystkiego, co nie jest łatwe do zbudowania:
stary kod (twoja propozycja):
nowy kod:
utwórz instancję MyGreatClass:
Oto
current_config_block
instancja twojejConfigBlock
klasy (ta, która zawiera wszystkie twoje wartości), aMyGreatClass
klasa otrzymujeGreatClassData
instancję. Innymi słowy, przekaż konstruktorom tylko te dane, których potrzebują, i dodaj udogodnienia, abyConfigBlock
je utworzyć.Ten kod sugeruje, że będziesz mieć globalną instancję CodingBlock. Nie rób tego: zwykle powinieneś mieć globalnie zadeklarowaną instancję, w dowolnym punkcie wejścia, którego używa Twoja aplikacja (funkcja główna, DllMain itp.) I przekaż ją jako argument, gdziekolwiek potrzebujesz (ale jak wyjaśniono powyżej, nie powinieneś przekazywać cała klasa wokół, po prostu odsłonić interfejsy wokół danych i przekazać je).
Nie łącz też klas klienta (twojego
MyGreatClass
) z typemCodingBlock
; Oznacza to, że jeśliMyGreatClass
weźmiesz ciąg i pięć liczb całkowitych, lepiej będzie, jeśli przekażesz ten ciąg i liczby całkowite, niż przejdziesz przezCodingBlock
.źródło
Krótka odpowiedź:
Ci nie muszą wszystkie ustawienia dla każdego z modułów / klas w kodzie. Jeśli tak, to coś jest nie tak z twoim projektowaniem obiektowym. Zwłaszcza w przypadku testowania jednostkowego ustawienie wszystkich zmiennych, których nie potrzebujesz i przekazanie tego obiektu nie pomogłoby w czytaniu lub utrzymywaniu.
źródło
ConfigBlock
klasą. Chodzi tutaj o to, aby nie podać całego, w tym przypadku, kontekstu stanu systemu, a konkretnie wymaganych wartości, aby to zrobić.