Wie können große, eng gekoppelte Klassen aufgeteilt werden?

14

Ich habe einige große Klassen mit mehr als 2 KB Codezeilen (und mehr), die ich nach Möglichkeit umgestalten möchte, um ein leichteres und übersichtlicheres Design zu erhalten.

Der Grund, warum es so groß ist, liegt hauptsächlich darin, dass diese Klassen eine Reihe von Zuordnungen verarbeiten, auf die die meisten Methoden zugreifen müssen, und dass die Methoden eng miteinander verbunden sind.

Ich werde ein sehr konkretes Beispiel geben: Ich habe eine Klasse namens Server, die eingehende Nachrichten behandelt. Es verfügt über Methoden wie joinChatroom, searchUsers, sendPrivateMessageusw. Alle diese Methoden manipulieren die Karten wie users, chatrooms, servers, ...

Vielleicht wäre es schön, wenn ich eine Klasse haben könnte, die Nachrichten in Bezug auf Chatrooms behandelt, eine andere, die sich mit Benutzern befasst, usw., aber das Hauptproblem dabei ist, dass ich in den meisten Methoden alle Karten verwenden muss. Das ist der Grund, warum sie im Moment alle in der ServerKlasse stecken bleiben, da sie sich alle auf diese gemeinsamen Maps verlassen und die Methoden sehr eng miteinander verbunden sind.

Ich würde eine Klasse Chatrooms erstellen müssen, aber mit einem Verweis auf jedes der anderen Objekte. Eine Klasse Benutzer wieder mit einem Verweis auf alle anderen Objekte usw.

Ich habe das Gefühl, ich würde etwas falsch machen.

Matthew
quelle
Wenn Sie Klassen wie User und Chatroom erstellen würden, würden diese Klassen nur einen Verweis auf die gemeinsame Datenstruktur benötigen oder würden sie sich gegenseitig referenzieren?
Hier gibt es mehrere zufriedenstellende Antworten. Wählen Sie eine aus.
Jeremyjjbrown
@jeremyjjbrown die frage wurde verschoben und ich habe sie verloren. Hat eine Antwort gewählt, danke.
Matthew

Antworten:

10

Aus Ihrer Beschreibung würde ich schließen, dass es sich bei Ihren Karten um reine Datentaschen handelt, mit all der Logik in den ServerMethoden. Indem Sie die gesamte Chatroom-Logik in eine separate Klasse verschieben, bleiben Sie bei Maps mit Daten hängen.

Versuchen Sie stattdessen, einzelne Chatrooms, Benutzer usw. als Objekte zu modellieren. Auf diese Weise übergeben Sie nur bestimmte Objekte, die für eine bestimmte Methode erforderlich sind, und nicht riesige Datenkarten.

Beispielsweise:

public class User {
  private String name;
  ...

  public void sendMessage(String message) {
    ...
  }
}

public class Chatroom {
  // users in this chatroom
  private Collection<User> users;

  public void add(User user) {
    users.add(user);
  }

  public void sendMessage(String msg) {
    for (User user : users)
      user.sendMessage(msg);
  }
}

public class Server {
  // all users on the server
  private Collection<User> users;

  // all chatrooms on the server
  private Collection<Chatroom> chatrooms;

  /* methods to handle incoming messages */
}

Es ist jetzt ganz einfach, einige spezielle Methoden zum Behandeln von Nachrichten aufzurufen:

Möchtest du einem Chatroom beitreten?

chatroom.add(user);

Möchten Sie eine private Nachricht senden?

user.sendMessage(msg);

Möchten Sie eine öffentliche Nachricht senden?

chatroom.sendMessage(msg);
casablanca
quelle
5

Sie sollten in der Lage sein, eine Klasse zu erstellen, die jede Sammlung enthält. Sie Serverbenötigen zwar einen Verweis auf jede dieser Sammlungen, benötigen jedoch nur einen minimalen logischen Aufwand, bei dem nicht auf die zugrunde liegenden Sammlungen zugegriffen oder diese verwaltet werden muss. Dadurch wird klarer, was der Server gerade tut, und es wird getrennt, wie er es tut.

