CodeGym/Java blog/Véletlen/SZILÁRD: A Java osztálytervezés öt alapelve
John Squirrels
Szint
San Francisco

SZILÁRD: A Java osztálytervezés öt alapelve

Megjelent a csoportban
Az osztályok az alkalmazások építőkövei. Akárcsak a téglák az épületben. A rosszul megírt órák végül problémákat okozhatnak. SZILÁRD: A Java osztálytervezés öt alapelve – 1Annak megértéséhez, hogy egy osztály megfelelően van-e megírva, ellenőrizheti, hogyan felel meg a „minőségi szabványoknak”. Javaban ezek az úgynevezett SOLID elvek, és ezekről fogunk beszélni.

SOLID alapelvek Java-ban

A SOLID az OOP és az osztálytervezés első öt alapelvének nagybetűiből képzett mozaikszó. Az elveket Robert Martin fogalmazta meg a 2000-es évek elején, majd a rövidítést később Michael Feathers vezette be. Íme a SOLID alapelvek:
  1. Egységes felelősség elve
  2. Nyitott zárt elv
  3. Liskov helyettesítési elv
  4. Interfész szegregációs elve
  5. A függőségi inverzió elve

Egységes Felelősség Elve (SRP)

Ez az elv kimondja, hogy soha nem lehet több oka egy osztály megváltoztatására. Minden objektumnak egy felelőssége van, amely teljes mértékben benne van az osztályban. Egy osztály minden szolgáltatása ennek a felelősségnek a támogatására irányul. Az ilyen osztályok szükség esetén mindig könnyen módosíthatók, mert egyértelmű, hogy mi az osztály és mi nem felelős. Más szóval, képesek leszünk változtatásokat végrehajtani, és nem kell félnünk a következményektől, azaz a többi objektumra gyakorolt ​​hatástól. Ezenkívül az ilyen kódokat sokkal könnyebb tesztelni, mivel a tesztek egy funkciót fednek le, az összes többitől elkülönítve. Képzeljen el egy modult, amely feldolgozza a rendeléseket. Ha egy rendelést megfelelően alakított ki, ez a modul elmenti azt egy adatbázisba, és e-mailt küld a megrendelés megerősítéséhez:
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
    }
}
Ez a modul három okból változhat. Először is, a megrendelések feldolgozásának logikája megváltozhat. Másodszor, a megbízások mentési módja (adatbázis típusa) változhat. Harmadszor, a visszaigazolás küldésének módja megváltozhat (például tegyük fel, hogy e-mail helyett szöveges üzenetet kell küldenünk). Az egységes felelősség elve azt jelenti, hogy a probléma három aspektusa valójában három különböző felelősség. Ez azt jelenti, hogy különböző osztályokban vagy modulokban kell lenniük. Ha több olyan entitást kombinálunk, amelyek különböző időpontokban és különböző okokból változhatnak, rossz tervezési döntésnek minősül. Sokkal jobb, ha egy modult három különálló modulra osztunk fel, amelyek mindegyike egyetlen funkciót lát el:
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);
        }
    }

}

Nyílt zárt elv (OCP)

Ezt az elvet a következőképpen írjuk le: a szoftver entitások (osztályok, modulok, függvények stb.) legyenek nyitottak a kiterjesztésre, de zárva legyenek a módosításra . Ez azt jelenti, hogy lehetővé kell tenni egy osztály külső viselkedésének megváltoztatását anélkül, hogy módosítanánk az osztály meglévő kódját. Ennek az elvnek megfelelően az osztályok úgy vannak kialakítva, hogy egy osztályt bizonyos feltételekhez igazítva egyszerűen ki kell terjeszteni, és felül kell írni bizonyos funkciókat. Ez azt jelenti, hogy a rendszernek rugalmasnak kell lennie, képesnek kell lennie változó körülmények között a forráskód megváltoztatása nélkül dolgozni. Folytatva a rendelés feldolgozásával kapcsolatos példánkat, tegyük fel, hogy végre kell hajtanunk néhány műveletet a megrendelés feldolgozása előtt, valamint a visszaigazoló e-mail elküldése után. Ahelyett, hogy megváltoztatná aOrderProcessormagát az osztályt, ki fogjuk terjeszteni, hogy elérjük célunkat a Nyitott zárt elv megsértése nélkül:
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
    }
}

Liskov helyettesítési elv (LSP)

Ez a korábban említett nyitott zárt elv egy változata. A következőképpen definiálható: az objektumok lecserélhetők alosztályok objektumaira anélkül, hogy a program tulajdonságait megváltoztatnánk. Ez azt jelenti, hogy az alaposztály kiterjesztésével létrehozott osztálynak felül kell írnia a metódusait, hogy a funkcionalitást az ügyfél szemszögéből ne veszélyeztessék. Ez azt jelenti, hogy ha egy fejlesztő kiterjeszti az osztályt, és egy alkalmazásban használja, akkor nem szabad megváltoztatnia a felülírt metódusok elvárt viselkedését. Az alosztályoknak felül kell írniuk az alaposztály metódusait, hogy a funkcionalitás ne sérüljön meg az ügyfél szemszögéből. Ezt a következő példában részletesen megvizsgálhatjuk. Tegyük fel, hogy van egy osztályunk, amely a megrendelés érvényesítéséért és annak ellenőrzéséért felelős, hogy a megrendelésben szereplő összes áru raktáron van-e.isValid()módszer, amely igaz vagy hamis értéket ad vissza :
public class OrderStockValidator {

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

