Lernen Sie langsam mit Python "Prinzip der Abhängigkeitsumkehr"

Es gibt viele Texte zum "Prinzip der Abhängigkeitsumkehr",

Für diejenigen, die sagen, werde ich meinen eigenen Artikel schreiben: "Es war leicht zu verstehen, wenn Sie dies erklärt haben."

github

https://github.com/koboriakira/koboridip

Was zu machen

Ein Tool zum Überprüfen der vier Regeln. Das Ergebnis wird wie folgt an die CLI ausgegeben.

$ python -m koboridip.main 8 2
8 + 2 = 10
8 - 2 = 6
8 * 2 = 16
8 / 2 = 4.0

Machen Sie, wie Ihnen gesagt wurde. Version 1

Projektstruktur

.
└── koboridip
    ├── calculator.py
    └── main.py

Quelle

calculator.py


class Calculator():
    def __init__(self, a: int, b: int) -> None:
        self.a = a
        self.b = b

    def print(self) -> None:
        print(f'add: {self.a + self.b}')
        print(f'subtract: {self.a - self.b}')
        print(f'multiply: {self.a * self.b}')
        print(f'divide: {self.a / self.b}')

main.py


import sys
from koboridip.calculator import Calculator

if __name__ == '__main__':
    #Holen Sie sich Argument
    a = sys.argv[1]
    b = sys.argv[2]

    #Erstellen Sie eine Rechnerinstanz
    calculator = Calculator(int(a), int(b))

    #Geben Sie die Ergebnisse jeder der vier Regeln aus
    calculator.print()

Erläuterung

Es ist ein einfaches Programm. Nachdem Sie der Klasse "Calculator" eine Zahl gegeben haben, lassen Sie die Instanz die "Berechnung (Verarbeitung)" und "Ausgabe" durchführen.

Plötzliche Spezifikationsänderung. Version 2

In Bezug auf dieses Produkt gab es die Anfrage "Ich möchte das Ausgabeergebnis im JSON-Format speichern". Daher werden wir die Quelle ändern.

Die Ausgabe wird in die Calculator-Klasse geschrieben. Korrigieren wir sie also.

Quelle

calculator.py


import json
from typing import Dict


class Calculator():
    def __init__(self, a: int, b: int) -> None:
        self.a = a
        self.b = b

    def print(self) -> None:
        # print(f'add: {self.a + self.b}')
        # print(f'subtract: {self.a - self.b}')
        # print(f'multiply: {self.a * self.b}')
        # print(f'divide: {self.a / self.b}')
        result: Dict[str, int] = {
            "add": self.a + self.b,
            "subtract": self.a - self.b,
            "multiply": self.a * self.b,
            "divide": self.a / self.b
        }
        with open('result.json', mode='w') as f:
            f.write(json.dumps(result))

Ausführungsergebnis

Nur für den Fall, dass Sie es ausführen, wird der folgende Text in result.json (formatiert) ausgegeben.

result.json


{
   "add":10,
   "subtract":6,
   "multiply":16,
   "divide":4.0
}

Refactoring

Die Klasse "Calculator" führt eine ** Verarbeitung ** der vier Betriebsregeln und eine ** Ausgabe ** des Ergebnisses durch.

Ich entschied, dass es besser wäre, diese zu trennen, und entschied mich daher, eine "Drucker" -Klasse zu erstellen, die für die Ausgabeverarbeitung zuständig ist.

.
└── koboridip
    ├── calculator.py
    ├── main.py
    └── printer.py

printer.py


import json
from typing import Dict


class Printer():
    def print(self, add, subtract, multiply, divide) -> None:
        result: Dict[str, int] = {
            "add": add,
            "subtract": subtract,
            "multiply": multiply,
            "divide": divide
        }
        with open('result.json', mode='w') as f:
            f.write(json.dumps(result))

calculator.py


from koboridip.printer import Printer


class Calculator():
    def __init__(self, a: int, b: int) -> None:
        self.a = a
        self.b = b

    def print(self) -> None:
        add = self.a + self.b
        subtract = self.a - self.b
        multiply = self.a * self.b
        divide = self.a / self.b

        printer = Printer()
        printer.print(add, subtract, multiply, divide)