Peter Lawrey
quelle
4

Wenn ich große Klassen wie diese gesehen habe, habe ich festgestellt, dass es oft eine Klasse (oder mehr) gibt, die versucht, herauszukommen. Wenn Sie eine Methode kennen, die Ihrer Meinung nach nicht mit dieser Klasse verwandt ist, machen Sie sie statisch. Der Compiler teilt Ihnen dann andere Methoden mit, die diese Methode aufruft. Java wird darauf bestehen, dass sie auch statisch sind. Sie machen sie statisch. Der Compiler teilt Ihnen erneut alle aufgerufenen Methoden mit. Sie tun dies immer und immer wieder, bis Sie keine Kompilierungsfehler mehr haben. Dann haben Sie eine Menge statischer Methoden in Ihrer großen Klasse. Sie können diese nun in eine neue Klasse ziehen und die Methode nicht statisch machen. Sie können diese neue Klasse dann aus Ihrer ursprünglichen großen Klasse aufrufen (die jetzt weniger Zeilen enthalten sollte).

Anschließend können Sie den Vorgang wiederholen, bis Sie mit dem Klassendesign zufrieden sind.

Martin Fowlers Buch ist eine wirklich gute Lektüre, daher würde ich es auch empfehlen, da Sie diesen statischen Trick manchmal nicht anwenden können.

RNJ
quelle
1
Das Buch von Martin Fowler martinfowler.com/books/refactoring.html
Arul
1

Da der größte Teil Ihres Codes vorhanden ist, würde ich vorschlagen, Hilfsklassen zu verwenden, um Ihre Methoden zu verschieben. Das erleichtert das Refactoring. Ihre Serverklasse enthält also weiterhin die darin enthaltenen Maps. Es wird jedoch eine Hilfsklasse namens ChatroomHelper mit Methoden wie join (Map-Chatrooms, String-Benutzer), List getUsers (Map-Chatrooms) und Map getChatrooms (String-Benutzer) verwendet.

Die Serverklasse enthält eine Instanz von ChatroomHelper, UserHelper usw., wodurch die Methoden in ihre logischen Hilfsklassen verschoben werden. Damit können Sie die öffentlichen Methoden auf dem Server intakt lassen, so dass sich kein Aufrufer ändern muss.

techuser soma
quelle
1

Um die aufschlussreiche Antwort von casablanca zu ergänzen : Wenn mehrere Klassen mit einem bestimmten Entitätstyp (Hinzufügen von Benutzern zu einer Sammlung, Verarbeiten von Nachrichten usw.) die gleichen grundlegenden Aufgaben ausführen müssen, sollten diese Prozesse ebenfalls getrennt gehalten werden.

Es gibt mehrere Möglichkeiten, dies zu tun - durch Vererbung oder Komposition. Für die Vererbung können Sie abstrakte Basisklassen mit konkreten Methoden verwenden, die die Felder und Funktionen zum Behandeln von Benutzern oder Nachrichten bereitstellen und über Entitäten wie chatroomund user(oder beliebige andere) Erweiterungen dieser Klassen verfügen .

Aus verschiedenen Gründen ist es eine gute allgemeine Regel, Komposition anstelle von Vererbung zu verwenden. Sie können Komposition verwenden, um dies auf verschiedene Arten zu tun. Da die Behandlung von Benutzern oder Nachrichten für die Verantwortung der Klassen von zentraler Bedeutung ist, kann argumentiert werden, dass die Konstruktorinjektion am besten geeignet ist. Auf diese Weise ist die Abhängigkeit transparent und ein Objekt kann nicht ohne die erforderliche Funktionalität erstellt werden. Wenn sich die Art und Weise, wie Benutzer oder Nachrichten behandelt werden, wahrscheinlich ändert oder erweitert, sollten Sie in Betracht ziehen, so etwas wie das Strategiemuster zu verwenden .

Stellen Sie in beiden Fällen sicher, dass Sie für die Flexibilität auf Schnittstellen und nicht auf konkrete Klassen programmieren.

