Ang mga klase ay ang mga bloke ng pagbuo ng mga aplikasyon. Parang mga brick sa isang gusali. Maaaring magdulot ng mga problema ang mga hindi magandang nakasulat na klase. SOLID: Limang pangunahing prinsipyo ng disenyo ng klase sa Java - 1Upang maunawaan kung maayos ang pagkakasulat ng isang klase, maaari mong tingnan kung paano ito sumusukat sa "mga pamantayan ng kalidad." Sa Java, ito ang mga tinatawag na SOLID na prinsipyo, at pag-uusapan natin ang mga ito.

SOLID na mga prinsipyo sa Java

Ang SOLID ay isang acronym na nabuo mula sa malalaking titik ng unang limang prinsipyo ng OOP at disenyo ng klase. Ang mga prinsipyo ay ipinahayag ni Robert Martin noong unang bahagi ng 2000s, at pagkatapos ay ang pagdadaglat ay ipinakilala mamaya ni Michael Feathers. Narito ang mga SOLID na prinsipyo:
  1. Prinsipyo ng Iisang Pananagutan
  2. Buksan ang Saradong Prinsipyo
  3. Prinsipyo ng Pagpapalit ng Liskov
  4. Prinsipyo ng Interface Segregation
  5. Dependency Inversion Principle

Single Responsibility Principle (SRP)

Ang prinsipyong ito ay nagsasaad na hindi dapat magkaroon ng higit sa isang dahilan upang baguhin ang isang klase. Ang bawat bagay ay may isang responsibilidad, na ganap na nakapaloob sa klase. Ang lahat ng serbisyo ng isang klase ay naglalayong suportahan ang responsibilidad na ito. Ang ganitong mga klase ay palaging madaling baguhin kung kinakailangan, dahil malinaw kung ano ang klase at hindi responsable. Sa madaling salita, makakagawa tayo ng mga pagbabago at hindi matatakot sa mga kahihinatnan, ibig sabihin, ang epekto sa iba pang mga bagay. Bukod pa rito, ang naturang code ay mas madaling subukan, dahil ang iyong mga pagsubok ay sumasaklaw sa isang piraso ng functionality sa paghihiwalay mula sa lahat ng iba pa. Isipin ang isang module na nagpoproseso ng mga order. Kung tama ang pagkakabuo ng isang order, ise-save ito ng module na ito sa isang database at magpapadala ng email upang kumpirmahin ang order:

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
    }
}
Maaaring magbago ang modyul na ito sa tatlong dahilan. Una, maaaring magbago ang lohika para sa pagproseso ng mga order. Pangalawa, ang paraan ng pag-save ng mga order (uri ng database) ay maaaring magbago. Pangatlo, ang paraan ng pagpapadala ng kumpirmasyon ay maaaring magbago (halimbawa, ipagpalagay na kailangan nating magpadala ng text message sa halip na isang email). Ang prinsipyo ng nag-iisang responsibilidad ay nagpapahiwatig na ang tatlong aspeto ng problemang ito ay talagang tatlong magkakaibang mga responsibilidad. Ibig sabihin dapat ay nasa iba't ibang klase o module sila. Ang pagsasama-sama ng ilang entity na maaaring magbago sa iba't ibang panahon at para sa iba't ibang dahilan ay itinuturing na isang hindi magandang desisyon sa disenyo. Mas mainam na hatiin ang isang module sa tatlong magkakahiwalay na module, na ang bawat isa ay gumaganap ng isang function:

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);
        }
    }

}

Open Closed Principle (OCP)

