Łączenie wzorców projektowych

0

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 :)

0

Tak.

0

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

7

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).

1

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

0

@WeiXiao: Co zamiast strategii w C#? Delegaty?

1

@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

2

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.

0

@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

2

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ł :-) ).

2
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.

0
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.

0

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

0

@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.

0

@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

0

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. ;)

0

@scibi92 @Gworys @kzkzg

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;
    }
}
3

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.

0

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.)

1

@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.

0

@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

1 użytkowników online, w tym zalogowanych: 0, gości: 1