Utility-Klassen sind böse? [geschlossen]

92

Ich habe diesen Thread gesehen

Wenn eine "Utilities" -Klasse böse ist, wo lege ich meinen generischen Code ab?

und dachte, warum sind Gebrauchsklassen böse?

Nehmen wir an, ich habe ein Domain-Modell, das Dutzende von Klassen umfasst. Ich muss in der Lage sein, Instanzen xml-ify. Mache ich eine toXml-Methode für das übergeordnete Element? Mache ich eine MyDomainXmlUtility.toXml-Hilfsklasse? Dies ist ein Fall, in dem der Geschäftsbedarf das gesamte Domänenmodell umfasst - gehört er wirklich als Instanzmethode? Was ist, wenn es eine Reihe von Hilfsmethoden für die XML-Funktionalität der Anwendung gibt?

hvgotcodes
quelle
26
Die Abwertung des Begriffs "böse" ist böse!
Matthew Lock
1
@ Matthew Ich habe die Bedingungen des Beitrags beibehalten, auf dem meine Frage basiert ...;)
hvgotcodes
Utility-Klassen sind aus den gleichen Gründen wie Singletons eine schlechte Idee.
CurtainDog
1
Die Debatte über die Verwendung einer toXML-Methode konzentriert sich auf Modelle mit reichhaltigen und anämischen Domänen. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
James P.
@james, das toXML ist nur ein Beispiel ... was ist mit einigen Regex-Funktionen, die überall verwendet werden? Sie müssen einige Dinge mit den Zeichenfolgen in Ihrem Domain-Modell tun, aber Sie konnten keine Unterklassen verwenden, da ein anderes übergeordnetes Problem darin bestand, dass Ihre eine Superklasse (in Java) verwendet wurde
hvgotcodes

Antworten:

125

Utility-Klassen sind nicht gerade böse, aber sie können die Prinzipien verletzen, die ein gutes objektorientiertes Design ausmachen. In einem guten objektorientierten Design sollten die meisten Klassen eine einzelne Sache und alle ihre Attribute und Operationen darstellen. Wenn Sie an einer Sache arbeiten, sollte diese Methode wahrscheinlich ein Mitglied dieser Sache sein.

Es gibt jedoch Situationen, in denen Sie Dienstprogrammklassen verwenden können, um eine Reihe von Methoden zu gruppieren. Ein Beispiel ist die java.util.CollectionsKlasse, die eine Reihe von Dienstprogrammen bereitstellt, die für jede Java-Sammlung verwendet werden können. Diese sind nicht spezifisch für einen bestimmten Sammlungstyp, sondern implementieren Algorithmen, die für jede Sammlung verwendet werden können.

Was Sie wirklich tun müssen, ist über Ihr Design nachzudenken und festzustellen, wo es am sinnvollsten ist, die Methoden zu platzieren. Normalerweise handelt es sich um Operationen innerhalb einer Klasse. Manchmal handelt es sich jedoch tatsächlich um eine Utility-Klasse. Wenn Sie jedoch eine Utility-Klasse verwenden, werfen Sie nicht nur zufällige Methoden hinein, sondern organisieren Sie die Methoden nach Zweck und Funktionalität.

