CodeGym/Java-blogg/Tilfeldig/SOLID: Fem grunnleggende prinsipper for klassedesign i Ja...
John Squirrels
Nivå
San Francisco

SOLID: Fem grunnleggende prinsipper for klassedesign i Java

Publisert i gruppen
Klasser er byggesteinene i applikasjoner. Akkurat som murstein i en bygning. Dårlig skriftlige klasser kan etter hvert skape problemer. SOLID: Fem grunnleggende prinsipper for klassedesign i Java - 1For å forstå om en klasse er riktig skrevet, kan du sjekke hvordan den holder seg til "kvalitetsstandarder". I Java er dette de såkalte SOLID-prinsippene, og vi skal snakke om dem.

SOLIDE prinsipper i Java

SOLID er et akronym dannet av de store bokstavene i de fem første prinsippene for OOP og klassedesign. Prinsippene ble uttrykt av Robert Martin på begynnelsen av 2000-tallet, og så ble forkortelsen introdusert senere av Michael Feathers. Her er de SOLIDE prinsippene:
  1. Enkelt ansvarsprinsipp
  2. Åpent lukket prinsipp
  3. Liskov Substitusjonsprinsipp
  4. Grensesnittsegregeringsprinsipp
  5. Avhengighetsinversjonsprinsipp

Single Responsibility Principle (SRP)

Dette prinsippet sier at det aldri skal være mer enn én grunn til å bytte klasse. Hvert objekt har ett ansvar, som er fullstendig innkapslet i klassen. Alle en klasses tjenester er rettet mot å støtte dette ansvaret. Slike klasser vil alltid være enkle å modifisere ved behov, fordi det er tydelig hva klassen er og ikke har ansvar for. Vi vil med andre ord kunne gjøre endringer og ikke være redde for konsekvensene, altså påvirkningen på andre objekter. I tillegg er slik kode mye enklere å teste, fordi testene dine dekker ett stykke funksjonalitet isolert fra alle andre. Se for deg en modul som behandler bestillinger. Hvis en bestilling er riktig utformet, lagrer denne modulen den i en database og sender en e-post for å bekrefte bestillingen:
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
    }
}
Denne modulen kan endres av tre grunner. For det første kan logikken for behandling av bestillinger endres. For det andre kan måten bestillinger lagres på (databasetype) endres. For det tredje kan måten bekreftelse sendes på endres (anta for eksempel at vi må sende en tekstmelding i stedet for en e-post). Enkeltansvarsprinsippet innebærer at de tre aspektene ved dette problemet faktisk er tre forskjellige ansvarsområder. Det betyr at de bør være i forskjellige klasser eller moduler. Å kombinere flere enheter som kan endre seg til ulike tider og av ulike årsaker anses som en dårlig designbeslutning. Det er mye bedre å dele en modul i tre separate moduler, som hver utfører en enkelt funksjon:
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);
        }
    }

}

Åpent lukket prinsipp (OCP)

Dette prinsippet er beskrevet som følger: programvareenheter (klasser, moduler, funksjoner osv.) skal være åpne for utvidelse, men stengt for modifikasjon . Dette betyr at det skal være mulig å endre en klasses eksterne atferd uten å gjøre endringer i klassens eksisterende kode. I henhold til dette prinsippet er klasser utformet slik at det å tilpasse en klasse for å passe spesifikke forhold ganske enkelt krever å utvide den og overstyre noen funksjoner. Dette betyr at systemet skal være fleksibelt, kunne fungere under skiftende forhold uten å endre kildekoden. For å fortsette vårt eksempel med bestillingsbehandling, anta at vi må utføre noen handlinger før en bestilling behandles så vel som etter at e-postbekreftelsen er sendt. I stedet for å endreOrderProcessorklasse selv, vil vi utvide den for å oppnå målet vårt uten å bryte Open Closed-prinsippet:
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 Substitusjonsprinsipp (LSP)

Dette er en variant av det åpne lukkede prinsippet som vi nevnte tidligere. Det kan defineres som følger: objekter kan erstattes av objekter av underklasser uten å endre et programs egenskaper. Dette betyr at en klasse opprettet ved å utvide en baseklasse må overstyre metodene slik at funksjonaliteten ikke blir kompromittert fra klientens synspunkt. Det vil si at hvis en utvikler utvider klassen din og bruker den i en applikasjon, bør han eller hun ikke endre den forventede oppførselen til noen overstyrte metoder. Underklasser må overstyre metodene til basisklassen slik at funksjonaliteten ikke brytes fra klientens synspunkt. Vi kan utforske dette i detalj i følgende eksempel. Anta at vi har en klasse som er ansvarlig for å validere en ordre og sjekke om alle varene i ordren er på lager.isValid()metode som returnerer sant eller usant :
public class OrderStockValidator {

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

