Sunteți pe pagina 1din 10

Sedinta proiect 3 - PIU

Obiective
1. Principii SOLID.

1. Principii SOLID
S.O.L.I.D este un acronim pentru primele principii propuse de Robert C. Martin cu privire la proiectarea
aplicatiilor orientate obiect.
 S – Single-responsiblity principle (principiul responsabilitatii unice)
 O– Open-closed principle (principiul open closed)
 L – Liskov substitution principle (principiul substitutiei Liskov)
 I – Interface segregation principle (principiul segregarii interfetelor)
 D – Dependency Inversion Principle (principiul inversarii dependentelor)
Aceste principii, utilizate impreuna, ajuta un programator sa dezvolte aplicatii care sunt usor de
intretinut si extins. Ele ajuta de asemenea programatorii sa reorganizeze usor codul si sa se adapteze
usor la modificarile de tehnologie.

1.1. S- Principiul responsabilității unice (Single responsibility principle-SRP)

Orice context (clasă, funcție, variabilă etc.) trebuie să aibă o unică responsabilitate, iar
această responsibilitate trebuie să fie în întregime încapsulată în context. Toate serviciile sale
trebuie să fie orientate pentru a servi această unică responsabilitate.

O “responsabilitate” poate fi definită și ca “motiv de schimbare”. Ca exemplu, putem considera


un program simplu care generează şi tipăreşte un raport. Un astfel de modul ar putea fi modificat
din două motive: fie se schimbă conținutul raportului, fie se schimbă formatul raportului.
Principiul responsabilității unice afirmă că aceste două aspecte ale problemei sunt două
responsabilități separate şi trebuie tratate în clase sau module diferite. A trata cele două lucruri
împreună în aceeași clasă sau modul ar reprezenta o problemă de design.

Astfel, este important să avem o clasă concentrată pe o unică responsabilitate pentru a o face
mai robustă. Pe exemplul de mai sus, o schimbare în modul de generare a raportului ar putea
produce erori în modul de tipărire, dacă aceste funcționalități fac parte din aceeaşi clasă.

Exemplu in care poate fi aplicat SRP


class Customer
{
public void Add()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
System.IO.File.WriteAllText(@"c:\Error.txt", ex.ToString());
}
}
}

Clasa Customer efectueaza operatii pe care NU AR TREBUI SA LE EFECTUEZE. Clasa


Customer ar trebui sa efectueze validari ale datelor despre un customer, sa adauge/extraga
datele despre un customer in/din baza de date, dar NU si operatia de logare (scriere intr-un fisier
text a erorilor) ce apare in blocul catch pentru ca ii atribuie o responsabilitate suplimentara (cea
de LOGARE). In situatia in care, dupa o anumita perioada, vrem sa adaugam mesajele de eroare
in EventViewer1 in loc de fisier text trebuie modificata clasa Customer, lucru neacceptat in
aplicatiile de mari dimensiuni.

Solutie

Aplicand SRP activitatea de logare poate fi mutata in alta clasa (FileLogger) care se va ocupa
doar de activitati de logare. Astfel, clasa Customer poate delega activitatea de logare catre clasa
FileLogger si se poate concentra doar pe activitatile legate de customeri.
class FileLogger
{
public void Handle(string error)
{
System.IO.File.WriteAllText(@"c:\Error.txt", error);
}
}

class Customer
{
private FileLogger obj = new FileLogger();
public void Add()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
obj.Handle(ex.ToString());
}
}
}

1
EventViewer este componenta Windows unde se pot vizualiza logurile sistemului de operare Windows
1.2. O - Principiul deschis/închis (Open-closed principle - OCP)

Acest principiu implică ca entitățile software (clase, module, metode) să fie deschise pentru
extensie dar închise pentru modificare. Astfel, o entitate îşi poate extinde comportamentul fără
modificari în codul sursă.

Exemplu in care poate fi aplicat OCP

Clasa Customer are o noua proprietate CustType care contine tipul customer-ului: 1 pentru un
customer de tip “Gold” sau 2 pentru un customer de tip “Silver”. In functie de acest tip se
calculeaza discount diferit in metoda getDiscount().

In conditiile in care apare un nou tip de customer („Bronze” de exemplu, cu discount doar de
25) trebuie modificata metoda getDiscount, deci trebuie MODIFICATA clasa Customer. La
fiecare astfel de schimbare trebuie sa verificam ca discount-urile sunt calculate corect pentru
vechile tipuri de customeri si ca aplicatiile care folosesc deja clasa Customer nu sunt afectate
de modificari.

