SOLID – (S)ingle Responsibility Principle

V tomto článku představím Single Responsibility Princip ze SOLIDu. Ukážeme si, jak tento princip dodržovat na konkrétním příkladu, namalujeme si nějaké UML diagramy, a nakonec přiložím celý příklad k dispozici v Typescriptu.

Seriál: SOLID (první díl)

  1. SOLID – (S)ingle Responsibility Principle 23.4.2018

Co je to SOLID

Jedná se o sadu principů (konkrétně 5) v rámci OOP, které pomáhájí k tvorbě softwarového designu, který je flexibilní, srozumitelný a udržovatelný.

SRP – Single Responsibility Principle

V tomto článku se vrhneme na ten první. Pro tento princip existuje mnoho definic. Já jsem si vybral tu od Uncle Boba (Robert C. Martin), který SRP definuje následovně:

Třída by měla dělat pouze jednu věc a dělat ji správně

To znamená, že by měl existovat jediný důvod pro její změnu!

Spousta lidí tak nějak intuitivně chápe, co se tím myslí. Problém nastává až tehdy, kdy se snažíme udělat vhodnou dekompozici tříd a určit jejich zodpovědnost. Uncle Bob v jeho knize The Clean Coder uvedl vhodnou poznámku, že tento princip je snadný k pochopení, ale těžký k dodržení. A to minimálně v mém případě určitě platí.

Pojďme si ho vysvělit na příkladu.

Příklad

Řekněme, že vytváříme nějaký systém, do kterého se uživatel musí zaregistrovat. Po registraci do systému pošleme uživateli uvítací zprávu na email. Pojďme si tedy navrhnout třídu, která bude řešit odesílání uvítacího emailu. Ta by mohla vypadat následovně.

EmailWelcomeMessage je třída, jejiž veřejné rozhranní tvoří metoda send()která odesílá uvítací zprávu uživateli na email, který se nastaví pomocí metody setRecipient(). Privátní metody buildMessageSubject a buildMessageBody slouží pro vytvoření předmětu a těla emailu. Pravděpodobně obsahují nějaký HTML snippet. Metoda configureConnection() slouží pro konfiguraci SMTP serveru a ostatních záležitostí pro přepravu pošty. 

Teď si představte, že za vámi přijde Mařenka z obchodního oddělení a řekne vám, že chce změnit text emailu, který se zasílá uživatelům. Abychom změnili text, tak musíme sáhnout do třídy, která se stará i o konfiguraci a zasílání emailu uživateli. Kód přidáváme, přesouváme, mažeme, až nakonec máme krásný cool text s obrázky v těle emailu. Kód se dostane do produkce a druhý den za vámi přijde naštvaná Mařenka, že se vůbec emaily neposílají, protože jsme nevědomky změnili i kód, který se staral o konfiguraci a přepravu pošty.

Jak by se vám líbilo, kdybyste si zavolali technika na opravu televize, u které nejde obraz, a ten vám ji vrátil „opravenou“ s funkčním obrazem, ale pro změnu by nešel zvuk?

Kde je v této konkrétní implementaci problém?

Problém je ten, že třída řeší spoustu věcí a existuje více než jeden důvod pro její změnu. Třídy, které řeší více než jeden problém jsou nejenom zbytečně velké, ale v případě změny jedné funkcionality se může stát, že rozbijete druhou. Takové třídy se i špatně testují a snadno v nich vznikají chyby. Už jen z toho důvodu, že je tam spoustu testovacích případů, na které můžete snadno zapomenout. O tom, že se veškeré změny v takové třídě dělají těžko, už snad ani nemusím psát (a stejně jsem napsal).

Podívejte se na schéma třídy a schválně si položte otázku: „Jaké jsou důvody pro změnu této třídy?“. Já vidím tyto:

  1. Změna textu nebo formátování emailu
  2. Změna konfigurace pro připojení na emailový server

Dekompozice: Změna textu nebo formátování emailu

A co třeba takto?

Třída EmailWelcomeMessage závisí na abstrakci EmailMessageBuilder. Ve třídě EmailWelcomeMessageBuilder pak najdeme kód, který řeší formátování emailu pro nově registrovaného uživatele. Pokud za námi přijde Mařenka s požadavkem na změnu tohoto textu, budeme zasahovat pouze do patřičné třídy, která opravdu řeší jen a pouze formátování textu!

Dekompozice: Změna konfigurace pro připojení na emailový server

První problém vyřešen. A co to ještě vylepšit?

EmailWelcomeMessage je jenom fasáda nad třídami, které řeší přepravu emailu a jeho formátování. To jakým způsobem se konfiguruje a přepravuje pošta ji nezajímá. Všechno je to skryté v nějaké implementaci EmailTransporteru. Pokud za námi přijde system admin Pepa, že se nám změnila autentifikace pro SMTP server, tak tuto konfiguraci změníme na jednom místě, a to v SomeConcreteEmailTransporteru! Nemůže se nám tedy stát, že změnou emailové konfigurace se nám rozbije formátování emailu a naopak.

Zdrojový kód příkladu

Celý příklad napsaný v typescriptu je k dispozici na jsfiddle.

Shrnutí

Dodržovat Single Responsibility Principle je těžké. Chce to zkušenosti a hlavně pevné nervy. V mém případě si dokonce pomáhám i tak, že si v hlavě říkám, co ta třída vlastně řeší za problém a dávám si pozor na spojky, protože pokud věta obsahuje nějaké spojky, tak je poměrně vysoká pravděpodobnost, že třída řeší víc věcí, než by měla. Například pro první verzi třídy EmailWelcomeMessage to vypadalo takhle: „Třída posílá email uživateli a nastavuje se v ní konfigurace pro přepravu pošty a umí naformátovat tělo emailu“. Všimněte si, že věty, které následují po spojce „a“, jsou přesně ty problémy, které jsme řešili nahoře. Nakonec bych ještě rád upozornil, že tento příklad byl vytvořen konkrétně pro ukázku tohoto principu, takže by skutečná implementace mohla vypadat trochu jinak.

Příště se podíváme na další princip, a to konkrétně Open Closed Principle.

Full-stack developer in IBB

Javascript enthusiast, focusing on server side scripting and clean code

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 16

Přehled komentářů

dzejkob
Tomáš Dusík Re:
lenoch
Tomáš Votruba Diky za srozumitelný a názorný příklad
Tomáš Dusík Re: Diky za srozumitelný a názorný příklad
Tomáš Votruba Re: Diky za srozumitelný a názorný příklad
Jaroslav Týc Takhle by to šlo
Vít Heřman SRP z jiného pohledu
Radek Re: SRP z jiného pohledu
Vít Heřman Re: SRP z jiného pohledu
Vasek
Radek
lenoch Re:
mirek Re:
d@rkWolf
lightWolf Re:
Zdroj: https://www.zdrojak.cz/?p=21077