Berücksichtigen Sie jedoch immer die Kosten der zusätzlichen Komplexität, wenn Sie solche Muster verwenden. Wenn Sie sie nicht benötigen, codieren Sie sie nicht. Wenn Sie wissen, dass Sie sehr wahrscheinlich nicht ändern werden, wie Benutzer / Nachrichten behandelt werden, brauchen Sie nicht die strukturelle Komplexität eines Strategiemusters - aber um Bedenken zu trennen und Wiederholungen zu vermeiden, sollten Sie sich dennoch von gemeinsamen Funktionen trennen aus den konkreten Instanzen, die es verwenden - und, wenn keine zwingenden Gründe vorliegen, die Benutzer solcher Handhabungsfunktionen (Chatrooms, Benutzer) mit einem Objekt zusammensetzen, das die Handhabung übernimmt.

Also, um zusammenzufassen:

  1. Wie casablanca schrieb: Trennen und kapseln Sie Chatrooms, Benutzer usw.
  2. Separate gemeinsame Funktionalität
  3. Ziehen Sie in Betracht, einzelne Funktionen zu trennen, um die Datendarstellung (sowie den Zugriff und die Mutation) von komplexeren Funktionen zu trennen, und zwar über einzelne Instanzen von Daten oder deren Aggregaten (z. B. über searchUserseine Sammlungsklasse oder ein Repository / eine Identitätszuordnung) )
Michael Bauer
quelle
0

Ich denke, Ihre Frage ist zu allgemein, um sie zu beantworten, da wir keine vollständige Beschreibung des Problems haben. Daher ist es unmöglich, ein gutes Design mit so wenig Wissen anzubieten. Ich kann nur als Beispiel eines Ihrer Anliegen in Bezug auf die mögliche Sinnlosigkeit eines besseren Designs ansprechen.

Sie sagen, dass Ihre Serverklasse und Ihre zukünftige Chatroom-Klasse Daten über Benutzer gemeinsam nutzen, diese Daten sollten sich jedoch unterscheiden. Mit dem Server ist wahrscheinlich eine Gruppe von Benutzern verbunden, während ein Chatroom, der selbst zu einem Server gehört, eine andere Gruppe von Benutzern hat, eine Untergruppe der ersten Gruppe, und zwar nur die Benutzer, die derzeit in einem bestimmten Chatroom angemeldet sind.

Dies ist nicht die gleiche Information, auch wenn die Datentypen identisch sind.
Gutes Design hat viele, viele Vorteile.

Ich habe das vorgenannte Buch von Fowler noch nicht gelesen, aber ich habe andere Sachen von Folwer gelesen und es wurde mir von Leuten empfohlen, denen ich vertraue, damit ich mich wohl genug fühle, um mit den anderen übereinzustimmen.

Shrulik
quelle
0

Die Notwendigkeit, auf die Karten zuzugreifen, rechtfertigt die Mega-Klasse nicht. Sie müssen die Logik in mehrere Klassen unterteilen und jede Klasse muss eine getMap-Methode haben, damit andere Klassen auf die Maps zugreifen können.

Tulains Córdova
quelle
0

Ich würde die gleiche Antwort verwenden, die ich an anderer Stelle gegeben habe: Nehmen Sie die monolithische Klasse und teilen Sie ihre Verantwortlichkeiten auf andere Klassen auf. Sowohl DCI als auch das Besuchermuster bieten hierfür gute Optionen.

Mario T. Lanza
quelle
-1

In Bezug auf die Software-Metrik ist große Klasse Tasche. Es gibt unbegrenzte Papiere, die diese Aussage belegen. Warum das ? weil große Klassen schwerer zu verstehen sind als kleine Klassen und es mehr Zeit braucht, um sie zu modifizieren. Außerdem sind große Klassen beim Testen so schwierig. Und große Klassen sind sehr schwierig für Sie, wenn Sie sie wiederverwenden möchten, da sie höchstwahrscheinlich unerwünschte Inhalte enthalten.

cat_minhv0
quelle