Witam wszystkich! Mam teoretyczne pytanie na które nie mogę znaleźć jednoznaczniej odpowiedzi. Czy wzorce projektowe można łączyć (przez można rozumiem czy jest to profesjonalne i czy robienie tego nie jest głupie, czy nie kłóci się z ideą stosowania wzorców projektowych)? Czy na przykład klasy napisane tak żeby tworzyły wzorzec proxy mogły sprawdzać dane i dalej przekazywać je do klasy używającej wzorca buildera do tworzenia obiektów na podstawie otrzymanych danych itp itd.
Dziękuję za odpowiedzi :)
Tak.
Odpowiedź tkwi w pytaniu: a DLACZEGO wzorce?
Brzmi: tak
Dlaczego: Nie dla samych nich, to byłby jakiś "faszyzm".
Ale podzielenia odpowiedzialności, separacji, czasem wydajność (nie algorytm naiwny tylko wzorzec), łatwości konserwacji i rozwoju, czytelność dla ludzi.
Wzorzec Proxy wręcz zakłada, że na obu swoich "końcach" są rozwiązania obiektowe, nie wyobrażam sobie ABY NIE BYŁ łączony
Ciężko oprzeć cały projekt o jeden wzorzec, więc pewnie, że można. Jeszcze jak.
Ale z drugiej strony nadmiar wzorców może spowodować niestrawność. Można połączyć ciasteczka z ptasim mleczkiem, ale to nie znaczy, że masz zjeść kilka kilogramów ciastek i kilka paczek ptasiego mleczka za jednym podejściem.
czy jest to profesjonalne
Najlepsze podejście do wzorców to wyrzucić ze słownika słowo "profesjonalne" i przestać próbować na siłę być profesjonalistą, bo efekt będzie opłakany.
Widziałem już wiele takich projektów-potworków, które były pisane właśnie pod dyktando "bycia profesjonalnym" i były wrzucane tam wzorce niemalże na siłę, bo ktoś chciał się dowartościować i poczuć profesjonalnym, żeby było tak jak przeczytał w książce. A efekty były żałosne, a projekty niepotrzebnie pokomplikowane. Bo programiści lubią komplikować proste rzeczy i jak programista dostanie zbyt proste zadanie do zrobienia w pracy, to żeby nie było nudno i żeby nie zrobić za szybko, po prostu dokłada do tego różnych wzorców i abstrakcji. Czyli taki sabotaż czyniony z nudy, a słowo "profesjonalny" to tylko przykrywka.
Lepszym słowem już jest "pragmatyzm" i po prostu trzeźwa ocena tego, co należy użyć w danej chwili, a czego nie używać w danej chwili (taką ocenę nabywa się wraz z doświadczeniem i osobistą refleksją). Czyli trzeba dużo kodzić, i dużo myśleć nad napisanym już kodem (a nie tylko kodzić wg danego schematu).
Część wzorców jest też trochę zależna od języka, tzn. np. stosowanie strategii w C# jest trochę bezsensu, bo są inne, bardziej współczesne mechanizmy do osiągnięcia tego samego.
Jak wspomniał @LukeJL wpychanie GoF na siłę gdziekolwiek się da, nie jest raczej dobrym pomysłem
@WeiXiao: Co zamiast strategii w C#? Delegaty?
Uzupełniając odpowiedź @LukeJL zostawię tu ten nieśmiertelny post https://medium.com/humans-create-software/the-growth-stages-of-a-programmer-funfunfunction-6-f03fcb9c1531
@WeiXiao: : chodzi o lambdy? Bo w Javie to właśnie lambdy i streamy (czyli chyba coś takiego jak LinQ w C#) zastępują często strategie
No ale labmdy używane w tym kontekście to przecież ciągle wzorzec strategii tylko zapisany bardziej wygodniej, tak samo jak eventy w C# to ciągle wzorzec obserwator, tylko zapisany wygodniej. Wzorce są te same, zmieniła się ich implementacja, bo języki pozwalają na więcej.
@neves:
No w sumie tak, chodzi o sposób myślenia o rozwiązaniu problemu. Nie ma znaczenia w Javie czy stworzymy nową klasę implementującą Comparator czy lambdę która jest anonimową implementacją, chodzi o to że inny obiekt "dostarcza" algorytm
Jest pewna różnica. Albo myślisz strategia
(+ inne wzorce), albo po prostu używasz po prostu high order function i starcza.
To tak jakby mieć osobne koncepcje i nazwy na sumę 2, 3, 4 i 5 liczb vs po prostu mieć sumowanie
.
Albo jeszcze lepiej - mieć koncept monoidu
czy semigrupy
.
Po prostu wiele wzorców (czynnościowych) to po prostu konkretne bardzo przypadki high order function, z jednej strony nazwa pomaga szybciej i konkretniej komunikować co robimy, ale jednocześnie myślenie na tym poziomie czasami nie pozwala dostrzec bardziej ogólnego rozwiązania (bo nikt nazwy nie wrzucił :-) ).
WeiXiao napisał(a):
Część wzorców jest też trochę zależna od języka, tzn. np. stosowanie strategii w C# jest trochę bezsensu, bo są inne, bardziej współczesne mechanizmy do osiągnięcia tego samego.
Jak wspomniał @LukeJL wpychanie GoF na siłę gdziekolwiek się da, nie jest raczej dobrym pomysłem
Jeszcze z wzorcami jest tak, że wszystko naprawdę może być wzorcem, nie tylko to z GoF. Ludzie są w stanie wymyślać nowe wzorce (np. w środowisku React już ileś wzorców wymyślono - Higher Order Components, Render Props itp.) albo modyfikować istniejące. Czasem z powodów ograniczeń języka tworzy się pewne wzorce. A czasem język zapewnia wzorzec out of the box, ew. zaimplementowanie wzorca w danym języku jest tak łatwe, że nie widzimy, że to wzorzec.
jarekr000000 napisał(a):
Jest pewna różnica. Albo myślisz
strategia
(+ inne wzorce), albo po prostu używasz po prostu high order function i starcza.
Dla mnie high order function to po prostu sposób, w jaki można zaimplementować strategię w JavaScript czy w innych językach, które mają funkcje jako first class object.
scibi92 napisał(a):
@WeiXiao: : chodzi o lambdy? Bo w Javie to właśnie lambdy i streamy (czyli chyba coś takiego jak LinQ w C#) zastępują często strategie
Na pewno nie chodzi o lambdy. @WeiXiao napisał wyraźnie bardziej współczesne
, a lambdy są przecież znacznie starsze niz C#. A poza tym lambdami można zastąpić strategie chyba tylko w tutorialach, no chyba, że ktoś ma taki plan emerytalny, że pisze lambdy na tysiąc linijek.
W Javie można na przykład użyć method reference, więc jak masz w Streamie metode sorted(Comparator<? super T> comparator> możesz przekazac odpowiednią metodę ważne żeby sygnatura pasowała:
public foo (Whatever whatever) {
//skąd bierzemy ordery
orders.stream()
.sort(this::compareOrders)
//whatever
}
int compareOrders(Order order1, Order order2) {
//some code
return result;
}
Nie trzeba lambd na 50 linijek.
I tak wiem że lambdy nie sa nowe, ale ani w Javie, ani w C# nie zostały wprowadzone od razu
@scibi92: jeśli będziesz miał jedną klasę, a w niej np. 50 metod realizujących różne strategie i każda nadal na 20 linijek, to to też nie jest piękne.
@somekind: ale ja nie twierdze że tak trzeba robić zawsze. Jeśli akurat potrzebuje dany algorytm zastować w jednej klasie i jest dosyć prosty to zrobię referencę do metody, ale jakbym potrzebował taka implementację w większej ilości klas albo byłaby bardziej złozona to bym wydzielił do osobnej klasy
No, czyli jeśli będziesz potrzebował strategii, to ją kanonicznie zaimplementujesz. Tak jak i ja. Czyli twierdzenie, że stosowanie tego wzorca jest bez sensu jest hmm... bez sensu. ;)
Delegaty, słowniki, a może nawet expression trees.
Może źle się wyraziłem, chodziło mi o sposób implementacji tej klasycznej strategii (przykład niżej, oraz przy założeniu, że w ogóle będziemy to nazywać tak samo) który można spokojnie zastąpić przy użyciu Dictionary<T, Func<U, V>>
. W porównaniu do podejścia klasa : interfejs
per strategia, to podejście ma taką zaletę, że można w runtime definiować nowe / usuwać stare strategię.
Jedynie w "trudniejszych" przypadkach trzeba się zastanowić czy warto
interface IBillingStrategy
{
double GetActPrice(double rawPrice);
}
// Normal billing strategy (unchanged price)
class NormalStrategy : IBillingStrategy
{
public double GetActPrice(double rawPrice)
{
return rawPrice;
}
}
// Strategy for Happy hour (50% discount)
class HappyHourStrategy : IBillingStrategy
{
public double GetActPrice(double rawPrice)
{
return rawPrice * 0.5;
}
}
Może źle się wyraziłem, chodziło mi o sposób implementacji tej klasycznej strategii, który można spokojnie zastąpić przy użyciu Dictionary<T, Func<U, V>>
W kontekście strategi, to raczej będzie naruszenie OCP.
No i od razu mówiłem, że o jakieś tutorialowe kody chodzi. :P
Potrzeba implementacji wzorca strategia nigdy nie pojawia się tam, gdzie są do wykonania jednolinijkowe operacje. W takich sytuacjach ludzie zbyt miękcy na użycie słowników od zawsze radzili sobie switchem, ale nawet słabi programiści nie robili zestawu klas aby umieścić w każdej z nich jedną linijkę faktycznego kodu.
(No chyba, że ktoś miał plan emerytalny polegający na overengineeringu.)
@WeiXiao
Ludzie się śmieją z JSa, ale jednak JS to potęga, bo pozwala zapisać te całe "strategie" tak:
const billingStrategies = {
normal: rawPrice => rawPrice,
happyHour: rawPrice => rawPrice * 0.5,
};
W innych językach pewnie też można by coś takiego osiągnąć, jednak w JS funkcje są tak podstawowym klockiem, że prędzej pewnie ludziom przyjdzie takie rozwiązanie do głowy, niż to na klasach. A jak ludzie siedzą w języku klasowym, to defaultową opcją jest robienie z tego klas.
Ludzie korzystają z tego, co mają w języku, tego co najmodniejsze w ich języku.
Widać zresztą jak ktoś przychodzi z jednego języka do drugiego, że potem próbuje na siłę stosować schematy z poprzedniego języka (czyli np. wszystko robić na klasach).
Czasem też pewna biblioteka rozpyla modę na jakieś podejście (np. Redux rozpylił modę na nadużywanie archaicznych konstrukcji switch/case w JS, zamiast stosowania bardziej naturalnego dla JSa podejścia - obiektów-"słowników"). Pewnie w innych językach też są jakieś biblioteki/frameworki, które rozpylają modę na jakieś konstrukcje językowe albo na jakieś konkretne wzorce projektowe.
@LukeJL: ale sam o czyms podobnym pisałem, tylko że ja uważam że to zalezy.
Np. mogę wydzielić osobną mini funkcje do porównywania DTOsów gdy chce je posortować i przekazac ja w lambdzie i to będzie taka "strategia", ale może być tak że takie cos będzie używane w 10 rożnych klasach, sam algorytm będzie na 20 linijek i będe chciał go przetestować i wtedy będzie logiczne że użyje tego jako klasy. Sam jaram sie i FP i OOP i nie widzę powodu do bycia fanatykiem stosowania wszedzie samych funkcji albo samych klas. Pragmadyzm i rozsądek przede wszystkim