Thomas Owens
quelle
Verbindungen in gut mit diesem Artikel über Dienstprogramm / Hilfsklasse gegen SOLID oo Prinzip als auch die Überprüfung readability.com/articles/rk1vvcqy
melaos
8
Wenn die Sprache keinen anderen Namespace-Mechanismus als Klassen bietet, haben Sie keine andere Wahl, als eine Klasse zu missbrauchen, in der ein Namespace funktionieren würde. In C ++ können Sie eine freistehende Funktion, die nicht mit anderen Funktionen zusammenhängt, in einen Namespace einfügen. In Java müssen Sie es als statisches (Klassen-) Mitglied in eine Klasse einfügen.
Stellen Sie Monica
1
Die Gruppierung als Methoden kann vom OOP-Standpunkt aus kanonisch sein. OOP ist jedoch selten die Lösung (unter den Ausnahmen sind Architektur- und Widget-Toolkits auf hoher Ebene als einer der Fälle, in denen die unglaublich gebrochene Semantik der Wiederverwendung von Code durch Vererbung tatsächlich passt), und im Allgemeinen erhöhen Methoden die Kopplung und Abhängigkeiten. Triviales Beispiel: Wenn Sie Ihrer Klasse Drucker- / Scannermethoden als Methoden bereitstellen, koppeln Sie die Klasse an Drucker- / Scannerbibliotheken. Außerdem legen Sie die Anzahl der möglichen Implementierungen auf eine fest. Es sei denn, Sie werfen die Schnittstelle ein und erhöhen die Abhängigkeiten weiter.
Jo So
Auf der anderen Seite stimme ich Ihrer Meinung "Gruppe nach Zweck und Funktionalität" zu. Lassen Sie uns das Drucker- / Scanner-Beispiel noch einmal betrachten und mit einer Reihe von Konzertklassen beginnen, die für einen gemeinsamen Zweck entworfen wurden. Möglicherweise möchten Sie Debug-Code schreiben und eine Textdarstellung für Ihre Klassen entwerfen. Sie können Ihre Debug-Drucker in einer einzigen Datei implementieren, die von all diesen Klassen und einer Druckerbibliothek abhängt. Nicht-Debugging-Code wird nicht mit den Abhängigkeiten dieser Implementierung belastet.
Jo So
1
Util- und Helper-Klassen sind ein unglückliches Artefakt solcher Einschränkungen, und diejenigen, die OOP nicht verstehen oder die Problemdomäne nicht verstehen, haben weiterhin prozeduralen Code geschrieben.
Bilelovitch
55

Ich denke , dass der allgemeine Konsens ist , dass die Utility - Klassen sind nicht böse per se . Sie müssen sie nur mit Bedacht einsetzen:

  • Entwerfen Sie die statischen Dienstprogrammmethoden so, dass sie allgemein und wiederverwendbar sind. Stellen Sie sicher, dass sie staatenlos sind. dh keine statischen Variablen.

  • Wenn Sie über viele Dienstprogrammmethoden verfügen, partitionieren Sie diese so in Klassen, dass Entwickler sie leichter finden können.

  • Verwenden Sie keine Dienstprogrammklassen, bei denen statische oder Instanzmethoden in einer Domänenklasse die bessere Lösung wären. Überlegen Sie beispielsweise, ob Methoden in einer abstrakten Basisklasse oder einer instanziierbaren Hilfsklasse eine bessere Lösung wären.

  • Ab Java 8 sind "Standardmethoden" in einer Schnittstelle möglicherweise eine bessere Option als Dienstprogrammklassen. (Siehe Schnittstelle Schnittstelle mit Standardmethoden im Vergleich zur Abstract-Klasse in Java 8. Beispiel.)


Die andere Möglichkeit, diese Frage zu betrachten, besteht darin, zu beobachten, dass in der zitierten Frage "Wenn Gebrauchsklassen" böse "sind" ein Strohmann-Argument ist. Es ist so, als würde ich fragen:

"Wenn Schweine fliegen können, sollte ich einen Regenschirm tragen?"

In der obigen Frage sage ich eigentlich nicht, dass Schweine fliegen können ... oder dass ich dem Vorschlag zustimme, dass sie fliegen könnten .

Typische "xyz ist böse" Aussagen sind rhetorische Mittel, die Sie zum Nachdenken anregen sollen, indem sie einen extremen Standpunkt vertreten. Sie sind selten (wenn überhaupt) als Aussagen wörtlicher Tatsachen gedacht.

Stephen C.
quelle
16

Dienstprogrammklassen sind problematisch, da sie Verantwortlichkeiten nicht mit den Daten gruppieren können, die sie unterstützen.

Sie sind jedoch äußerst nützlich und ich baue sie die ganze Zeit entweder als dauerhafte Strukturen oder als Sprungbrett während eines gründlicheren Refaktors.

Aus Sicht von Clean Code verstoßen Dienstprogrammklassen gegen die Einzelverantwortung und das Open-Closed-Prinzip. Sie haben viele Gründe zu ändern und sind von Natur aus nicht erweiterbar. Sie sollten eigentlich nur während des Refactorings als Zwischenkruft existieren.

