Dziś piąty wpis, w którym postanowiłem zapoznać się bardziej szczegółowo z tematem SOLID. Zasady SOLID są powszechnie znane przez grupy programistów pracujących z językami obiektowymi. Muszę się przyznać, że na jednej z pierwszych rozmów rekrutacyjnych w moim zawodowym życiu poległem na pytaniu z tego zagadnienia.

Dochodzenie do mistrzostwa wymaga dwóch elementów: wiedzy i pracy.

Robert Cecil Martin

Nadrobiłem zależności, zacząłem stosować SOLID w praktyce (z rozsądkiem!) i postanowiłem osobiście się nad tym pochylić. Postaram się w tym artykule naświetlić takie zagadnienia jak:

  • SRP
  • OCP
  • LSP
  • ISP
  • DIP
  • Jak wygląda implementacja?

Pragnę zauważyć, że temat SOLID jest jednym z popularniejszych tematów poruszanych w publikacjach dotyczących programowania obiektowego i dobrych praktyk programowania. Nie mogło go więc zabraknąć na moim blogu.

Zaczynamy!

SOLID – wprowadzenie

W obiektowym programowaniu SOLID jest akronimem pięciu zasad projektowania, mającym na celu uczynienie oprogramowania zrozumiałymi, elastycznymi i łatwymi w utrzymaniu. Twórcą zasad jest amerykański programista Robert Cecil Martin (zwany też jako Uncle Bob). Zasady opracował w 2000 roku w pracy pod tytułem Zasady projektowania i wzorce projektowe.

Sam akronim SOLID został wprowadzony później przez Michaela Feathersa i prezentuje się następująco:

  • Single-Responsibility Principle – SRP (Zasada pojedynczej odpowiedzialności),
  • Open/Closed Principle – OCP (Zasada otwarte – zamknięte),
  • Liskov Substitution Principle – LSP (Zasada podstawiania Liskov),
  • Interface Segregation Principle – ISP (Zasada segregacji interfejsów),
  • Dependency Inversion Principle – DIP (Zasada odwracania zależności).

Robert Cecil Martin znany jest też dodatkowo z tego, że jest jednym z twórców manifestu zwinnego programowania agile. Poprzez to jest jedną z najczęściej przewijających się osób, jeśli chodzi o czysty kod. Powyższe zasady pomagają w pisaniu takiego kodu.

Problemem wśród wielu programistów, nie tylko początkujących jest zrozumienie sensu i potrzeby stosowania SOLID podczas programowania. Granice między zasadami potrafią się zacierać. Zdarzają się sytuacje, gdzie ciężko jest je wszystkie zastosować i wtedy trzeba wybrać między lepszym a gorszym złem. Po prostu łamiąc jedną lub kilka z zasad. Z własnego doświadczenia powiedzieć mogę, że temat wałkowany jest na praktycznie każdej rozmowie rekrutacyjnej, lecz później okazuje się, że projekt, do którego trafiałem, nie stosuje SOLID w ogóle. Dobrym sposobem na to jest świadome programowanie i przemycanie dobrych praktyk do projektu samemu, bez patrzenia na innych.

S jak Single Responsibility Principle

Po pierwsze zasada SRP, która jednocześnie jest najprostszą i najbardziej użyteczną zasadą. Potocznie nazywana jest zasadą pojedynczej Odpowiedzialności, przez co możemy rozumieć to, że każda klasa powinna być odpowiedzialna tylko za jeden spójny element programu. Częste pominięcie tej zasady spowoduje powstanie tak zwanego spaghetti code lub scyzoryka, którego funkcjonalność często jest zbyt rozbudowana w porównaniu do jego pierwotnego zamysłu. Głównym założeniem jest to, by klasy były jak najbardziej atomowe.

Zasada pojedynczej odpowiedzialności – klasa powinna mieć tylko jeden powód do modyfikacji.

