Czym są zasady SOLID?

Podobne dokumenty
Programowanie Zespołowe

SOLIDnie śmierdzący kod.

Programowanie obiektowe

Programowanie obiektowe

Metodyki zwinne wytwarzania oprogramowania

Informacje ogólne. Karol Trybulec p-programowanie.pl 1. 2 // cialo klasy. class osoba { string imie; string nazwisko; int wiek; int wzrost;

Klasy abstrakcyjne i interfejsy

JAVA W SUPER EXPRESOWEJ PIGUŁCE

WSNHiD, Programowanie 2 Lab. 2 Język Java struktura programu, dziedziczenie, abstrakcja, polimorfizm, interfejsy

Czym są właściwości. Poprawne projektowanie klas

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Języki i techniki programowania Ćwiczenia 3 Dziedziczenie

Programowanie obiektowe i zdarzeniowe

Różne odmiany budowniczego. Po co używać wzorca budowniczy? Kiedy używać budowniczego

Diagram klas UML jest statycznym diagramem, przedstawiającym strukturę aplikacji bądź systemu w paradygmacie programowania obiektowego.

Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.

Polimorfizm. dr Jarosław Skaruz

Programowanie bez fabryki. Po co używać wzorca fabryki?

Zaawansowane programowanie w języku C++ Programowanie obiektowe

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej

TEMAT : KLASY DZIEDZICZENIE

Aplikacje w środowisku Java


Czym jest polimorfizm?

Zaawansowane programowanie w C++ (PCP)

Metodyki zwinne wytwarzania oprogramowania

Wykład 5 Okna MDI i SDI, dziedziczenie

Projektowanie obiektowe. Roman Simiński Wzorce projektowe Wybrane wzorce strukturalne

Programowanie obiektowe

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Metody Metody, parametry, zwracanie wartości

Szablony funkcji i szablony klas

Listy powiązane zorientowane obiektowo

Dzisiejszy wykład. Wzorce projektowe. Visitor Client-Server Factory Singleton

10. Programowanie obiektowe w PHP5

Wprowadzenie do projektu QualitySpy

Java - tablice, konstruktory, dziedziczenie i hermetyzacja

Czym jest polimorfizm?

Programowanie obiektowe

UML a kod w C++ i Javie. Przypadki użycia. Diagramy klas. Klasy użytkowników i wykorzystywane funkcje. Związki pomiędzy przypadkami.

Wyjątki (exceptions)

Szablony funkcji i klas (templates)

Programowanie obiektowe

Typy, klasy typów, składnie w funkcji

Zaawansowane programowanie w C++ (PCP)

Programowanie obiektowe

Dziedziczenie. Streszczenie Celem wykładu jest omówienie tematyki dziedziczenia klas. Czas wykładu 45 minut.

Programowanie obiektowe

1. Które składowe klasa posiada zawsze, niezależnie od tego czy je zdefiniujemy, czy nie?

Dokumentacja do API Javy.

Programowanie obiektowe

Polimorfizm, metody wirtualne i klasy abstrakcyjne

Czym jest stos i sterta?

Klasy abstrakcyjne, interfejsy i polimorfizm

Enkapsulacja, dziedziczenie, polimorfizm

Kurs WWW. Paweł Rajba.

Rozdział 4 KLASY, OBIEKTY, METODY

Programowanie II. Lista 3. Modyfikatory dostępu plik TKLientBanku.h

Kurs programowania. Wykład 2. Wojciech Macyna. 17 marca 2016

Java Język programowania

Klasy cd. Struktury Interfejsy Wyjątki

Projektowanie obiektowe. Roman Simiński Polimorfizm

PARADYGMATY PROGRAMOWANIA Wykład 4

Technologie i usługi internetowe cz. 2

Konstruktory. Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut.

Programowanie obiektowe

Programowanie w Javie 1 Wykład i Ćwiczenia 3 Programowanie obiektowe w Javie cd. Płock, 16 października 2013 r.

Programowanie obiektowe w VB cz 2

Ekspert radzi. mechanizm w enova, umożliwiający wskazanie domyślnej drukarki dla danego stanowiska i wydruku. Strona 1 z 8. Ekspert radzi.

