Allt du behöver veta om solida principer i Java



I den här artikeln lär du dig i detalj om vad som är solida principer i java med exempel och deras betydelse med exempel på verkliga livet.

I världen av (OOP), det finns många designriktlinjer, mönster eller principer. Fem av dessa principer är vanligtvis grupperade och är kända under förkortningen SOLID. Medan var och en av dessa fem principer beskriver något specifikt, överlappar de också så att antagande av en av dem innebär eller leder till att anta en annan. I den här artikeln kommer vi att förstå SOLID-principer i Java.

Historik om SOLID-principer i Java

Robert C. Martin gav fem objektorienterade designprinciper och förkortningen ”S.O.L.I.D” används för den. När du använder alla principerna i S.O.L.I.D på ett kombinerat sätt blir det lättare för dig att utveckla programvara som enkelt kan hanteras. De andra funktionerna i att använda S.O.L.I.D är:





  • Det undviker kodlukt.
  • Snabb refraktorkod.
  • Kan göra anpassningsbar eller smidig programutveckling.

När du använder principen för S.O.L.I.D i din kodning börjar du skriva koden som är både effektiv och effektiv.



Vad är innebörden av S.O.L.I.D?

Solid representerar fem principer för java som är:

  • S : Enhetsansvarsprincip
  • ELLER : Öppen-stängd princip
  • L : Liskovs substitutionsprincip
  • Jag : Gränssnittsegregeringsprincip
  • D : Beroendeförändringsprincip

I den här bloggen kommer vi att diskutera alla de fem SOLID-principerna för Java i detalj.



Princip för ett enda ansvar i Java

Vad står det?

Robert C. Martin beskriver det som att en klass bara borde ha ett enda ansvar.

Enligt principen om ett enda ansvar borde det bara finnas en anledning på grund av vilken en klass måste ändras. Det betyder att en klass ska ha en uppgift att göra. Denna princip kallas ofta för subjektiv.

Principen kan väl förstås med ett exempel. Tänk dig att det finns en klass som utför följande operationer.

  • Ansluten till en databas

  • Läs data från databastabeller

  • Slutligen, skriv det till en fil.

Har du föreställt dig scenariot? Här har klassen flera anledningar att ändra, och få av dem är modifiering av filutdata, ny antagande av databas. När vi talar om enstaka principansvar, skulle vi säga, det finns för många skäl för klassen att förändras, det passar inte ordentligt i principen om enstaka ansvar.

Till exempel kan en bilklass starta eller stoppa sig själv men uppgiften att tvätta den tillhör CarWash-klassen. I ett annat exempel har en bokklass egenskaper för att lagra sitt eget namn och text. Men uppgiften att skriva ut boken måste tillhöra klassen Book Printer. Klassen bokskrivare kan skriva ut till konsol eller annat medium men sådana beroenden tas bort från bokklassen

Varför krävs detta princip?

När principen om ett enda ansvar följs är det enklare att testa. Med ett enda ansvar kommer klassen att ha färre testfall. Mindre funktionalitet innebär också färre beroenden till andra klasser. Det leder till bättre kodorganisation eftersom mindre och välmenande klasser är lättare att söka.

Ett exempel för att klargöra denna princip:

Antag att du blir ombedd att implementera en UserSetting-tjänst där användaren kan ändra inställningarna men innan det måste användaren verifieras. Ett sätt att genomföra detta skulle vara:

public class UserSettingService {public void changeEmail (User user) {if (checkAccess (user)) {// Grant option to change}} public boolean checkAccess (User user) {// Verifiera om användaren är giltig. }}

Allt ser bra ut tills du vill återanvända checkAccess-koden på någon annan plats ELLER vill du göra ändringar i hur checkAccess görs. I alla två fall skulle du sluta ändra samma klass och i det första fallet måste du använda UserSettingService för att också kontrollera åtkomst.
Ett sätt att korrigera detta är att bryta ner UserSettingService till UserSettingService och SecurityService. Och flytta checkAccess-koden till SecurityService.

public class UserSettingService {public void changeEmail (User user) {if (SecurityService.checkAccess (user)) {// Grant option to change}}} public class SecurityService {public static boolean checkAccess (User user) {// check the access. }}

Öppna stängd princip i Java

Robert C. Martin beskriver det som att programvarukomponenter ska vara öppna för förlängning, men stängda för modifiering.

För att vara exakt, enligt denna princip bör en klass skrivas på ett sådant sätt att den utför sitt jobb felfritt utan antagandet att människor i framtiden helt enkelt kommer och ändrar det. Därför bör klassen förbli stängd för modifiering, men den bör ha möjlighet att bli utökad. Sätt att utöka klassen inkluderar:

  • Arv från klassen

  • Skriva över det beteende som krävs från klassen

  • Utöka vissa beteenden i klassen

Ett utmärkt exempel på öppen-stängd princip kan förstås med hjälp av webbläsare. Kommer du ihåg att du installerade tillägg i din Chrome-webbläsare?

Grundfunktionen för Chrome-webbläsaren är att surfa på olika webbplatser. Vill du kontrollera grammatiken när du skriver ett e-postmeddelande med hjälp av Chrome-webbläsaren? Om ja, kan du helt enkelt använda Grammarly-tillägget, det ger dig grammatikcheck på innehållet.

Denna mekanism där du lägger till saker för att öka webbläsarens funktionalitet är ett tillägg. Därför är webbläsaren ett perfekt exempel på funktionalitet som är öppen för förlängning men är stängd för modifiering. Med enkla ord kan du förbättra funktionaliteten genom att lägga till / installera plugins i din webbläsare, men kan inte bygga något nytt.

Varför är denna princip nödvändig?

OCP är viktigt eftersom klasser kan komma till oss via tredjepartsbibliotek. Vi borde kunna utöka dessa klasser utan att oroa oss för om dessa basklasser kan stödja våra tillägg. Men arv kan leda till underklasser som beror på basklassimplementering. För att undvika detta rekommenderas användning av gränssnitt. Denna extra abstraktion leder till lös koppling.

Låt oss säga att vi måste beräkna områden i olika former. Vi börjar med att skapa en klass för vår första form rektangelsom har två attribut längd& bredd.

offentlig klass Rektangel {offentlig dubbel längd offentlig dubbel bredd}

Därefter skapar vi en klass för att beräkna arean för denna rektangelsom har en metod beräknaRektangelAreasom tar rektangelnsom inmatningsparameter och beräknar dess area.

public class AreaCalculator {public double CalcRectangleArea (Rectangle rectangle) {return rectangle.length * rectangle.width}}

Än så länge är allt bra. Låt oss säga att vi får vår andra formcirkel. Så vi skapar omedelbart en ny klasscirkelmed en enda attributradie.

public class Circle {offentlig dubbelradie}

Sedan ändrar vi Areacalculatorklass för att lägga till cirkelberäkningar genom en ny metod beräknaCircleaArea ()

public class AreaCalculator {public double calcRectangleArea (Rectangle rectangle) {return rectangle.length * rectangle.width} public double calcCircleArea (Cirkelcirkel) {return (22/7) * cirkel.radie * cirkel.radie}}

Observera dock att det fanns brister i hur vi utformade vår lösning ovan.

Låt oss säga att vi har en ny form femkant. I så fall kommer vi att sluta ändra klassen AreaCalculator. När typerna av former växer blir det mer rörligt eftersom AreaCalculator fortsätter att förändras och alla konsumenter i den här klassen måste fortsätta uppdatera sina bibliotek som innehåller AreaCalculator. Som ett resultat kommer AreaCalculator-klassen inte att baseras (slutföras) med säkerhet eftersom varje gång en ny form kommer kommer den att ändras. Så denna design är inte stängd för modifiering.

AreaCalculator måste fortsätta lägga till sin beräkningslogik i nyare metoder. Vi utvidgar egentligen inte omfattningen av former utan vi gör helt enkelt bitmjöl-lösning för varje form som läggs till.

Ändring av ovanstående design för att överensstämma med öppnad / stängd princip:

Låt oss nu se en mer elegant design som löser bristerna i ovanstående design genom att följa den öppna / stängda principen. Vi kommer först och främst att göra designen utdragbar. För detta måste vi först definiera en bastyp Shape och låta Circle & Rectangle implementera Shape-gränssnittet.

hashmap och hashtable i java
offentligt gränssnitt Form {offentlig dubbel beräknaArea ()} offentlig klass Rektangelredskap Form {dubbel längd dubbel bredd offentlig dubbel beräknaArea () {returlängd * bredd}} offentlig klass Cirkelredskap Form {offentlig dubbelradie offentlig dubbel beräknaArea () {retur (22 / 7) * radie * radie}}

Det finns en basgränssnittsform. Alla former implementerar nu basgränssnittet Shape. Formgränssnittet har en abstrakt metod, beräknaArea (). Både cirkel och rektangel ger sin egen åsidosatta implementering av calcArea () -metoden med egna attribut.
Vi har tagit in en viss töjbarhet eftersom former nu är en förekomst av formgränssnitt. Detta gör att vi kan använda form istället för enskilda klasser
Den sista punkten ovan nämnde konsumenten av dessa former. I vårt fall kommer konsumenten att vara AreaCalculator-klassen som nu skulle se ut så här.

offentlig klass AreaCalculator {offentlig dubbel beräknaShapeArea (Shape form) {return shape.calculateArea ()}}

Denna AreaCalculatorklass tar nu helt bort våra designfel som nämnts ovan och ger en ren lösning som följer den öppna stängda principen. Låt oss gå vidare med andra SOLID-principer i Java

Liskovs substitutionsprincip i Java

Robert C. Martin beskriver det som härledda typer måste vara helt utbytbara för sina bastyper.

Liskov-substitutionsprincipen antar att q (x) är en egenskap, som kan bevisas om enheter av x som tillhör typ T. Nu, enligt denna princip, bör q (y) nu kunna bevisas för objekt y som tillhör typ S, och S är faktiskt en undertyp av T. Är du nu förvirrad och vet inte vad Liskovs substitutionsprincip egentligen betyder? Definitionen av det kan vara lite komplex, men i själva verket är det ganska enkelt. Det enda är att varje underklass eller härledd klass ska kunna bytas ut mot sin förälder eller basklass.

Du kan säga att det är en unik objektorienterad princip. Principen kan ytterligare förenklas av en barntyp av en viss föräldrartyp utan att göra några komplikationer eller att spränga saker bör ha förmågan att stå för den föräldern. Denna princip är nära relaterad till Liskov-substitutionsprincipen.

Varför är denna princip nödvändig?

Detta undviker missbruk av arv. Det hjälper oss att anpassa oss till ”is-a” -förhållandet. Vi kan också säga att underklasser måste uppfylla ett kontrakt som definieras av basklassen. I den meningen är det relaterat tillDesign efter kontraktsom först beskrevs av Bertrand Meyer. Det är till exempel frestande att säga att en cirkel är en typ av ellips men cirklar inte har två foci eller stora / mindre axlar.

LSP förklaras populärt med exempel på fyrkant och rektangel. om vi antar ett ISA-förhållande mellan kvadrat och rektangel. Således kallar vi 'Square är en rektangel.' Koden nedan representerar förhållandet.

public class Rectangle {privat int längd privat int bredd offentligt int getLength () {returlängd} offentligt tomrum setLength (int längd) {detta.längd = längd} offentligt int getBreadth () {retur bredd} offentligt tomrum setBreadth (int bredd) { this.breadth = bredd} public int getArea () {return this.length * this.breadth}}

Nedan är koden för Square. Observera att Square sträcker sig rektangel.

offentlig klass Fyrkanten sträcker sig Rektangel {public void setBreadth (int bredd) {super.setBreadth (bredd) super.setLength (bredd)} public void setLength (int längd) {super.setLength (längd) super.setBreadth (längd)}}

I det här fallet försöker vi etablera ett ISA-förhållande mellan kvadrat och rektangel så att anropet 'kvadrat är en rektangel' i koden nedan skulle börja fungera oväntat om en instans av kvadrat passeras. Ett påståendefel kommer att kastas i fallet med att söka efter 'Area' och kontrollera för 'Breadth', även om programmet kommer att avslutas när påståendefelet kastas på grund av att områdekontrollen misslyckades.

offentlig klass LSPDemo {public void calcalArea (Rectangle r) {r.setBreadth (2) r.setLength (3) assert r.getArea () == 6: printError ('area', r) assert r.getLength () == 3: printError ('längd', r) hävda r.getBreadth () == 2: printError ('bredd', r)} privat String printError (String errorIdentifer, Rectangle r) {return 'Oväntat värde av' + errorIdentifer + ' till exempel '+ r.getClass (). getName ()} public static void main (String [] args) {LSPDemo lsp = new LSPDemo () // En instans av rektangel passeras lsp.calculateArea (ny rektangel ()) // En förekomst av Square passeras lsp.calculateArea (new Square ())}}

Klassen demonstrerar Liskovs substitutionsprincip (LSP) Enligt principen måste funktionerna som använder referenser till basklasserna kunna använda objekt av härledd klass utan att veta det.

Således, i exemplet som visas nedan, bör funktionen calcAArea som använder referensen för 'Rectangle' kunna använda objekten av härledd klass som Square och uppfylla kravet som ställs av Rectangle definition. Man bör notera att enligt definitionen av rektangel måste följande alltid vara sant med tanke på data nedan:

  1. Längden måste alltid vara lika med den längd som gått som inmatning till metod, setLength
  2. Bredden måste alltid vara lika med den bredd som skickas som inmatning till metod, setBreadth
  3. Området måste alltid vara lika med produkten av längd och bredd

Om vi ​​försöker etablera ISA-förhållandet mellan kvadrat och rektangel så att vi kallar 'kvadrat är en rektangel', ovanför kod skulle börja fungera oväntat om en instans av kvadrat passeras påståendefel kommer att kastas vid kontroll av område och kontroll för bredd, även om programmet avslutas när påståendefelet kastas på grund av fel i områdekontrollen.

Square-klassen behöver inte metoder som setBreadth eller setLength. LSPDemo-klassen skulle behöva känna till detaljerna för härledda klasser av rektangel (som fyrkant) för att koda på rätt sätt för att undvika kastfel. Förändringen av den befintliga koden bryter principen om öppen stängning.

Princip för gränssnittssegregation

Robert C. Martin beskriver det som att klienter inte ska tvingas implementera onödiga metoder som de inte kommer att använda.

EnligtGränssnittsegregeringsprincipen klient, oavsett vad som aldrig ska tvingas att implementera ett gränssnitt som den inte använder eller klienten bör aldrig vara skyldig att vara beroende av någon metod som inte används av dem. gränssnitt, som är små men klientspecifika istället för monolitiska och större gränssnitt. Kort sagt, det skulle vara dåligt för dig att tvinga klienten att vara beroende av en viss sak som de inte behöver.

Till exempel är ett enda loggningsgränssnitt för att skriva och läsa loggar användbart för en databas men inte för en konsol. Att läsa loggar är inte meningsfullt för en konsollogger. Fortsätter med dessa SOLID-principer i Java-artikeln.

Varför är denna princip nödvändig?

Låt oss säga att det finns ett restauranggränssnitt som innehåller metoder för att ta emot beställningar från onlinekunder, uppringnings- eller telefonkunder och walk-in-kunder. Den innehåller också metoder för hantering av onlinebetalningar (för online-kunder) och personliga betalningar (för walk-in-kunder samt telefonkunder när deras beställning levereras hemma).

Låt oss nu skapa ett Java-gränssnitt för restaurang och namnge det som RestaurantInterface.java.

offentligt gränssnitt RestaurantInterface {public void acceptOnlineOrder () public void takeTelephoneOrder () public void payOnline () public void walkInCustomerOrder () public void payInPerson ()}

Det finns fem metoder definierade i RestaurantInterface som är för att acceptera onlinebeställning, ta telefonbeställning, acceptera beställningar från en walk-in-kund, acceptera onlinebetalning och acceptera betalning personligen.

Låt oss börja med att implementera RestaurantInterface för online-kunder som OnlineClientImpl.java

public class OnlineClientImpl implementerar RestaurantInterface {public void acceptOnlineOrder () {// logik för att placera online order} public void takeTelephoneOrder () {// Ej tillämpligt för Online Order kasta nytt UnsupportedOperationException ()} public void payOnline () {// logik för betalning online} public void walkInCustomerOrder () {// Ej tillämpbart för onlinebeställning kasta nytt UnsupportedOperationException ()} public void payInPerson () {// Ej tillämpligt för onlineorder kasta nytt UnsupportedOperationException ()}}
  • Eftersom ovanstående kod (OnlineClientImpl.java) är för onlinebeställningar, kasta UnsupportedOperationException.

  • Online-, telefon- och walk-in-klienter använder RestaurantInterface-implementeringen som är specifik för var och en av dem.

  • Implementeringsklasserna för telefonisk klient och walk-in-klient har metoder som inte stöds.

  • Eftersom de 5 metoderna är en del av RestaurantInterface måste implementeringsklasserna implementera alla 5 av dem.

  • Metoderna som var och en av implementeringsklasserna kastar UnsupportedOperationException. Som du tydligt kan se - att implementera alla metoder är ineffektivt.

  • Varje ändring av någon av metoderna i RestaurantInterface kommer att spridas till alla implementeringsklasser. Underhållet av koden börjar bli riktigt besvärligt och regressionseffekter av förändringar kommer att fortsätta öka.

    konvertera från dubbel till int Java
  • RestaurantInterface.java bryter mot principen om enstaka ansvarsfördelar eftersom logiken för betalningar såväl som för orderplacering grupperas i ett enda gränssnitt.

För att övervinna de ovannämnda problemen använder vi gränssnittssegregationsprincip för att refaktorera ovanstående design.

  1. Separera ut betalnings- och orderplaceringsfunktioner i två separata lean-gränssnitt, PaymentInterface.java och OrderInterface.java.

  2. Var och en av klienterna använder en implementering vardera av PaymentInterface och OrderInterface. Till exempel - OnlineClient.java använder OnlinePaymentImpl och OnlineOrderImpl och så vidare.

  3. Enstaka ansvarsprinciper är nu bifogade som betalningsgränssnitt (PaymentInterface.java) och Ordering-gränssnitt (OrderInterface).

  4. Ändring av något av order- eller betalningsgränssnitten påverkar inte den andra. De är oberoende nu. Det kommer inte att behöva görs någon dummyimplementering eller kasta ett Ej stödtOperationException eftersom varje gränssnitt bara har metoder som det alltid kommer att använda.

Efter applicering av ISP

Princip för beroende av inversion

Robert C. Martin beskriver det som att det beror på abstraktioner och inte på konkretioner. Enligt det får högnivåmodulen aldrig förlita sig på någon lågnivåmodul. till exempel

Du går till en lokal butik för att köpa något och du väljer att betala för det med ditt betalkort. Så när du ger ditt kort till expediten för att göra betalningen, bryr sig expediten inte om vilken typ av kort du har gett.

Även om du har gett ett Visa-kort kommer han inte att lägga ut en Visa-maskin för att svepa ditt kort. Den typ av kreditkort eller betalkort som du har för att betala spelar inte ens någon roll, de kommer bara att dra det. Så i det här exemplet kan du se att både du och kontoristen är beroende av kreditkortabstraktionen och att du inte är orolig för kortets detaljer. Det här är en princip för inversion av beroende.

Varför är denna princip nödvändig?

Det tillåter en programmerare att ta bort hårddiskberoende beroenden så att applikationen blir löst kopplad och utdragbar.

offentlig klass Student {privat adress adress allmän student () {adress = ny adress ()}}

I exemplet ovan kräver studentklassen ett adressobjekt och det ansvarar för att initiera och använda adressobjektet. Om adressklassen ändras i framtiden måste vi också göra ändringar i studentklassen. Detta gör den täta kopplingen mellan student- och adressobjekt. Vi kan lösa detta problem med hjälp av designmönstret för beroendeinversion. d.v.s. adressobjektet kommer att implementeras oberoende och kommer att ges till studenten när studenten instansieras genom att använda konstruktörbaserad eller setterbaserad beroendevändning.

Med detta kommer vi till ett slut på dessa SOLID-principer i Java.

Kolla in av Edureka, ett pålitligt inlärningsföretag online med ett nätverk av mer än 250 000 nöjda elever spridda över hela världen. Edurekas Java J2EE- och SOA-utbildning och certifieringskurs är utformad för studenter och yrkesverksamma som vill vara Java-utvecklare. Kursen är utformad för att ge dig ett försprång till Java-programmering och träna dig för både kärn- och avancerade Java-koncept tillsammans med olika Java-ramverk som Hibernate & Spring.

Har du en fråga till oss? Vänligen nämna det i kommentarsektionen i denna “SOLID Principles in Java” -blogg så återkommer vi till dig så snart som möjligt.