Jako że pracuję na co dzień z systemami CRM pokaże poniżej przykład klasy Address, która jest jedną z klas bazowych i jednocześnie łamie zasadę pojedynczej odpowiedzialności. Poza właściwościami i jednym z konstruktorów dodałem prywatną metodę, która sprawdza poprawność kodu pocztowego. Niby wszystko wydaje się ok, ale co w przypadku, gdy poprawność kodu pocztowego będziemy chcieli sprawdzić w innym miejscu aplikacji, lecz nie potrzebujemy do tego całej klasy z adresem? Wyjściem z tej sytuacji jest stworzenie klasy Validator, która będzie zawierała w sobie wszystkiego rodzaju walidacje elementów, a co za tym idzie, kod będzie bardziej atomowy i niezależny. Podobnie mieć się będzie sytuacja, gdy będziemy mieli klasę User a w niej pole o nazwie Email. Wtedy metoda ValidateEmail byłaby przeniesiona do klasy Validator i zostałaby zachowana zasada pojedynczej odpowiedzialności.

//przykład łamiący zasadę pojedynczej odpowiedzialności
class Address
{
    public Country CountryId { get; set; }
    public string Voivodeship { get; set; }
    public string County { get; set; }
    public string Community { get; set; }
    public string Place { get; set; }
    public string Street { get; set; }
    public int HouseNumber { get; set; }    
    public string ApartmentNumber { get; set; }
    public string PostCode { get; set; }

    public Address(Country _countryId, string _place, string _street, int _houseNumer, string 
    _postCode)
    {
        CountryId = _countryId;
        Place = _place;
        Street = _street;
        HouseNumber = _houseNumer;
        PostCode = ValidatePostCode(_postCode);
    }

    private string ValidatePostCode(string postCode) 
    {
       if (!Regex.IsMatch(postCode, @"^\d{2}-\d{3}$"))
            throw new Exception("Pod pocztowy nie jest poprawny");

        return postCode;
    }
}
//przykład z uwzględnieniem zasady pojedynczej odpowiedzialności
class Address
{
    public Country CountryId { get; set; }
    public string Voivodeship { get; set; }
    public string County { get; set; }
    public string Community { get; set; }
    public string Place { get; set; }
    public string Street { get; set; }
    public int HouseNumber { get; set; }    
    public string ApartmentNumber { get; set; }
    public string PostCode { get; set; }

    public Address(Country _countryId, string _place, string _street, int _houseNumer, string 
    _postCode)
    {
        CountryId = _countryId;
        Place = _place;
        Street = _street;
        HouseNumber = _houseNumer;
        PostCode = _postCode;
    }
}

class Validator
{
    public string ValidatePostCode(string postCode) 
    {
       if (!Regex.IsMatch(postCode, @"^\d{2}-\d{3}$"))
            throw new Exception("Pod pocztowy nie jest poprawny");

        return postCode;
    }

    public string ValidateEmail(string email) 
    {
       if (!Regex.IsMatch(email, @"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$"))
            throw new Exception("Email nie jest poprawny");

        return email;
    }
}

Podsumowując

Przykład ten wydaje się banalny i powielany na wielu innych blogach w takiej lub podobnej formie, ale idealnie pokazuje ideę zasady Single Responsibility Principle. Można spróbować rozbudować walidację o próbę logowania błędów do pliku lub bazy danych — oczywiście tworząc do tego klasy o nazwach LogToFile oraz LogToDatabase, które dodatkowo mogą dziedziczyć z klasy bazowej Log, ale nie jest nam to potrzebne w niniejszym artykule.

Pierwsza zasada pozwala nam uniknąć powielania kodu, ale z drugiej strony może nas zapędzić w błędne koło. Spędzimy wtedy długie godziny na refaktoryzacji i rozbijaniu kodu na coraz bardziej atomowe elementy. Uważam, że prędzej czy później i tak to będzie potrzebne, ale niestety idealny świat nie istnieje, więc i nasz kod nie będzie nigdy perfekcyjny. Dlatego, gdy następnym razem zaczniesz tworzyć nową klasę, to miej proszę z tyłu głowy informację o tym, by była ona jak najbardziej atomowa, ale nie przesadzaj z tym!

O jak Open Close Principle

Jest to jedna z tych zasad, które ciężko na początku przyjąć do wiadomości. Definicja jej mówi nam, że kod ma być otwarty na rozbudowę, ale jednocześnie zamknięty na modyfikację. Jak!? A no tu musimy pomyśleć bardziej abstrakcyjnie.

Zasada otwarte zamknięte – elementy systemu powinny być otwarte na rozbudowę, ale zamknięte na modyfikacje.