Podstawy programowania. Wykład Funkcje. Krzysztof Banaś Podstawy programowania 1

.NET Klasy, obiekty. ciąg dalszy

Programowanie obiektowe w języku

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

Programowanie obiektowe i zdarzeniowe wykład 4 Kompozycja, kolekcje, wiązanie danych

Wskazówki projektowe. Programowanie Obiektowe Mateusz Cicheński

Podstawy Programowania Obiektowego

C++ Przeładowanie operatorów i wzorce w klasach

Języki Programowania. Prowadząca: dr inż. Hanna Zbroszczyk. tel: Konsultacje: piątek:

Java: interfejsy i klasy wewnętrzne

Laboratorium z przedmiotu: Inżynieria Oprogramowania INEK Instrukcja 6

Związek między pojęciami Zasada podstawialności Podklasy muszą realizować kontrakt zawarty przez nadklasy

Wzorce Strukturalne. Adapter: opis. Tomasz Borzyszkowski

Mechanizm dziedziczenia

Programowanie obiektowe

Kurs programowania. Wstęp - wykład 0. Wojciech Macyna. 22 lutego 2016

Język Java część 2 (przykładowa aplikacja)

Podstawy Programowania Programowanie Obiektowe

Dziedziczenie. » Dodawanie nowych elementów klasy (składowych funkcyjnych, danych składowych)» Modyfikacje odziedziczonych składowych funkcyjnych

Zofia Kruczkiewicz - Modelowanie i analiza systemów informatycznych 1

Wykład 9: Polimorfizm i klasy wirtualne

Dariusz Brzeziński. Politechnika Poznańska, Instytut Informatyki

KLASA UCZEN Uczen imię, nazwisko, średnia konstruktor konstruktor Ustaw Wyswietl Lepszy Promowany

Aplikacje RMI

Dziedziczenie. dr Jarosław Skaruz

Programowanie współbieżne Wykład 8 Podstawy programowania obiektowego. Iwona Kochaoska

1 Definiowanie prostych klas

Obiektowy PHP. Czym jest obiekt? Definicja klasy. Składowe klasy pola i metody

Programowanie w języku Java - Wyjątki, obsługa wyjątków, generowanie wyjątków

Transkrypt:

Zasady SOLID to termin jakim zostało nazwane pięć podstawowych zasad, którymi należy się kierować programując obiektowo. Skrót pochodzi od pierwszych liter poszczególnych zasad, są to: single responsibility, open/closed, liskov substitution, interface segregation oraz dependency inversion. Zasady SOLID zostały wymyślone przez znanego amerykańskiego programistę Roberta Martina. Słynie on ze swojego podejścia do czystego kodu, przyczynił się także do rozwoju manifestu zwinnego programowania. Znajomość SOLIDu może Ci znacznie pomóc, a na pewno nie podziała na Twoją niekorzyść. Czym są zasady SOLID? Zasady SOLID to pięć podstawowych zasad podpowiadających jak pisać dobry kod zorientowany obiektowo. Zaproponował je słynny Amerykański programista Robert Martin. Jest on także jednym z twórców manifestu zwinnego programowania agile. Do napisania artykułu opisującego zasady SOLID skłoniło mnie osobiste doświadczenie. Zauważyłem, że wielu początkujących programistów najzwyczajniej nie rozumie sensu poszczególnych zasad. W większości przypadków każdy kto programuje, wie czym jest SOLID, jednak po chwili krótkiej dyskusji każdego można złapać na złym rozumieniu jakiejś zasady. Pytanie o SOLID często pojawia się podczas rozmów kwalifikacyjnych w szczególności programistom z mniejszym stażem (a więc np. studentom). Nieumiejętność lub co gorsze niewiedza na temat niniejszych zasad wiele mówi o poziomie wiedzy danej osoby. Single responsibility Zasada pojedynczej odpowiedzialności mówi o tym, aby każda klasa była odpowiedzialna za jedną konkretną rzecz. W szczególności powinien istnieć jeden konkretny powód do modyfikacji danej klasy. Stosowanie tej zasady znacząco zwiększa ilość klas w programie, a jednocześnie zmniejsza ilość klas typu scyzoryk szwajcarski. Takim mianem określa się wielkie kilkuset linijkowe klasy skupiające za dużo funkcjonalności. Zasada pojedynczej odpowiedzialności (ang. single responsibility principle) każda klasa powinna być odpowiedzialna za jedną konkretną rzecz Jednym z podstawowych kroków każdej refaktoryzacji jest zawsze wydzielenie mniejszych klas z tych już istniejących. Budowanie dużych klas zawsze wcześniej czy później prowadzi do Karol Trybulec p-programowanie.pl

