Single Responsibility Principle – ciąg dalszy

Wczoraj mówiliśmy o single responsibility principle (SRP) czyli o zasadzie pojedynczej odpowiedzialności. Jest to zasada, która moim zdaniem najwięcej zmienia w dotychczasowych przyzwyczajeniach programistycznych. Na początku jest trochę męcząca ponieważ zgodnie z nią w klasie nie powinniśmy tworzyć innych obiektów.

Jak to? Nie mogę używać słowa kluczowego new? Nie mogę tworzyć obiektów?

No właściwie to nie. Jeżeli chcesz w klasie tworzyć obiekty to to już jest odpowiedzialność. Wiec klasa nic poza tworzeniem obiektów nie powinna robić. Ale przecież klasa która nie robi nic poza tworzeniem obiektów to fabryka. Wszystko jak najbardziej się zgadza. Fabryka – wzorzec kreacyjny (hm… a może znacie jakieś bardziej odpowiednie słowo) gangu czterech.

Wydaje Ci się trochę za dużo pisania w porównaniu do starego sposobu? No cóż, na początek (i bez wprawy) pewnie tak ale popatrzmy po co to robić, co to nam da i dlaczego pozwoli pisać lepsze programy.

Wczorajszy przykład z zapisywaniem obiektu mógł by wyglądać tak:

[csharp]
private readonly IFactory _factory;

public Employee(IFactory factory)
{

_factory = factory;

}

public void Save()
{

var persistingObject = _factory.PersistingObject();

persistingObject.Save(this);

}
[/csharp]

Klasa pracownik już nie musi implememntować metod do zapisywania – ma od tego „ludzi”

[csharp]
public interface ISomeInterfaceToSaveObject
{
void Save(IEmployee employee);
}

public interface IFactory
{
ISomeInterfaceToSaveObject PersistingObject();
}

public class SaveToXml : ISomeInterfaceToSaveObject
{
public void Save(IEmployee employee)
{
//zapis do xml-a
}
}

public class SaveToTextFile : ISomeInterfaceToSaveObject
{
public void Save(IEmployee employee)
{
// zapis do pliku tekstowego
}
}

public class SaveToMsSql : ISomeInterfaceToSaveObject
{
public void Save(IEmployee employee)
{
// zapisz do sql-a
}
}
[/csharp]

Pozostaje napisać fabrykę, która zwraca odpowiedną klasę – zapisującą czy to do sql-a czy pliku tekstowego czy innego.

a tak może wyglądać użycie powyższego cuda.

[csharp]
IEmplyee someEmployee = GetEmployeeFromSomewhere;

someEmployee.Save();
[/csharp]

Plusem takiego rozwiązania jest to, że możemy praktycznie w locie podmieniać kod odpowiedzialny za zapisywanie. Bez względu gdzie i jak chcemy zapisywać, klasa Pracownik nie zmienia się. Co więcej klasa ta nawet nie posiada świadomości gdzie i jak zapisywać – nie ma takiej potrzeby.

Jest też druga strona medalu, klasy do zapisywania możemy łatwo wykorzystać czy to w innym miejscu aplikacji lub nawet w innej aplikacji. To znowu skutkuje, że raz dobrze napisany kawałek kodu może być użyty wielokrotnie. Jeśli znowu nie stosujemy się do SRP to próbując wykorzystać kod w innym miejscu musimy ciągnąć całkiem sporo referencji i zbędnego kodu ze sobą.

Jest jeszcze jeden plus korzystania z SRP. Jeżeli np. zapis danych do bazy danych mamy w osobnej klasie, to co stoi na przeszkodzie aby napisać nową wersję korzystającą z innej bazy? Zamiast MS SQL-a Oracle-a albo MySql-a zamiast Oracle-a a może cos z NoSql. SRP ułatwi nam takie zadanie i nawet jeśli nie potrzebujemy takiej możliwości dzisiaj to jaką mamy pewność, że za miesiąc lub za rok jakiś klient lub nie będzie chciał (lub potrzebował) aby nasza aplikacja działała na innej bazie danych?

No i ostatni argument, jeżeli dodamy do konstruktora jakiś parametr to w przypadku korzystania z fabryki mamy jedno (słownie JEDNO) miejsce do poprawy – fabryka. Jeśli natomiast używamy operatora new to tu, to tam, wówczas po dodaniu parametru do konstruktora otrzymujemy kilkadziesiąt lub nawet kilkaset miejsc gdzie ten parametr należy dopisać. Niestety najczęściej kończy się to tak, że w wielu miejscach zamiast parametru wpisujemy null, a potem w metodach, które się sypią pojawia się warunek if(….. != null) i tak literka za literką i linijka za linijką kod zaczyna zmieniać się w potwora.

Potwór ten powoli spowalnia nas (jak na krzywej z wpisu Podstawy programowania) aby na końcu urwać dupę  zmusić do rozpoczęcia pracy od początku.