Często zdarza się, że stworzymy klasę z kilkoma właściwościami i metodami, a za jakiś czas biznes przychodzi do nas i prosi o zmiany. Dodanie czegoś będzie jednoznaczne z potrzebą rozszerzenia danej klasy. Oczywiście by nie było tak prosto, zmiana ta pociąga za sobą nie tylko dodanie nowej metody, ale też rozbudowę już istniejącej. Znajomy przykład?

Przygotowałem niewielki przykład poniżej — trywialny, ale pokazuje, kiedy spotykamy się z brakiem zastosowania zasady otwarte zamknięte. Klasa CreateDocument w kodzie poniżej zawiera kilka metod, gdzie główną jest CreateDoc. Pojawia się w niej instrukcja switch, dzięki której na podstawie typu dokumentu wywołuje się jedna z metod tworząca odpowiednio fakturę, ofertę lub zamówienie. Co w przypadku gdy będzie trzeba dodać kolejny typ? Na przykład fakturę proformę. Łamiemy w tego typu kodzie omawianą zasadę OCP. Musimy rozbudować instrukcję wyboru o nowy przypadek oraz rozbudować klasę CreateDocument o nową metodę CreateInvoiceProformDoc.

//przykład łamiący zasadę otwarte zamknięte
public class CreateDocument
{
    public void CreateDoc(string type, List<Product> products)
    {
        switch (type)
        {
            case "invoice":
                CreateInvoice(products);
                break;
            case "offer":
                CreateOffer(products);
                break;
            case "order":
                CreateOrder(products);
                break;
            default:
                break;
         }
    }
		
    private void CreateInvoiceDoc(List<Product> products)
    {
        // tresc metody
    }
		
    private void CreateOfferDoc(List<Product> products)
    {
        // tresc metody
    }
		
    private void CreateOrderDoc(List<Product> products)
    {
        // tresc metody
    }
}
//przykład z uwzględnieniem zasady otwarte zamknięte
public abstract class CreateDocument
{
    public abstract void CreateDoc(List<Product> products)  { }    
}

public class CreateInvoiceDocument : CreateDocument
{
    public override CreateDoc(List<Product> products)
    {
       // tresc metody w klasie dotyczącej faktur
    }    
}

public class CreateOfferDocument : CreateDocument
{
    public override CreateDoc(List<Product> products)
    {
       // tresc metody w klasie dotyczącej ofert
    }    
}

public class CreateOrderDocument : CreateDocument
{
    public override CreateDoc(List<Product> products)
    {
       // tresc metody w klasie dotyczącej zapytań
    }    
}

public class CreateInvoiceProformDocument : CreateDocument
{
    public override CreateDoc(List<Product> products)
    {
       // tresc metody w klasie dotyczącej faktur proform
    }    
}

Podsumowując

Powyższy kod bez dwóch zdań wymaga refaktoryzacji. Pomysł mój na to jest następujący. Utworzę klasy abstrakcyjnej CreateDocument, będzie ona zawierać metodę publiczną CreateDoc. Następnie utworzę klasy CreateInvoiceDocument, CreateOfferDocument, CreateOrderDocument, które będą dziedziczyć po klasie abstrakcyjnej i w każdej z nich dodam metodę CreateDoc, które to metody będą zawierać różne ciała metod zgodnie z tym, czego wymaga biznes w każdym z przypadków. Zakładam, że wiesz, jak działają klasy abstrakcyjne. Jeżeli nie to z czasem napiszę o tym więcej na blogu — na ten moment musisz poszukać informacji w innych publikacjach.

Jak widać, rozwiązuje nam to problemy z rozbudową projektu o nowe typy dokumentów — wspominałem wyżej o fakturze proforma. Teraz wystarczy dodać nową klasę, która będzie dziedziczyć z klasy CreateDocument, zaimplementować metodę tworzenia dokumentu i cieszyć się z zachowania Open Close Principle.

jak Liskov Substitution Principle

Trzecia zasada to już trochę inny kaliber. Nie tak łatwo jest ją załapać a co dopiero komuś wytłumaczyć. Spróbujmy rozłożyć ją na części pierwsze! Ogólnym założeniem zasady opracowaną przez Baraberę Liskov i nazywającą się Liskov Substitution Principle jest to by dziedziczenia planować i używać tak, by klasy pochodne mogły używać wszystkich metod z klasy bazowej.