        return true;
    }
}
Tételezzük fel azt is, hogy egyes rendeléseket másként kell érvényesíteni, mint másokat, pl. bizonyos megrendeléseknél ellenőriznünk kell, hogy a rendelésben szereplő összes áru raktáron van-e, és minden áru be van-e csomagolva. Ehhez kiterjesztjük az osztályt az osztály OrderStockValidatorlétrehozásával :OrderStockAndPackValidator
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;
    }
}
De itt megsértettük a Liskov-helyettesítés elvét, mert ahelyett, hogy hamis értéket adnánk vissza , ha a sorrend nem érvényesül, a módszerünk egy -et dob IllegalStateException. Az ezt a kódot használó ügyfelek nem számítanak erre: igaz vagy false visszatérési értéket várnak el . Ez futásidejű hibákhoz vezethet.

Interfész szegregációs elv (ISP)

Ezt az elvet a következő kijelentés jellemzi: az ügyfelet nem szabad olyan módszerek megvalósítására kényszeríteni, amelyeket nem fog használni . Az interfész szegregáció elve azt jelenti, hogy a túl "vastag" felületeket kisebb, specifikusabb interfészekre kell felosztani, hogy a kis felületeket használó kliensek csak a munkájukhoz szükséges módszereket ismerjék. Ennek eredményeként, ha egy interfész metódus megváltozik, az adott módszert nem használó ügyfelek nem változhatnak. Tekintsük ezt a példát: Alex, egy fejlesztő létrehozott egy „jelentés” felületet, és két módszert adott hozzá: generateExcel()ésgeneratedPdf(). Most egy kliens szeretné használni ezt a felületet, de csak PDF formátumban kívánja használni a jelentéseket, nem Excelben. Ez a funkció kielégíti ezt az ügyfelet? Nem. Az ügyfélnek két módszert kell megvalósítania, amelyek közül az egyikre nagyrészt nincs szükség, és csak Alexnek köszönhetően létezik, aki a szoftvert tervezte. Az ügyfél egy másik felületet használ, vagy nem csinál semmit az Excel-jelentések metódusával. Szóval mi a megoldás? A meglévő interfész két kisebbre osztása. Az egyik a PDF-jelentésekhez, a másik az Excel-jelentésekhez. Ez lehetővé teszi az ügyfelek számára, hogy csak a szükséges funkciókat használják.

Függőség-inverziós elv (DIP)

A Java nyelvben ezt a SOLID elvet a következőképpen írják le: a rendszeren belüli függőségek absztrakciókon alapulnak. A magasabb szintű modulok nem függnek az alacsonyabb szintű moduloktól. Az absztrakciók nem függhetnek a részletektől. A részleteknek az absztrakcióktól kell függniük. A szoftvereket úgy kell megtervezni, hogy a különböző modulok önállóak legyenek, és absztrakción keresztül kapcsolódjanak egymáshoz. Ennek az elvnek a klasszikus alkalmazása a tavaszi keretrendszer. A Spring Frameworkben az összes modul különálló komponensként van megvalósítva, amelyek együtt tudnak működni. Annyira önállóak, hogy ugyanolyan könnyen használhatók a Spring Framework-től eltérő programmodulokban. Ez a zárt és nyitott elvek függésének köszönhetően érhető el. Minden modul csak az absztrakcióhoz biztosít hozzáférést, amely egy másik modulban használható. Próbáljuk meg ezt egy példa segítségével illusztrálni. Az egységes felelősség elvéről szólva úgy gondoltuk, aOrderProcessorosztály. Vessünk még egy pillantást ennek az osztálynak a kódjára:
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);
        }
    }

}
Ebben a példában az osztályunk OrderProcessorkét meghatározott osztálytól függ: MySQLOrderRepositoryés ConfirmationEmailSender. Bemutatjuk ezen osztályok kódját is:
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
    }
}
Ezek az osztályok távol állnak attól, amit absztrakcióknak neveznénk. A függőségi inverzió elve szempontjából pedig jobb lenne néhány absztrakció létrehozásával kezdeni, amelyekkel a jövőben dolgozni tudunk, nem pedig konkrét implementációkkal. Hozzunk létre két interfészt: MailSenderés OrderRepository). Ezek lesznek a mi absztrakcióink:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Most ezeket a felületeket olyan osztályokban valósítjuk meg, amelyek erre már felkészültek:
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;
    }
}
Az előkészítő munkát úgy végeztük OrderProcessor, hogy az osztályunk ne a konkrét részletektől, hanem az absztrakcióktól függjön. Megváltoztatjuk úgy, hogy hozzáadjuk függőségeinket az osztálykonstruktorhoz:
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);
        }
    }
}
Most az osztályunk az absztrakcióktól függ, nem pedig a konkrét megvalósításoktól. Könnyen megváltoztathatjuk viselkedését, ha hozzáadjuk a kívánt függőséget az OrderProcessorobjektum létrehozásakor. Megvizsgáltuk a Java SOLID tervezési elveit. A CodeGym tanfolyamon többet megtudhat az OOP-ról általában és a Java programozás alapjairól – semmi unalmas és több száz óra gyakorlás. Ideje megoldani néhány feladatot :)
Hozzászólások
  • Népszerű
  • Új
  • Régi
Hozzászólás írásához be kell jelentkeznie
Ennek az oldalnak még nincsenek megjegyzései