Nie SOLID-nie #03: Liskov Substitution Principle

N

Seria zainspirowana bardzo dobrym kursem SOLID od Jarka Stadnickiego, dost臋pnym na platformie Udemy – SOLID praktyczny kurs

Nie jest to reklama, ani 偶adna afiliacja. Wyra偶am swoje zdanie 馃檪 . Polecam zerkn膮膰. Jarek za pomoc膮 obrazowych, trafnych por贸wna艅 t艂umaczy poszczeg贸lne zasady.

 


Spis post贸w z serii Nie SOLID-nie:

  1. Nie SOLID-nie #01: Single Responsibility Principle
  2. Nie SOLID-nie #02: Open Close Principle
  3. Nie SOLID-nie #03: Liskov Substitution Principle
  4. Nie SOLID-nie #04: Interface Segregation Principle
  5. Nie SOLID-nie #05:聽Dependency Inversion Principle

Definicja

Na pocz膮tek, jak zwykle, odrobina teorii. Regu艂a Liskov brzmi:

 

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.

 

Jak podaje Wikipedia, zasada ta zosta艂a sformu艂owana po raz pierwszy przez聽Barbar臋 Liskov聽i Jannette Wing we wsp贸lnej pracy pt. „A Behavioral Notion of Subtyping„, zaprezentowana przez Pani膮 Liskov w przem贸wieniu pt.聽„Data Abstraction and Hierarchy„, a spopularyzowana i podana w obecnym brzmieniu przez聽Roberta C. Martina聽w artykule „Principles of Object Oriented Design”聽oraz ksi膮偶ce „Agile Software Development: Principles, Patterns, and Practices

 

Research

W ramach przypomnienia i upewnienia si臋, czy na pewno dobrze pami臋tam o co chodzi w Liskov Substitution Principle, przeczesa艂em internet w poszukiwaniu jakiego艣 sensownego wyt艂umaczenia czego ta zasada dotyczy i jak jest przedstawiana przez naszych bran偶owych koleg贸w. I przyznam, 偶e si臋 troszk臋 zawiod艂em. Najcz臋艣ciej podawany jest przyk艂ad z figurami geometrycznymi lub wydziwiana jest jaka艣 inna „abstrakcja” kt贸ra nie ma nic wsp贸lnego z programowaniem. Mam na my艣li przyk艂ady takie jak Foo czy klasy Pojazd i Samoch贸d. Postaram si臋聽opisa膰 zasad臋 LSP jak najbardziej w prosty, zrozumia艂y spos贸b i jednocze艣nie pokaza膰 j膮 u偶ywaj膮c rzeczywistego przyk艂adu.

 

Anty-przyk艂ad

Moj膮 propozycj膮 s膮 klasy SmtpSender i AlertSmtpSender. Realizuj膮 one zwyk艂膮 funkcjonalno艣膰 wysy艂ania wiadomo艣ci mailowych.

 

Tak wygl膮da klasa bazowa SmtpSender

public class SmtpSender
{
    private readonly ISmtpSettings _smtpSettings;

    protected IMailTemplates MailTemplates;

    public SmtpSender(ISmtpSettings smtpSettings, IMailTemplates mailTemplates)
    {
        _smtpSettings = smtpSettings;

        MailTemplates = mailTemplates;
    }

    public virtual async Task<bool> SendMail(string toAddr, string toName,
        string mailSubject, string body)
    {
        using (var memoryStream = new MemoryStream())
        {
            using (var mailMessage = new MailMessage())
            {
                mailMessage.From = new MailAddress(_smtpSettings.SenderAddress,
                    _smtpSettings.SenderName);
                mailMessage.To.Add(new MailAddress(toAddr, toName));
                mailMessage.Subject = mailSubject;
                mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
                mailMessage.BodyEncoding = System.Text.Encoding.UTF8;
                mailMessage.Body = body;

                using (var smtpClient = new SmtpClient(_smtpSettings.ServerAddress,
                    _smtpSettings.ServerPort))
                {
                    var networkCredential = new NetworkCredential(_smtpSettings.UserName,
                        _smtpSettings.UserPassword);

                    smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
                    smtpClient.EnableSsl = _smtpSettings.UseSsl;
                    smtpClient.UseDefaultCredentials = false;
                    smtpClient.Credentials = networkCredential;

                    await smtpClient.SendMailAsync(mailMessage);

                    return true;
                }
            }
        }
    }
}

 