Ang prinsipyong ito ay inilalarawan tulad ng sumusunod: ang mga entidad ng software (mga klase, module, function, atbp.) ay dapat na bukas para sa extension, ngunit sarado para sa pagbabago . Nangangahulugan ito na posible na baguhin ang panlabas na gawi ng isang klase nang hindi gumagawa ng mga pagbabago sa umiiral na code ng klase. Ayon sa prinsipyong ito, ang mga klase ay idinisenyo upang ang pagsasaayos ng isang klase upang umangkop sa mga partikular na kundisyon ay nangangailangan lamang ng pagpapalawak nito at pag-override sa ilang mga pag-andar. Nangangahulugan ito na ang system ay dapat na may kakayahang umangkop, magagawang magtrabaho sa pagbabago ng mga kondisyon nang hindi binabago ang source code. Sa pagpapatuloy ng aming halimbawa na kinasasangkutan ng pagpoproseso ng order, ipagpalagay na kailangan naming magsagawa ng ilang pagkilos bago maproseso ang isang order gayundin pagkatapos ipadala ang email ng kumpirmasyon. Sa halip na baguhin angOrderProcessorclass mismo, palawigin namin ito upang maisakatuparan ang aming layunin nang hindi lumalabag sa Open Closed Principle:

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 Substitution Principle (LSP)

Ito ay isang pagkakaiba-iba ng bukas na saradong prinsipyo na nabanggit namin kanina. Maaari itong tukuyin bilang mga sumusunod: ang mga bagay ay maaaring mapalitan ng mga bagay ng mga subclass nang hindi binabago ang mga katangian ng isang programa. Nangangahulugan ito na ang isang klase na nilikha sa pamamagitan ng pagpapalawak ng isang base class ay dapat na i-override ang mga pamamaraan nito upang ang pag-andar ay hindi makompromiso mula sa pananaw ng kliyente. Iyon ay, kung pinalawig ng isang developer ang iyong klase at ginagamit ito sa isang application, hindi niya dapat baguhin ang inaasahang pag-uugali ng anumang na-override na mga pamamaraan. Dapat i-override ng mga subclass ang mga pamamaraan ng base class upang hindi masira ang functionality mula sa punto ng view ng client. Maaari nating tuklasin ito nang detalyado sa sumusunod na halimbawa. Ipagpalagay na mayroon kaming isang klase na responsable para sa pagpapatunay ng isang order at pagsuri kung ang lahat ng mga kalakal sa order ay nasa stock.isValid()paraan na nagbabalik ng totoo o mali :

public class OrderStockValidator {

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

        return true;
    }
}
Ipagpalagay din na ang ilang mga order ay kailangang ma-validate nang iba kaysa sa iba, hal para sa ilang mga order kailangan nating suriin kung ang lahat ng mga kalakal sa order ay nasa stock at kung ang lahat ng mga kalakal ay nakaimpake. Upang gawin ito, pinalawak namin ang OrderStockValidatorklase sa pamamagitan ng paglikha ng OrderStockAndPackValidatorklase:

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;
    }
}
Ngunit dito ay nilabag namin ang prinsipyo ng pagpapalit ng Liskov, dahil sa halip na ibalik ang mali kung ang order ay nabigo sa pagpapatunay, ang aming pamamaraan ay nagtatapon ng isang IllegalStateException. Hindi ito inaasahan ng mga kliyenteng gumagamit ng code na ito: inaasahan nila ang isang return value na true o false . Maaari itong humantong sa mga error sa runtime.

Interface Segregation Principle (ISP)

Ito ang prinsipyo ay nailalarawan sa pamamagitan ng sumusunod na pahayag: hindi dapat pilitin ang kliyente na ipatupad ang mga pamamaraan na hindi nila gagamitin . Ang prinsipyo ng segregation ng interface ay nangangahulugan na ang mga interface na masyadong "makapal" ay dapat na hatiin sa mas maliit, mas tiyak, para ang mga kliyenteng gumagamit ng maliliit na interface ay alam lamang ang tungkol sa mga pamamaraan na kailangan nila para sa kanilang trabaho. Bilang resulta, kapag nagbago ang isang paraan ng interface, hindi dapat magbago ang sinumang kliyente na hindi gumagamit ng paraang iyon. Isaalang-alang ang halimbawang ito: Gumawa si Alex, isang developer, ng interface na "ulat" at nagdagdag ng dalawang pamamaraan: generateExcel()atgeneratedPdf(). Ngayon gusto ng isang kliyente na gamitin ang interface na ito, ngunit nilayon lamang na gumamit ng mga ulat sa format na PDF, hindi sa Excel. Mabibigyang-kasiyahan ba ng functionality na ito ang kliyenteng ito? Hindi. Ang kliyente ay kailangang magpatupad ng dalawang pamamaraan, ang isa ay hindi kinakailangan at umiiral lamang salamat kay Alex, ang nagdisenyo ng software. Ang kliyente ay gagamit ng alinman sa ibang interface o walang gagawin sa pamamaraan para sa mga ulat sa Excel. Kaya ano ang solusyon? Ito ay upang hatiin ang umiiral na interface sa dalawang mas maliit. Ang isa para sa mga ulat na PDF, ang isa para sa mga ulat sa Excel. Nagbibigay-daan ito sa mga kliyente na gamitin lang ang functionality na kailangan nila.