problemów. Określenie szwajcarskiego scyzoryka zawsze pojawia się podczas opisywania zasady pojedynczej odpowiedzialności, ponieważ jest to przykład idealny. Powszechnie wiadomo, że jeżeli coś jest do wszystkiego to jest do niczego. W programowaniu to stwierdzenie ma podwójną moc. Oto przykład złej klasy: 0 class Person public string Name get; set; public string Lastname get; set; public string City get; set; public string Street get; set; public int HouseNumber get; set; public string Email get; set; public Person(string name, string lastname, string email) Name = name; Lastname = lastname; Email = validateemail(email); private string validateemail(string email) if (!email.contains("@")!email.contains(".")) throw new FormatException("Email address has a wrong format!"); return email; Powyższy przykład jest typowym przykładem złej klasy. Zawiera ona w sobie metodę sprawdzającą poprawność adresu e-mail a nie powinno leżeć to w obowiązku typu Person. Czy są jeszcze błędy? tak. Klasa osoba nie powinna zawierać atrybutów, które nie są z nią powiązane. W przyszłości ktoś będzie musiał tę klasę refaktoryzować lub powielać kod chcąc przechować sam adres zameldowania. Poprawna klasa powinna wyglądać następująco: Karol Trybulec p-programowanie.pl

0 0 class Address public string City get; set; public string Street get; set; public int HouseNumber get; set; class Person public string Name get; set; public string Lastname get; set; public string Email get; set; public Address PersonAddress get; set; public Person(string name, string lastname, string email) Name = name; Lastname = lastname; Email = email; class EmailValidator public void validateemail(string email) if (!email.contains("@")!email.contains(".")) throw new FormatException("Email address has a wrong format!"); Klasa została rozdrobniona aż na mniejsze klasy. Czy wydaje Ci się, że kod jest poprawny? niestety nie. W klasie EmailValidator występuje fragment kodu odpowiedzialny za rzucanie wyjątku. Równie dobrze wyjątek mógłby zostać zapisany do logów, a takiej logiki w naszym walidatorze nie chcemy. Ma on spełniać prostą funkcję walidacji, bez żadnej dodatkowej logiki. W tym wypadku rzucenie wyjątku powinno zostać wyniesione wyżej: Karol Trybulec p-programowanie.pl

0... class EmailValidator public bool isvalid(string email) if (!email.contains("@")!email.contains(".")) return false; return true; class Program static void Main() EmailValidator emailvalidator = new EmailValidator(); Person person = new Person("Karol", "Trybulec", "email@test.com"); if (!emailvalidator.isvalid(person.email)) throw new FormatException("Email address has a wrong format!"); Jak widać prosta zasada jaką jest pojedyncza odpowiedzialność nawet w prostych przykładach potrafi zaskoczyć. W praktyce bardzo rzadko programiści rozdzielają swój kod na klasy w wystarczającym stopniu. Nie miej wrażenia, że zbytnie modularyzowanie kodu jest złe. Nawet w tak trywialnym przypadku jak ten wyżej, zaszycie walidacji w typie Person byłoby bardzo złe. Wcześniej czy później doprowadziłoby do powtarzania kodu, jeżeli ktoś chciałby zwalidować adres e-mail gdziekolwiek poza instancją klasy Person. A co gdyby ktoś chciał zwalidować adres e-mail bez rzucania wyjątku? Takie przykłady można podawać w nieskończoność. Zasada pojedynczej odpowiedzialności dotyczy również interfejsów. Lepiej zdefiniować ich dostatecznie dużo, co da w przyszłości dużą elastyczność. Dotyczy to szczególnie interfejsów polimorficznych. Więcej możesz przeczytać w tej odpowiedzi: https://stackoverflow.com/a// Open/closed Zasada otwarty/zamknięty powinna być zawsze rozwijana do postaci otwarty na Karol Trybulec p-programowanie.pl