Zasada podstawienia Liskov – funkcje, które używają wskaźników lub referencji do klas bazowych, muszą być w stanie używać również obiektów klas dziedziczących po klasach bazowych, bez dokładnej znajomości tych obiektów.

Wyobraź sobie sytuację, którą pewnie nieraz się przytrafiła zarówno mi, jak i Tobie — posiadamy klasę bazową z sześcioma metodami, używamy jej do dziedziczenia w klasie pochodnej, aby użyć trzech z tych sześciu metod. Takie zbudowanie kodu powoduje złamanie omawianej zasady. Gdy nastąpiłoby takie dziedziczenie zgodnie z zasadą podstawienia Liskov to musielibyśmy potrzebować wszystkich sześciu metod.

Poniżej przykład kompletnie nieprzydatny w produkcyjnym środowisku, ale bardzo dobrze pokazuje ideę omawianej zasady.

//przykład łamiący zasadę podstawiania Liskov
public abstract class Bird
{
    public void Fly() { }		
}
	
public class Dove : Bird
{
    public void Fly() 
	{ 
        // tresc metody w klasie gołąb
	}		
}

public class Penguin : Bird
{
    public void Fly() 
	{ 
	    // tresc metody w klasie pingwin
	}		
}
//przykład z uwzględnieniem zasady podstawiania Liskov
public abstract class FlyingBird
{
    public void Fly() { }		
}

public abstract class WalkingBird
{
    public void Walk() { }		
}
	
public class Dove : FlyingBird
{
    public void Fly() 
	{ 
        // tresc metody w klasie gołąb
	}		
}

public class Penguin : WalkingBird
{
    public void Walk() 
	{ 
	    // tresc metody w klasie pingwin
	}		
}

Podsumowując

Powyżej widzimy prostą klasę abstrakcyjną Bird a w niej metodę, która wydaje się dla ptaka oczywista, czyli Fly. Wszystko idzie zgodnie z planem, tworzymy klasę Dove, która dziedziczy z klasy bazowej Bird. Gdy jednak dodajemy klasę Penguin i również dodajemy dziedziczenie wtedy pojawia się problem z metodą Fly. Tak naprawdę pingwiny nie potrafią latać, dlatego bez sensu jest zaimplementowanie ciała metody w tej klasie. Nastąpiło tu złamanie zasady podstawienia Liskov.

By dwie klasy abstrakcyjne, z których to klas będą dziedziczyć kolejne dwie klasy. Odpowiednio dla klasy Dove będzie to klasa abstrakcyjna FlyingBird. Zawiera ona metodę Fly a dla klasy Penguin będzie to dziedziczenie po klasie WalkingBird, które zawiera metodę Walk.

Jak możemy zauważyć, jest to jedna z cięższych zasad i naprawdę wymaga sporej wprawy, by zacząć ją w pełni poprawnie wykorzystywać.

I jak Interface Segregation Principle

Zasada segregacji interfejsów jest kolejną z prostych zasad SOLID. Jak sama nazwa mówi, robi pewien porządek w interfejsach oraz klasach abstrakcyjnych. Trochę przypomina zasadę numer trzy. W zasadzie podstawienia Liskov głównie chodzi o to, by oczywiście klasy bazowe były odpowiednio małe, ale również o to, by metod w środku klas dziedziczących używać intuicyjnie. W aktualnie omawianej zasadzie natomiast chodzi o maksymalną atomowość, czyli uproszczenie interfejsu lub klasy bazowej.

Zasada segregacji interfejsów – wiele dedykowanych interfejsów jest lepsze niż jeden ogólny.

Ten przypadek też możemy sobie wyobrazić. Ja dwa razy w swoich projektach spotykałem się z interfejsem Account, który był przerośnięty. Zawierał definicję kilkudziesięciu metod, od utworzenia, edycji, usuwania po walidacje i nadawanie ról czy uprawnień. Gigant taki był następnie w paru miejscach wykorzystywany jako element po którym jedna czy druga klasa dziedziczyła tworząc problemy z metodami, które w danym fragmencie kodu były zbędne.