Unangenehme Vorahnung. Version 3

Bei der nachfolgenden Richtlinienänderung wurde entschieden, dass "ich sowohl die Ergebnisausgabe an die CLI als auch den Speicher im JSON-Format verwenden möchte". Schalten Sie den Modus wie folgt um.

$ python -m koboridip.main 8 2 simple
>(Ausgabe an CLI)

$ python -m koboridip.main 8 2 json
> (result.Ausgabe json)

Daher wurde die Klasse "Drucker" in zwei Typen unterteilt, damit sie umgeschaltet werden können.

Projektstruktur

.
└── koboridip
    ├── calculator.py
    ├── json_printer.py ->Ausgabe im JSON-Format
    ├── main.py
    ├── simple_printer.py ->Ausgabe an CLI

Quelle

simple_printer.py


class SimplePrinter():
    def print(self, add, subtract, multiply, divide) -> None:
        print(f'add: {add}')
        print(f'subtract: {subtract}')
        print(f'multiply: {multiply}')
        print(f'divide: {divide}')

json_printer.py


import json
from typing import Dict


class JsonPrinter():
    def print(self, add, subtract, multiply, divide) -> None:
        result: Dict[str, int] = {
            "add": add,
            "subtract": subtract,
            "multiply": multiply,
            "divide": divide
        }
        with open('result.json', mode='w') as f:
            f.write(json.dumps(result))

Es liegt an calculator.py zu entscheiden, welche ausgegeben werden soll.

Die angegebene Zeichenfolge "simple" oder "json" kann durch Speichern in der Variablen "mode" umgeschaltet werden.

calculator.py


from koboridip.simple_printer import SimplePrinter
from koboridip.json_printer import JsonPrinter


class Calculator():
    def __init__(self, a: int, b: int, mode: str) -> None:
        self.a = a
        self.b = b
        self.mode = mode

    def print(self) -> None:
        add = self.a + self.b
        subtract = self.a - self.b
        multiply = self.a * self.b
        divide = self.a / self.b

        #Schalten Sie die Ausgabemethode um
        if self.mode == 'json':
            json_printer = JsonPrinter()
            json_printer.print(add, subtract, multiply, divide)
        elif self.mode == 'simple':
            simple_printer = SimplePrinter()
            simple_printer.print(add, subtract, multiply, divide)

Lassen Sie uns auch main.py ändern, damit wir die Argumente erhalten können.

main.py


import sys
from koboridip.calculator import Calculator

if __name__ == '__main__':
    #Holen Sie sich Argument
    a = sys.argv[1]
    b = sys.argv[2]
    #Ausgabemethode
    mode = sys.argv[3]

    #Erstellen Sie eine Rechnerinstanz
    calculator = Calculator(int(a), int(b), mode)

    #Geben Sie die Ergebnisse jeder der vier Regeln aus
    calculator.print()

[Wichtig] Produktprobleme

Was ist jetzt los

Derzeit importiert die Klasse "Rechner" der vier Regeln ** "Verarbeitung" ** die Klasse "Drucker" des Ergebnisses ** "Ausgabe".

Dieser Staat,

** " Rechner (Verarbeitung) hängt von Drucker (Ausgabe) ab" **

Es wird ausgedrückt als.

Was ist "abhängig"

Abhängigkeit (Import) bedeutet, dass ** eine Änderung der Abhängigkeit eine Änderung der Abhängigkeitsquelle erfordert **.

Wie wir in Version 3 gesehen haben, hat dieses Projekt auch die Klasse "Calculator" geändert, um die Ausgabemethode hinzuzufügen (zu ändern).

** Ich wollte nur die Ausgabe ändern, aber ich musste auch die Verarbeitung ändern. ** **.

Nehmen wir an, dass es in Zukunft weitere Anforderungen wie "Ich möchte im CSV-Format ausgeben" und "Ich möchte das Ergebnis an einen Server senden" gibt.

Jedes Mal muss nicht nur die Klasse "Drucker", sondern auch die Klasse "Rechner" einige Änderungen vornehmen.