Dependency Inversion Principle (DIP)

Sa Java, ang SOLID na prinsipyong ito ay inilarawan bilang mga sumusunod: ang mga dependency sa loob ng system ay binuo batay sa mga abstraction. Ang mas mataas na antas ng mga module ay hindi nakadepende sa mga mas mababang antas ng mga module. Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang mga detalye ay dapat nakadepende sa mga abstraction. Ang software ay kailangang idinisenyo upang ang iba't ibang mga module ay self-contained at konektado sa isa't isa sa pamamagitan ng abstraction. Ang isang klasikong aplikasyon ng prinsipyong ito ay ang Spring Framework. Sa Spring Framework, ang lahat ng mga module ay ipinatupad bilang hiwalay na mga bahagi na maaaring gumana nang magkasama. Napakadaling gamitin ng mga ito sa mga module ng programa maliban sa Spring Framework. Nakamit ito salamat sa pagtitiwala sa sarado at bukas na mga prinsipyo. Ang lahat ng mga module ay nagbibigay ng access lamang sa abstraction, na maaaring magamit sa isa pang module. Subukan nating ilarawan ito gamit ang isang halimbawa. Sa pagsasalita tungkol sa nag-iisang prinsipyo ng responsibilidad, isinasaalang-alang namin angOrderProcessorklase. Tingnan natin muli ang code ng klase na ito:

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);
        }
    }

}
Sa halimbawang ito, OrderProcessornakadepende ang aming klase sa dalawang partikular na klase: MySQLOrderRepositoryat ConfirmationEmailSender. Ipapakita rin namin ang code ng mga klase na ito:

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
    }
}
Ang mga klaseng ito ay malayo sa tinatawag nating abstraction. At mula sa punto ng view ng dependency inversion na prinsipyo, ito ay mas mahusay na magsimula sa pamamagitan ng paglikha ng ilang abstraction na maaari naming gamitin sa hinaharap, kaysa sa mga partikular na pagpapatupad. Gumawa tayo ng dalawang interface: MailSenderat OrderRepository). Ito ang magiging abstraction natin:

public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Ngayon ipinatupad namin ang mga interface na ito sa mga klase na naihanda na para dito:

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;
    }
}
Ginawa namin ang gawaing paghahanda upang ang aming OrderProcessorklase ay nakasalalay, hindi sa mga konkretong detalye, kundi sa mga abstraction. Babaguhin namin ito sa pamamagitan ng pagdaragdag ng aming mga dependency sa tagabuo ng klase:

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);
        }
    }
}
Ngayon ang aming klase ay nakasalalay sa mga abstraction, hindi mga partikular na pagpapatupad. Madali nating mababago ang pag-uugali nito sa pamamagitan ng pagdaragdag ng nais na dependency sa oras OrderProcessorna nilikha ang isang bagay. Sinuri namin ang mga prinsipyo ng SOLID na disenyo sa Java. Matututunan mo ang higit pa tungkol sa OOP sa pangkalahatan at ang mga pangunahing kaalaman ng Java programming — walang nakakabagot at daan-daang oras ng pagsasanay — sa kursong CodeGym. Oras na para malutas ang ilang mga gawain :)