Przeanalizuj proszę przypadek poniżej.

//przykład łamiący zasadę segregacji interfejsów
public interface IAccount
{
	public void CreateUser() { }
	public void EditUser() { }
	public void DeleteUser() { }
	public void ValidateLoginUser() { }	
}
	
public class Register : IAccount
{
    public void CreateUser() 
    { 
         // tresc metody
    }

     public void EditUser()
     {
          throw new NotImplementedException();
     }	

     public void DeleteUser()
     {
          throw new NotImplementedException();
     }	

     public bool ValidateLoginUser()
     {
          // tresc metody
     }
}

public class UserProfile : IAccount
{
    public void CreateUser() 
    { 
         throw new NotImplementedException();
    }

     public void EditUser()
     {
          // tresc metody
     }	

     public void DeleteUser()
     {
          throw new NotImplementedException();
     }	

     public bool ValidateLoginUser()
     {
          throw new NotImplementedException();
     }	
}
//przykład z uwzględnieniem zasady segregacji interfejsów
public interface ICreateU
{
	public void CreateUser() { }		
}

public interface IEditU
{
        public void EditUser() { }
}

public interface IDeleteU
{
	public void DeleteUser() { }
}

public interface IValidateU
{
	public void ValidateLoginUser() { }
}
	
public class Register : ICreateU, IValidateU
{
    public void CreateUser() 
	{ 
		// tresc metody
	}	

	public bool ValidateLoginUser()
	{
		// tresc metody
	}
}

public class UserProfile : IEditU
{
    public void EditUser() 
	{ 
		// tresc metody
	}		
}

Podsumowując

Powyżej bardzo dobrze widać, jak dużo zbędnego kodu w pierwszym przykładzie nam się wytworzyło. Mamy jeden interfejs IAccount, który zawiera cztery metody — CreateUser, EditUser, DeleteUser oraz ValidateLoginUser. Klasy Register i UserProfile dziedziczą z interfejsu wszystkie metody, mimo że nie używamy niektórych z nich. Wtedy musimy wrzucić Exception w ciało metody.

Inaczej ma się sytuacja w drugim przypadku. Zrobiłem niewielką refaktoryzację, duży interfejs IAccount zamieniłem na cztery mniejsze ICreateU, IEditU, IDeleteU oraz IValidateU. Teraz w klasach Register i UserProfile dodałem dziedziczenie odpowiednio do potrzeby. Jak wiadomo, klasy mogą dziedziczyć po wielu interfejsach, więc skorzystałem z tej możliwości i w przypadku rejestracji dziedziczę po ICreateU oraz IValidateU a w przypadku UserProfile tylko po IEditU.

Aż miło patrzy się na tak poukładany kod! Jak widać, zasada jest prosta w zastosowaniu, ale musisz uważać, można bardzo łatwo wpaść w pułapkę i zbyt mocno rozbić interfejsy. Warto to przemyśleć przed napisaniem pierwszej linii kodu.

D jak Dependency Inversion Principle

Bardzo sprawnie dobrnęliśmy do ostatniej zasady — odwrócenia zależności. Ta zasada obok zasady podstawienia Liskov sprawiła mi osobiście najwięcej problemów ze zrozumieniem i poprawną implementacją. Rozmawiając z osobami z branży, wiem też, że nie tylko ja miałem tego typu problem. Jednak trening czyni mistrza i wystarczy trochę poczytać, przeanalizować i zacząć wplatać w projekty, by załapać, o co chodzi.

Zasada odwrócenia zależności – wszystkie zależności powinny w jak największym stopniu zależeć od abstrakcji a nie od konkretnego typu.

Zastanówmy się chwilę nad definicją. Można z niej wywnioskować, że moduły aplikacji takie jak klasy nie powinny być zależne. Zależność w tym przypadku rozumiemy jako na przykład dziedziczenie. Klasa więc powinna dziedziczyć tylko po obiektach abstrakcyjnych takich jak klasy abstrakcyjne czy interfejsy. Analizując głębiej piątą zasadę, musimy dopowiedzieć, że moduły wysokopoziomowe nie powinny zależeć od tych niższego poziomu. Chodzi o to, by klasy, które zawierają logikę biznesową, nie były zależne od klas, które nie odgrywają kluczowej roli.

