CodeGym/Blog Java/Aleatoriu/SOLID: Cinci principii de bază ale designului clasei în J...
John Squirrels
Nivel
San Francisco

SOLID: Cinci principii de bază ale designului clasei în Java

Publicat în grup
Clasele sunt elementele de bază ale aplicațiilor. Exact ca cărămizile într-o clădire. Cursurile prost scrise pot cauza în cele din urmă probleme. SOLID: Cinci principii de bază ale designului clasei în Java - 1Pentru a înțelege dacă o clasă este scrisă corect, puteți verifica cum se ridică la „standardele de calitate”. În Java, acestea sunt așa-numitele principii SOLID și vom vorbi despre ele.

Principii SOLID în Java

SOLID este un acronim format din majusculele primelor cinci principii ale OOP și designul de clasă. Principiile au fost exprimate de Robert Martin la începutul anilor 2000, iar apoi abrevierea a fost introdusă mai târziu de Michael Feathers. Iată principiile SOLID:
  1. Principiul responsabilității unice
  2. Principiul deschis închis
  3. Principiul substituirii Liskov
  4. Principiul segregării interfeței
  5. Principiul inversării dependenței

Principiul responsabilității unice (SRP)

Acest principiu afirmă că nu ar trebui să existe niciodată mai mult de un motiv pentru a schimba o clasă. Fiecare obiect are o responsabilitate, care este complet încapsulată în clasă. Toate serviciile unei clase sunt menite să susțină această responsabilitate. Astfel de clase vor fi întotdeauna ușor de modificat dacă este necesar, deoarece este clar de ce este și nu este responsabilă clasa. Cu alte cuvinte, vom putea face schimbări și să nu ne fie frică de consecințe, adică de impactul asupra altor obiecte. În plus, un astfel de cod este mult mai ușor de testat, deoarece testele dvs. acoperă o singură bucată de funcționalitate, izolat de toate celelalte. Imaginați-vă un modul care procesează comenzi. Dacă o comandă este formată corect, acest modul o salvează într-o bază de date și trimite un e-mail pentru a confirma comanda:
public class OrderProcessor {

    public void process(Order order){
        if (order.isValid() && save(order)) {
            sendConfirmationEmail(order);
        }
    }

    private boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }

    private void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
Acest modul se poate schimba din trei motive. În primul rând, logica procesării comenzilor se poate schimba. În al doilea rând, modul în care sunt salvate comenzile (tipul bazei de date) se poate schimba. În al treilea rând, se poate schimba modul în care este trimisă confirmarea (de exemplu, să presupunem că trebuie să trimitem un mesaj text mai degrabă decât un e-mail). Principiul responsabilității unice implică faptul că cele trei aspecte ale acestei probleme sunt de fapt trei responsabilități diferite. Asta înseamnă că ar trebui să fie în diferite clase sau module. Combinarea mai multor entități care se pot schimba în momente diferite și din motive diferite este considerată o decizie proastă de proiectare. Este mult mai bine să împărțiți un modul în trei module separate, fiecare dintre ele îndeplinește o singură funcție:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}

public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}

Principiul deschis și închis (OCP)

Acest principiu este descris după cum urmează: entitățile software (clase, module, funcții etc.) ar trebui să fie deschise pentru extindere, dar închise pentru modificare . Aceasta înseamnă că ar trebui să fie posibil să se schimbe comportamentul extern al unei clase fără a face modificări codului existent al clasei. Conform acestui principiu, clasele sunt concepute astfel încât ajustarea unei clase pentru a se potrivi unor condiții specifice necesită pur și simplu extinderea acesteia și suprascrierea unor funcții. Aceasta înseamnă că sistemul trebuie să fie flexibil, capabil să funcționeze în condiții schimbătoare fără a schimba codul sursă. Continuând exemplul nostru care implică procesarea comenzii, să presupunem că trebuie să efectuăm unele acțiuni înainte ca o comandă să fie procesată, precum și după trimiterea e-mailului de confirmare. În loc să schimbiOrderProcessorclasa în sine, o vom extinde pentru a ne îndeplini obiectivul fără a încălca Principiul Deschis Închis:
public class OrderProcessorWithPreAndPostProcessing extends OrderProcessor {

