CodeGym/Java blog/Tilfældig/SOLID: Fem grundlæggende principper for klassedesign i Ja...
John Squirrels
Niveau
San Francisco

SOLID: Fem grundlæggende principper for klassedesign i Java

Udgivet i gruppen
Klasser er byggestenene i applikationer. Ligesom mursten i en bygning. Dårligt skrevne klasser kan i sidste ende give problemer. SOLID: Fem grundlæggende principper for klassedesign i Java - 1For at forstå, om en klasse er korrekt skrevet, kan du tjekke, hvordan den lever op til "kvalitetsstandarder". I Java er det de såkaldte SOLID-principper, og dem skal vi tale om.

SOLIDE principper i Java

SOLID er et akronym dannet af de store bogstaver i de første fem principper for OOP og klassedesign. Principperne blev udtrykt af Robert Martin i begyndelsen af ​​2000'erne, og så blev forkortelsen introduceret senere af Michael Feathers. Her er de SOLIDE principper:
  1. Enkelt ansvarsprincip
  2. Åbent lukket princip
  3. Liskov Substitutionsprincip
  4. Interfacesegregationsprincip
  5. Afhængighedsinversionsprincip

Single Responsibility Principle (SRP)

Dette princip siger, at der aldrig bør være mere end én grund til at skifte klasse. Hvert objekt har ét ansvar, som er fuldt indkapslet i klassen. Alle en klasses tjenester er rettet mod at understøtte dette ansvar. Sådanne klasser vil altid være nemme at ændre, hvis det er nødvendigt, fordi det er klart, hvad klassen er og ikke er ansvarlig for. Vi vil med andre ord være i stand til at lave ændringer og ikke være bange for konsekvenserne, altså påvirkningen af ​​andre objekter. Derudover er en sådan kode meget nemmere at teste, fordi dine tests dækker ét stykke funktionalitet isoleret fra alle andre. Forestil dig et modul, der behandler ordrer. Hvis en ordre er korrekt udformet, gemmer dette modul den i en database og sender en e-mail for at bekræfte ordren:
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
    }
}
Dette modul kan ændre sig af tre grunde. For det første kan logikken for behandling af ordrer ændre sig. For det andet kan den måde, ordrer gemmes på (databasetype), ændre sig. For det tredje kan den måde, bekræftelse sendes på, ændre sig (antag for eksempel, at vi skal sende en sms i stedet for en e-mail). Princippet om et enkelt ansvar indebærer, at de tre aspekter af dette problem faktisk er tre forskellige ansvarsområder. Det betyder, at de skal være i forskellige klasser eller moduler. At kombinere flere enheder, der kan ændre sig på forskellige tidspunkter og af forskellige årsager, betragtes som en dårlig designbeslutning. Det er meget bedre at opdele et modul i tre separate moduler, som hver udfører en enkelt funktion:
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);
        }
    }

}

Åbent lukket princip (OCP)

Dette princip er beskrevet som følger: softwareenheder (klasser, moduler, funktioner osv.) skal være åbne for forlængelse, men lukkede for modifikation . Det betyder, at det skal være muligt at ændre en klasses eksterne adfærd uden at foretage ændringer i klassens eksisterende kode. Ifølge dette princip er klasser designet således, at tilpasning af en klasse, så den passer til specifikke forhold, blot kræver at udvide den og tilsidesætte nogle funktioner. Det betyder, at systemet skal være fleksibelt, kunne arbejde under skiftende forhold uden at ændre kildekoden. Fortsætter vores eksempel, der involverer ordrebehandling, antag, at vi skal udføre nogle handlinger, før en ordre behandles såvel som efter bekræftelses-e-mailen er sendt. I stedet for at ændreOrderProcessorklasse selv, vil vi udvide den for at nå vores mål uden at overtræde Open Closed-princippet:
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 substitutionsprincippet (LSP)

Dette er en variation af det åbne lukkede princip, som vi nævnte tidligere. Det kan defineres som følger: objekter kan erstattes af objekter af underklasser uden at ændre et programs egenskaber. Det betyder, at en klasse oprettet ved at udvide en basisklasse skal tilsidesætte dens metoder, så funktionaliteten ikke kompromitteres fra klientens synspunkt. Det vil sige, at hvis en udvikler udvider din klasse og bruger den i en applikation, bør han eller hun ikke ændre den forventede adfærd for nogen tilsidesatte metoder. Underklasser skal tilsidesætte basisklassens metoder, så funktionaliteten ikke brydes set fra klientens synspunkt. Vi kan udforske dette i detaljer i det følgende eksempel. Antag, at vi har en klasse, der er ansvarlig for at validere en ordre og kontrollere, om alle varer i ordren er på lager.isValid()metode, der returnerer sand eller falsk :
public class OrderStockValidator {

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