Alain O'Dea
quelle
2
Ich würde es ein praktisches Problem nennen, da Sie beispielsweise in Java keine Funktionen erstellen können, die keine Methoden in einer Klasse sind. In einer solchen Sprache ist die "Klasse ohne Variablen und nur mit statischen Methoden" eine Redewendung, die für einen Namespace von Dienstprogrammfunktionen steht ... Wenn man mit einer solchen Klasse in Java konfrontiert wird, ist es meiner Meinung nach ungültig und sinnlos, von einer Klasse zu sprechen , obwohl das classSchlüsselwort verwendet wird. Es quakt wie ein Namespace, es läuft wie ein Namespace - es ist einer ...
Stellen Sie Monica
2
Es tut mir leid, stumpf zu sein, aber "staatenlose statische Methoden [...] in einem [...] philosophischen Problem des OOP-Systems" sind äußerst falsch. Es ist großartig, wenn Sie den Status vermeiden können, da dies das Schreiben von korrektem Code erleichtert. Es ist GEBROCHEN, wenn ich schreiben muss, x.equals(y)weil 1) die Tatsache, dass dies wirklich keinen Zustand ändern sollte, überhaupt nicht vermittelt wird (und ich kann mich nicht darauf verlassen, dass ich die Implementierung nicht kenne) 2) Ich hatte nie vor, xeinen " bevorzugte "oder" behandelte "Position im Vergleich zu y3) Ich möchte keine" Identitäten "von xoder berücksichtigen, ysondern bin nur an deren Werten interessiert.
Jo So
4
Wirklich die meisten Funktionen in der realen Welt werden am besten als statische, zustandslose, mathematische Funktionen ausgedrückt. Ist Math.PI ein Objekt, das mutiert werden sollte? Muss ich wirklich einen AbstractSineCalculator instanziieren, der IAbstractRealOperation implementiert, um den Sinus einer Zahl zu erhalten?
Jo So
1
@JoSo Ich stimme voll und ganz dem Wert von Staatenlosigkeit und direkter Berechnung zu. Naive OO ist philosophisch dagegen und ich denke, das macht es zutiefst fehlerhaft. Es fördert die Abstraktion von Dingen, die es nicht benötigen (wie Sie gezeigt haben), und bietet keine aussagekräftigen Abstraktionen für die tatsächliche Berechnung. Ein ausgewogener Ansatz zur Verwendung einer OO-Sprache führt jedoch zu Unveränderlichkeit und Staatenlosigkeit, da sie Modularität und Wartbarkeit fördern.
Alain O'Dea
2
@ AlainO'Dea: Mir ist klar, dass ich Ihren Kommentar als gegen staatenlose Funktionalität stehend falsch verstanden habe. Ich entschuldige mich für meinen Ton - ich bin derzeit an meinem ersten Java-Projekt beteiligt (nachdem ich OOP während meiner 10-jährigen Programmierzeit größtenteils vermieden habe) und bin unter Schichten abstrakter Dinge mit unklarem Zustand und unklarer Bedeutung begraben. Und ich brauche nur frische Luft :-).
Jo So
8

Ich nehme an, es wird böse, wenn

1) Es wird zu groß (gruppieren Sie sie in diesem Fall einfach in sinnvolle Kategorien).
2) Methoden, die keine statischen Methoden sein sollten, sind vorhanden

Aber solange diese Bedingungen nicht erfüllt sind, halte ich sie für sehr nützlich.

Enno Shioji
quelle
"Methoden, die keine statischen Methoden sein sollten, sind vorhanden." Wie ist das möglich?
Koray Tugay
@KorayTugay Betrachten Sie die nicht statische Widget.compareTo(Widget widget)gegenüber der statischen WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). Der Vergleich ist ein Beispiel für etwas, das nicht statisch durchgeführt werden sollte.
ndm13
5

Faustregel

Sie können dieses Problem aus zwei Perspektiven betrachten:

  • Allgemeine *UtilMethoden sind oft ein Hinweis auf schlechtes Code-Design oder eine faule Namenskonvention.
  • Es ist eine legitime Designlösung für wiederverwendbare domänenübergreifende zustandslose Funktionen. Bitte beachten Sie, dass es für fast alle gängigen Probleme bereits Lösungen gibt.

Beispiel 1. Richtige Verwendung von utilKlassen / Modulen. Beispiel für eine externe Bibliothek