    @Override
    public void process(Order order) {
        beforeProcessing();
        super.process(order);
        afterProcessing();
    }

    private void beforeProcessing() {
        // Take some action before processing the order
    }

    private void afterProcessing() {
        // Take some action after processing the order
    }
}

Principiul substituției Liskov (LSP)

Aceasta este o variație a principiului deschis închis pe care l-am menționat mai devreme. Poate fi definit astfel: obiectele pot fi înlocuite cu obiecte din subclase fără a modifica proprietățile unui program. Aceasta înseamnă că o clasă creată prin extinderea unei clase de bază trebuie să-și suprascrie metodele, astfel încât funcționalitatea să nu fie compromisă din punctul de vedere al clientului. Adică, dacă un dezvoltator vă extinde clasa și o folosește într-o aplicație, el sau ea nu ar trebui să schimbe comportamentul așteptat al vreunei metode suprascrise. Subclasele trebuie să suprascrie metodele clasei de bază, astfel încât funcționalitatea să nu fie întreruptă din punctul de vedere al clientului. Putem explora acest lucru în detaliu în exemplul următor. Să presupunem că avem o clasă care este responsabilă pentru validarea unei comenzi și verificarea dacă toate mărfurile din comandă sunt în stoc.isValid()metoda care returneaza adevarat sau fals :
public class OrderStockValidator {

    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if (!item.isInStock()) {
                return false;
            }
        }

        return true;
    }
}
Să presupunem, de asemenea, că unele comenzi trebuie validate diferit de altele, de exemplu, pentru unele comenzi trebuie să verificăm dacă toate mărfurile din comandă sunt în stoc și dacă toate mărfurile sunt ambalate. Pentru a face acest lucru, extindem OrderStockValidatorclasa prin crearea OrderStockAndPackValidatorclasei:
public class OrderStockAndPackValidator extends OrderStockValidator {

    @Override
    public boolean isValid(Order order) {
        for (Item item : order.getItems()) {
            if ( !item.isInStock() || !item.isPacked() ){
                throw new IllegalStateException(
                     String.format("Order %d is not valid!", order.getId())
                );
            }
        }

        return true;
    }
}
Dar aici am încălcat principiul substituției Liskov, deoarece în loc să returnăm false dacă comanda eșuează validarea, metoda noastră aruncă un IllegalStateException. Clienții care folosesc acest cod nu se așteaptă la asta: se așteaptă la o valoare returnată fie true , fie false . Acest lucru poate duce la erori de rulare.

Principiul de segregare a interfeței (ISP)

Acest principiu este caracterizat de următoarea afirmație: clientul nu trebuie să fie forțat să implementeze metode pe care nu le va folosi . Principiul segregării interfețelor înseamnă că interfețele prea „groase” trebuie împărțite în altele mai mici, mai specifice, astfel încât clienții care folosesc interfețe mici să cunoască doar metodele de care au nevoie pentru munca lor. Ca rezultat, atunci când o metodă de interfață se schimbă, orice client care nu utilizează acea metodă nu ar trebui să se schimbe. Luați în considerare acest exemplu: Alex, un dezvoltator, a creat o interfață de „raport” și a adăugat două metode: generateExcel()șigeneratedPdf(). Acum un client dorește să folosească această interfață, dar intenționează să folosească doar rapoarte în format PDF, nu în Excel. Va satisface această funcționalitate acest client? Nu. Clientul va trebui să implementeze două metode, dintre care una în mare măsură nu este necesară și există doar datorită lui Alex, cel care a proiectat software-ul. Clientul va folosi fie o interfață diferită, fie nu va face nimic cu metoda pentru rapoartele Excel. Deci care este soluția? Este de a împărți interfața existentă în două mai mici. Unul pentru rapoarte PDF, celălalt pentru rapoarte Excel. Acest lucru permite clienților să folosească doar funcționalitatea de care au nevoie.