rozbudowę, zamknięty na modyfikacje. Dzięki temu, jest to praktycznie jej cała i kompletna definicja. Jest to bardzo ważna zasada, szczególnie w dużych projektach, nad którymi pracuje wielu programistów. Każdą klasę powinniśmy pisać tak, aby możliwa była jej rozbudowa bez konieczności jej modyfikacji. Modyfikacja jest surowo zabroniona, ponieważ zmiana deklaracji jakiejkolwiek metody może spowodować awarię systemu w innym miejscu. Zasada ta jest szczególnie ważna dla twórców wszelkich wtyczek i bibliotek programistycznych. Zasada otwarty/zamknięty (ang. open/close principle) każda klasa powinna być otwarta na rozbudowę ale zamknięta na modyfikacje Istnieje pewna zależność, im bardziej trzymamy się zasady pojedynczej odpowiedzialności, tym bardziej musimy dbać o zasadę otwarty na rozbudowę, zamknięty na modyfikacje. Rozważmy przykład: 0 class Square public int A get; set; class Rectangle public int A get; set; public int B get; set; class Calculator public int Area(object shape) if (shape is Square) Square square = (Square)shape; return square.a * square.a; else if (shape is Rectangle) Rectangle rectangle = (Rectangle)shape; return rectangle.a * rectangle.b; return 0; Karol Trybulec p-programowanie.pl

Jest to oczywiście przykład zły. Dodanie nowej figury spowoduje konieczność modyfikacji istniejącej klasy Calc a dokładniej jej metody służącej do obliczenia pola figury. Bardzo dobrym mechanizmem wychodzenia z takich opresji jest polimorfizm. Dzięki niemu można obarczyć koniecznością implementacji metody liczącej pole figury każdą klasę reprezentującą figurę. Rozważmy przykład: 0 0 abstract class Shape public abstract int Area(); class Square : Shape public int A get; set; public override int Area() return A * A; class Rectangle : Shape public int A get; set; public int B get; set; public override int Area() return A * B; class Calculator public int Area(Shape shape) return shape.area(); Dzięki użyciu polimorfizmu i mechanizmu dziedziczenia w dobry sposób dbamy o zasadę otwarty/zamknięty. W tym prostym przykładzie zamiast polimorfizmu mogłem użyć tylko interfejsu. Jednak interfejs jest bezstanowy i w przypadku bardziej rozbudowanych klas lepszym rozwiązaniem jest klasa wirtualna lub abstrakcyjna, w której można dodatkowo zdefiniować inne atrybuty. Karol Trybulec p-programowanie.pl

Czy powyższy przykład przekonał Cię do konieczności trzymania się zasady otwarty/zamknięty? Być może nie. Aby rozumieć konieczność używania tej zasady, pomyśl o klasie Calculator jako klasie zahermetyzowanej w pliku DLL, który jest udostępniony tysiącom klientów. Drobna poprawka w kodzie zmusza tysiące programistów do pobrania nowej wersji pliku DLL z nowszą wersją metody liczenia pola. Dlatego właśnie klasa powinna być otwarta na modyfikacje bez możliwości jej edycji. Popatrzymy na problem od drugiej strony, rozważmy inny prosty przykład biblioteki do generowania raportów: public class RaportGenerator public RaportGenerator(string content) // generate raport in PDF class Program static void Main() string content = "Lorem ipsum lorem ipsum.. "; RaportGenerator raportgenerator = new RaportGenerator(content); Console.ReadKey(); Załóżmy, że jest to kod klasy odpowiedzialnej za generowanie raportów w formacie PDF. Czy kod jest poprawny? Teoretycznie tak. Do momentu, w którym twórca klasy nie zechce dodać do niej opcji generowania raportu w formacie Excel. Co zrobić w tym momencie? Dołożenie parametru do konstruktora jest złamaniem zasady otwarty/zamknięty. Spowoduje, że u tysięcy użytkowników naszej biblioteki kod przestanie działać. W tym przypadku, problem leży w błędnym zaprojektowaniu klasy od samego początku. Jednym ze sposobów poradzenia sobie z takim problemem jest użycie konstruktora wieloargumentowego. W przypadku wielu konstruktorów dobry nawykiem jest korzystanie ze wzorca constructor chaining. Poprawiony kod: Karol Trybulec p-programowanie.pl