Auch hier ist es notwendig, die Verarbeitungsfunktion zu ändern, obwohl es keine Änderungen in den Spezifikationen der "Verarbeitung (vier Betriebsregeln)" gibt.

Es ist wichtig, sich hier "unwohl" zu fühlen.

Erstellen Sie entsprechende Abhängigkeiten

An dieser Stelle können Sie zu dem Schluss kommen: "Sollten wir dann die Abhängigkeit reduzieren, damit sie nicht von der Änderung der Abhängigkeit betroffen ist?"

Es besteht jedoch immer eine Abhängigkeit, da Importe in Python-Projekten nicht verwendet werden können.

Mit anderen Worten, der Einfallsreichtum, den wir brauchen, besteht darin, "angemessene Abhängigkeiten" zu schaffen.

Es bedeutet ** "abhängig von dem mit den wenigsten Änderungen" **.

Ergänzung (OK zum Überspringen)

Ein weiteres Problem bei diesem Projekt ist, dass Calculator die ** Details ** der Ausgabe kennt.

Der Zweck von "Calculator" ist es, "das Ergebnis ausgeben" zu können, und ob es sich um ein CLI- oder ein JSON-Format handelt, ich möchte vermeiden, mir darüber Sorgen zu machen.

Abhängigkeit und Umkehrung. Version 4

Lassen Sie uns nun die Abhängigkeiten umkehren.

Platzieren Sie die Klasse "Drucker", bei der es sich um eine abstrakte Klasse handelt, in "calculator.py" und importieren Sie die erforderlichen "ABCMeta" und "abstractmethod".

calculator.py


from abc import ABCMeta, abstractmethod
from koboridip.simple_printer import SimplePrinter
from koboridip.json_printer import JsonPrinter


class Printer(metaclass=ABCMeta):
    @abstractmethod
    def print(self, add, subtract, multiply, divide):
        pass


class Calculator():
    def __init__(self, a: int, b: int, mode: str) -> None:
        self.a = a
        self.b = b
        self.mode = mode

    def print(self) -> None:
        add = self.a + self.b
        subtract = self.a - self.b
        multiply = self.a * self.b
        divide = self.a / self.b

        #Schalten Sie die Ausgabemethode um
        if self.mode == 'json':
            json_printer = JsonPrinter()
            json_printer.print(add, subtract, multiply, divide)
        elif self.mode == 'simple':
            simple_printer = SimplePrinter()
            simple_printer.print(add, subtract, multiply, divide)

Ändern Sie dann jeweils "SimplePrinter" und "JsonPrinter", um die "Printer" -Klasse zu erben.

simple_printer.py


from koboridip.calculator import Printer


class SimplePrinter(Printer):
    def print(self, add, subtract, multiply, divide) -> None:
        print(f'add: {add}')
        print(f'subtract: {subtract}')
        print(f'multiply: {multiply}')
        print(f'divide: {divide}')

json_printer.py


import json
from typing import Dict
from koboridip.calculator import Printer


class JsonPrinter(Printer):
    def print(self, add, subtract, multiply, divide) -> None:
        result: Dict[str, int] = {
            "add": add,
            "subtract": subtract,
            "multiply": multiply,
            "divide": divide
        }
        with open('result.json', mode='w') as f:
            f.write(json.dumps(result))

Wichtig hierbei ist, dass die "SimplePrinters" von "calculator.py" abhängen.

** Hier wurden die Abhängigkeiten umgekehrt. ** "Ausgabe" hängt von "Verarbeitung" ab.

Natürlich ist es noch nicht perfekt, also entfernen wir den Status, in dem die Klasse "Calculator" von der Klasse "SimplePrinter" abhängt.

Lassen Sie den Konstruktor daher entscheiden, welcher Drucker verwendet werden soll.

calculator.py


from abc import ABCMeta, abstractmethod


class Printer(metaclass=ABCMeta):
    @abstractmethod
    def print(self, add, subtract, multiply, divide):
        pass


