Mam zamiar zapisać trochę ładunku ciągu w bazie danych. Mam dwie konfiguracje globalne:
- szyfrowanie
- kompresja
Można je włączyć lub wyłączyć za pomocą konfiguracji w taki sposób, że tylko jedna z nich jest włączona, obie są włączone lub obie są wyłączone.
Moja obecna implementacja to:
if (encryptionEnable && !compressEnable) {
encrypt(data);
} else if (!encryptionEnable && compressEnable) {
compress(data);
} else if (encryptionEnable && compressEnable) {
encrypt(compress(data));
} else {
data;
}
Myślę o wzorze dekoratora. Czy to właściwy wybór, czy może jest lepsza alternatywa?
design
design-patterns
object-oriented-design
refactoring
Damith Ganegoda
źródło
źródło
if
wypowiedzi?Odpowiedzi:
Projektując kod, zawsze masz dwie opcje.
Nie zamierzam skupiać się na pierwszym z nich, ponieważ tak naprawdę nie ma nic do powiedzenia. Jeśli po prostu chcesz go uruchomić, możesz zostawić kod bez zmian.
Ale co by się stało, gdybyś zdecydował się zrobić to pedantycznie i faktycznie rozwiązał problem z wzorami projektowymi, tak jak tego chciałeś?
Możesz patrzeć na następujący proces:
Podczas projektowania kodu OO większość z nich,
if
które są w kodzie, nie musi tam być. Oczywiście, jeśli chcesz porównać dwa typy skalarne, takie jakint
s lubfloat
s, prawdopodobnie będziesz miećif
, ale jeśli chcesz zmienić procedury oparte na konfiguracji, możesz użyć polimorfizmu, aby osiągnąć to, czego chcesz, przenieść decyzje (if
s) od logiki biznesowej do miejsca, w którym tworzone są obiekty - do fabryk .W tej chwili proces może przebiegać 4 osobnymi ścieżkami:
data
nie jest zaszyfrowany ani skompresowany (nic nie dzwoń, zwracajdata
)data
jest skompresowany (zadzwońcompress(data)
i zwróć)data
jest zaszyfrowany (zadzwońencrypt(data)
i zwróć)data
jest skompresowany i zaszyfrowany (zadzwońencrypt(compress(data))
i zwróć)Patrząc na 4 ścieżki, możesz znaleźć problem.
Masz jeden proces, który wywołuje 3 (teoretycznie 4, jeśli nie nazywasz niczego niczym jednym) różnymi metodami, które manipulują danymi, a następnie je zwracają. Metody mają różne nazwy , różne tak zwane publiczne API (sposób, w jaki metody komunikują swoje zachowanie).
Używając wzorca adaptera , możemy rozwiązać występującą kolizję nazw (możemy zjednoczyć publiczny interfejs API). Mówiąc wprost, adapter pomaga współpracować ze sobą dwóch niekompatybilnych interfejsów. Adapter działa również poprzez zdefiniowanie nowego interfejsu adaptera, który to klasy próbują zjednoczyć swoją implementację API.
Zakładam, że teraz możesz mieć dwie klasy odpowiedzialne za kompresję i szyfrowanie.
W świecie korporacyjnym nawet te określone klasy najprawdopodobniej zostaną zastąpione interfejsami, takimi jak
class
słowo kluczowe zostanie zastąpioneinterface
(jeśli masz do czynienia z językami takimi jak C #, Java i / lub PHP) lubclass
słowo kluczowe pozostanie, aleCompress
aEncrypt
metody byłyby zdefiniowane jako czysty wirtualny , jeśli kodujesz w C ++.Aby utworzyć adapter, definiujemy wspólny interfejs.
Następnie musimy zapewnić implementacje interfejsu, aby był on użyteczny.
W ten sposób powstają 4 klasy, z których każda robi coś zupełnie innego, ale każda z nich zapewnia ten sam publiczny interfejs API.
Process
Metoda.W logice biznesowej, w której masz do czynienia z decyzją none / encryption / kompression / oba, zaprojektujesz swój obiekt, aby był on zależny od
DataProcessing
interfejsu, który projektowaliśmy wcześniej.Sam proces może wtedy być tak prosty:
Nigdy więcej warunków warunkowych. Klasa
DataService
nie ma pojęcia, co tak naprawdę zrobi z danymi, gdy zostaną one przekazanedataProcessing
członkowi, i tak naprawdę nie dba o to, nie jest to jej odpowiedzialność.Idealnie byłoby mieć testy jednostkowe testujące 4 klasy adapterów, które utworzyłeś, aby upewnić się, że działają, musisz zdać test. A jeśli przejdą, możesz być pewien, że będą działać bez względu na to, jak je wywołasz w kodzie.
Więc robiąc to w ten sposób, że już nigdy nie będę miał
if
w swoim kodzie?Nie. Mniej prawdopodobne jest, że w twojej logice biznesowej będą warunkowe, ale wciąż muszą gdzieś być. To miejsce to twoje fabryki.
I to jest dobre. Oddzielasz obawy związane z tworzeniem i faktycznym użyciem kodu. Jeśli sprawisz, że twoje fabryki będą niezawodne (w Javie możesz nawet posunąć się nawet do korzystania z czegoś takiego jak Google Guice ), w logice biznesowej nie martwisz się, że wybierzesz odpowiednią klasę do wstrzyknięcia. Ponieważ wiesz, że twoje fabryki działają i dostarczy to, o co poprosisz.
Czy konieczne jest posiadanie wszystkich tych klas, interfejsów itp.?
To przywraca nas do początku.
W OOP, jeśli wybierzesz ścieżkę do zastosowania polimorfizmu, naprawdę chcesz użyć wzorców projektowych, chcesz wykorzystać cechy języka i / lub chcesz podążać za wszystkim, jest ideologią obiektową, to jest. I nawet wtedy, przykład ten nawet nie pokazuje wszystkich fabryk masz zamiar trzeba, a jeśli były byłaby się
Compression
iEncryption
klas i ich interfejsy zamiast, musisz włączyć ich wdrożenia, jak również.W końcu dostajesz setki małych klas i interfejsów, skoncentrowanych na bardzo specyficznych rzeczach. Co niekoniecznie jest złe, ale może nie być najlepszym rozwiązaniem dla Ciebie, jeśli chcesz zrobić coś tak prostego jak dodanie dwóch liczb.
Jeśli chcesz to zrobić szybko i szybko, możesz pobrać rozwiązanie Ixreca , któremu przynajmniej udało się wyeliminować bloki
else if
ielse
, które moim zdaniem są nawet odrobinę gorsze niż zwykłyif
.Aktualizacja 2: Odbyła się szalona dyskusja na temat pierwszej wersji mojego rozwiązania. Dyskusja głównie spowodowana przeze mnie, za co przepraszam.
Postanowiłem edytować odpowiedź w taki sposób, że jest to jeden ze sposobów spojrzenia na rozwiązanie, ale nie jedyny. Usunąłem również część dekoratora, w której zamiast tego miałem na myśli fasadę, którą ostatecznie postanowiłem całkowicie pominąć, ponieważ adapter jest odmianą fasady.
źródło
Compression
iEncryption
wydają się całkowicie zbędne. Nie jestem pewien, czy sugerujesz, że są one w jakiś sposób niezbędne do procesu dekoracji, czy po prostu sugerujesz, że reprezentują wyodrębnione koncepcje. Drugi problem polega na tym, że tworzenie klasy takiejCompressionEncryptionDecorator
prowadzi do tego samego rodzaju kombinatorycznej eksplozji, co warunki warunkowe PO. Nie widzę też wystarczająco jasno wzoru dekoratora w sugerowanym kodzie.Jedyny problem, jaki widzę w twoim obecnym kodzie, to ryzyko eksplozji kombinatorycznej, gdy dodajesz więcej ustawień, które można łatwo złagodzić, konstruując kod w ten sposób:
Nie znam żadnego „wzorca projektowego” ani „idiomu”, którego można by uznać za przykład.
źródło
else
między moimi dwoma instrukcjami if i dlaczego przypisuję dodata
nich za każdym razem. Jeśli obie flagi są prawdziwe, wówczas kompresor () zostanie wykonany, a następnie szyfrowany () zostanie wykonany na wyniku kompresji (), tak jak chcesz.Myślę, że twoje pytanie nie szuka praktyczności, w takim przypadku odpowiedź lxrec jest prawidłowa, ale do poznania wzorców projektowych.
Oczywiście wzorzec poleceń jest przesadą w przypadku tak trywialnego problemu jak ten, który proponujesz, ale dla ilustracji:
Jak widać, umieszczenie poleceń / transformacji na liście pozwala na ich sekwencyjne wykonywanie. Oczywiście wykona oba, lub tylko jedno z nich zależy od tego, co umieścisz na liście, bez spełnienia warunków.
Oczywiście warunki warunkowe skończą w jakiejś fabryce, która tworzy listę poleceń.
EDYTUJ dla komentarza @ texacre:
Istnieje wiele sposobów uniknięcia warunków if w części kreacyjnej rozwiązania, weźmy na przykład aplikację graficznego interfejsu użytkownika . Możesz mieć pola wyboru dla opcji kompresji i szyfrowania. W
on clic
przypadku tych pól wyboru tworzysz odpowiednie polecenie i dodajesz je do listy lub usuwasz z listy, jeśli odznaczasz opcję.źródło
commands.add(new EncryptCommand());
lubcommands.add(new CompressCommand());
odpowiednio.Myślę, że „wzorce projektowe” są niepotrzebnie ukierunkowane na „wzorce oo” i całkowicie unikają znacznie prostszych pomysłów. Mówimy tutaj o (prostym) potoku danych.
Spróbowałbym to zrobić clojure. Każdy inny język, w którym funkcje są najwyższej klasy, jest prawdopodobnie również w porządku. Może mógłbym później użyć C #, ale to nie jest tak miłe. Moim sposobem rozwiązania tego są następujące kroki z niektórymi wyjaśnieniami dla osób niebędących clojurianami:
1. Reprezentuj zestaw transformacji.
To jest mapa, tzn. Tablica przeglądowa / słownik / cokolwiek, od słów kluczowych po funkcje. Kolejny przykład (słowa kluczowe do ciągów):
Więc pisanie
(transformations :encrypt)
lub(:encrypt transformations)
zwróciłoby funkcję szyfrowania. ((fn [data] ... )
to tylko funkcja lambda).2. Uzyskaj opcje jako sekwencję słów kluczowych:
3. Filtruj wszystkie transformacje za pomocą dostarczonych opcji.
Przykład:
4. Połącz funkcje w jedną:
Przykład:
5. A następnie razem:
Zmiany TYLKO, jeśli chcemy dodać nową funkcję, powiedzmy „debug-print”, są następujące:
źródło
funcs-to-run-here (map options funcs)
wykonuje filtrowanie, wybierając w ten sposób zestaw funkcji do zastosowania. Może powinienem zaktualizować odpowiedź i podać trochę więcej szczegółów.[Zasadniczo moja odpowiedź jest kontynuacją odpowiedzi @Ixrec powyżej . ]
Ważne pytanie: czy liczba odrębnych kombinacji, które musisz pokryć, wzrośnie? Jesteś lepiej świadomy swojej domeny tematycznej. To jest twój osąd.
Czy liczba wariantów może wzrosnąć? Cóż, nie jest to nie do pomyślenia. Na przykład może być konieczne dostosowanie większej liczby różnych algorytmów szyfrowania.
Jeśli spodziewasz się, że liczba różnych kombinacji będzie rosła, wzór strategii może ci pomóc. Jest zaprojektowany do enkapsulacji algorytmów i zapewnia wymienny interfejs do kodu wywołującego. Nadal będziesz mieć niewielką logikę podczas tworzenia (tworzenia) odpowiedniej strategii dla każdego konkretnego łańcucha.
Skomentowałeś powyżej , że nie spodziewasz się zmiany wymagań. Jeśli nie spodziewasz się, że liczba wariantów wzrośnie (lub jeśli możesz odroczyć to refaktoryzowanie), zachowaj logikę taką, jaka jest. Obecnie masz małą logikę, którą można zarządzać. (Może dodaj notatkę do siebie w komentarzach na temat możliwego refaktoryzacji do wzorca strategii).
źródło
Jednym ze sposobów zrobienia tego w scali byłoby:
Używanie wzorca dekoratora do osiągnięcia powyższych celów (oddzielenie logiki przetwarzania i sposobu ich łączenia) byłoby zbyt szczegółowe.
Tam, gdzie byś potrzebował wzorca projektowego do osiągnięcia tych celów projektowych w paradygmacie programowania OO, język funkcjonalny oferuje natywne wsparcie, używając funkcji jako obywateli pierwszej klasy (linia 1 i 2 w kodzie) i składu funkcjonalnego (linia 3)
źródło