Super nagranie! Podobało mi się zwłaszcza tempo nagrania jak i dobrze dobrane problemy jakie zostały rozwiązane przez zastosowanie wzorców. W tle cytowanie teorii, którą i tak wciąż cofałem, aby sobie utrwalić - mówienie w ten sposób to gwarant, że dobrze wypadnie się na rozmowie rekrutacyjnej ;)
Jestem też znana zasada Brzytwy Ockhama która mówi "Nie należy mnożyć bytów ponad potrzebę... W programowaniu obiektowym chodzi o to by właśnie dana klasa miała metody jakie wykonuje, więc w klasie FAKTURA jak najbardziej pasują metody ZAPISZ / WYDRUKUJ. Po co rozbijać to na osobne klasy.... IMHO Sztuka dla sztuki.
Fajne wideo, dzięki! Wydaje mi się, że ISP zostało pokazane ze zbyt niskiego poziomu. Problem byłby widoczny w klasie, która chce agregować obiekty tylko skanujące albo tylko drukujące, a my mamy wyłącznie interfejs drukarki, który dostarcza znacznie szerszy interfejs. Podział interfejsu drukarek jest spoko, ale przydałoby się przykład jeszcze faksowni i drukarni, które początkowo używają interfejsu iPrinter, a ostatecznie drukarnia używa iPrinter o ograniczonej funkcjonalności, a faksownia iFaxContent.
Super nagranie! Po obejrzeniu filmiku mam dwa pytania. Co do pierwszej zasady: Jak defniujemy odpowiedzialności? Np. Mamy logikę biznesową do rozliczania wpływów i wydatków w budżecie. Czy całość rozliczania mam rozumiec jaka jedyna odpowiedzialnosc? Czy może rozliczanie wpływu i rozliczanie wydatku mamy rozbic? Oraz pytanie do 3 zasady: Jak nie przedobrzyć z rozbijaniem interfejsów?
34:50 no nie jesteśmy w stanie służyć sie kwadratem tak aby reprezentował dowolny czworokąt. tak samo jak kaczka górska nie reprezentuje dowolnej kaczki ... tylko górską :) więc o co chodzi nie wiadomo dalej. Chyba żę ... nie możemy modyfikować w sposób sztuczny np properties z klasy bazowej tylko po to żeby interfejs pasował. ???
35:20 nie rozumiem tej zasady. Dlatego kwadrat dziedziczy po czworokącie a nie czworokąt po kwadracie. Bo kwadrat jest czworokątem ale czworokąt nie musi być w kwadratem?
Trzeba pamiętać że SOLID to ZALECENIA a nie obowiązek - zapewne będą przypadki gdzie bardziej będzie się opłacało złamać tę zasadę choć nie jestem jeszcze takim kozakiem aby o tym wyrokować, mówię co mi się wydaje ;-)
Mam pytanie odnośnie przykładu do Open-Close Principle. Po co w ogóle tworzyć klasę InvoicePersistence? Za jej pomocą można wyłącznie zapisać dokument w danym formacie, a czy tego samego nie można zrobić wykorzystując konkretny saver? Co zyskujemy dzięki tej klasie?
W przypadku przedstawionym na filmiku klasy 'Saver' odpowiadają za logikę zapisu do odpowiedniego formatu. A klasa 'Persistence' odpowiada jedynie za wywołanie tej logiki, więc zamiast wywoływać każdą klasę 'Saver' osobno zależnie od formatu do jakiego chcesz zapisać to używasz klasy 'Persistence', podajesz odpowiednie argumenty i na podstawie tych danych zostanie wywołana odpowiednia klasa 'Saver', która zapisze plik do odpowiedniego formatu.
Dobra robota! I pytanie, w przykładzie z kaczkami przy Liskov Substitution Principle w sumie pokazałeś identyczny anty-przykład gdzie RubberDuck nie jest w stanie zaimplementować metody Fly() jak w przypadku Interface Segregation Principle gdzie mamy to samo z drukarką Canon i jej Scan() metodą. Czy to jest tak że zawsze jakieś naruszenie ISP powduje naruszenie LSP ?
dzięki za komentarz ;p co do pytania, to ogólnie rzecz biorąc to mimo, że zasady SOLID mają w teorii osobne definicję, to w praktyce ich granice, w wielu przypadkach się 'zacierają'. Także jeśli potraktujemy interface po prostu jako abstrakcyjną klasę bazową, to naruszająć ISP naruszy LSP, jak tylko pojawi się konkretna klasa, która nie potrafi zaimplementować jakiejś metody. Ale z drugiej strony nie każde naruszenie LSP musi być spowodowane naruszeniem ISP
19:21 - bardzo ciekawe, tylko, że w efekcie, żeby zapisać fakturę do np. pdf musimy utworzyć osobny obiekt klasy InvoicePersistence, który będzie zawierał odpowiedni obiekt invoiceSaver. Trochę to kuriozalne :D Naprawdę takie postępowanie jest standardowe? W końcu gdzieś chyba i tak będziesz musiał zaimplementować osobny metody do tego, ew. metodę, która przyjmuje jakiś argument, co nie?
Nie, nie będzie musiał. Zwróć uwagę, że pole 'private IInoivceSaver' w klasie InvoicePersistance reprezentuje abstrakcję, tutaj interface. Dzięki temu klasa ta nie podlega już koniecznym modyfikacjom. Konstruktor klasy InvoicePersistance przyjmuje jako argumenty a) jakąś fakturę, b) jakąś klasę reprezentującą owy interface (czyli w tym prostym przykładzie defacto klasę zajmującą się zapisem faktury do konkretnego pliku/rozszerzenia). W rezultacie tworzysz sobie instancję(obiekt) klasy InvoicePersistance -> new InvoicePersistance(myInvoice, InvoicePDFSaver); InvoicePDFSaver wygląda tak: public class InvoicePDFSaver implements IInvoiceSaver { //implementujemy interface jako Naszą abstrakcję void save(Invoice invoice) { //metoda biznesowa zapisująca fakturę do PDFa } I cyk, to tyle. Domyślnie metoda save() w klasie InvoicePersistance nie ma pojęcia o tym, co robi metoda save() w klasie InvoicePDFSaver. Nie potrzebuje tego wiedzieć, jest niezależna. Dzięki takiemu podejściu możesz tworzyć kolejne wariacje klas, które wykorzystują metodę save() z interface do zapisywania w różnych innych formatach Naszej faktury. Dzięki temu uwalniasz się również od problemu "mój wysokopoziomowy kod jest zależny od niskopoziomowego kodu!" -> ostatnia zasada DIP. Mam nadzieję, że coś tam pomogłem rozjaśnić. :)
Czyli wedle SRP posiadanie klasy takiej jak np. TransactionsService gdzie implementujemy metody uzywane przez kontrolery API to nie jest najczystsze wyjscie? Lepiej wtedy utworzyc kilka klas - po jednej dla kazdej metody Get, GetById, Create etc? Czy taki przypadek jest wyjatkiem i tutaj jest cicha zgoda na to, aby trzymac serwisy w jednej klasie? Pozdrawiam
Jestem programistą z "nastoletnim" doświadczeniem i muszę przyznać, że to jest pierwszy polski kanał na jaki trafiłem, w którym pokazuje się jak na prawdę wygląda praca programisty. Większość materiałów na te tematy to "prezentacje instrukcji obsługi" na poziomie przedszkolnym, często całkowicie oderwane od rzeczywistości w jakiej funkcjonują prawdziwi programiści. Wyglądają one tak, jakby ktoś przeczytał lub obejrzał jakiś kurs 2 dni temu i już robi własny kurs na podstawie tej wiedzy.
Odnośnie single responsibility principle - nie jest przypadkiem tak, że przez tą regułę będziemy mieli zawsze klasę z tylko jedną metodą? :D nie licząc setterów i getterów. No bo np możemy mieć klasę samochód i przydałby się metody na ruszenie, hamowanie i inne takie, ale z tego trochę wynika to, że powinniśmy na każdą akcję tworzyć inną klasę :D jak jest?
Imo wydaje mi się, że to co wymieniłeś, jak np. ruszanie i hamowanie mogłoby jak najbardziej być w jednej klasie, np. CarProcessor (czy cokolwiek). Głównie dlatego, że metody te odpowiadałyby za jedną funkcjonalność. Za pewną dynamikę pojazdu. Natomiast gdybyś do tej samej klasy dodał metodę, która sprawdza Ci, czy auto przeszło przegląd, czy ma sprawne jakieś czujniki lub metodę porównującą parametry tego auto do parametrów innego auto, to w tym momencie łamiesz zasadę SRP. Jak to zauważysz to podejmujesz decyzję, że pozostałe funkcjonalności (nie związane z "ruchem samochodu") należy przenieść do innych klas, które będą miały swoją konkretną odpowiedzialność.
Mnie zastanawia inna rzeczy -> w klasie Invoice przy Single Responsibility, mamy metodę CalculateTotal a w filmiku powiedziane jest, że klasa Invoice w takim kształcie ma jedną odpowiedzialność -> przechowywanie danych o Invoice... a dla mnie to tak nie wygląda przez metodę Calculate, gdyż ona przelicza (sumuje) zakupione rzeczy a więc klasa Invoice ma dwie odpowiedzialności: #1 Wyznacza strukturę danych i je przechowuje (czyli taki model) dla Invoice oraz #2 Wylicza wartość Total dla Invoice. Czy nie powinno być tak, że dla Total wartość jest wyliczana ale przez inną klasę, której odpowiedzialnością byłoby wyliczanie wartości potrzebnych dla Invoice? chodzi o to aby implementacja CalculateTotal nie była w Invoice a w innej klasie np. InvoiceCalculation. Wtedy w Invoice tylko "wywołujemy" metodę przeliczającą poprzez klasę InvoiceCalculation. Wyobraźmy sobie sytuację, gdy logika wyliczania wartości Total będzie się od czasu do czasu zmieniać np przez zmiany w prawie związane z podatkiem od grupy usług / towarów. Sama struktura przetrzymywanych danych w Invoice się nie zmieni ALE sposób wyliczania Total już tak co prowadzi mnie do wniosku, że klasa Invoice w przedstawionym kształcie na początku filmiku ma dwie odpowiedzialności. Gdybyśmy mieli drugą klasę czyli InvoiceCalculation, której odpowiedzialnością byłoby wyliczanie rzeczy dla Invoice mielibyśmy jeden powód do zmiany Invoice (czyli zmiana struktury danych, np. więcej pól) oraz jeden powód do zmiany InvoiceCalculation (czyli zmiana sposobu wyliczania wartości dla Invoice).
Bardzo dobry materiał, dużo się dowiedziałem z Twoich filmów ale jako że wracam do programowania po latach (bardziej hobbistycznie na razie) mam pytanie do DI. Wszystkie te zasady SOLID w głównej mierze opierają się na Interfejsach. Czy nie lepiej do konstruktorów, metod itd. wstrzykiwać nie metodę zrzutowaną na interfejs a delegata zawierającego taki rzut? Tak, jesteśmy ograniczeni do jednej metody, a w przypadku delegata z metodami rzutowanymi na interfejs moglibyśmy dynamicznie zmieniać ile i jakie metody chcemy w danym momencie użyć. Przepraszam za ewentualne błędy w nazewnictwie ale wdrażam się dopiero w obiektowe programowanie. Pozdrawiam
dla sporej części przypadków efekt byłby dosyć podobny, ale interface nei musi się ograniczać do implementacji jednej metody A poza tym jako że C# to język obiektowy, to raczej preferuje się operowanie na obiektach niżbezpośrednio na funkcjach/metodach
Kurczę, Robert C. Martin miał rację, że zasada SRP jest faktycznie najsłabiej rozumiana. Z książki Czysta architektura: "Spośród wszystkich reguł SOLID to reguła jednej odpowiedzialności (SRP) jest chyba najsłabiej zrozumiała. Prawdopodobnie wynika to z tego, że ma trochę niewłaściwą nazwę. Programiści, słysząc tę nazwę, od razu zakładają, że każdy z modułów (m. in. klas) powinien wykonywać tylko jedno zadanie. Polecam tę książkę. A zgodnie z SRP, ta klasa może mieć kilka funkcjonalności. Pytanie tylko, które grupy aktorów są powodami do zmian tych funkcjonalności - jeżeli jest ich więcej niż 1, to wtedy SRP jest zlamane.
Dokładnie , według wyjaśnienia w tym filmiku zasada SRP narzuca jedną odpowiedzialność dla każdej klasy , tak to zrozumiałem i się zastanawiam co ta zasada tak interpretowana ma polepszać. Ilość klas jakie trzeba by utworzyć dla każdej odpowiedzialności nie przyczynia się do czytelności projektu.
Hermetyzacja i bezpieczeństwo - Pytanie mam, jak się ma to do bezpieczeństwa że pola prywatne robimy publiczne? jasne zawsze można zrobić get'era, ale wartość pola i tak jest dostępna na zewnątrz. Próbuję to zrozumieć.
Klasyczny przykład to konto bankowe. Masz saldo jako public. Możesz się wtedy podpiąć dowolnym klientem i ustawić sobie saldo 1 mln euro. I jesteś milionerem:) W przypadku private - nie zrobisz tak. Możesz się wpiąć klientem tylko w metodę setStanKonta a tam już będzie blok ifów który sprawdzi czy jesteś klientem banku, czy masz uprawnienia do rachunku, sprawdzi skąd kasa wpływa, wyliczy prowizję od przelewu itd.
Jak wygląda wstrzykiwanie przez właściwości i metody, czy mozesz pokazać przykład? Czy w przykładzie z drukarka nie naruszamy zasady single responsibility?
wstrzykiwanie przez właściwości/metody, to po prostu przekazanie do właściwości/metody jakąś referencję, która następnie będzie przypisana do pola prywatnego w klasie, po konkretny przykład (zamiast wklejać kod w komentarz) odsyłam do: www.plukasiewicz.net/Artykuly/DIP_IoC_DI A co do drukarki, to tutaj żeby zdefiniować odpowiedzialność klasy Canon/HpLaserJet trzeba odpowiedzieć na pytanie 'za co jest odpowiedzialna ta klasa' I w tym przypadku odpowiedzią byłoby 'za obsługę drukarki z funkcją skanera...' i mimo, że ta konkretna klasa implementuje/ma kilka publicznych metod, to są one w obrębie jednej odpowiedzialności. Naruszeniem byłoby tu tutaj np stwierdzenie, że klasa jest odpowiedzialna za 'za obsługę drukarki z funkcją skanera i zapisanie informacji o wydruku na dysku' Także mimo tego że jakaś klasa ma kilka publicznych metod, to jeżeli pełnią one jedną funkcjonalność to nie jest to naruszenie SRP, ale z tego co zauważyłem to sporo osób właśnie rozumie SRP jako maksymalnie jedną publiczną metodę, przez co później w kodzie powstaje wiele klas z pojedynczymi metodami (co oczywiście jest lepsze niż posiadanie jednej wielkiej klasy z dziesiątkami metod ;p, ale takie programowanie bardziej przypomina programowanie funkcyjne niż obiektowe)
Chodzi mi o to, że z invoice wyrzuciliśmy metody które robiły coś z obiektem (zapisywanie do pdf itp) a w shape dodaliśmy taką metodę (render). Nie daje mi to spokoju :D.
@@michagauza93 jeżeli metody i właściwości klasy są ze sobą powiązne i wspólnie dostarczają jedną funkcjonalność (odpowiedzialność), to nie jest to złamanie SRP, złamanie tej zasady jest w tedy kiedy w jednej klasie mamy rzeczy nie powiązane ze sobą robiące dwie różne funkcjonalności
@@michagauza93 także czasem teżsię może zdażyć że obiekt ma więcej niż jedną metode publiczną, a to nie będzie oznaczać że ma wiele odpowiedzialności ;p
@@FullstackDeveloperPL ChatGPT wypluł mi taki przykład: using System; namespace MonetaryValuesExample { class Program { static void Main(string[] args) { float floatAmount = 100.0f; float floatTaxRate = 0.23f; float floatTotal = floatAmount * (1 + floatTaxRate); decimal decimalAmount = 100.0m; decimal decimalTaxRate = 0.23m; decimal decimalTotal = decimalAmount * (1 + decimalTaxRate); Console.WriteLine("Float calculation: {0}", floatTotal); Console.WriteLine("Decimal calculation: {0}", decimalTotal); Console.WriteLine("Difference: {0}", decimalTotal - floatTotal); } } } W jakiejś porządnej aplikacji do bankowości czy księgowości taki kod nie przeszedł by testów jednostkowych. Oczywiście ludzie posiłkują się zaokrągleniami, żeby rozjazdy się z czasem nie zakumulowały do znacznej wartości, ale nie tak powinno się to robić (wydajność).
@@technics6215 dzięki za przykład, z ciekawości odpaliłem go w programie konsolowym i wyszly w porządku wyniki: Float calculation: 123 Decimal calculation: 123.000 Difference: 0 ogólnie wiem że ten problem istnieje, ale nie spotkałem się jeszcze z teog typu błędami w c# (może w nowysz wersjach to poprawili)
Aktualne kupony zniżkowe na moje kursy:
fullstackdeveloper.tech/kursy
Zapraszam na serwer Discord:
discord.gg/UDHXQxhM4r
Super film, wreszcie to rozumiem. Dzięki!
Super nagranie! Podobało mi się zwłaszcza tempo nagrania jak i dobrze dobrane problemy jakie zostały rozwiązane przez zastosowanie wzorców. W tle cytowanie teorii, którą i tak wciąż cofałem, aby sobie utrwalić - mówienie w ten sposób to gwarant, że dobrze wypadnie się na rozmowie rekrutacyjnej ;)
Jestem też znana zasada Brzytwy Ockhama która mówi "Nie należy mnożyć bytów ponad potrzebę...
W programowaniu obiektowym chodzi o to by właśnie dana klasa miała metody jakie wykonuje, więc w klasie FAKTURA jak najbardziej pasują metody ZAPISZ / WYDRUKUJ. Po co rozbijać to na osobne klasy.... IMHO Sztuka dla sztuki.
Fajne wideo, dzięki! Wydaje mi się, że ISP zostało pokazane ze zbyt niskiego poziomu. Problem byłby widoczny w klasie, która chce agregować obiekty tylko skanujące albo tylko drukujące, a my mamy wyłącznie interfejs drukarki, który dostarcza znacznie szerszy interfejs. Podział interfejsu drukarek jest spoko, ale przydałoby się przykład jeszcze faksowni i drukarni, które początkowo używają interfejsu iPrinter, a ostatecznie drukarnia używa iPrinter o ograniczonej funkcjonalności, a faksownia iFaxContent.
Jak zawsze świetny materiał i przejrzyste tłumaczenie!
Dzięki za komentarz 😀
Super nagranie! Po obejrzeniu filmiku mam dwa pytania. Co do pierwszej zasady: Jak defniujemy odpowiedzialności? Np. Mamy logikę biznesową do rozliczania wpływów i wydatków w budżecie. Czy całość rozliczania mam rozumiec jaka jedyna odpowiedzialnosc? Czy może rozliczanie wpływu i rozliczanie wydatku mamy rozbic? Oraz pytanie do 3 zasady: Jak nie przedobrzyć z rozbijaniem interfejsów?
27:36 klasa Duck ma 2 odpowiedzialności, Duck oraz Swim. Czy to nie jest naruszenie S?
dzięki bardzo dobre przypomnienie :)
34:50 no nie jesteśmy w stanie służyć sie kwadratem tak aby reprezentował dowolny czworokąt.
tak samo jak kaczka górska nie reprezentuje dowolnej kaczki ... tylko górską :)
więc o co chodzi nie wiadomo dalej.
Chyba żę ... nie możemy modyfikować w sposób sztuczny np properties z klasy bazowej tylko po to żeby interfejs pasował. ???
35:20 nie rozumiem tej zasady. Dlatego kwadrat dziedziczy po czworokącie a nie czworokąt po kwadracie. Bo kwadrat jest czworokątem ale czworokąt nie musi być w kwadratem?
42:22 Jak sobie poradzic w Javie? tam możemy imprelentować tylko 1 interfejs
A czy klasa HpLaserJet w finalnej postaci (pokazanej w 43:08) nie narusza zasady pojedynczej odpowiedzialności?
Trzeba pamiętać że SOLID to ZALECENIA a nie obowiązek - zapewne będą przypadki gdzie bardziej będzie się opłacało złamać tę zasadę choć nie jestem jeszcze takim kozakiem aby o tym wyrokować, mówię co mi się wydaje ;-)
Mam pytanie odnośnie przykładu do Open-Close Principle.
Po co w ogóle tworzyć klasę InvoicePersistence?
Za jej pomocą można wyłącznie zapisać dokument w danym formacie, a czy tego samego nie można zrobić wykorzystując konkretny saver? Co zyskujemy dzięki tej klasie?
W przypadku przedstawionym na filmiku klasy 'Saver' odpowiadają za logikę zapisu do odpowiedniego formatu. A klasa 'Persistence' odpowiada jedynie za wywołanie tej logiki, więc zamiast wywoływać każdą klasę 'Saver' osobno zależnie od formatu do jakiego chcesz zapisać to używasz klasy 'Persistence', podajesz odpowiednie argumenty i na podstawie tych danych zostanie wywołana odpowiednia klasa 'Saver', która zapisze plik do odpowiedniego formatu.
Dobra robota! I pytanie, w przykładzie z kaczkami przy Liskov Substitution Principle w sumie pokazałeś identyczny anty-przykład gdzie RubberDuck nie jest w stanie zaimplementować metody Fly() jak w przypadku Interface Segregation Principle gdzie mamy to samo z drukarką Canon i jej Scan() metodą. Czy to jest tak że zawsze jakieś naruszenie ISP powduje naruszenie LSP ?
dzięki za komentarz ;p
co do pytania, to ogólnie rzecz biorąc to mimo, że zasady SOLID mają w teorii osobne definicję, to w praktyce ich granice, w wielu przypadkach się 'zacierają'. Także jeśli potraktujemy interface po prostu jako abstrakcyjną klasę bazową, to naruszająć ISP naruszy LSP, jak tylko pojawi się konkretna klasa, która nie potrafi zaimplementować jakiejś metody. Ale z drugiej strony nie każde naruszenie LSP musi być spowodowane naruszeniem ISP
19:21 - bardzo ciekawe, tylko, że w efekcie, żeby zapisać fakturę do np. pdf musimy utworzyć osobny obiekt klasy InvoicePersistence, który będzie zawierał odpowiedni obiekt invoiceSaver. Trochę to kuriozalne :D Naprawdę takie postępowanie jest standardowe? W końcu gdzieś chyba i tak będziesz musiał zaimplementować osobny metody do tego, ew. metodę, która przyjmuje jakiś argument, co nie?
Nie, nie będzie musiał. Zwróć uwagę, że pole 'private IInoivceSaver' w klasie InvoicePersistance reprezentuje abstrakcję, tutaj interface.
Dzięki temu klasa ta nie podlega już koniecznym modyfikacjom.
Konstruktor klasy InvoicePersistance przyjmuje jako argumenty a) jakąś fakturę, b) jakąś klasę reprezentującą owy interface (czyli w tym prostym przykładzie defacto klasę zajmującą się zapisem faktury do konkretnego pliku/rozszerzenia).
W rezultacie tworzysz sobie instancję(obiekt) klasy InvoicePersistance -> new InvoicePersistance(myInvoice, InvoicePDFSaver);
InvoicePDFSaver wygląda tak:
public class InvoicePDFSaver implements IInvoiceSaver { //implementujemy interface jako Naszą abstrakcję
void save(Invoice invoice) {
//metoda biznesowa zapisująca fakturę do PDFa
}
I cyk, to tyle. Domyślnie metoda save() w klasie InvoicePersistance nie ma pojęcia o tym, co robi metoda save() w klasie InvoicePDFSaver. Nie potrzebuje tego wiedzieć, jest niezależna.
Dzięki takiemu podejściu możesz tworzyć kolejne wariacje klas, które wykorzystują metodę save() z interface do zapisywania w różnych innych formatach Naszej faktury.
Dzięki temu uwalniasz się również od problemu "mój wysokopoziomowy kod jest zależny od niskopoziomowego kodu!" -> ostatnia zasada DIP.
Mam nadzieję, że coś tam pomogłem rozjaśnić. :)
Czyli wedle SRP posiadanie klasy takiej jak np. TransactionsService gdzie implementujemy metody uzywane przez kontrolery API to nie jest najczystsze wyjscie? Lepiej wtedy utworzyc kilka klas - po jednej dla kazdej metody Get, GetById, Create etc? Czy taki przypadek jest wyjatkiem i tutaj jest cicha zgoda na to, aby trzymac serwisy w jednej klasie?
Pozdrawiam
Patrząc pod kątem samego SRP to tak masz rację, ale jak sam wspomniałeś nie zawsze do każdej klasy stosujemy tą zasadę np do klas utility
Jestem programistą z "nastoletnim" doświadczeniem i muszę przyznać, że to jest pierwszy polski kanał na jaki trafiłem, w którym pokazuje się jak na prawdę wygląda praca programisty. Większość materiałów na te tematy to "prezentacje instrukcji obsługi" na poziomie przedszkolnym, często całkowicie oderwane od rzeczywistości w jakiej funkcjonują prawdziwi programiści. Wyglądają one tak, jakby ktoś przeczytał lub obejrzał jakiś kurs 2 dni temu i już robi własny kurs na podstawie tej wiedzy.
Odnośnie single responsibility principle - nie jest przypadkiem tak, że przez tą regułę będziemy mieli zawsze klasę z tylko jedną metodą? :D nie licząc setterów i getterów. No bo np możemy mieć klasę samochód i przydałby się metody na ruszenie, hamowanie i inne takie, ale z tego trochę wynika to, że powinniśmy na każdą akcję tworzyć inną klasę :D jak jest?
Ej, ja ciągle czekam na odpowiedź jakby co, wchodzę tu czasami zobaczyć czy jest, więc jak ktoś wie to proszę o odpowiedź xD
Imo wydaje mi się, że to co wymieniłeś, jak np. ruszanie i hamowanie mogłoby jak najbardziej być w jednej klasie, np. CarProcessor (czy cokolwiek). Głównie dlatego, że metody te odpowiadałyby za jedną funkcjonalność. Za pewną dynamikę pojazdu. Natomiast gdybyś do tej samej klasy dodał metodę, która sprawdza Ci, czy auto przeszło przegląd, czy ma sprawne jakieś czujniki lub metodę porównującą parametry tego auto do parametrów innego auto, to w tym momencie łamiesz zasadę SRP. Jak to zauważysz to podejmujesz decyzję, że pozostałe funkcjonalności (nie związane z "ruchem samochodu") należy przenieść do innych klas, które będą miały swoją konkretną odpowiedzialność.
@@jakubdudek4133 rozumiem, dzięki :)
Mnie zastanawia inna rzeczy -> w klasie Invoice przy Single Responsibility, mamy metodę CalculateTotal a w filmiku powiedziane jest, że klasa Invoice w takim kształcie ma jedną odpowiedzialność -> przechowywanie danych o Invoice... a dla mnie to tak nie wygląda przez metodę Calculate, gdyż ona przelicza (sumuje) zakupione rzeczy a więc klasa Invoice ma dwie odpowiedzialności: #1 Wyznacza strukturę danych i je przechowuje (czyli taki model) dla Invoice oraz #2 Wylicza wartość Total dla Invoice.
Czy nie powinno być tak, że dla Total wartość jest wyliczana ale przez inną klasę, której odpowiedzialnością byłoby wyliczanie wartości potrzebnych dla Invoice?
chodzi o to aby implementacja CalculateTotal nie była w Invoice a w innej klasie np. InvoiceCalculation. Wtedy w Invoice tylko "wywołujemy" metodę przeliczającą poprzez klasę InvoiceCalculation.
Wyobraźmy sobie sytuację, gdy logika wyliczania wartości Total będzie się od czasu do czasu zmieniać np przez zmiany w prawie związane z podatkiem od grupy usług / towarów.
Sama struktura przetrzymywanych danych w Invoice się nie zmieni ALE sposób wyliczania Total już tak co prowadzi mnie do wniosku, że klasa Invoice w przedstawionym kształcie na początku filmiku ma dwie odpowiedzialności.
Gdybyśmy mieli drugą klasę czyli InvoiceCalculation, której odpowiedzialnością byłoby wyliczanie rzeczy dla Invoice mielibyśmy jeden powód do zmiany Invoice (czyli zmiana struktury danych, np. więcej pól) oraz jeden powód do zmiany InvoiceCalculation (czyli zmiana sposobu wyliczania wartości dla Invoice).
Bardzo dobry materiał, dużo się dowiedziałem z Twoich filmów ale jako że wracam do programowania po latach (bardziej hobbistycznie na razie) mam pytanie do DI. Wszystkie te zasady SOLID w głównej mierze opierają się na Interfejsach. Czy nie lepiej do konstruktorów, metod itd. wstrzykiwać nie metodę zrzutowaną na interfejs a delegata zawierającego taki rzut? Tak, jesteśmy ograniczeni do jednej metody, a w przypadku delegata z metodami rzutowanymi na interfejs moglibyśmy dynamicznie zmieniać ile i jakie metody chcemy w danym momencie użyć. Przepraszam za ewentualne błędy w nazewnictwie ale wdrażam się dopiero w obiektowe programowanie. Pozdrawiam
dla sporej części przypadków efekt byłby dosyć podobny, ale interface nei musi się ograniczać do implementacji jednej metody
A poza tym jako że C# to język obiektowy, to raczej preferuje się operowanie na obiektach niżbezpośrednio na funkcjach/metodach
Kurczę, Robert C. Martin miał rację, że zasada SRP jest faktycznie najsłabiej rozumiana.
Z książki Czysta architektura:
"Spośród wszystkich reguł SOLID to reguła jednej odpowiedzialności (SRP) jest chyba najsłabiej zrozumiała. Prawdopodobnie wynika to z tego, że ma trochę niewłaściwą nazwę. Programiści, słysząc tę nazwę, od razu zakładają, że każdy z modułów (m. in. klas) powinien wykonywać tylko jedno zadanie.
Polecam tę książkę.
A zgodnie z SRP, ta klasa może mieć kilka funkcjonalności. Pytanie tylko, które grupy aktorów są powodami do zmian tych funkcjonalności - jeżeli jest ich więcej niż 1, to wtedy SRP jest zlamane.
Dokładnie , według wyjaśnienia w tym filmiku zasada SRP narzuca jedną odpowiedzialność dla każdej klasy , tak to zrozumiałem i się zastanawiam co ta zasada tak interpretowana ma polepszać. Ilość klas jakie trzeba by utworzyć dla każdej odpowiedzialności nie przyczynia się do czytelności projektu.
powiedziałbym prędzej, że ta zasada jest źle nazwana, a nie źle rozumiana ;)
Hermetyzacja i bezpieczeństwo - Pytanie mam, jak się ma to do bezpieczeństwa że pola prywatne robimy publiczne?
jasne zawsze można zrobić get'era, ale wartość pola i tak jest dostępna na zewnątrz. Próbuję to zrozumieć.
Klasyczny przykład to konto bankowe. Masz saldo jako public. Możesz się wtedy podpiąć dowolnym klientem i ustawić sobie saldo 1 mln euro. I jesteś milionerem:) W przypadku private - nie zrobisz tak. Możesz się wpiąć klientem tylko w metodę setStanKonta a tam już będzie blok ifów który sprawdzi czy jesteś klientem banku, czy masz uprawnienia do rachunku, sprawdzi skąd kasa wpływa, wyliczy prowizję od przelewu itd.
Jak wygląda wstrzykiwanie przez właściwości i metody, czy mozesz pokazać przykład? Czy w przykładzie z drukarka nie naruszamy zasady single responsibility?
wstrzykiwanie przez właściwości/metody, to po prostu przekazanie do właściwości/metody jakąś referencję, która następnie będzie przypisana do pola prywatnego w klasie, po konkretny przykład (zamiast wklejać kod w komentarz) odsyłam do: www.plukasiewicz.net/Artykuly/DIP_IoC_DI
A co do drukarki, to tutaj żeby zdefiniować odpowiedzialność klasy Canon/HpLaserJet trzeba odpowiedzieć na pytanie 'za co jest odpowiedzialna ta klasa'
I w tym przypadku odpowiedzią byłoby 'za obsługę drukarki z funkcją skanera...' i mimo, że ta konkretna klasa implementuje/ma kilka publicznych metod, to są one w obrębie jednej odpowiedzialności.
Naruszeniem byłoby tu tutaj np stwierdzenie, że klasa jest odpowiedzialna za 'za obsługę drukarki z funkcją skanera i zapisanie informacji o wydruku na dysku'
Także mimo tego że jakaś klasa ma kilka publicznych metod, to jeżeli pełnią one jedną funkcjonalność to nie jest to naruszenie SRP, ale z tego co zauważyłem to sporo osób właśnie rozumie SRP jako maksymalnie jedną publiczną metodę, przez co później w kodzie powstaje wiele klas z pojedynczymi metodami (co oczywiście jest lepsze niż posiadanie jednej wielkiej klasy z dziesiątkami metod ;p, ale takie programowanie bardziej przypomina programowanie funkcyjne niż obiektowe)
@@FullstackDeveloperPL Jak zawsze rozwiałeś wszystkie wątpliwość, dziekuje bardzo za odpowiedź! Super robota
Czy dodając do kasy shape funkcji draw nie łamiemy w ten sposób pierwszej zasady SOLID?
mógłbyś wskazać o który moment w materiale Ci chodzi? ;p
nie moge znależć metody Draw (patrze po repo)
@@FullstackDeveloperPL 23:40 :D sorrki na początku to było draw i zmieniłeś na render już w Shape. :D
Chodzi mi o to, że z invoice wyrzuciliśmy metody które robiły coś z obiektem (zapisywanie do pdf itp) a w shape dodaliśmy taką metodę (render). Nie daje mi to spokoju :D.
@@michagauza93 jeżeli metody i właściwości klasy są ze sobą powiązne i wspólnie dostarczają jedną funkcjonalność (odpowiedzialność), to nie jest to złamanie SRP, złamanie tej zasady jest w tedy kiedy w jednej klasie mamy rzeczy nie powiązane ze sobą robiące dwie różne funkcjonalności
@@michagauza93 także czasem teżsię może zdażyć że obiekt ma więcej niż jedną metode publiczną, a to nie będzie oznaczać że ma wiele odpowiedzialności ;p
Suma faktury jako float? A nie powinno być decimal?
Mógłby też być decimal 😀
@@FullstackDeveloperPL Radzę poczytać na temat "never use floats for money".
@@technics6215 w C# raczej nie ma takich problemów, masz może konkretny przykład który by się nie sprawdził?, sprawdziłbym z ciekawości ;p
@@FullstackDeveloperPL ChatGPT wypluł mi taki przykład:
using System;
namespace MonetaryValuesExample
{
class Program
{
static void Main(string[] args)
{
float floatAmount = 100.0f;
float floatTaxRate = 0.23f;
float floatTotal = floatAmount * (1 + floatTaxRate);
decimal decimalAmount = 100.0m;
decimal decimalTaxRate = 0.23m;
decimal decimalTotal = decimalAmount * (1 + decimalTaxRate);
Console.WriteLine("Float calculation: {0}", floatTotal);
Console.WriteLine("Decimal calculation: {0}", decimalTotal);
Console.WriteLine("Difference: {0}", decimalTotal - floatTotal);
}
}
}
W jakiejś porządnej aplikacji do bankowości czy księgowości taki kod nie przeszedł by testów jednostkowych. Oczywiście ludzie posiłkują się zaokrągleniami, żeby rozjazdy się z czasem nie zakumulowały do znacznej wartości, ale nie tak powinno się to robić (wydajność).
@@technics6215 dzięki za przykład, z ciekawości odpaliłem go w programie konsolowym i wyszly w porządku wyniki:
Float calculation: 123
Decimal calculation: 123.000
Difference: 0
ogólnie wiem że ten problem istnieje, ale nie spotkałem się jeszcze z teog typu błędami w c# (może w nowysz wersjach to poprawili)
Czyli można przyjąć, że jedna klasa musi mieć tylko jedną metodę ;)