Nehmen wir an, Sie schreiben einen Antrag, der Kredite und Kreditkarten verwaltet. Daten aus diesen Modulen werden über Webdienste im jsonFormat verfügbar gemacht . Theoretisch können Sie Objekte manuell in Zeichenfolgen konvertieren, die sich in befinden json, aber das würde das Rad neu erfinden. Die richtige Lösung wäre, in beide Module eine externe Bibliothek aufzunehmen, die zum Konvertieren von Java-Objekten in das gewünschte Format verwendet wird. (im Beispielbild habe ich gson gezeigt )

Geben Sie hier die Bildbeschreibung ein


Beispiel 2. Richtige Verwendung von utilKlassen / Modulen. Schreiben Sie Ihre eigenen utilohne Ausreden an andere Teammitglieder

Als Anwendungsfall wird angenommen, dass wir einige Berechnungen in zwei Anwendungsmodulen durchführen müssen, aber beide müssen wissen, wann es in Polen Feiertage gibt. Theoretisch können Sie diese Berechnungen in Modulen durchführen. Es ist jedoch besser, diese Funktionalität zu extrahieren, um das Modul zu trennen.

Hier ist ein kleines, aber wichtiges Detail. Klasse / Modul, die Sie geschrieben haben, heißt HolidayUtilaber nicht PolishHolidayCalculator. Funktionell ist es eine utilKlasse, aber wir haben es geschafft, generische Wörter zu vermeiden.

Geben Sie hier die Bildbeschreibung ein

Marcin Szymczak
quelle
1
Ein yEd Fan sehe ich;) Erstaunliche App.
Sridhar Sarnobat
3

Utility-Klassen sind schlecht, weil sie bedeuten, dass Sie zu faul waren, um sich einen besseren Namen für die Klasse auszudenken :)

Davon abgesehen bin ich faul. Manchmal müssen Sie nur die Arbeit erledigen und Ihre Gedanken sind leer. Dann schleichen sich "Utility" -Klassen ein.

Larry Watanabe
quelle
3

Wenn ich jetzt auf diese Frage zurückblicke, würde ich sagen, dass C # -Erweiterungsmethoden die Notwendigkeit von Dienstprogrammklassen vollständig zerstören. Aber nicht alle Sprachen haben ein so geniales Konstrukt.

Sie haben auch JavaScript, mit dem Sie dem vorhandenen Objekt einfach eine neue Funktion hinzufügen können.

Aber ich bin mir nicht sicher, ob es wirklich eine elegante Möglichkeit gibt, dieses Problem in einer älteren Sprache wie C ++ zu lösen ...

Guter OO-Code ist schwer zu schreiben und schwer zu finden, da das Schreiben von gutem OO viel mehr Zeit / Wissen erfordert als das Schreiben von anständigem Funktionscode.

Und wenn Sie ein begrenztes Budget haben, ist Ihr Chef nicht immer froh zu sehen, dass Sie den ganzen Tag damit verbracht haben, eine Reihe von Klassen zu schreiben ...

HumbleWebDev
quelle
3

Ich stimme nicht ganz zu, dass Utility-Klassen böse sind.

Während eine Utility-Klasse in gewisser Weise gegen OO-Principals verstoßen kann, sind sie nicht immer schlecht.

Stellen Sie sich beispielsweise vor, Sie möchten eine Funktion, die eine Zeichenfolge aller Teilzeichenfolgen bereinigt, die dem Wert entsprechen x.

stl c ++ (ab sofort) unterstützt dies nicht direkt.

Sie können eine polymorphe Erweiterung von erstellen std::string.

Das Problem ist jedoch, dass JEDER String, den Sie in Ihrem Projekt verwenden, wirklich Ihre erweiterte String-Klasse sein soll.

Es gibt Zeiten, in denen OO nicht wirklich Sinn macht, und dies ist eine davon. Wir möchten, dass unser Programm mit anderen Programmen kompatibel ist, also bleiben wir bei std::stringeiner Klasse StringUtil_(oder etwas anderem) und erstellen sie .

Ich würde sagen, es ist am besten, wenn Sie sich an einen Util pro Klasse halten. Ich würde sagen, es ist albern, einen Util für alle Klassen oder viele Utils für eine Klasse zu haben.

HumbleWebDev
quelle
2

Es ist sehr einfach, etwas als Dienstprogramm zu kennzeichnen, nur weil der Designer sich keinen geeigneten Ort für die Eingabe des Codes vorstellen konnte. Es gibt oft nur wenige echte "Dienstprogramme".