class Calculator():
    def __init__(self, a: int, b: int, printer:Printer) -> None:
        self.a = a
        self.b = b
        self.printer = printer

    def print(self) -> None:
        add = self.a + self.b
        subtract = self.a - self.b
        multiply = self.a * self.b
        divide = self.a / self.b
        self.printer.print(add, subtract, multiply, divide)

Geben Sie dann main.py an, welcher Drucker verwendet werden soll.

main.py


import sys
from koboridip.calculator import Calculator, Printer
from koboridip.json_printer import JsonPrinter
from koboridip.simple_printer import SimplePrinter

if __name__ == '__main__':
    #Holen Sie sich Argument
    a = sys.argv[1]
    b = sys.argv[2]
    #Ausgabemethode
    mode = sys.argv[3]

    #Geben Sie die Druckerklasse an ("simple"Da es schwierig zu beurteilen ist, habe ich es anders gemacht)
    printer: Printer = JsonPrinter() if mode == 'json' else SimplePrinter()

    #Erstellen Sie eine Rechnerinstanz
    calculator = Calculator(int(a), int(b), printer)

    #Geben Sie die Ergebnisse jeder der vier Regeln aus
    calculator.print()

Es gibt keinen Import in berechne.py, stattdessen gibt es einen Import in simple_printer.pys.

Damit ist die Abhängigkeitsumkehr abgeschlossen.

Epilog. Version 5

Wie erwartet wurde auch die Ausgabe im CSV-Format angefordert.

Bisher war die Klasse "Calculator" auch jedes Mal betroffen, wenn die Ausgabemethode geändert wurde. Mal sehen, was passiert.

.
└── koboridip
    ├── calculator.py
    ├── csv_printer.py
    ├── json_printer.py
    ├── main.py
    └── simple_printer.py

csv_printer.py


import csv
from typing import List
from koboridip.calculator import Printer


class CsvPrinter(Printer):
    def print(self, add, subtract, multiply, divide) -> None:
        result: List[List] = []
        result.append(["add", add])
        result.append(["subtract", subtract])
        result.append(["multiply", multiply])
        result.append(["divide", divide])

        with open('result.csv', 'w') as f:
            writer = csv.writer(f)
            writer.writerows(result)

main.py


import sys
from koboridip.calculator import Calculator, Printer
from koboridip.json_printer import JsonPrinter
from koboridip.simple_printer import SimplePrinter
from koboridip.csv_printer import CsvPrinter


if __name__ == '__main__':
    #Holen Sie sich Argument
    a = sys.argv[1]
    b = sys.argv[2]
    #Ausgabemethode
    mode = sys.argv[3]

    #Geben Sie die Druckerklasse an
    printer: Printer = JsonPrinter() if mode == 'json' else CsvPrinter(
    ) if mode == 'csv' else SimplePrinter()

    #Erstellen Sie eine Rechnerinstanz
    calculator = Calculator(int(a), int(b), printer)

    #Geben Sie die Ergebnisse jeder der vier Regeln aus
    calculator.print()

Auf diese Weise wurde auch die CSV-Datei ausgegeben.

Sie können sich vorstellen, dass Sie die Ausgabemethode danach leicht ändern können.

schließlich

Ich hoffe, es hilft Ihnen, das Prinzip der Abhängigkeitsumkehr zu verstehen. Ein letzter Punktzusatz.

War die vorherige Version falsch?

Diejenigen, die sagen "Ich verstehe das Prinzip der Umkehrung von Abhängigkeiten!", Werden sofort versuchen, das Design und die Implementierung zu korrigieren und sagen "Dies ist ein Problem!", Wenn sie ein Projekt sehen, das keine angemessene Abhängigkeit zu haben scheint. Ich bin).

Nach dem Refactoring von Version 2 hängt die Klasse "Calculator" beispielsweise von der Klasse "Printer" ab. Daher möchten Sie möglicherweise an dieser Stelle das Prinzip der Abhängigkeitsumkehr anwenden.

Das ist aber verfrüht. Wenn Sie wissen, dass "die Ausgabemethode zu diesem Zeitpunkt so stark erhöht werden kann, wie Sie möchten", sollten Sie sie natürlich anwenden. Wenn sich "die Ausgabemethode wahrscheinlich nicht ändert", wenden Sie ** << hold an 》 ** Ich denke, es kann eine gute Entscheidung sein.