//przykład łamiący zasadę odwrócenia zależności
public class Message
{

	public SMS sendSms { get; set; }
    public MAIL sendMail { get; set; }
    
	
	public Message()
	{
	    sendSms = new SMS();
        sendMail = new MAIL();
	}
	
	public void SendMessages()
	{
		sendSMS.Send();
		sendMail.Send();
	}
}
//przykład z uwzględnieniem zasady odwrócenia zależności
public interface IMessage
{
	public void Send();
}

public class SMS : IMessage
{
	public void Send()
	{
		// treść metody obsługująca SMS
	}
}

public class MAIL : IMessage
{
	public void Send()
	{
		// treść metody obsługująca MAIL
	}
}

public class Message
{
    private IEnumerable<IMessage> allMessages;

	public Messenger(IEnumerable<IMessage> messages)
	{
		allMessages = messages;
	}

	public void Send()
	{
		 allMessages.AsEnumerable().ToList().ForEach(n => n.Send());
	}
}

Podsumowując

Ciężko o dobry przykład. Zaproponowałem coś, co wyszperałem w sieci. Stworzona zostaje klasa wyższego poziomu o nazwie Message, która w konstruktorze tworzy dwa obiekty na podstawie klas SMS oraz MAIL. Jak widać, już złamana została zasada, bo klasa wyższego poziomu zależy od dwóch klas niższego. W metodzie SendMessages następuje wywołanie metod z dwóch powiązanych klas, które to metody obsługują wysyłkę wiadomości. Przykład następny prostuje tę sytuację za pomocą abstrakcyjnego bytu, jakim jest w tym przypadku interfejs IMessage. Zawiera on definicję metody Send. Następuje dziedziczenie w klasach SMS oraz MAIL i utworzenie wymaganej metody w każdej z tych klas. W głównej klasie Message mogę teraz zadeklarować prywatny obiekt IEnumerable z interfejsu, przypisać go w konstruktorze, a następnie w sposób iteracyjny wszystkie obiekty znajdujące się w obiekcie allMessages uruchamiając poziom wyżej metodę Send. Dzięki temu pozbywamy się za pomocą obiektów abstrakcyjnych zależności między klasami wysokiego i niskiego poziomu.

Mam nadzieję, że rozumiesz, o co chodzi. Mnie naprawdę było to trudno zrozumieć. Czasami patrzę na kod i nie zawsze zauważam.

od razu, że właśnie w tym miejscu użyta została zasada odwrócenia zależności.

SOLID – Podsumowanie

Zasady SOLID to temat na pewno potrzebny każdemu programiście. Z jednej strony lekki i przyjemny, z drugiej natomiast wymaga skupienia, by go dobrze zrozumieć. Nie ukrywam, że fajnie mi było usystematyzować wiedzę na ten temat i wylać to na bloga!

Ty jako programista musisz mieć świadomość, że im bardziej elastyczny kod wytworzysz, tym łatwiej będzie go później utrzymywać i modyfikować. SOLID to nie jest coś, co ma być dla Ciebie karą tylko przyjemnością! Znajdź w tym sens i stosuj, tylko proszę z umiarem, zachowując zdrowy rozsądek i stosując kompromis. Nie zawsze zastosowanie wszystkich zasad w określonym fragmencie kodu jest konieczne i wpłynie pozytywnie na resztę, dlatego wszystko z umiarem.

A już w następnym wpisie pochylę się nad tematem zarówno matematycznym, jak i programistycznym, czyli rekurencją.

PS. Będę wdzięczny za komentarze pod tym postem!

PS2. Czeka też na Ciebie prezent. Wystarczy, że zapiszesz się na Newsletter poniżej!

PS3. Dołącz do naszej grupy na Facebook!


Subskrybuj

Zapisz się na Newsletter, odbierz NAGRODĘ w postaci 10 omówionych algorytmów pojawiających się w pytaniach podczas REKRUTACJI.

Dodatkowo otrzymuj co niedzielę informacje na temat nowych wpisów, wiadomości ze świata IT i matematyki oraz ciekawych wydarzeniach. Nie przegap i dołącz już dziś do 596 osób!.

Źródła