Als Faustregel behalte ich normalerweise Code in dem Paket, in dem er zuerst verwendet wird, und überarbeite ihn dann nur dann an einen allgemeineren Ort, wenn ich später feststelle, dass er wirklich woanders benötigt wird. Die einzige Ausnahme ist, wenn ich bereits ein Paket habe, das ähnliche / verwandte Funktionen ausführt, und der Code dort am besten passt.

mdma
quelle
2

Dienstprogrammklassen mit zustandslosen statischen Methoden können hilfreich sein. Diese sind oft sehr einfach zu testen.

Seand
quelle
1

Mit Java 8 können Sie statische Methoden in Schnittstellen verwenden ... Problem gelöst.

Marc T.
quelle
Es geht nicht auf die Probleme ein, die in stackoverflow.com/a/3340037/2859065
Luchostein
Wie unterscheidet sich das vom Einordnen und Importieren?
Wilmol
1

Die meisten Util-Klassen sind schlecht, weil:

  1. Sie erweitern den Methodenumfang. Sie machen Code öffentlich, der sonst privat wäre. Wenn die util-Methode von mehreren Aufrufern in separaten Klassen benötigt wird und stabil ist (dh nicht aktualisiert werden muss), ist es meiner Meinung nach besser, private Hilfsmethoden zu kopieren und in die aufrufende Klasse einzufügen. Sobald Sie es als API verfügbar machen, wird es schwieriger zu verstehen, was der öffentliche Einstiegspunkt für eine JAR-Komponente ist (Sie pflegen einen Baum mit der Bezeichnung Hierarchie mit einem übergeordneten Element pro Methode. Dies ist einfacher, wenn Sie sich mental in Komponenten aufteilen, was schwieriger ist, wenn Sie haben Methoden, die von mehreren übergeordneten Methoden aufgerufen wurden.
  2. Sie führen zu totem Code. Util-Methoden werden im Laufe der Zeit nicht mehr verwendet, wenn sich Ihre App weiterentwickelt und Sie nicht verwendeten Code verwenden, der Ihre Codebasis verschmutzt. Wenn es privat geblieben wäre, würde Ihr Compiler Ihnen mitteilen, dass die Methode nicht verwendet wird und Sie sie einfach entfernen könnten (der beste Code ist überhaupt kein Code). Sobald Sie eine solche Methode nicht privat machen, kann Ihr Computer nicht mehr verwendeten Code entfernen. Es kann aus einer anderen JAR-Datei aufgerufen werden, soweit der Computer dies weiß.

Es gibt einige Analogien zu statischen und dynamischen Bibliotheken.

Sridhar Sarnobat
quelle
0

Wenn ich einer Klasse keine Methode hinzufügen kann (z. B. Accountgegen Änderungen durch Jr. Developers gesperrt ist), füge ich meiner Utilities-Klasse nur einige statische Methoden hinzu:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  
Herr weiß
quelle
1
Sieht so aus, als wären Sie der Jr für mich ..: P Der nicht böse Weg, dies zu tun, besteht darin, Ihren "Jr-Entwickler" höflich zu bitten, die AccountDatei zu entsperren . Oder besser mit nicht sperrenden Versionsverwaltungssystemen.
Sudeep
1
Ich nehme an, er meinte, dass die AccountDatei so gesperrt war, dass Jr. Entwickler keine Änderungen daran vornehmen können. Als Jr. Developer hat er wie oben beschrieben vorgegangen, um das Problem zu umgehen. Das heißt, noch besser, nur Schreibzugriff auf die Datei zu bekommen und es richtig zu machen.
Doc
Fügen Sie +1 hinzu, weil Abstimmungen hart erscheinen, wenn niemand den vollständigen Kontext dafür hat, warum Sie diese Antwort geliefert haben
joelc
0

Utility-Klassen sind nicht immer böse. Sie sollten jedoch nur die Methoden enthalten, die für eine Vielzahl von Funktionen gelten. Wenn es Methoden gibt, die nur für eine begrenzte Anzahl von Klassen verwendet werden können, sollten Sie eine abstrakte Klasse als gemeinsames übergeordnetes Element erstellen und die Methoden darin ablegen.

Sid J.
quelle