Tak wygl膮da klasa pochodna AlertSmtpSender

public class AlertSmtpSender : SmtpSender
{
    private readonly string _machineId;
    private readonly string _creationTime;
    private readonly string _alertName;
    private readonly string _alertInfo;
    private readonly bool _usePlainText;
    

    public string MachineId { get; set; }
    public string CreationTime { get; set; }
    public string AlertName { get; set; }
    public string AlertInfo { get; set; }
    public bool UsePlainText { get; set; }


    public AlertSmtpSender(ISmtpSettings smtpSettings, IMailTemplates mailTemplates)
        : base(smtpSettings, mailTemplates){}

    public override Task<bool> SendMail(string toAddr, string toName,
        string mailSubject, string body)
    {
        var contentReplacements = new Dictionary<string, string>
        {
            {"MACHINEID", _machineId},
            {"ALERTTIME", _creationTime.ToString(CultureInfo.CurrentUICulture)},
            {"ALERTNAME", _alertName},
            {"ALERTINFO", _alertInfo}
        };

        var subject = $"Alert:{_alertName}, machine name:{_machineId}. Subject:{mailSubject}";
        var mailBody = MailTemplates.CreateMailContent("core", "alert", _usePlainText,
            contentReplacements, body);

        return base.SendMail(toAddr, toName, subject, mailBody);
    }
}

 

Natomiast poni偶ej mo偶ecie zobaczy膰 jak wygl膮da definicja metody/funkcji kt贸ra w parametrze przyjmuje obiekt klasy bazowej.

public async void SendNewsletter(SmtpSender sender)
        {
            await sender.SendMail("patryk.kubiela@programista-doswiadczony.pl",
                "Patryk", "Test mail", "Message body");
        }

 

Z艂amana regu艂a

W jaki spos贸b powy偶szy kod 艂amie regu艂臋 LSP? Ot贸偶 przede wszystkim funkcjonalno艣膰 metody SendMail w klasie pochodnej, totalnie zmienia funkcjonalno艣膰 metody SendMail z klasy bazowej. Teoretycznie w obu przypadkach zostanie wys艂any mail. Jednak diabe艂 tkwi w szczeg贸艂ach.

Po pierwsze,聽mail kt贸ry zostanie wys艂any b臋dzie zupe艂nie inny i b臋dzie mia艂 inn膮 funkcj臋 biznesow膮. Jak wida膰, mail wys艂any za pomoc膮 klasy pochodnej, jest swoistym rodzajem alertu, a nie generycznej wiadomo艣ci. Dzia艂anie klasy pochodnej jest bardziej ograniczone, szczeg贸艂owe.

Klasa pochodna AlertSmtpSender, rzecz jasna, mo偶e rozszerza膰 funkcjonalno艣膰 klasy bazowej, ale wedle regu艂y LSP, tylko w taki spos贸b, aby nie „zepsu膰” bazowej funkcjonalno艣ci.

Po drugie, projektuj膮c metod臋 klasy bazowej, b臋dziemy chcieli obs艂u偶y膰 wyj膮tki. Ju偶 wiesz co mam na my艣li? Ot贸偶 metoda SendMail, klasy pochodnej dodaj膮c „swoj膮” funkcjonalno艣膰, mo偶e generowa膰 inne typy wyj膮tk贸w, kt贸re w pewnych wariantach mog膮 by膰 nieobs艂u偶one, przez co k艂opotliwe.