        return true;
    }
}
Antag også, at nogle ordrer skal valideres anderledes end andre, f.eks. skal vi for nogle ordrer kontrollere, om alle varer i ordren er på lager, og om alle varerne er pakket. For at gøre dette udvider vi klassen OrderStockValidatorved at oprette OrderStockAndPackValidatorklassen:
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;
    }
}
Men her har vi brudt Liskov substitutionsprincippet, for i stedet for at returnere falsk , hvis ordren ikke validerer, kaster vores metode en IllegalStateException. Klienter, der bruger denne kode, forventer ikke dette: de forventer en returværdi på enten sand eller falsk . Dette kan føre til runtime fejl.

Interface Segregation Principle (ISP)

Dette princip er karakteriseret ved følgende udsagn: klient bør ikke tvinges til at implementere metoder, som de ikke vil bruge . Interfacesegregationsprincippet betyder, at grænseflader, der er for "tykke", skal opdeles i mindre, mere specifikke, så klienter, der bruger små grænseflader, kun kender til de metoder, de har brug for til deres arbejde. Som et resultat, når en grænseflademetode ændres, bør alle klienter, der ikke bruger denne metode, ikke ændre sig. Overvej dette eksempel: Alex, en udvikler, har oprettet en "rapport"-grænseflade og tilføjet to metoder: generateExcel()oggeneratedPdf(). Nu ønsker en klient at bruge denne grænseflade, men har kun til hensigt at bruge rapporter i PDF-format, ikke i Excel. Vil denne funktionalitet tilfredsstille denne klient? Nej. Klienten skal implementere to metoder, hvoraf den ene stort set ikke er nødvendig og kun eksisterer takket være Alex, ham der har designet softwaren. Klienten vil enten bruge en anden grænseflade eller ikke gøre noget med metoden til Excel-rapporter. Så hvad er løsningen? Det er at opdele den eksisterende grænseflade i to mindre. Den ene til PDF-rapporter, den anden til Excel-rapporter. Dette lader klienter kun bruge den funktionalitet, de har brug for.

Afhængighedsinversionsprincip (DIP)

I Java er dette SOLID-princip beskrevet som følger: afhængigheder i systemet er bygget på baggrund af abstraktioner. Moduler på højere niveau er ikke afhængige af moduler på lavere niveau. Abstraktioner bør ikke afhænge af detaljer. Detaljer bør afhænge af abstraktioner. Software skal designes, så de forskellige moduler er selvstændige og forbundet med hinanden gennem abstraktion. En klassisk anvendelse af dette princip er Spring Framework. I Spring Framework er alle moduler implementeret som separate komponenter, der kan arbejde sammen. De er så autonome, at de lige så nemt kan bruges i andre programmoduler end Spring Framework. Dette opnås takket være afhængigheden af ​​de lukkede og åbne principper. Alle modulerne giver kun adgang til abstraktionen, som kan bruges i et andet modul. Lad os prøve at illustrere dette ved hjælp af et eksempel. Når vi taler om princippet om enkelt ansvar, overvejede viOrderProcessorklasse. Lad os se på koden til denne klasse igen:
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);
        }
    }

}
I dette eksempel afhænger vores OrderProcessorklasse af to specifikke klasser: MySQLOrderRepositoryog ConfirmationEmailSender. Vi vil også præsentere koden for disse klasser:
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
    }
}
Disse klasser er langt fra, hvad vi ville kalde abstraktioner. Og set fra afhængighedsinversionsprincippet ville det være bedre at starte med at skabe nogle abstraktioner, som vi kan arbejde med i fremtiden, frem for specifikke implementeringer. Lad os oprette to grænseflader: MailSenderog OrderRepository). Disse vil være vores abstraktioner:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Nu implementerer vi disse grænseflader i klasser, der allerede er forberedt til dette:
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;
    }
}
Vi lavede det forberedende arbejde, så vores OrderProcessorklasse afhænger, ikke af konkrete detaljer, men af ​​abstraktioner. Vi ændrer det ved at tilføje vores afhængigheder til klassekonstruktøren:
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);
        }
    }
}
Nu afhænger vores klasse af abstraktioner, ikke specifikke implementeringer. Vi kan nemt ændre dens adfærd ved at tilføje den ønskede afhængighed på det tidspunkt, et OrderProcessorobjekt skabes. Vi har undersøgt SOLID designprincipperne i Java. Du vil lære mere om OOP generelt og det grundlæggende i Java-programmering — intet kedeligt og hundredvis af timers øvelse — i CodeGym-kurset. Tid til at løse et par opgaver :)
Kommentarer
  • Populær
  • Ny
  • Gammel
Du skal være logget ind for at skrive en kommentar
Denne side har ingen kommentarer endnu