Persönlich möchte ich die Abhängigkeiten von "Details" wie der Ausgabe so schnell wie möglich klären, aber ich denke, es ist wichtig zu denken, dass "Sie es jederzeit ändern können".

Abhängigkeitsinjektion (Injektion)

Wenn ich Zeit habe, möchte ich über "DI = Dependency Injection" schreiben.

Wenn Sie Anregungen oder Fragen haben, können Sie diese gerne kommentieren.

Recommended Posts

Lernen Sie langsam mit Python "Prinzip der Abhängigkeitsumkehr"
1. Mit Python 1-3 gelernte Statistiken. Berechnung verschiedener Statistiken (Statistiken)
1. Mit Python 1-2 gelernte Statistiken. Berechnung verschiedener Statistiken (Numpy)
1. Mit Python gelernte Statistiken 2. Wahrscheinlichkeitsverteilung [Gründliches Verständnis von scipy.stats]
[Python] Mit Pokemon erlernte objektorientierte Programmierung
Erste Schritte mit Python Grundlagen von Python
Perceptron-Lernexperiment mit Python
Python-Datenstruktur mit Chemoinfomatik gelernt
Lebensspiel mit Python! (Conways Spiel des Lebens)
Effiziente Netzaufnahme mit Python
1. Mit Python 1-1 gelernte Statistiken. Grundlegende Statistiken (Pandas)
Implementierung der Dyxtra-Methode durch Python
Koexistenz von Python2 und 3 mit CircleCI (1.0)
Grundlegendes Studium von OpenCV mit Python
[Python] Mit RxPY (3.0.1) gelernte reaktive Erweiterungen [Rx]
Grundlagen der binärisierten Bildverarbeitung durch Python
[Beispiel für eine Python-Verbesserung] Python mit Codecademy lernen
Führen Sie das Python-Skript mit TS-220 cron aus
Bedingte Verzweigung von Python mit Chemoinfomatik gelernt
Überprüfen Sie die Existenz der Datei mit Python
Verstopft mit Python-Update der GCP-Konsole ①
Einfache Einführung der Spracherkennung mit Python
Lassen Sie uns mit Python langsam sprechen
Quellcode für die Trennung von Tonquellen (Übungsreihe zum maschinellen Lernen), der mit Python gelernt wurde
UnicodeEncodeError hat Probleme mit der Standardausgabe von Python3
Zeichnen mit Matrix-Reinventor von Python Image Processing-
Empfehlung von Altair! Datenvisualisierung mit Python
Deep Learning von Grund auf neu Die Theorie und Implementierung des mit Python erlernten Deep Learning Kapitel 3
[AtCoder] Lösen Sie ein Problem von ABC101 ~ 169 mit Python
Ich habe Hunderte Millionen SQLite mit Python ausprobiert
Bereiten Sie die Ausführungsumgebung von Python3 mit Docker vor
Automatischer Betrieb von Chrome mit Python + Selen + Pandas
Leistungsvergleich des Gesichtsdetektors mit Python + OpenCV
[Python] Grenzachse des 3D-Graphen mit Matplotlib
2016 Todai Mathematik mit Python gelöst
[Hinweis] Exportieren Sie das HTML der Site mit Python.
Verstopft mit Python-Update der GCP-Konsole ② (Lösung)
Berechnen Sie die Gesamtzahl der Kombinationen mit Python
Verwenden Sie mit pyenv mehrere Versionen der Python-Umgebung
Überprüfen Sie das Datum der Flaggenpflicht mit Python
Löse A ~ D des Yuki-Codierers 247 mit Python
[Python] Werden Sie die Datierung mit regulären Ausdrücken los
So legen Sie Attribute mit Mock of Python fest
Poetry-Virtualenv-Umgebungskonstruktion mit Centos-Sclo-Rh-Python ~ Hinweise
Automatisieren einfacher Aufgaben mit Python Inhaltsverzeichnis