class Customer
{
private int _CustType;

public int CustType


{
get { return _CustType; }
set { _CustType = value; }
}

public double getDiscount(double TotalSales)


{
if (_CustType == 1)
{
return TotalSales - 100;
}
else
{
return TotalSales - 50;
}
}
}

Solutie

Aplicand OCP, in loc sa ne propunem MODIFICAREA clasei Customer ne vom concentra pe


EXTINDEREA clasei Customer, asa cum este prezentat in exemplul de mai jos in care avem
doua noi clase SilverCustomer si GoldCustomer care extind clasa Customer si reimplementeaza
metoda getDiscount in functie de propriile necesitati. Cand apare un nou tip de customer se va
crea o noua clasa si nu se va modifica clasa Customer existenta. Deci clasa Customer impreuna
cu clasele derivate SilverCustomer si GoldCustomer raman neschimbate, ceea ce implica
testarea doar a noii clase adaugate.
class Customer
{
public virtual double getDiscount(double TotalSales)
{
return TotalSales;
}
}

class SilverCustomer : Customer


{
public override double getDiscount(double TotalSales)
{
return base.getDiscount(TotalSales) - 50;
}
}

class GoldCustomer : Customer


{
public override double getDiscount(double TotalSales)
{
return base.getDiscount(TotalSales) - 100;
}
}

1.3. L- Principiul de substituție Liskov (Liskov substitution principle - LSP)

Dacă S este un subtip al lui T, atunci obiectele de tip T pot fi substituite cu obiecte de tip S
fără a afecta niciuna din proprietățile programului (corectitudine, realizarea execuției etc.).

Exemplu in care poate fi aplicat LSP

Consideram ca dorim sa calculam discountul si pentru posibilele cereri de oferta venite din
partea unor customeri generici. Aceste cereri nu se doresc a fi salvate in baza de date, prin
urmare implementarea va fi ca in secventa de mai jos. Clasa CerereOferta extinde clasa
Customer, supradefineste metoda getDiscount pentru a specifica discount-ul acordat si, de
asemenea, supradefineste metoda Add pentru a arunca o exceptie in caz de apel, deoarece aceste
cereri nu se doresc a fi salvate in baza de date.
class CerereOferta : Customer
{
public override double getDiscount(double TotalSales)
{
return base.getDiscount(TotalSales) - 5;
}

public override void Add()


{
throw new Exception("Not allowed");
}
}
Considerand regulile de polimorfism, clasa parinte Cutomer poate fi utilizata pentru oricare din
tipurile derivate “Gold”, “Silver” sau “CerereOferta”. In exemplul de mai jos a fost initializata
o lista de obiecte de tip Customer in care au fost adaugate 3 obiecte apartinand claselor derivate.
Se observa in structura foreach ca la apelarea metodei Add apare exceptie in cazul obiectului
de tip CerereOferta.

List<Customer> Customers = new List<Customer>();


Customers.Add(new SilverCustomer());
Customers.Add(new GoldCustomer());
Customers.Add(new CerereOferta());

foreach (Customer o in Customers)


{
o.Add();
}

Clasa CerereOferta implementeaza metoda de calcul discount, deci seamana cu un Customer,


dar NU ESTE UN CUSTOMER pentru ca clasa parinte nu poate inlocui clasa copil fara
exceptii. Cu alte cuvinte, clasa Customer nu este parinte pentru clasa CerereOferta.

Solutie

Principiul LISKOV spune ca parintele ar trebui sa inlocuiasca cu usurinta copilul. Prin urmare,
pentru a implementa principiul LISKOV avem solutia prezentata mai jos: creare a doua
interfete: una pentru discount si una pentru accesul la baza de date.

Clasa CerereOferta va implementa doar interfata “IDiscount” pentru ca nu este interesata de


metoda Add specifica inserarii in baza de date. Clasa Customer va implementa ambele intefete
(IDiscount, IDatabase), deoarece, pe langa calculul discount-ului este interesata de adaugarea
datelor despre customer in baza de date.

interface IDiscount
{
double getDiscount(double TotalSales);
}

interface IDatabase
{
void Add();
}

class Enquiry : IDiscount


{
public double getDiscount(double TotalSales)
{
return TotalSales - 5;
}
}

class Customer : IDiscount, IDatabase


{
private FileLogger obj = new FileLogger();
public virtual void Add()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
obj.Handle(ex.Message.ToString());
}
}

public virtual double getDiscount(double TotalSales)


{
return TotalSales;
}
}

1.4. I - Principiul de segregare a interfețelor (Interface segregation principle - ISP)