        return true;
    }
}
Anta også at noen bestillinger må valideres annerledes enn andre, f.eks. for noen bestillinger må vi sjekke om alle varene i bestillingen er på lager og om alle varene er pakket. For å gjøre dette utvider vi OrderStockValidatorklassen ved å lage 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 brutt Liskov-substitusjonsprinsippet, fordi i stedet for å returnere falsk hvis ordren mislykkes i valideringen, kaster metoden vår en IllegalStateException. Klienter som bruker denne koden forventer ikke dette: de forventer en returverdi på enten sant eller usant . Dette kan føre til kjøretidsfeil.

Interface Segregation Principle (ISP)

Dette prinsippet er preget av følgende utsagn: klienten skal ikke tvinges til å implementere metoder som de ikke vil bruke . Grensesnittsegregeringsprinsippet innebærer at grensesnitt som er for «tykke» må deles inn i mindre, mer spesifikke, slik at klienter som bruker små grensesnitt kun vet om metodene de trenger for arbeidet sitt. Som et resultat, når en grensesnittmetode endres, bør ikke klienter som ikke bruker denne metoden endres. Tenk på dette eksemplet: Alex, en utvikler, har laget et "rapport"-grensesnitt og lagt til to metoder: generateExcel()oggeneratedPdf(). Nå ønsker en klient å bruke dette grensesnittet, men har kun til hensikt å bruke rapporter i PDF-format, ikke i Excel. Vil denne funksjonaliteten tilfredsstille denne klienten? Nei. Klienten må implementere to metoder, hvorav den ene stort sett ikke er nødvendig og kun eksisterer takket være Alex, han som designet programvaren. Klienten vil enten bruke et annet grensesnitt eller ikke gjøre noe med metoden for Excel-rapporter. Så hva er løsningen? Det er å dele det eksisterende grensesnittet i to mindre. En for PDF-rapporter, den andre for Excel-rapporter. Dette lar klienter bruke bare funksjonaliteten de trenger.

Dependency Inversion Principle (DIP)

I Java beskrives dette SOLID-prinsippet som følger: avhengigheter i systemet bygges basert på abstraksjoner. Moduler på høyere nivå er ikke avhengige av moduler på lavere nivå. Abstraksjoner bør ikke avhenge av detaljer. Detaljer bør avhenge av abstraksjoner. Programvare må utformes slik at de ulike modulene er selvstendige og koblet til hverandre gjennom abstraksjon. En klassisk anvendelse av dette prinsippet er Spring Framework. I Spring Framework er alle moduler implementert som separate komponenter som kan fungere sammen. De er så autonome at de like enkelt kan brukes i andre programmoduler enn Spring Framework. Dette oppnås takket være avhengigheten av de lukkede og åpne prinsippene. Alle modulene gir kun tilgang til abstraksjonen, som kan brukes i en annen modul. La oss prøve å illustrere dette ved å bruke et eksempel. Når vi snakker om enkeltansvarsprinsippet, vurderte viOrderProcessorklasse. La oss ta en ny titt på koden til denne klassen:
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 eksemplet er klassen vår OrderProcessoravhengig av to spesifikke klasser: MySQLOrderRepositoryog ConfirmationEmailSender. Vi vil også presentere koden for disse klassene:
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 klassene er langt fra det vi vil kalle abstraksjoner. Og fra synspunktet til avhengighetsinversjonsprinsippet, ville det være bedre å starte med å lage noen abstraksjoner som vi kan jobbe med i fremtiden, i stedet for spesifikke implementeringer. La oss lage to grensesnitt: MailSenderog OrderRepository). Dette vil være våre abstraksjoner:
public interface MailSender {
    void sendConfirmationEmail(Order order);
}

public interface OrderRepository {
    boolean save(Order order);
}
Nå implementerer vi disse grensesnittene i klasser som allerede er forberedt for 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 gjorde det forberedende arbeidet slik at OrderProcessorklassen vår ikke er avhengig av konkrete detaljer, men av abstraksjoner. Vi vil endre det ved å legge til avhengighetene våre 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);
        }
    }
}
Nå er klassen vår avhengig av abstraksjoner, ikke spesifikke implementeringer. Vi kan enkelt endre oppførselen ved å legge til ønsket avhengighet på det tidspunktet et OrderProcessorobjekt opprettes. Vi har undersøkt SOLID designprinsippene i Java. Du vil lære mer om OOP generelt og det grunnleggende om Java-programmering – ikke noe kjedelig og hundrevis av timer med trening – i CodeGym-kurset. På tide å løse noen oppgaver :)
Kommentarer
  • Populær
  • Ny
  • Gammel
Du må være pålogget for å legge igjen en kommentar
Denne siden har ingen kommentarer ennå