0 0 public class RaportGenerator public enum RaportFormat Pdf, Excel public RaportGenerator(string content) : this(content, RaportFormat.Pdf) public RaportGenerator(string content, RaportFormat raportformat) // generate raport in pdf or excel format class Program static void Main() string content = "Lorem ipsum lorem ipsum.. "; RaportGenerator raportgeneratorpdf = new RaportGenerator(content); RaportGenerator raportgeneratorexcel = new RaportGenerator(content, RaportGenerator.RaportFormat.Excel); Console.ReadKey(); Przykład jest trywialny. Nie do końca udało się nie złamać zasady otwarty/zamknięty, ale udało się wyjść z trudnej sytuacji. Funkcjonalność klasy została rozszerzona bez problemów z kompatybilnością wstecz. Więcej możesz przeczytać w tej odpowiedzi: https://stackoverflow.com/a// Liskov substitution Zasada podstawienia Liskov jest w moim mniemaniu zasadą, którą najciężej zrozumieć, a ludzie bardzo często mylą ją z wszelkimi innymi zasadami. Jej nazwa pochodzi od nazwiska amerykańskiej programistki Barbary Liskov. W skrócie zasada polega na tym, że w miejscu klasy bazowej można zawsze użyć dowolnej klasy pochodnej. Oznacza to, że w 0% musi być zachowana zgodność interfejsu i wszystkich metod. Karol Trybulec p-programowanie.pl

Zasada podstawienia Liskov (ang. liskov substitution principle) w miejscu klasy bazowej można użyć dowolnej klasy pochodnej (zgodność wszystkich metod) Sztandarowym przykładem złamania zasady podstawienia Liskov, jest kwadrat dziedziczący z prostokąta. W matematyce kwadrat jest prostokątem, jednak w programowaniu nie jest, nie można użyć relacji dziedziczenia (is-a) pomiędzy tymi dwoma typami. Pole kwadratu jest liczone z innego wzoru, skutkiem czego kwadrat musi przesłonić metodę liczenia pola prostokąta. Jest to złamanie zasady podstawienia Liskov. Spójrzmy na inny przykład: 0 0 abstract class Animal public string Name get; set; public abstract void Run(); class Dog : Animal public override void Run() Console.WriteLine("Dog runs"); class Fish : Animal public override void Run() //wtf? fishes can not run throw new NotImplementedException(); class Program static void Main() List<Animal> animals = new List<Animal>(); animals.add(new Dog()); animals.add(new Fish()); animals.foreach(o => o.run()); Karol Trybulec p-programowanie.pl

W powyższym przykładzie utworzyliśmy abstrakcję Animal jednak występuje tutaj zjawisko źle przemyślanego mechanizmu dziedziczenia. Ryba jest zwierzęciem, ale została obarczona implementacją metody Run() znajdującej się w klasie bazowej. Ryba jak to ryba, nie może biegać i jest to złamanie zasady podstawienia Liskov. Dziedziczenie należy zaplanować inaczej, tak aby każda klasa pochodna mogła wykorzystać funkcje klasy bazowej. Zasada podstawienia Liskov najczęściej łamana jest w przypadkach: kiedy programista źle rozplanował mechanizm dziedziczenia, interfejs polimorficzny jest zbyt ogólny zastosowane dziedziczenie bez mechanizmu polimorfizmu (mało efektywne i często prowadzi do złamania Liskov) klasy pochodne nadpisują metody klasy bazowej zastępując jej niepasującą logikę W dobrze zaplanowanym mechanizmie dziedziczenia, klasy pochodne nie powinny nadpisywać metod klas bazowych. Mogą je ewentualnie rozszerzać, wywołując metodę z klasy bazowej (np. poprzez słowo kluczowe base będącym wskaźnikiem na klasę bazową). Spójrzmy na przykład: 0 0 class CoffeeMachine public virtual void Brew() Console.WriteLine("Pour coffee to the cup"); Console.WriteLine("Pour water to the cup"); class CoffeeLatteMachine : CoffeeMachine public override void Brew() base.brew(); Console.WriteLine("Pour milk to the cup"); class Program static void Main() CoffeeMachine coffee; Console.WriteLine("Making normal coffee"); coffee = new CoffeeMachine(); coffee.brew(); Console.WriteLine("Making latte coffee"); coffee = new CoffeeLatteMachine(); coffee.brew(); Karol Trybulec p-programowanie.pl