Principiul inversării dependenței (DIP)

În Java, acest principiu SOLID este descris după cum urmează: dependențele din cadrul sistemului sunt construite pe baza abstracțiilor. Modulele de nivel superior nu depind de modulele de nivel inferior. Abstracțiile nu ar trebui să depindă de detalii. Detaliile ar trebui să depindă de abstracții. Software-ul trebuie proiectat astfel încât diferitele module să fie autonome și conectate între ele prin abstractizare. O aplicare clasică a acestui principiu este Spring Framework. În cadrul Spring Framework, toate modulele sunt implementate ca componente separate care pot lucra împreună. Sunt atât de autonome încât pot fi folosite la fel de ușor și în alte module de program decât Spring Framework. Acest lucru se realizează datorită dependenței de principiile închise și deschise. Toate modulele oferă acces numai la abstractizare, care poate fi folosită într-un alt modul. Să încercăm să ilustrăm acest lucru folosind un exemplu. Vorbind despre principiul responsabilității unice, am considerat căOrderProcessorclasă. Să aruncăm o altă privire la codul acestei clase:
public class OrderProcessor {
    public void process(Order order){

        MySQLOrderRepository repository = new MySQLOrderRepository();
        ConfirmationEmailSender mailSender = new ConfirmationEmailSender();

        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }

}
În acest exemplu, OrderProcessorclasa noastră depinde de două clase specifice: MySQLOrderRepositoryși ConfirmationEmailSender. De asemenea, vom prezenta codul acestor clase:
public class MySQLOrderRepository {
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}

public class ConfirmationEmailSender {
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }
}
Aceste clase sunt departe de ceea ce am numi abstracții. Și din punctul de vedere al principiului inversării dependenței, ar fi mai bine să începem prin a crea niște abstracții cu care să putem lucra în viitor, mai degrabă decât implementări specifice. Să creăm două interfețe: MailSenderși OrderRepository). Acestea vor fi abstracțiile noastre:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Acum implementăm aceste interfețe în clase care au fost deja pregătite pentru asta:
public class ConfirmationEmailSender implements MailSender {

    @Override
    public void sendConfirmationEmail(Order order) {
        String name = order.getCustomerName();
        String email = order.getCustomerEmail();

        // Send an email to the customer
    }

}

public class MySQLOrderRepository implements OrderRepository {

    @Override
    public boolean save(Order order) {
        MySqlConnection connection = new MySqlConnection("database.url");
        // Save the order in the database

        return true;
    }
}
Am făcut lucrările pregătitoare astfel încât OrderProcessorclasa noastră să depindă, nu de detalii concrete, ci de abstracțiuni. O vom schimba adăugând dependențele noastre la constructorul de clasă:
public class OrderProcessor {

    private MailSender mailSender;
    private OrderRepository repository;

    public OrderProcessor(MailSender mailSender, OrderRepository repository) {
        this.mailSender = mailSender;
        this.repository = repository;
    }

    public void process(Order order){
        if (order.isValid() && repository.save(order)) {
            mailSender.sendConfirmationEmail(order);
        }
    }
}
Acum, clasa noastră depinde de abstracții, nu de implementări specifice. Îi putem schimba cu ușurință comportamentul adăugând dependența dorită în momentul în care un OrderProcessorobiect este creat. Am examinat principiile de proiectare SOLID în Java. Veți afla mai multe despre OOP în general și despre elementele de bază ale programării Java - nimic plictisitor și sute de ore de practică - în cursul CodeGym. E timpul să rezolvi câteva sarcini :)
Comentarii
  • Popular
  • Nou
  • Vechi
Trebuie să fii conectat pentru a lăsa un comentariu
Această pagină nu are încă niciun comentariu