Zobrazují se příspěvky se štítkemSingle Responsibility Principle. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemSingle Responsibility Principle. Zobrazit všechny příspěvky

čtvrtek 27. ledna 2011

Princip jedné odpovědnosti

Princip jedné odpovědnosti (SRP - Single Responsibility Principle) je jeden z pěti základních principů metodologie SOLID objektového návrhu. Jedna z jeho nejčastějších definic zní:

Třída by měla mít právě jeden důvod ke změně.

Třída by tedy měla mít pouze jednu odpovědnost - pouze jednu logickou funkcionalitu. Svou definicí patří SRP mezi nejjednodušší principy objektového návrhu, nezkušení objektoví návrháři však tento princip často ve svých programech porušují.

Vysvětleme si problém na příkladě třídy Uzivatel:

public class Uzivatel
{
    public String Id { get; set; }
    public String Heslo { get; set; }

    public void Ulozit(IUloziste uloziste)
    {
        uloziste.Ulozit<Uzivatel>(this);
    }
}

Třída nese informace o uživateli (Id a Heslo) a zajišťuje jeho uložení do úložiště, např. databáze. Zkušenější čtenáři ihned rozpoznali porušení princip jedné odpovědnosti. První odpovědností třídy je reprezentace entity typu uživatel v systému (veřejné atributy Id a Heslo). Druhou odpovědností je uložení stavu objektu do úložiště - metoda Ulozit. Provedeme refaktorizaci do dvou samostatných tříd:

public class Uzivatel
{
    public String Id { get; set; }
    public String Heslo { get; set; }
}

public class UzivatelDao
{
    private IUloziste uloziste;

    public UzivatelDao(IUloziste uloziste)
    {
        this.uloziste = uloziste;
    }

    public void Ulozit(Uzivatel uzivatel) 
    {
        uloziste.Ulozit<Uzivatel>(uzivatel);
    }
}

Ze třídy Uzivatel jsme odstranili metodu Ulozit. Vznikla nová třída UzivatelDao, jejíž odpovědností je zpřístupňovat funkcionalitu související s úložištěm entit Uzivatel, tedy například i uložení.

Pro úplnost zjednodušená definice rozhraní IUloziste:

interface IUloziste
{
    void Ulozit<T>(T objekt);
}

Proč je nutné princip dodržovat

  • Čitelnost kódu. Někdy dochází k obavám, že rozdělením větší třídy na více menších jednoúčelových tříd se zvýší složitost systému a bude problematické porozumět celku. Opak je pravdou. Navigace ve třídách, které dělají to, co jejich název deklaruje, je mnohem efektivnější. Budete-li procházet velké třídy s více odpovědnostmi, budete zatěžování další funkcionalitou, která Vám bude překážet v nalezení toho, co hledáte. Robert C. Martin přirovnává třídy mající více odpovědností k velké krabici v garáži, ve které máte naházeno všechno různé nářadí. Jednoúčelové třídy naopak působí jako úhledně urovnané malé krabičky obsahující pouze související věci jednoho typu.
  • Robustnost. Rozdělením tříd podle odpovědnosti docílíme toho, že případné pozdější požadavky na změnu ovlivní pouze třídu, které by se měly týkat. Např. začnete evidovat nový atribut bool PovolenPristup třídy Uzivatel. Rozšíříte třídu Uzivatel, ale třída UzivatelDao tím nebude nijak zasažena.
  • Snížení vzájemné provázanosti tříd. Velké třídy budou potřebovat vazby na více jiných tříd. Např. v našem příkladě problematická třída Uzivatel odkazovala rozhraní IUloziste. Ale entita typu uživatel je přeci v systému na business vrstvě nezávislá na způsobu jeho uložení.
  • Testovatelnost. Třída mající pouze jednu odpovědnost je lépe testovatelná. Souvisí to také se snížením provázanosti tříd. Test bude omezen pouze na jednu funkcionalitu. Např. pro otestování třídy Uzivatel nebudete potřebovat instanci IUloziste.
  • Soudržnost. Malé třídy mají větší soudržnost, která je v OO návrhu doporučovaná. Více o soudržnosti v některém z dalších příspěvků.

Jak odhalit porušení principu jedné zodpovědnosti

Důležité je samozřejmě získat zkušenosti s objektovým návrhem. Měli byste být také schopni se kriticky ohlédnout za dokončenou prací a zhodnotit dodržování SRP. V případě potřeby třídy refaktorovat.

Jedna poučka říká, že pokud při popisu významu třídy použijete spojku "a", měli byste zbystřit a prověřit dodržování SRP. Podezřelé jsou však i souvětí jiného typu. Pozor na třídy typu "reprezentace auta a výpočet jeho spotřeby", "sestava se zpracováním parametrů", "kreditní karta, která si umí sama strhnout poplatek", apod.

Dejte si pozor na slučování reprezentace entity a funkcionality jejího vytvoření. V těchto případech lze refaktorovat funkcionalitu vzniku instance třídy podle některého z návrhových vzorů pro vytváření objektů.

Další zdroje a ukázky