Powyższy przykład idealnie przestrzega metode podstawienia Liskov. Nie dość że obiekt klasy pochodnej można użyć w miejscu klasy bazowej, to na dodatek mimo użycia polimorfizmu nie nadpisujemy metod klasy bazowej, tylko z nich korzystamy. Należy się także wystrzegać wszelkich instrukcji warunkowych sprawdzających typ pochodny klasy przed wywołaniem danej funkcji. Przykładowo: static void Main() List<Vehicle> vehiclelist = new List<Vehicle>(); vehiclelist.add(new Car()); vehiclelist.add(new Bike()); vehiclelist.add(new Boat()); foreach (Vehicle obj in vehiclelist) if (obj is Boat) break; // boat does not have wheels obj.getwheelsamount(); Ten kod także jest błędny, złe dziedziczenie powoduje konieczność dodawania dodatkowej logiki sprawdzającej typ pochodny, ponieważ nie wszystkie są w 0% możliwe do podstawienia pod typ bazowy. Więcej możesz przeczytać w tej odpowiedzi: https://stackoverflow.com/a// Interface segregation Zasada segregacji interfejsów jest bardzo prosta, mówi aby nie tworzyć interfejsów z metodami, których nie używa klasa. Interfejsy powinny być konkretne i jak najmniejsze. Zasada segregacji interfejsów (ang. interface segregation principle) interfejsy powinny być małe i konkretne aby klasy nie implementowały metod, których nie potrzebują Do tworzenia typu bazowego przeważnie lepiej użyć klasy abstrakcyjnej. Może ona Karol Trybulec p-programowanie.pl

opisywać konkretny typ, zawierać odpowiednie atrybuty oraz metody, którymi następnie obarcza wszystkie klasy pochodne. Klasa bazowa definiuje model biznesowy, który akurat potrzebujemy. Interfejs natomiast jest bezstanowy, nie powinien definiować modelu biznesowego. Interfejs powinien zapewniać kontrakt, informujący programistę o zachowaniach danego typu. Przykładowy kod: 0 0 interface IRaportable void PrintPdf(); void PrintExcel(); class SalaryRaport : IRaportable public void PrintPdf() // print pdf public void PrintExcel() // print excel class HighSchoolExam : IRaportable public void PrintPdf() // print Pdf here public void PrintExcel() throw new NotImplementedException(); Kod jest błędny, ponieważ nie każda metoda definiowana przez interfejs jest wykorzystana w klasach pochodnych. Zamiast głównego interfejsu IRaportable można utworzyć wiele mniejszych interfejsów. Przykładowy kod: Karol Trybulec p-programowanie.pl

