Alles wat u moet weten over solide principes in Java



In dit artikel leer je in detail over wat Solid-principes in Java zijn met voorbeelden en hun belang met voorbeelden uit het echte leven.

In de wereld van (OOP), er zijn veel ontwerprichtlijnen, patronen of principes. Vijf van deze principes zijn gewoonlijk gegroepeerd en staan ​​bekend onder de afkorting SOLID. Hoewel elk van deze vijf principes iets specifieks beschrijft, overlappen ze elkaar ook, zodat het aannemen van een ervan impliceert of leidt tot het aannemen van een ander. In dit artikel zullen we SOLID-principes in Java begrijpen.

Geschiedenis van SOLID-principes in Java

Robert C. Martin gaf vijf objectgeoriënteerde ontwerpprincipes en daarvoor wordt de afkorting 'S.O.L.I.D' gebruikt. Wanneer u alle principes van S.O.L.I.D op een gecombineerde manier toepast, wordt het gemakkelijker voor u om software te ontwikkelen die gemakkelijk kan worden beheerd. De andere kenmerken van het gebruik van S.O.L.I.D zijn:





  • Het vermijdt codegeuren.
  • Snel refractorcode.
  • Kan adaptieve of agile softwareontwikkeling doen.

Wanneer u het principe van S.O.L.I.D gebruikt bij uw codering, begint u met het schrijven van de code die zowel efficiënt als effectief is.



Wat is de betekenis van S.O.L.I.D?

Solid vertegenwoordigt vijf principes van Java die zijn:

  • S : Beginsel van enkele verantwoordelijkheid
  • OF : Open-gesloten principe
  • L. : Liskov-substitutieprincipe
  • ik : Principe van scheiding van interfaces
  • D : Afhankelijkheidsinversieprincipe

In deze blog gaan we in detail in op alle vijf SOLID-principes van Java.



Single Responsibility Principle in Java

Wat zegt het?

Robert C. Martin beschrijft het als één klas die maar één en enige verantwoordelijkheid zou moeten hebben.

Volgens het beginsel van enkele verantwoordelijkheid mag er maar één reden zijn waarom een ​​klasse moet worden gewijzigd. Het betekent dat een klas één taak moet hebben. Dit principe wordt vaak subjectief genoemd.

Het principe kan goed worden begrepen met een voorbeeld. Stel je voor dat er een klasse is die de volgende bewerkingen uitvoert.

  • Verbonden met een database

  • Lees enkele gegevens uit databasetabellen

  • Schrijf het ten slotte naar een bestand.

Heeft u zich het scenario voorgesteld? Hier heeft de klasse meerdere redenen om te veranderen, en enkele daarvan zijn de wijziging van de bestandsuitvoer, de acceptatie van nieuwe databases. Als we het hebben over verantwoordelijkheid van één beginsel, zouden we zeggen dat er te veel redenen zijn voor de klas om te veranderen, en daarom past het niet goed in het beginsel van enkele verantwoordelijkheid.

Een Automobile-klasse kan bijvoorbeeld zichzelf starten of stoppen, maar het wassen ervan behoort tot de CarWash-klasse. In een ander voorbeeld heeft een klasse Book eigenschappen om zijn eigen naam en tekst op te slaan. Maar de taak van het afdrukken van het boek moet tot de klasse Boekprinter behoren. De klasse Boekprinter kan naar console of een ander medium worden afgedrukt, maar dergelijke afhankelijkheden worden uit de klasse Boek verwijderd

Waarom is dit principe vereist?

Wanneer het Single Responsibility Principle wordt gevolgd, is testen eenvoudiger. Met een enkele verantwoordelijkheid heeft de klas minder testcases. Minder functionaliteit betekent ook minder afhankelijkheid van andere klassen. Het leidt tot een betere code-organisatie, aangezien kleinere en goedbedoelde klassen gemakkelijker te doorzoeken zijn.

Een voorbeeld om dit principe te verduidelijken:

toepassing van big data-analyse

Stel dat u wordt gevraagd om een ​​UserSetting-service te implementeren waarbij de gebruiker de instellingen kan wijzigen, maar daarvoor moet de gebruiker worden geverifieerd. Een manier om dit te implementeren zou zijn:

public class UserSettingService {public void changeEmail (User user) {if (checkAccess (user)) {// Grant option to change}} public boolean checkAccess (User user) {// Controleer of de gebruiker geldig is. }}

Alles ziet er goed uit totdat u de checkAccess-code op een andere plaats wilt hergebruiken OF u wijzigingen wilt aanbrengen in de manier waarop checkAccess wordt uitgevoerd. In alle 2 gevallen zou je uiteindelijk dezelfde klasse veranderen en in het eerste geval zou je UserSettingService moeten gebruiken om ook op toegang te controleren.
Een manier om dit te corrigeren is door de UserSettingService op te splitsen in UserSettingService en SecurityService. En verplaats de checkAccess-code naar 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) {// controleer de toegang. }}

Open gesloten principe in Java

Robert C. Martin beschrijft het als Softwarecomponenten moeten openstaan ​​voor uitbreiding, maar gesloten voor wijziging.

Om precies te zijn, volgens dit principe moet een klas zo worden geschreven dat het zijn werk feilloos uitvoert zonder aan te nemen dat mensen in de toekomst het gewoon zullen komen veranderen. Daarom moet de klasse gesloten blijven voor wijziging, maar moet deze de optie hebben om uitgebreid te worden. Manieren om de les uit te breiden zijn onder meer:

  • Overnemen van de klas

  • Het vereiste gedrag uit de klas overschrijven

  • Bepaald gedrag van de klas uitbreiden

Een uitstekend voorbeeld van het open-gesloten-principe kan worden begrepen met behulp van browsers. Weet je nog dat je extensies in je Chrome-browser hebt geïnstalleerd?

De basisfunctie van de Chrome-browser is om op verschillende sites te surfen. Wilt u de grammatica controleren wanneer u een e-mail schrijft met de Chrome-browser? Zo ja, dan kunt u gewoon de Grammatica-extensie gebruiken, deze biedt u grammaticacontrole van de inhoud.

Dit mechanisme waar je dingen aan toevoegt om de functionaliteit van de browser te vergroten, is een extensie. Daarom is de browser een perfect voorbeeld van functionaliteit die openstaat voor uitbreiding maar gesloten is voor wijziging. In eenvoudige bewoordingen kunt u de functionaliteit verbeteren door plug-ins in uw browser toe te voegen / te installeren, maar u kunt niets nieuws bouwen.

Waarom is dit principe vereist?

OCP is belangrijk omdat lessen naar ons kunnen komen via bibliotheken van derden. We zouden die klassen moeten kunnen uitbreiden zonder ons zorgen te hoeven maken of die basisklassen onze extensies kunnen ondersteunen. Maar overerving kan leiden tot subklassen die afhankelijk zijn van de implementatie van de basisklasse. Om dit te voorkomen, wordt het gebruik van interfaces aanbevolen. Deze extra abstractie leidt tot een losse koppeling.

Laten we zeggen dat we gebieden met verschillende vormen moeten berekenen. We beginnen met het maken van een klasse voor onze eerste vorm Rechthoekdie 2 attributen lengte heeft& breedte.

openbare klasse Rechthoek {openbare dubbele lengte openbare dubbele breedte}

Vervolgens maken we een klasse om de oppervlakte van deze rechthoek te berekenendie een methode heeft berekenenRectangleAreadie de rechthoek neemtals invoerparameter en berekent zijn oppervlakte.

openbare klasse AreaCalculator {openbare dubbele berekenRectangleArea (Rechthoek rechthoek) {retourneer rechthoek.lengte * rechthoek.breedte}}

Tot zover goed. Laten we nu zeggen dat we onze tweede vormcirkel krijgen. We maken dus prompt een nieuwe klassencirkelmet een enkele attribuutradius.

openbare klas Cirkel {openbare dubbele straal}

Vervolgens passen we Areacalculator aanklasse om cirkelberekeningen toe te voegen via een nieuwe methode berekenCircleaArea ()

openbare klasse AreaCalculator {openbare dubbele berekenRectangleArea (Rechthoekige rechthoek) {retourneer rechthoek.lengte * rechthoek.breedte} openbare dubbele berekenCircleArea (Cirkelcirkel) {return (22/7) * cirkel.radius * cirkel.radius}}

Merk echter op dat er gebreken waren in de manier waarop we onze oplossing hierboven hebben ontworpen.

Laten we zeggen dat we een nieuwe vijfhoek hebben. In dat geval zullen we uiteindelijk de klasse AreaCalculator wijzigen. Naarmate het type vormen groeit, wordt dit rommeliger omdat AreaCalculator blijft veranderen en alle consumenten van deze klasse zullen hun bibliotheken die AreaCalculator bevatten moeten blijven bijwerken. Als gevolg hiervan zal de klasse AreaCalculator niet met zekerheid worden gebaseerd (gefinaliseerd), omdat elke keer dat er een nieuwe vorm komt, deze zal worden gewijzigd. Dit ontwerp is dus niet gesloten voor wijziging.

AreaCalculator zal hun berekeningslogica in nieuwere methoden moeten blijven toevoegen. We zijn niet echt de reikwijdte van vormen aan het uitbreiden, maar we doen gewoon een stukje maaltijd (stukje bij beetje) oplossing voor elke vorm die wordt toegevoegd.

Wijziging van bovenstaand ontwerp om te voldoen aan het open / gesloten principe:

Laten we nu een eleganter ontwerp bekijken dat de gebreken in het bovenstaande ontwerp oplost door vast te houden aan het Open / Gesloten-principe. We maken het ontwerp allereerst uitbreidbaar. Hiervoor moeten we eerst een basistype Shape definiëren en een Circle & Rectangle Shape-interface implementeren.

openbare interface Vorm {openbare dubbele berekenArea ()} openbare klasse Rechthoek implementeert Vorm {dubbele lengte dubbele breedte openbare dubbele berekenArea () {retourlengte * breedte}} openbare klasse Cirkel implementeert Vorm {openbare dubbele straal openbaar dubbele berekenArea () {retour (22 / 7) * straal * straal}}

Er is een basisinterface Shape. Alle vormen implementeren nu de basisinterface Shape. Shape-interface heeft een abstracte methode berekenArea (). Zowel cirkel als rechthoek bieden hun eigen overschreven implementatie van de methode berekenArea () met behulp van hun eigen attributen.
We hebben een zekere mate van uitbreidbaarheid gebracht, aangezien vormen nu een voorbeeld zijn van Shape-interfaces. Hierdoor kunnen we Shape gebruiken in plaats van individuele klassen
Het laatste punt hierboven genoemde consument van deze vormen. In ons geval is de consument de klasse AreaCalculator, die er nu zo uitziet.

openbare klasse AreaCalculator {openbare dubbele berekenShapeArea (vorm Shape) {return shape.calculateArea ()}}

Deze AreaCalculatorclass verwijdert nu volledig onze ontwerpfouten die hierboven zijn opgemerkt en geeft een schone oplossing die voldoet aan het open-gesloten principe. Laten we verder gaan met andere SOLID-principes in Java

Liskov-substitutieprincipe in Java

Robert C. Martin beschrijft het als afgeleide typen die volledig substitueerbaar moeten zijn voor hun basistypen.

Het Liskov-substitutieprincipe gaat ervan uit dat q (x) een eigenschap is, aantoonbaar over entiteiten van x die tot type T behoren.Nu zou volgens dit principe de q (y) nu aantoonbaar moeten zijn voor objecten y die tot type S behoren, en de S is eigenlijk een subtype van T. Bent u nu in de war en weet u niet wat het Liskov-substitutieprincipe eigenlijk betekent? De definitie ervan is misschien wat ingewikkeld, maar in feite is het vrij eenvoudig. Het enige is dat elke subklasse of afgeleide klasse substitueerbaar moet zijn voor hun bovenliggende of basisklasse.

Je kunt zeggen dat het een uniek objectgeoriënteerd principe is. Het principe kan verder worden vereenvoudigd door een child-type van een bepaald oudertype zonder complicaties te veroorzaken of dingen op te blazen die de mogelijkheid moeten hebben om voor die ouder in te staan. Dit principe is nauw verwant aan het Liskov-substitutieprincipe.

Waarom is dit principe vereist?

Dit voorkomt misbruik van overerving. Het helpt ons ons te conformeren aan de 'is-a' -relatie. We kunnen ook zeggen dat subklassen een contract moeten vervullen dat is gedefinieerd door de basisklasse. In die zin is het gerelateerd aanOntwerp op contractdat werd voor het eerst beschreven door Bertrand Meyer. Het is bijvoorbeeld verleidelijk om te zeggen dat een cirkel een soort ellips is, maar dat cirkels geen twee brandpunten of grote / secundaire assen hebben.

Het LSP wordt in de volksmond uitgelegd aan de hand van het vierkant en rechthoek voorbeeld. als we uitgaan van een ISA-relatie tussen vierkant en rechthoek. Daarom noemen we 'Vierkant is een rechthoek'. De onderstaande code geeft de relatie weer.

public class Rechthoek {private int lengte private int breedte public int getLength () {return length} public void setLength (int length) {this.length = length} public int getBreadth () {return breedte} public void setBreadth (int breedte) { this.breadth = breedte} openbare int getArea () {retourneer this.length * this.breadth}}

Hieronder staat de code voor Square. Merk op dat Vierkant rechthoek verlengt.

wat serialiseerbaar is in java
openbare klasse Vierkant breidt Rechthoek uit {public void setBreadth (int breedte) {super.setBreadth (breedte) super.setLength (breedte)} public void setLength (int lengte) {super.setLength (lengte) super.setBreadth (lengte)}}

In dit geval proberen we een ISA-relatie tot stand te brengen tussen Square en Rectangle, zodat het aanroepen van 'Square is a Rectangle' in de onderstaande code zich onverwacht zou gaan gedragen als een instantie van Square wordt doorgegeven. Een assertion-fout wordt gegenereerd in het geval dat wordt gecontroleerd op 'Area' en gecontroleerd op 'Breadth', hoewel het programma wordt beëindigd als de assertion-fout wordt gegenereerd als gevolg van het mislukken van de Area-controle.

openbare klasse LSPDemo {openbare leegte berekenArea (Rechthoek r) {r.setBreadth (2) r.setLength (3) assert r.getArea () == 6: printError ('gebied', r) assert r.getLength () == 3: printError ('length', r) assert r.getBreadth () == 2: printError ('breadth', r)} private String printError (String errorIdentifer, Rectangle r) {return 'Onverwachte waarde van' + errorIdentifer + ' bijvoorbeeld van '+ r.getClass (). getName ()} public static void main (String [] args) {LSPDemo lsp = new LSPDemo () // Een instantie van Rectangle wordt doorgegeven lsp.calculateArea (new Rectangle ()) // Een instantie van Square wordt doorgegeven lsp.calculateArea (new Square ())}}

De klas demonstreert het Liskov-vervangingsprincipe (LSP) Volgens het principe moeten de functies die verwijzingen naar de basisklassen gebruiken, objecten van afgeleide klassen kunnen gebruiken zonder het te weten.

In het onderstaande voorbeeld zou de functie berekenArea die de verwijzing naar 'Rechthoek' gebruikt, in staat moeten zijn om de objecten van een afgeleide klasse zoals Square te gebruiken en te voldoen aan de vereiste van de Rechthoekdefinitie. Men moet opmerken dat volgens de definitie van Rectangle, het volgende altijd waar moet zijn gezien de onderstaande gegevens:

  1. Lengte moet altijd gelijk zijn aan de lengte die is doorgegeven als invoer voor methode setLength
  2. Breedte moet altijd gelijk zijn aan de breedte die als invoer is doorgegeven aan de methode setBreadth
  3. De oppervlakte moet altijd gelijk zijn aan het product van lengte en breedte

In het geval dat we proberen een ISA-relatie tussen Vierkant en Rechthoek tot stand te brengen, zodat we 'Vierkant is een rechthoek' noemen, zou bovenstaande code zich onverwacht gaan gedragen als een instantie van Vierkant wordt gepasseerd. voor de breedte, hoewel het programma wordt beëindigd als de beweringsfout wordt gegenereerd als gevolg van het mislukken van de gebiedscontrole.

De klasse Square heeft geen methoden nodig zoals setBreadth of setLength. De LSPDemo-klasse zou de details van de afgeleide klassen van Rectangle (zoals Square) moeten kennen om op de juiste manier te coderen om het gooien van fouten te voorkomen. De wijziging van de bestaande code doorbreekt in de eerste plaats het open-gesloten principe.

Interface Segregatie Principe

Robert C. Martin beschrijft het als klanten niet moeten worden gedwongen om onnodige methoden te implementeren die ze niet zullen gebruiken.

VolgensInterface segregatie principeeen klant, ongeacht wat, mag nooit worden gedwongen om een ​​interface te implementeren die hij niet gebruikt, of de klant mag nooit worden verplicht om afhankelijk te zijn van een methode die niet door hem wordt gebruikt. dus in feite de principes van de scheiding van interfaces zoals u de voorkeur geeft aan de interfaces, die klein maar klantspecifiek zijn in plaats van monolithische en grotere interface. Kortom, het zou slecht voor je zijn om de klant te dwingen afhankelijk te zijn van een bepaald ding dat ze niet nodig hebben.

Een enkele logboekinterface voor het schrijven en lezen van logboeken is bijvoorbeeld handig voor een database, maar niet voor een console. Logboeken lezen heeft geen zin voor een consolelogger. Verder gaan met dit SOLID Principles in Java-artikel.

Waarom is dit principe vereist?

Laten we zeggen dat er een Restaurant-interface is die methoden bevat voor het accepteren van bestellingen van online klanten, inbel- of telefonische klanten en klanten die binnenlopen. Het bevat ook methoden voor het afhandelen van online betalingen (voor online klanten) en persoonlijke betalingen (voor binnenlopende klanten en telefonische klanten wanneer hun bestelling thuis wordt afgeleverd).

Laten we nu een Java-interface voor Restaurant maken en deze de naam RestaurantInterface.java geven.

openbare interface RestaurantInterface {public void acceptOnlineOrder () public void takeTelephoneOrder () public void payOnline () public void walkInCustomerOrder () public void payInPerson ()}

Er zijn 5 methoden gedefinieerd in RestaurantInterface die zijn voor het accepteren van een online bestelling, het aannemen van telefonische bestellingen, het accepteren van bestellingen van een inloopklant, het accepteren van online betalingen en het persoonlijk accepteren van betaling.

Laten we beginnen met het implementeren van de RestaurantInterface voor online klanten als OnlineClientImpl.java

public class OnlineClientImpl implementeert RestaurantInterface {public void acceptOnlineOrder () {// logica voor het plaatsen van online bestelling} public void takeTelephoneOrder () {// Niet van toepassing op online bestelling throw new UnsupportedOperationException ()} public void payOnline () {// logica voor betalen online} public void walkInCustomerOrder () {// Niet van toepassing op online bestelling throw new UnsupportedOperationException ()} public void payInPerson () {// Niet van toepassing op online bestelling throw new UnsupportedOperationException ()}}
  • Aangezien de bovenstaande code (OnlineClientImpl.java) voor online bestellingen is, gooit u UnsupportedOperationException.

  • Online, telefonische en walk-in klanten gebruiken de RestaurantInterface-implementatie die specifiek voor hen is.

  • De implementatieklassen voor Telephonic-client en Walk-in-client hebben niet-ondersteunde methoden.

  • Omdat de 5 methoden deel uitmaken van de RestaurantInterface, moeten de implementatieklassen ze alle 5 implementeren.

  • De methoden die elk van de implementatieklassen UnsupportedOperationException genereren. Zoals je duidelijk kunt zien, is het implementeren van alle methoden inefficiënt.

    scrum master rollen en verantwoordelijkheden pdf
  • Elke wijziging in een van de methoden van de RestaurantInterface zal worden doorgegeven aan alle implementatieklassen. Het onderhoud van code begint dan echt omslachtig te worden en de regressie-effecten van wijzigingen zullen blijven toenemen.

  • RestaurantInterface.java breekt het Single Responsibility Principle omdat zowel de logica voor betalingen als die voor het plaatsen van bestellingen in één interface zijn gegroepeerd.

Om de bovengenoemde problemen op te lossen, passen we Interface Segregation Principle toe om het bovenstaande ontwerp te refactoren.

  1. Scheid de betalings- en orderplaatsingsfunctionaliteiten in twee afzonderlijke slanke interfaces, PaymentInterface.java en OrderInterface.java.

  2. Elk van de klanten gebruikt één implementatie van PaymentInterface en OrderInterface. Bijvoorbeeld - OnlineClient.java gebruikt OnlinePaymentImpl en OnlineOrderImpl enzovoort.

  3. Single Responsibility Principle is nu bijgevoegd als betalingsinterface (PaymentInterface.java) en bestelinterface (OrderInterface).

  4. Wijziging van een van de bestel- of betalingsinterfaces heeft geen invloed op de andere. Ze zijn nu onafhankelijk. Het is niet nodig om een ​​dummy-implementatie uit te voeren of een UnsupportedOperationException te gooien, aangezien elke interface alleen methoden heeft die het altijd zal gebruiken.

Na het toepassen van ISP

Afhankelijkheidsinversieprincipe

Robert C. Martin beschrijft het omdat het afhangt van abstracties en niet van concreties. Volgens hem mag de high-level module nooit vertrouwen op een low-level module. bijvoorbeeld

U gaat naar een plaatselijke winkel om iets te kopen en u besluit ervoor te betalen met uw bankpas. Dus als u uw kaart aan de receptionist geeft om de betaling uit te voeren, doet de receptionist niet de moeite om te controleren wat voor soort kaart u heeft gegeven.

Zelfs als u een Visa-kaart heeft gegeven, zal hij geen Visa-automaat uitsturen om uw kaart door te halen. Het type creditcard of betaalpas dat u heeft om te betalen, maakt niet eens uit, ze zullen er gewoon overheen vegen. In dit voorbeeld kunt u dus zien dat zowel u als de receptionist afhankelijk zijn van de abstractie van de creditcard en dat u zich geen zorgen maakt over de specifieke kenmerken van de kaart. Dit is wat een afhankelijkheidsinversieprincipe is.

Waarom is dit principe vereist?

Het stelt een programmeur in staat om hard gecodeerde afhankelijkheden te verwijderen, zodat de applicatie losjes gekoppeld en uitbreidbaar wordt.

openbare klas Student {privé adres adres openbare student () {adres = nieuw adres ()}}

In het bovenstaande voorbeeld vereist de Student-klasse een Address-object en is deze verantwoordelijk voor het initialiseren en gebruiken van het Address-object. Als de adresklasse in de toekomst wordt gewijzigd, moeten we ook wijzigingen aanbrengen in de studentenklasse. Dit zorgt voor een nauwe koppeling tussen Student- en Adresobjecten. We kunnen dit probleem oplossen met behulp van het ontwerppatroon voor afhankelijkheidsinversie. Dat wil zeggen dat het adresobject onafhankelijk wordt geïmplementeerd en aan de student wordt verstrekt wanneer de student wordt geïnstantieerd door gebruik te maken van op constructor gebaseerde of op setter gebaseerde afhankelijkheidsinversie.

Hiermee komen we een einde aan deze SOLID Principles in Java.

Bekijk de door Edureka, een vertrouwd online leerbedrijf met een netwerk van meer dan 250.000 tevreden leerlingen verspreid over de hele wereld. De training- en certificeringcursus Java J2EE en SOA van Edureka is bedoeld voor studenten en professionals die Java-ontwikkelaar willen worden. De cursus is bedoeld om u een voorsprong te geven in het programmeren van Java en u te trainen in zowel kern- als geavanceerde Java-concepten, samen met verschillende Java-frameworks zoals Hibernate & Spring.

Heeft u een vraag voor ons? Vermeld het in het commentaargedeelte van deze 'SOLID Principles in Java' -blog en we nemen zo snel mogelijk contact met u op.