Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Industrial Internet of Things >> Cloud computing

SOLID:Objectgeoriënteerde ontwerpprincipes

SOLID is een ezelsbruggetje voor klasseontwerp in objectgeoriënteerd programmeren. De principes zorgen voor praktijken die helpen bij het ontwikkelen van goede programmeergewoonten en onderhoudbare code.

Door rekening te houden met code-onderhoud en uitbreidbaarheid op de lange termijn, verrijken SOLID-principes de Agile code-ontwikkelomgeving. Accounting voor en optimalisatie van code-afhankelijkheden helpt bij het creëren van een meer rechtlijnige en georganiseerde levenscyclus van softwareontwikkeling.

Wat zijn SOLID-principes?

SOLID vertegenwoordigt een reeks principes voor het ontwerpen van klassen. Robert C. Martin (oom Bob) introduceerde de meeste ontwerpprincipes en bedacht het acroniem.
SOLID staat voor:

  • Principe van één verantwoordelijkheid
  • Open-Gesloten Principe
  • Liskov-substitutieprincipe
  • Interface Segregatie Principe
  • Principe van omkering van afhankelijkheid

SOLID-principes vertegenwoordigen een verzameling best practices voor softwareontwerp. Elk idee vertegenwoordigt een ontwerpkader, wat leidt tot betere programmeergewoonten, verbeterd codeontwerp en minder fouten.

SOLID:5 principes uitgelegd

De beste manier om te begrijpen hoe SOLID-principes werken, is aan de hand van voorbeelden. Alle principes zijn complementair en van toepassing op individuele gebruiksgevallen. De volgorde waarin de principes worden toegepast is onbelangrijk, en niet alle principes zijn in elke situatie toepasbaar.

Elke sectie hieronder geeft een overzicht van elk SOLID-principe in de programmeertaal Python. De algemene ideeën van SOLID zijn van toepassing op elke objectgeoriënteerde taal, zoals PHP, Java of C#. Door de regels te generaliseren, zijn ze toepasbaar op moderne programmeerbenaderingen, zoals microservices.

Single-Responsibility Principle (SRP)

Het principe van één verantwoordelijkheid (SRP) stelt:"Er mag nooit meer dan één reden zijn voor een klas om te veranderen."

Bij het wijzigen van een klasse moeten we slechts één enkele functionaliteit wijzigen, wat inhoudt dat elk object slechts één taak zou moeten hebben.

Bekijk als voorbeeld de volgende klasse:

# A class with multiple responsibilities
class Animal:
    # Property constructor
    def __init__(self, name):
        self.name = name

    # Property representation
    def __repr__(self):
        return f'Animal(name="{self.name}")'

    # Database management
    def save(animal):
        print(f'Saved {animal} to the database')

if __name__ == '__main__':
    # Property instantiation
    a = Animal('Cat')
    # Saving property to a database
    Animal.save(a)

Wanneer u wijzigingen aanbrengt in de save() methode, vindt de wijziging plaats in de Animal klas. Bij het maken van eigenschapswijzigingen vinden de wijzigingen ook plaats in de Animal klasse.

De klas heeft twee redenen om te veranderen en schendt het principe van één verantwoordelijkheid. Hoewel de code werkt zoals verwacht, maakt het niet respecteren van het ontwerpprincipe de code op de lange termijn moeilijker te beheren.

Merk op dat de voorbeeldklasse twee verschillende taken heeft om het principe van één verantwoordelijkheid te implementeren:

  • Vastgoedbeheer (de constructor en get_name() ).
  • Databasebeheer (save() ).

Daarom is de beste manier om het probleem aan te pakken, de databasebeheermethode op te splitsen in een nieuwe klasse. Bijvoorbeeld:

# A class responsible for property management
class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Animal(name="{self.name}")'

# A class responsible for database management
class AnimalDB:
    def save(self, animal):
        print(f'Saved {animal} to the database')

if __name__ == '__main__':
    # Property instantiation
    a = Animal('Cat')
    # Database instantiation
    db = AnimalDB()
    # Saving property to a database
    db.save(a)

De AnimalDB . wijzigen klasse heeft geen invloed op de Animal klasse met het principe van één verantwoordelijkheid. De code is intuïtief en gemakkelijk aan te passen.

Open-Gesloten Principe (OCP)

Het open-gesloten principe (OCP) stelt:"Software-entiteiten moeten open staan ​​voor uitbreiding, maar gesloten voor wijziging."

Voor het toevoegen van functionaliteiten en use-cases aan het systeem hoeven bestaande entiteiten niet te worden gewijzigd. De formulering lijkt tegenstrijdig - het toevoegen van nieuwe functionaliteiten vereist het wijzigen van de bestaande code.

Het idee is eenvoudig te begrijpen aan de hand van het volgende voorbeeld:

class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Animal(name="{self.name}")'

class Storage:
    def save_to_db(self, animal):
        print(f'Saved {animal} to the database')

De Storage class slaat de informatie op van een Animal instantie naar een database. Voor het toevoegen van nieuwe functionaliteiten, zoals opslaan in een CSV-bestand, moet code worden toegevoegd aan de Storage klas:

class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'Animal(name="{self.name}")'

class Storage:
    def save_to_db(self, animal):
        print(f'Saved {animal} to the database')
    def save_to_csv(self,animal):
        printf(f’Saved {animal} to the CSV file’)

De save_to_csv methode wijzigt een bestaande Storage class om de functionaliteit toe te voegen. Deze benadering schendt het open-gesloten-principe door een bestaand element te wijzigen wanneer een nieuwe functionaliteit verschijnt.

De code vereist het verwijderen van de algemene Storage class en het creëren van individuele klassen voor opslag in specifieke bestandsformaten.

De volgende code demonstreert de toepassing van het open-gesloten principe:

class DB():
    def save(self, animal):
        print(f'Saved {animal} to the database')

class CSV():
    def save(self, animal):
        print(f'Saved {animal} to a CSV file')

De code voldoet aan het open-gesloten principe. De volledige code ziet er nu als volgt uit:

class Animal:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return f'"{self.name}"'

class DB():
    def save(self, animal):
        print(f'Saved {animal} to the database')

class CSV():
    def save(self, animal):
        print(f'Saved {animal} to a CSV file')

if __name__ == '__main__':
    a = Animal('Cat')
    db = DB()
    csv = CSV()
    db.save(a)
    csv.save(a) 

Uitbreiden met extra functionaliteiten (zoals opslaan naar een XML-bestand) wijzigt de bestaande klassen niet.

Liskov-substitutieprincipe (LSP)

Het Liskov-substitutieprincipe (LSP) stelt:"Functies die pointers of verwijzingen naar basisklassen gebruiken, moeten objecten van afgeleide klassen kunnen gebruiken zonder het te weten."

Het principe stelt dat een bovenliggende klasse een onderliggende klasse kan vervangen zonder merkbare veranderingen in functionaliteit.

Bekijk het onderstaande voorbeeld voor het schrijven van bestanden:

# Parent class
class FileHandling():
    def write_db(self):
        return f'Handling DB'
    def write_csv(self):
        return f'Handling CSV'

# Child classes
class WriteDB(FileHandling):
    def write_db(self):
        return f'Writing to a DB'
    def write_csv(self):
        return f"Error: Can't write to CSV, wrong file type."

class WriteCSV(FileHandling):
    def write_csv(self):
        return f'Writing to a CSV file'
    def write_db(self):
        return f"Error: Can't write to DB, wrong file type."

if __name__ == "__main__":
   # Parent class instantiation and function calls
    db = FileHandling()
    csv = FileHandling()
    print(db.write_db())
    print(db.write_csv())

    # Children classes instantiations and function calls
    db = WriteDB()
    csv = WriteCSV()
    print(db.write_db())
    print(db.write_csv())
    print(csv.write_db())
    print(csv.write_csv())

De bovenliggende klasse (FileHandling ) bestaat uit twee methoden voor het schrijven naar een database en een CSV-bestand. De klasse behandelt beide functies en retourneert een bericht.

De twee kinderklassen (WriteDB en WriteCSV ) eigenschappen erven van de bovenliggende klasse (FileHandling ). Beide kinderen geven echter een fout wanneer ze proberen de ongepaste schrijffunctie te gebruiken, wat in strijd is met het Liskov-substitutieprincipe omdat de overschrijvende functies niet overeenkomen met de bovenliggende functies.

De volgende code lost de problemen op:

# Parent class
class FileHandling():
    def write(self):
        return f'Handling file'

# Child classes
class WriteDB(FileHandling):
    def write(self):
        return f'Writing to a DB'

class WriteCSV(FileHandling):
    def write(self):
        return f'Writing to a CSV file'

if __name__ == "__main__":
   # Parent class instantiation and function calls
    db = FileHandling()
    csv = FileHandling()
    print(db.write())
    print(csv.write())

    # Children classes instantiations and function calls
    db = WriteDB()
    csv = WriteCSV()
    print(db.write())
    print(csv.write())

De onderliggende klassen komen correct overeen met de ouderfunctie.

Interface Segregation Principle (ISP)

Het interface-segregatieprincipe (ISP) stelt:"Veel klantspecifieke interfaces zijn beter dan één interface voor algemeen gebruik."

Met andere woorden, uitgebreidere interactie-interfaces worden opgesplitst in kleinere. Het principe zorgt ervoor dat klassen alleen de methoden gebruiken die ze nodig hebben, waardoor de algehele redundantie wordt verminderd.

Het volgende voorbeeld toont een interface voor algemeen gebruik:

class Animal():
    def walk(self):
        pass
    def swim(self):
        pass
    
class Cat(Animal):
    def walk(self):
        print("Struts")
    def fly(self):
        raise Exception("Cats don't swim")
    
class Duck(Animal):
    def walk(self):
        print("Waddles")
    def swim(self):
        print("Floats")