https://www.samouczekprogramisty.pl/solid-czyli-dobre-praktyki-w-programowaniu-obiektowym/
https://devcave.pl/notatnik-juniora/zasady-projektowania-kodu
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
https://web.archive.org/web/20150906155800/http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf
https://web.archive.org/web/20150905081024/http://www.objectmentor.com/resources/articles/srp.pdf
https://web.archive.org/web/20150905081105/http://www.objectmentor.com/resources/articles/ocp.pdf
https://web.archive.org/web/20150905081111/http://www.objectmentor.com/resources/articles/lsp.pdf
https://web.archive.org/web/20150905081110/http://www.objectmentor.com/resources/articles/isp.pdf
https://web.archive.org/web/20150905081103/http://www.objectmentor.com/resources/articles/dip.pdf
https://www.p-programowanie.pl/paradygmaty-programowania/zasady-solid/
https://javadeveloper.pl/solid/
https://www.youtube.com/watch?v=agkWYPUcLpg
https://sii.pl/blog/solid-dobre-praktyki-programowania/
https://www.youtube.com/watch?v=t86v3N4OshQ 
http://lubimyczytac.pl/ksiazka/4825997/adaptywny-kod-zwinne-programowanie-wzorce-projektowe-i-solid-ne-zasady-wydanie-ii 
http://itcraftsman.pl/solidny-jak-solid-pisanie-solidnych-aplikacji-w-jezyku-c/
Autor

👨‍💻 .NET and Python programming passionate 🏦 Digtial Banking Solutions 🎓 Student 📊 Psychology 📚 Bookworm 🏠 Warsaw

7 komentarzy

  1. Fajny wpis. Pierwszy raz zetknąłem się z definicją SOLID. Fajnie że wspomniałeś o tym że przewija się to na rozmowach o pracę. Będę wiedział z czego się przygotować :-).

    Sam SOLID wydaje mi się trudny w przyswojeniu, nie mniej jednak musi być bardzo przydatny w programowaniu. Postaram się go nauczyć i stosować w pisanym kodzie, choć pewnie zajmie mi to trochę czasu. SOLID kojarzy mi się trochę ze wzorcami projektowymi, jednak są to dwa zupełnie różne pojęcia. A więc mam już dwie rzeczy do nauki.

    Pozdrawiam Mateusz :-).

  2. Wszystko fajnie jakby nie te formatowanie c#… Fuuu…
    W Scala, C, C++, Typescript, Java, Groovy, Kotlin i sporo więcej jest to zrobione po Bożemu…I te metody z wielkich liter tragedia..

  3. Hej!
    Fantastyczny wpis o SOLID, podobnie zresztą jak inne Twoje wpisy na blogu 🙂
    Chciałem krótko nawiązać do LSP, gdyż w tym temacie wychwyciłem kilka drobnych nieścisłości, które warto poprawić w tekście, gdyż wpis na duży potencjał i pewnie masa ludzie tutaj zajrzy 🙂
    LSP to definicja podtypowania Behavioral Subtyping. Tyle i aż tyle. Barbara Liskov i Jeannette Wing w swojej pracy zdefiniowały 7 warunków, których łamanie, to właśnie łamanie LSP. Nie jest konieczne używanie wszystkich metod z klasy bazowej, niemniej całkowicie się zgadzam, że takie podejście szybko doprowadzi do problemów, jednak nie będzie to wprost łamaniem LSP. Co więcej, gdy w podklasie nie użyjemy żadnej z metod (lub właściwości i pól) klasy bazowej, wtedy na 100% nie złamiemy LSP.
    Warto też wspomnieć, że LSP traktuje nie o klasach, a o typach i dzięki temu dotyczy nie tylko języków obiektowych.
    W skrócie, LSP jest łamane wtedy, gdy używanie podtypu w miejscu nadtypu prowadzi do nieprawidłowości w działaniu aplikacji.
    Po szczegóły zapraszam do krótkiej serii na moim blogu, gdzie 2 pierwsze posty są poświęcone LSP.
    https://www.seeitsharp.pl/2019/01/seria-podejrzane-typy/

    Trzymam kciuki za bloga, będę go regularnie odwiedzać!
    Tomek

Napisz komentarz

This site uses Akismet to reduce spam. Learn how your comment data is processed.