Projektując kod, zawsze masz dwie opcje.
- po prostu to załatw, w takim przypadku praktycznie każde rozwiązanie będzie dla Ciebie działać
- bądź pedantyczny i zaprojektuj rozwiązanie, które wykorzystuje dziwactwa języka i jego ideologię (w tym przypadku języki OO - wykorzystanie polimorfizmu jako środka do podjęcia decyzji)
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 jak int
s lub float
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ń, zwracaj data
)
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.
To nie jest konkretny język. Jest to podejście ogólne, każde słowo kluczowe, które może reprezentować, może być dowolnego typu, w języku takim jak C # można zastąpić je słowem ogólnym ( <T>
).
Zakładam, że teraz możesz mieć dwie klasy odpowiedzialne za kompresję i szyfrowanie.
class Compression
{
Compress(data : any) : any { ... }
}
class Encryption
{
Encrypt(data : any) : any { ... }
}
W świecie korporacyjnym nawet te określone klasy najprawdopodobniej zostaną zastąpione interfejsami, takimi jak class
słowo kluczowe zostanie zastąpione interface
(jeśli masz do czynienia z językami takimi jak C #, Java i / lub PHP) lub class
słowo kluczowe pozostanie, ale Compress
a Encrypt
metody byłyby zdefiniowane jako czysty wirtualny , jeśli kodujesz w C ++.
Aby utworzyć adapter, definiujemy wspólny interfejs.
interface DataProcessing
{
Process(data : any) : any;
}
Następnie musimy zapewnić implementacje interfejsu, aby był on użyteczny.
// when neither encryption nor compression is enabled
class DoNothingAdapter : DataProcessing
{
public Process(data : any) : any
{
return data;
}
}
// when only compression is enabled
class CompressionAdapter : DataProcessing
{
private compression : Compression;
public Process(data : any) : any
{
return this.compression.Compress(data);
}
}
// when only encryption is enabled
class EncryptionAdapter : DataProcessing
{
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(data);
}
}
// when both, compression and encryption are enabled
class CompressionEncryptionAdapter : DataProcessing
{
private compression : Compression;
private encryption : Encryption;
public Process(data : any) : any
{
return this.encryption.Encrypt(
this.compression.Compress(data)
);
}
}
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.
class DataService
{
private dataProcessing : DataProcessing;
public DataService(dataProcessing : DataProcessing)
{
this.dataProcessing = dataProcessing;
}
}
Sam proces może wtedy być tak prosty:
public ComplicatedProcess(data : any) : any
{
data = this.dataProcessing.Process(data);
// ... perhaps work with the data
return data;
}
Nigdy więcej warunków warunkowych. Klasa DataService
nie ma pojęcia, co tak naprawdę zrobi z danymi, gdy zostaną one przekazane dataProcessing
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
i Encryption
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
i else
, które moim zdaniem są nawet odrobinę gorsze niż zwykły if
.
Weź pod uwagę, że to mój sposób na dobry projekt OO. Zamiast kodowania interfejsów, a nie implementacji, robiłem to w ciągu ostatnich kilku lat i jest to podejście, które najbardziej mi odpowiada.
Osobiście bardziej podoba mi się programowanie if-less i znacznie bardziej doceniłbym dłuższe rozwiązanie w 5 liniach kodu. W taki sposób przyzwyczaiłem się do projektowania kodu i bardzo wygodnie go czytam.
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.
if
wypowiedzi?