De facto, metoda聽SendNewsletter nie spodziewa si臋 obiektu konkretnej klasy AlertSmtpSender. Spodziewa si臋 obiektu klasy spe艂niaj膮cej kontrakt (Design by Contract), jaki sugerujemy, 偶e powinien by膰 spe艂niony definiuj膮c t臋 metod臋. Chodzi o to, 偶eby艣my mogli spokojnie poda膰 jako argument obiekt klasy AlertSmtpSender, czy mo偶e jakiej艣 np. ErrorSmtpSender i nie ba膰 si臋, 偶e metoda SendNewsletter zostanie wywo艂ana niepoprawnie, lub wygeneruje jaki艣 b艂膮d, lub wygeneruje b艂膮d nie taki jaki trzeba.

Oczywi艣cie tylko w przypadku je艣li zachowali艣my odpowiedni膮 higien臋 projektuj膮c klas臋 pochodn膮, przestrzegaj膮c Liskov Substitution Principle.

Po trzecie, skoro ju偶 m贸wimy o zawieraniu kontraktu, mo偶e i liczba parametr贸w kt贸re przyjmuj膮 obie metody SendMail, jest taka sama, a ich typ ten sam, to jednak s膮 one inne. Obie metody spodziewaj膮 si臋 biznesowo inncyh parametr贸w. Przez co efekt ko艅cowy, rezultat, b臋dzie odbiega艂 od spodziewanego. Du偶o 艂atwiej i przyznam, 偶e lepiej, by艂oby to pokaza膰 na liczbach.

 

S艂owem zako艅czenia

Wiem, 偶e聽LSP聽nie m贸wi jedynie o funkcjach u偶ywaj膮cych wska偶nik贸w, czy referencji, kr贸te mog膮 u偶ywa膰 obiekt贸w tych typ贸w, niezale偶nie od tego czy jest to klasa bazowa czy pochodna. Wiem, 偶e mo偶na (a mo偶e powinno si臋?) rozumie膰 t臋 regu艂臋 odrobin臋 szerzej, ale jestem zdania, 偶e programista powinien wiedzie膰 po co stosuj臋 regu艂臋 i czy napewno powinien j膮 zastosowa膰. Tylko tyle. Zw艂aszcza dzisiaj, kiedy nie ma miejsca na „sztuk臋 dla sztuki”.

Dlatego sk艂aniam si臋 ku najbardziej prostej definicji Liskov Substitution Principle. Tej, kt贸r膮 cytuj臋 na samym pocz膮tku.

 

Tyle i a偶 tyle.

 

Ju偶 pisa艂em, ale skoro jest to wa偶ne, powt贸rz臋.

Klasa pochodna mo偶e rozszerza膰 funkcjonalno艣膰 bazowej, mo偶e j膮 zmienia膰, natomiast wa偶ne jest, aby nie mia艂o to wp艂ywu na dzia艂anie programu i wynik dzia艂ania programu.

 


Programi艣ci, t艂umacz膮c regu艂臋 LSP, prze艣cigaj膮 si臋 w wymy艣laniu interpretacji tej regu艂y. Nie zawsze trafnie. Je艣li chcecie si臋 szybko dowiedzie膰 na temat LSP czego艣 wi臋cej (dok艂adniej), polecam podobny post, a raczej seri臋 post贸w dotycz膮cych zasad SOLID, na blogu Tomka Sitarka.

Tak jak ja, Tomek zaczyna przygod臋 z blogowaniem, ale mam wra偶enie, 偶e jego wiedza jest lepiej uporz膮dkowana, a przede wszystkim, potrafi j膮 lepiej ode mnie przekaza膰 na 艂amach swojego bloga. LINK. Macie dzi臋ki temu mo偶liwo艣膰 zgromadzi膰 wiedz臋 z dw贸ch miejsc, jak i por贸wna膰 dwa r贸偶ne podej艣cia do tematu.

Polecam r贸wnie偶 inne wpisy Tomka!


 

A zatem do dzie艂a! Napisz komentarz! Dodaj co艣 od siebie!

Czo艂em!

 



 

About the author

Add comment

By Patryk

Autor serwisu

Patryk

Spo艂eczno艣ciowe

Instagram

Newsletter



Historycznie

Tagi