Acest principiu afirmă că niciun client nu trebuie să fie forțat să depindă de metode pe care
nu le utilizează. Interfețele trebuie separate în alte interfețe mai mici şi mai specifice.

Exemplu in care poate fi aplicat ISP

În exemplu de mai jos, clasa Robot este forțată să implementeze metoda eat. Putem introduce
o metodă care să nu facă nimic, dar pot apărea efecte nedorite în aplicație – de exemplu, rapoarte
care să arate mai multe mese servite decât numărul de oameni.

interface IWorker
{
void work();
void eat();

class HumanWorker : IWorker


{
public void work()
{
//…
}
public void eat()
{
//…
}

class Robot : IWorker


{
public void work()
{
//…
}
public void eat()
{
//do nothing…
}

Solutie

Separarea interfeței Worker în două interfețe diferite are ca rezultat faptul că noua clasă Robot
nu mai este forțată să implementeze metoda eat.

interface IWorker
{
void work();
}

interface IEater
{
void eat();
}
class WorkerImpl : IWorker, IEater {
public void work()
{
// …
}
public void eat()
{
// …
}
}
class Robot : IWorker
{
public void work()
{
// …
}
}

1.5. D- Principiul de inversare a dependențelor (Dependency Inversion Principle - DIP)

Modulele de înalt nivel nu ar trebui să depindă de modulele de nivel scăzut. Ambele ar trebui
să depindă de abstracții.

Exemplu in care poate fi aplicat DIP

In clasa Customer a fost utilizata clasa FileLogger pentru a respecta principiul SRP.
class Customer
{
private FileLogger obj = new FileLogger();
public void Add()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
obj.Handle(ex.ToString());
}
}
}

Presupunem ca implementam noi clase (EverViewerLogger, EmailLogger) pentru moduri


diferite de logare si, de asemenea, o noua interfata comuna va fi implementata pentru a
simplifica utilizarea lor in clasa Customer.
interface ILogger
{
void Handle(string error);
}

class FileLogger : ILogger


{
public void Handle(string error)
{
System.IO.File.WriteAllText(@"c:\Error.txt", error);
}
}

class EverViewerLogger : ILogger


{
public void Handle(string error)
{
// log errors to event viewer
}
}

class EmailLogger : ILogger


{
public void Handle(string error)
{
// send errors in email
}
}

In functie de setarile aplicatiei, vor fi utilizate loggere diferite la apelurile metodei Add. Pentru
a mentine codul simplu, s-a utilizat o conditie IF care decide ce clasa va fi instantiata.

Acest cod incalca principiul SRP, deoarece are o noua responsabilitate : sa decida ce obiect sa
creeze. Aceasta responsabilitate nu este specifica unui customer.
class Customer : IDiscount, IDatabase
{
private ILogger obj;

public virtual void Add(int Exhandle)


{
try
{
// Database code goes here
}
catch (Exception ex)
{
if (Exhandle == 1)
{
obj = new FileLogger();
}
else
{
obj = new EmailLogger();
}
obj.Handle(ex.Message.ToString());
}
}
}

Solutie

Solutia este INVERSAREA/DELEGAREA acestei responsabilitati de a decide ce obiect


trebuie creat unei alte clase.

class Customer : IDiscount, IDatabase


{
private Ilogger obj;
public Customer(ILogger i)
{
obj = i;
}
public virtual void Add()
{
try
{
// Database code goes here
}
catch (Exception ex)
{
obj.Handle(ex.Message.ToString());
}
}
}

class Program
{
static void Main(string[] args)
{
IDatabase i = new Customer(new EmailLogger());
}
}
Constructorul a devenit mai flexibil si permite trimiterea din exterior a obiectului folosit pentru
logare. In acest mod este responsabilitatea aplicatiei care utilizeaza clasa Customer sa decida
ce obiect de tip Logger sa injecteze.

Clasa Customer a delegat operatia de decidere si creare a obiectului corespunzator de Logare


catre aplicatia client care o utilizeaza. Ea se poate concentra pe sarcinile corespunzatoare unui
customer.

Tema:
1. Proiectati clasele pentru proiectul propriu astfel incat sa respectati cat
mai multe din principiile de proiectare a aplicatiilor orientate obiect.

Bibliografie:
1. https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design
2. http://ctrl-d.ro/tips-and-tricks/programarea-orientata-pe-obiect-si-dezvoltarea-de-aplicatii-
5-principii-de-design/
3. https://blogs.msdn.microsoft.com/cdndevs/2009/07/15/the-solid-principles-explained-with-
motivational-posters/
4. https://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-
Csharp#WhatisSOLID

S-ar putea să vă placă și