SOLID – (S)ingle Responsibility Principle
Publikováno: 23.4.2018
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.
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 šá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ázkama 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ěnily i kód, který se staral o konfiguraci a přepravu pošty.
Jak by se vám libílo, 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škěré 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:
- Změna textu nebo formátování emailu
- 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.