0 0 interface IPrintablePdf void PrintPdf(); interface IPrintableExcel void PrintExcel(); class SalaryRaport : IPrintablePdf, IPrintableExcel public void PrintPdf() // print pdf public void PrintExcel() // print excel class HighSchoolExam : IPrintablePdf public void PrintPdf() // print Pdf here Dzięki podzieleniu interfejsu na mniejsze, utrzymujemy porządek w interfejsie polimorficznym typu. Dzięki temu typy pochodne nie są związane kontraktami, które nie są im potrzebne. Łamanie zasady segregacji interfejsów prowadzi do niemiłych sytuacji, kiedy iterując po liście typów bazowych ze wspólnym interfejsem polimorficznym rzucony zostaje wyjątek, ponieważ któraś z klas nie implementuje metody rozbudowanego interfejsu. Więcej możesz przeczytać w tej odpowiedzi: https://stackoverflow.com/a// Dependency inversion Zasada odwrócenia odpowiedzialności jest prostą i bardzo ważną zasadą. Polega ona na używaniu interfejsu polimorficznego wszędzie tam gdzie jest to możliwe, szczególnie w parametrach funkcji. Karol Trybulec p-programowanie.pl

Zasada odwrócenia odpowiedzialności (ang. dependency inversion principle) wszystkie zależności powinny w jak największym stopniu zależeć od abstrakcji a nie od konkretnego typu Pojęcia odwrócenia odpowiedzialności nie należy mylić ze wstrzyknięciem zależności (ang. dependency injection). Jeżeli mamy parametr funkcji, który przyjmuje figurę matematyczną, znaczenie lepszym rozwiązaniem będzie przyjęcie interfejsu lub klasy abstrakcyjnej figur matematycznych niż konkretnej figury. Dzięki przestrzeganiu zasady nie uzależniamy pojedynczej metody od konkretnego typu, tylko od interfejsu, który mogą implementować duże grupy podtypów. Dlaczego warto przestrzegać zasad SOLID? Zasady SOLID są niezłą bazą dla każdego początkującego programisty. W dużych projektach nie zawsze wszystkie zasady da się idealnie przestrzegać, jednak powinniśmy dążyć do poprawy jakości kodu i wdrażania zasad SOLID jeżeli tylko jest to możliwe. Zły kod w większości przypadków łamie kilka zasad SOLID jednocześnie. Jeżeli na drodze refaktoryzacji okaże się, że łamie już tylko jedną lub wcale, jest to ogromny sukces. Nawet jeżeli teraz nie dostrzeżesz tego sukcesu, dostrzeże go zapewne ktoś, kto będzie pracował na tym kodzie za kilka miesięcy lub lat. Pisząc kod osobiście w większości przypadków wszystko się rozumie, nawet gdyby kod był najgorszej jakości. Każdy po prostu rozumie to co sam napisał. Prawdziwy problem pojawia się gdy obca osoba jest zmuszona przesiąść się do nieswojego projektu i pisać w kodzie, którego nigdy wcześniej nie widziała. Są to momenty, w których bardzo pomaga to że: zamiast jednej klasy zawierającej 000 linii kodu jest 0 małych klas, z której każda jest odpowiedzialna za jedną, małą, konkretną rzecz (zasada pojedynczej odpowiedzialności) autor klasy przewidział poszerzenie funkcjonalności jego klasy bez konieczności przerabiania jej kodu np. poprzez mechanizm dziedziczenia i polimorfizmu (zasada otwarty/zamknięty) korzystając z klas pochodnych mamy pewność, że implementują one wszystkie metody klas bazowych i nie musimy tego sprawdzać (zasada liskov substitution) system zbudowany jest z małych interfejsów (często tylko z jedną metodą), dzięki czemu jesteśmy w stanie zaimplementować w nowo dopisanej przez nas klasie interfejsy których potrzebujemy i ani jednego więcej, bez niepotrzebnych metod (interface segregation) poprzedni programista używał typów abstrakcyjnych tam gdzie to tylko możliwe (np. w Karol Trybulec p-programowanie.pl

parametrach funkcji) więc możemy przekazać do funkcji każdą kolekcję implementującą interfejs IEnumerable a nie tylko listę. A interfejsy znajdujące się w projekcie mają sens i mogą zostać użyte, a nie są tylko sztuką dla sztuki, bo po co nam interfejsy, jeżeli wszystkie funkcje przyjmują jako parametry typy pochodne (dependency inversion) Mam nadzieje, że po przeczytaniu tego artykułu zrozumienie zasad SOLID będzie dla Ciebie łatwiejsze. Jest to pierwszy krok na drodze do pisania czystszego kodu. Karol Trybulec p-programowanie.pl