[JAVA] Aggregation / Kopplung / zyklische Komplexität

Überblick

Hier sind einige Tipps zum Schreiben von Code, der einfach zu warten / zu testen ist.

Die Sprache ist übrigens in Java geschrieben (bitte lesen Sie andere Sprachen)

Möglicherweise verstehen Sie auch, dass der Verbindungsgrad falsch ist. In diesem Fall weisen Sie bitte darauf hin. (Weil es sich von anderen Dingen unterscheidet, die von Büchern und Websites geschrieben wurden ... (´ ・ ω ・ `))

Beschreibung von jedem

Gerinnungsgrad

Wird auch als Modulstärke bezeichnet. Ein Maß dafür, wie sehr sich ein Code auf die Aufteilung der Verantwortlichkeiten für diese Klasse konzentriert. ** Klassen, die nur für eine Rolle existieren (alle Methoden existieren nur für diese Rolle), sind sehr kohärent. ** ** ** Im Gegenteil, die Klassen, die in jeder Methode keine Gemeinsamkeiten haben und verschiedene Rollen spielen, weisen einen geringen Aggregationsgrad auf.

Was ist los mit geringer Agglomeration?

Klassen mit geringer Agglomeration haben tendenziell viele Mitgliedsvariablen

Wenn der Aggregationsgrad niedrig ist, ist die Anzahl der Mitgliedsvariablen tendenziell groß. Da die Mitgliedsvariable der Zustand der Klasse selbst ist, nimmt bei vielen Mitgliedsvariablen das Muster des Zustands, den die Instanz annehmen kann, zu, die Komplexität nimmt zu und die Wartbarkeit wird erheblich verringert. Es macht auch das Testen schwieriger und mehr Muster.

Beispiel für eine sehr zusammenhängende Klasse: String-Klasse

Diese Klasse existiert nur zum "Behandeln von Zeichenfolgen" und hat keine andere Rolle. String-Klasse

Spezifisches Beispiel

Betrachten Sie eine Klasse namens Price. Die Klasse verfügt nur über Methoden, die Preisdaten verarbeiten.

public class Price {
   private final int value;
   public Price(int value) {
      this.value = value;
   }
   public Price() {
      this(0);
   }

   /**Preis hinzufügen*/
   public Price add(Price other) {
       return new Price(this.value + other.value);
   }

   /**Den Preis ausgeben*/
   public Price multiply(int amount) {
       return new Price(this.value * amount);
   }

   /**Rabatt*/
   public Price discount(double percentage) {
      return new Price(this.value * percentage);
   }

   /**Formatieren Sie den Preis und geben Sie ihn zurück*/
   public String format() {
       return NumberFormat.getCurrencyInstance().format(this.value);
   }

   public void print() {
       System.out.println(this.format());
   }
}

//Benutzerseite
Price price = new Price(1000);
price.print(); // ¥1,000
System.out.println(price.format()); // ¥1,000
System.out.println(price.add(new Price(500)).format());  // ¥1,500
System.out.println(price.discount(0.9).format()); // ¥900
//Ergänzung:Der Preis ist unveränderlich und der anfänglich eingestellte Wert ändert sich nicht
//Wert beim Aufrufen des letzten Rabattes=Die Berechnung erfolgt bei 1000

Erhöhung des Aggregationsgrades

Kopplungsgrad

Einfach ausgedrückt: "** Wie viel der Benutzer und die Benutzerklassen und -methoden wissen (von wie viel sie abhängen) **".

Je höher der Kopplungsgrad ist, desto weniger wartbar ist er und desto schwieriger ist es zu testen.

Art der Bindung

Der Kopplungsgrad steigt in der folgenden Reihenfolge an.

Nachrichten verbinden

Es ruft nur Methoden ohne Argumente auf und hat keinen Rückgabewert. Die ideale Form.

public class Main {
   private final Price price;
   
   public Main(Price price) {
      this.price = price;
   }

   public void output() {
      this.price.print();
   }
}

Price price = new Price(1000);
Main main = new Main(price);
main.output(); // ¥1,Wird als 000 angezeigt

Oben teilen sich die Klasse "Main" und die Klasse "Price" keine Daten, und "Main # output ()" hängt nicht von der Implementierung von "print ()" ab.

Die "Price" -Klasse weiß nichts über die "Main" -Klasse, und die "Main" -Klasse kümmert sich auch nicht darum, was "Price # print ()" im Inneren tut. ** Selbst wenn Sie beispielsweise die Implementierung der Methode "print ()" ändern, um eine Zeichenfolge in eine Datei zu drucken, ist es "Main # output" egal, selbst wenn Sie nichts tun. ** ** **

Solche Verknüpfungen werden auch als "Message Passing" bezeichnet, und Interaktionen zwischen solchen Objekten werden als "Messaging" bezeichnet. Es wurde "Messaging" genannt, weil es die Arbeit vollständig an das andere Objekt delegiert und es ist, als würde man jemandem sagen, "das ist endlich erledigt".

Datenverbindung

Übergeben Sie nur einfache Daten.

//Beispiel für Datenverknüpfung

public class NameFormatter {
    public String format(String firstName , String familyName) {
        return new StringBuilder().add(familyName).add(" ").add(firstName).toString();
    }
}
public class Main {
   private final String firstName;
   private final String familyName;

   public Main(String firstName, String familyName) {
      this.firstName = firstName;
      this.familyName = familyName;
   }

   public void printName() {
      NameFormatter formatter = new NameFormatter();
      System.out.println(formatter.format(this.firstName, this.familyName)); 
   }
}

Main main = new Main("Kazuki", "Oda");
main.printName(); //Kazuki Oda

Die Kombination mit NameFormatter in Main # printName () oben ist eine Datenkombination. Teilen Sie bei der Datenbindung ** nur "notwendige Informationen" ** mit. Die hier genannten "Informationen" entsprechen den folgenden.

Die Datenbindung führt immer zu derselben Ausgabe für dieselbe Eingabe. Da nur die erforderlichen Informationen an "NameFormatter" übergeben werden, verwendet "printName ()" nur den Wert, der schließlich vom "NameFormatter # -Format" zurückgegeben wird, und welche Art der Implementierung intern erfolgt. Sie müssen nicht wissen, ob Sie dort sind.

Im Allgemeinen werden Daten zusammengefügt, um Daten als Argument [unveränderlich] zu übergeben (https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%9F%E3%83%A5%E3%83] % BC% E3% 82% BF% E3% 83% 96% E3% 83% AB) oder [Primitiver Typ](https://ja.wikipedia.org/wiki/%E3%83%97%E3%83% AA% E3% 83% 9F% E3% 83% 86% E3% 82% A3% E3% 83% 96% E5% 9E% 8B) Oder bezieht sich auf einen Join beim Übergeben einer Schnittstelle (glaube ich). Selbst im Fall der Übergabe eines Objekts vom Referenztyp kann es jedoch einer Datenbindung entsprechen (zum Beispiel werden nur die erforderlichen Informationen durch das aufgerufene Verfahren offenbart).

Stempelkombination

Übergeben Sie eine Datenstruktur (dh eine Instanz mit mehreren Mitgliedsvariablen) als Argument.

public class Item {
    //In dem Körper gibt es einen Konstruktor, Getter und Setter
    private String name;
    private String group;
}

public class CashRegister {
    public String format(Item item, Price price) {
        return item.getName() + ":" + price.format();
    }
}

CashRegister regi = new CashRegister();
System.out.println(regi.format(new Item("Smash Bra", "Spiel"), new Price(6800))); //Smash Bra:¥6,800

Bei einer Stempelkombination können Daten, die nicht vom Angerufenen verwendet werden, in der Struktur referenziert werden (dh der Angerufene kennt auch unnötige Informationen). Im obigen Fall wird "Gruppe" nicht verwendet, aber wenn Sie dies möchten, können Sie im "CashRegister # -Format" auf eine von ihnen verweisen.

Es spielt keine Rolle, ob es sich nur um eine Referenz handelt, aber in einigen Fällen ist es möglich, den Wert der Daten in der aufgerufenen Methode neu zu schreiben. Also ** Bei einer Stempelkombination, wenn der Anrufer sie korrekt verwenden möchte, gibt es Fälle, in denen es erforderlich ist, bis zu einem gewissen Grad zu wissen, welche Daten in der aufgerufenen Methode verarbeitet werden. ** ** **

Das Obige ist eine Datenkombination mit dem Anrufer, wenn CashRegister wie folgt eingestellt ist. Anrufer und Artikel sowie Anrufer und Preis werden jedoch zusammen gestempelt </ font>

public class CashRegister {
    public String format(String item, int price) {
        return item + ":" + price;
    }
}

CashRegister regi = new CashRegister();
Item item = new Item("Smash Bra", "Spiel");
Price price = new Price(6800);
System.out.println(regi.format(item.getName(), price.format())); //Smash Bra:¥6,800

Daher kann die Stempelbindung nicht einfach beseitigt werden, solange die Klasse verwendet wird. Durch die Verwendung der Schnittstelle ist es möglich, den Grad der Bindung an Datenbindung oder Nachrichtenbindung zu verringern, dieser wird jedoch länger sein, sodass ich ihn weglassen werde.

  • Persönlich ist es bei einem mittelgroßen Projekt realistischer, eine Stempelkombination normal zuzulassen, als nach dem objektorientierten Prinzip zu arbeiten.

Control Join

Ein Fall, in dem sich der Rückgabewert abhängig vom Inhalt des angegebenen Arguments ändert.

public class Item {
//In dem Körper gibt es einen Konstruktor, Getter und Setter
   private String name;
   private Price unitPrice;
}

public class SaleService {
   public Price fixPrice(Item item, int amount, boolean discount) {
       Price newPrice = item.getUnitPrice().multiply(amount);
       if (discount == false) {
           return newPrice;
       }
       return newPrice.discount(0.90);
   }
}

Item item = new Item("Smash Bra", new Price(6800));
SaleService service = new SaleService();
System.out.prinln(service.fixPrice(item, 2, false)); // ¥13,600
System.out.prinln(service.fixPrice(item, 2, true)); // ¥12,240

In dieser Steuerkombination unterscheidet sich der Rückgabewert durch die Anzahl der Muster, selbst wenn ein ähnlicher Wert als Argument eingegeben wird, abhängig von den Mustern, die verwendet werden können. Mit anderen Worten, ** die Muster, die genommen werden können, hängen von den Argumenten ab. ** ** **

Diese Kontrollbindung kann durch Verwendung von Polymorphismus auf weniger als die Stempelbindung reduziert werden. (Ich persönlich denke jedoch, dass eine Steuerkopplung toleriert werden kann, wenn es sich um 3 bis 4 Muster handelt.)

Äußere Verbindung

Der äußere Join bezieht sich auf den Status, in dem sich mehrere Module auf eine globale Ressource beziehen. Wenn Sie diesen Beitritt von nun an zulassen, ist es sehr schwierig, ihn zu testen.

Die globalen Ressourcen beziehen sich beispielsweise auf Folgendes.

  • Globale statische Variablen
  • Bearbeitbar in Singleton Daten
  • Datei
  • Datenbankressourcen
  • Rückgabewert von einem anderen Server (Web-API usw.)

Für diese wirkt sich der in anderen Instanzen usw. geänderte Wert auf die gesamte Klasse aus, auf die von anderen verwiesen wird. Wenn sich die Struktur dieser Ressourcen ändert, müssen alle aufrufenden Klassen geändert werden.

** Um testbaren Code zu schreiben, ist es wichtig (glaube ich), wie die Anzahl der Bindungen nach dieser äußeren Verknüpfung verringert werden kann. ** ** **

Gemeinsame Verbindung

Ein Zustand, in dem verschiedene Klassen auf mehrere Daten globaler Ressourcen verweisen. Es wird chaotischer sein als die äußere Verbindung.

Inhaltskombination

Ein Join, der sogar die privaten Methoden und Variablennamen anderer Klassen kennt und diese direkt referenziert / aufruft. Wenn sich in der Inhaltskombination die interne Implementierung (unveröffentlichte Implementierung) der abhängigen Klasse ändert, ist dies direkt betroffen. Selbst wenn der Variablenname der Abhängigkeit geändert wird, ist dies betroffen.

  • Wenn Reflexion verwendet wird, besteht die Gefahr eines Absturzes.

Um den Bindungsgrad zu reduzieren

Unter Berücksichtigung der folgenden Punkte kann der Kopplungsgrad niedrig gehalten werden.

  • Verweisen Sie nicht so oft wie möglich direkt auf Daten
  • Verwenden Sie Setter / Getter nicht so oft wie möglich (insbesondere Setter).
  • Versuchen Sie, eine Schnittstelle zu erstellen
  • Insbesondere Klassen, die mit der Außenwelt zusammenarbeiten, befinden sich außerhalb und werden zu einer Schnittstelle.
  • Prinzip der Abhängigkeitsumkehr
  • Verwenden Sie die Abhängigkeitsinjektion
  • Es gibt verschiedene DI-Bibliotheken wie Guice
  • Effektiver in Kombination mit Werksmethoden
  • Demeter-Gesetz bewusst

Zirkuläre Komplexität (zyklomatische Komplexität)

Einfach ausgedrückt, die Nummer von "if-Anweisung / for-Anweisung / while-Anweisung / switch-Anweisung / try-Anweisung / catch-Anweisung" und so weiter. Wenn es keine von ihnen gibt, ist es 1, und wenn die Anzahl der Zweige zunimmt, nimmt sie um 1 zu. Beispielsweise beträgt die zyklische Komplexität des folgenden Verfahrens 4.

public void hogehoge(int test) {
   if (test > 5 ) {
       // doSomething1
   } else if (test < 1) {
       // doSomething2
   }
   for (int i = 0; i < 5; i++) {
       // doSomething3
   }
}

Wenn der Grad der zyklischen Referenz hoch ist

Natürlich gibt es mehr Testmuster. Darüber hinaus wird die Lesbarkeit verringert und die Wartung wird schwierig.

So reduzieren Sie den zyklischen Bezug

  • Teilen Sie die Methode
  • Machen Sie das Nest flach (versuchen Sie früh zurückzukehren)
  • Beachten Sie das DRY-Prinzip
  • Teilen Sie die Verarbeitung durch Polymorphismus
  • Verwenden Sie die Stream-API usw.

Zusammenfassung

  • Eine höhere Agglomeration ist besser
  • Je niedriger der Bindungsgrad, desto besser
  • Geringe zyklische Komplexität ist besser

Recommended Posts

Aggregation / Kopplung / zyklische Komplexität
Über zyklische Komplexität