De onderliggende klassen erven van de bovenliggende Animal klasse, die walk . bevat en fly methoden. Hoewel beide functies voor bepaalde dieren acceptabel zijn, hebben sommige dieren overtollige functionaliteiten.

Om de situatie aan te pakken, splitst u de interface in kleinere secties. Bijvoorbeeld:

class Walk():
    def walk(self):
        pass

class Swim(Walk):
    def swim(self):
        pass

class Cat(Walk):
    def walk(self):
        print("Struts")
    
class Duck(Swim):
    def walk(self):
        print("Waddles")
    def swim(self):
        print("Floats")

De Fly klasse erft van de Walk , waardoor extra functionaliteit wordt geboden aan geschikte kinderklassen. Het voorbeeld voldoet aan het principe van scheiding van interfaces.
Om een ​​ander dier, zoals een vis, toe te voegen, moet de interface verder worden verstoven, omdat vissen niet kunnen lopen.

Dependency Inversion Principle (DIP)

Het principe van de afhankelijkheidsinversie stelt:“Afhankelijk van abstracties, niet van concreties.”

Het principe is bedoeld om verbindingen tussen klassen te verminderen door een abstractielaag toe te voegen. Het verplaatsen van afhankelijkheden naar abstracties maakt de code robuust.

Het volgende voorbeeld demonstreert klassenafhankelijkheid zonder een abstractielaag:

class LatinConverter:
    def latin(self, name):
        print(f'{name} = "Felis catus"')
        return "Felis catus"

class Converter:
    def start(self):
        converter = LatinConverter()
        converter.latin('Cat')

if __name__ ==  '__main__':
    converter = Converter()
    converter.start()

Het voorbeeld heeft twee klassen:

  • LatinConverter gebruikt een denkbeeldige API om de Latijnse naam voor een dier op te halen (hardcoded “Felis catus ” voor de eenvoud).
  • Converter is een module op hoog niveau die een instantie van LatinConverter . gebruikt en zijn functie om de opgegeven naam te converteren. De Converter hangt sterk af van de LatinConverter klasse, die afhankelijk is van de API. Deze benadering schendt het principe.

Het principe van afhankelijkheidsinversie vereist het toevoegen van een abstractie-interfacelaag tussen de twee klassen.

Een voorbeeldoplossing ziet er als volgt uit:

from abc import ABC

class NameConverter(ABC):
    def convert(self,name):
        pass

class LatinConverter(NameConverter):
    def convert(self, name):
        print('Converting using Latin API')
        print(f'{name} = "Felis catus"')
        return "Felis catus"

class Converter:
    def __init__(self, converter: NameConverter):
        self.converter = converter
    def start(self):
        self.converter.convert('Cat')

if __name__ ==  '__main__':
    latin = LatinConverter()
    converter = Converter(latin)
    converter.start()

De Converter klasse is nu afhankelijk van de NameConverter interface in plaats van op de LatinConverter direct. Toekomstige updates maken het mogelijk om naamconversies te definiëren met een andere taal en API via de NameConverter interface.

Waarom is er behoefte aan solide principes?

SOLID-principes helpen bij het bestrijden van problemen met ontwerppatronen. Het algemene doel van SOLID-principes is om code-afhankelijkheden te verminderen, en het toevoegen van een nieuwe functie of het wijzigen van een deel van de code verbreekt niet de hele build.

Als gevolg van het toepassen van SOLID-principes op objectgeoriënteerd ontwerp, wordt de code gemakkelijker te begrijpen, beheren, onderhouden en wijzigen. Omdat de regels beter geschikt zijn voor grote projecten, verhoogt het toepassen van de SOLID-principes de algehele snelheid en efficiëntie van de ontwikkelingslevenscyclus.

Zijn SOLID-principes nog steeds relevant?

Hoewel SOLID-principes meer dan 20 jaar oud zijn, bieden ze nog steeds een goede basis voor het ontwerpen van software-architectuur. SOLID biedt degelijke ontwerpprincipes die van toepassing zijn op moderne programma's en omgevingen, niet alleen op objectgeoriënteerd programmeren.

SOLID-principes zijn van toepassing in situaties waarin code wordt geschreven en gewijzigd door mensen, georganiseerd in modules en interne of externe elementen bevat.

Conclusie

SOLID-principes helpen bij het bieden van een goed raamwerk en gids voor het ontwerpen van software-architectuur. De voorbeelden in deze handleiding laten zien dat zelfs een dynamisch getypte taal zoals Python baat heeft bij het toepassen van de principes op codeontwerp.

Lees vervolgens over de 9 DevOps-principes waarmee uw team het meeste uit DevOps kan halen.


Cloud computing

  1. C# statisch trefwoord
  2. C# geneste klasse
  3. C++-klassesjablonen
  4. Java anonieme klasse
  5. Java ObjectOutputStream-klasse
  6. Java-generieken
  7. Java-bestandsklasse
  8. 5 ontwerpprincipes voor het toepassen van robuuste interconnects voor data-intensieve toepassingen
  9. C# - Overerving
  10. C# - Polymorfisme
  11. Principes van wrijving en lagerontwerp