Klasse mit Einzelmethode - bester Ansatz?


172

Angenommen, ich habe eine Klasse, die eine einzelne Funktion ausführen soll. Nach Ausführung der Funktion kann sie zerstört werden. Gibt es einen Grund, einen dieser Ansätze zu bevorzugen?

// Initialize arguments in constructor
MyClass myObject = new MyClass(arg1, arg2, arg3);
myObject.myMethod();

// Pass arguments to method
MyClass myObject = new MyClass();
myObject.myMethod(arg1, arg2, arg3);

// Pass arguments to static method
MyClass.myMethod(arg1, arg2, arg3);

Ich war absichtlich vage in Bezug auf die Details, um zu versuchen, Richtlinien für verschiedene Situationen zu erhalten. Aber ich hatte nicht wirklich einfache Bibliotheksfunktionen wie Math.random () im Sinn. Ich denke eher an Klassen, die eine bestimmte, komplexe Aufgabe ausführen, aber nur eine (öffentliche) Methode benötigen, um dies zu tun.

Antworten:


264

Ich liebte Utility-Klassen, die mit statischen Methoden gefüllt waren. Sie haben eine große Konsolidierung von Hilfsmethoden vorgenommen, die sonst herumliegen und Redundanz und Wartungshölle verursachen würden. Sie sind sehr einfach zu bedienen, keine Instanziierung, keine Entsorgung, nur Feuer und Vergessen. Ich denke, dies war mein erster unabsichtlicher Versuch, eine serviceorientierte Architektur zu erstellen - viele zustandslose Services, die nur ihre Arbeit erledigt haben und sonst nichts. Wenn ein System wächst, kommen jedoch Drachen.

Polymorphismus
Nehmen wir an, wir haben die Methode UtilityClass.SomeMethod, die gerne mitschwirrt. Plötzlich müssen wir die Funktionalität leicht ändern. Die meisten Funktionen sind gleich, aber wir müssen trotzdem einige Teile ändern. Wäre es keine statische Methode gewesen, könnten wir eine Ableitungsklasse erstellen und den Methodeninhalt nach Bedarf ändern. Da es sich um eine statische Methode handelt, können wir dies nicht. Sicher, wenn wir nur vor oder nach der alten Methode Funktionen hinzufügen müssen, können wir eine neue Klasse erstellen und die alte darin aufrufen - aber das ist nur grob.

Schnittstellenprobleme
Statische Methoden können aus logischen Gründen nicht über Schnittstellen definiert werden. Und da wir statische Methoden nicht überschreiben können, sind statische Klassen nutzlos, wenn wir sie über ihre Schnittstelle weitergeben müssen. Dies macht es uns unmöglich, statische Klassen als Teil eines Strategiemusters zu verwenden. Wir könnten einige Probleme beheben, indem wir Delegierte anstelle von Schnittstellen übergeben .

Testen
Dies geht grundsätzlich Hand in Hand mit den oben genannten Schnittstellenproblemen. Da unsere Möglichkeiten zum Austausch von Implementierungen sehr begrenzt sind, werden wir auch Probleme haben, Produktionscode durch Testcode zu ersetzen. Auch hier können wir sie einpacken, aber wir müssen große Teile unseres Codes ändern, um Wrapper anstelle der eigentlichen Objekte akzeptieren zu können.

Fördert Blobs
Da statische Methoden normalerweise als Dienstprogrammmethoden verwendet werden und Dienstprogrammmethoden normalerweise unterschiedliche Zwecke haben, erhalten wir schnell eine große Klasse mit nicht kohärenter Funktionalität - im Idealfall sollte jede Klasse einen einzigen Zweck innerhalb des Systems haben . Ich hätte viel lieber eine fünffache Unterrichtsstunde, solange ihre Ziele genau definiert sind.

Parameter Kriechen
Zunächst könnte diese kleine niedliche und unschuldige statische Methode einen einzelnen Parameter annehmen. Mit zunehmender Funktionalität werden einige neue Parameter hinzugefügt. Bald werden weitere Parameter hinzugefügt, die optional sind, sodass wir Überladungen der Methode erstellen (oder einfach Standardwerte in Sprachen hinzufügen, die sie unterstützen). In Kürze haben wir eine Methode, die 10 Parameter akzeptiert. Nur die ersten drei sind wirklich erforderlich, die Parameter 4-7 sind optional. Wenn jedoch Parameter 6 angegeben wird, müssen auch 7-9 ausgefüllt werden ... Hätten wir eine Klasse mit dem einzigen Zweck erstellt, das zu tun, was diese statische Methode getan hat, könnten wir dies lösen, indem wir die erforderlichen Parameter in der Konstruktor und Ermöglichen, dass der Benutzer optionale Werte über Eigenschaften oder Methoden zum gleichzeitigen Festlegen mehrerer voneinander abhängiger Werte festlegt. Wenn eine Methode auf diese Komplexität angewachsen ist,

Fordern Sie die Verbraucher auf, ohne Grund eine Instanz von Klassen zu erstellen
Eines der häufigsten Argumente ist, warum Verbraucher unserer Klasse eine Instanz zum Aufrufen dieser einzelnen Methode erstellen müssen, ohne die Instanz danach zu verwenden. Das Erstellen einer Instanz einer Klasse ist in den meisten Sprachen eine sehr, sehr billige Operation, daher ist Geschwindigkeit kein Problem. Das Hinzufügen einer zusätzlichen Codezeile zum Verbraucher ist kostengünstig, um den Grundstein für eine viel wartbarere Lösung in der Zukunft zu legen. Wenn Sie das Erstellen von Instanzen vermeiden möchten, erstellen Sie einfach einen Singleton-Wrapper Ihrer Klasse, der eine einfache Wiederverwendung ermöglicht. Dies setzt jedoch voraus, dass Ihre Klasse zustandslos ist. Wenn es nicht zustandslos ist, können Sie dennoch statische Wrapper-Methoden erstellen, die alles verarbeiten und Ihnen auf lange Sicht alle Vorteile bieten. Schließlich,

Nur ein Sith handelt absolut.
Natürlich gibt es Ausnahmen von meiner Abneigung gegen statische Methoden. Echte Utility-Klassen, die kein Aufblähungsrisiko darstellen, eignen sich hervorragend für statische Methoden - beispielsweise System.Convert. Wenn es sich bei Ihrem Projekt um ein Einzelprojekt handelt, für das keine zukünftigen Wartungsarbeiten erforderlich sind, ist die Gesamtarchitektur nicht sehr wichtig - statisch oder nicht statisch, spielt keine Rolle - die Entwicklungsgeschwindigkeit jedoch.

Standards, Standards, Standards!
Die Verwendung von Instanzmethoden hindert Sie nicht daran, auch statische Methoden zu verwenden und umgekehrt. Solange die Differenzierung begründet und standardisiert ist. Es gibt nichts Schlimmeres, als einen Blick auf eine Geschäftsschicht zu werfen, die sich über verschiedene Implementierungsmethoden erstreckt.


Wie Mark sagt, sind Polymorphismus, die Möglichkeit, die Methoden als Strategieparameter zu übergeben und Optionen zu konfigurieren / festzulegen, alles Gründe, eine Instanz zu verwenden . Beispiel: Ein ListDelimiter oder DelimiterParser könnte so konfiguriert werden, welche Trennzeichen verwendet / akzeptiert werden sollen, ob Leerzeichen von den analysierten Token abgeschnitten werden sollen, ob die Liste in Klammern gesetzt werden soll, wie eine leere / Null-Liste behandelt werden soll usw.
Thomas W

4
"Wenn ein System wächst ..." Sie Refactor?
Rodney Gitzel

3
@ user3667089 Allgemeine Argumente, die erforderlich sind, damit die Klasse logisch existiert, die ich im Konstruktor übergeben würde. Diese werden normalerweise in den meisten / allen Methoden verwendet. Methodenspezifische Argumente, die ich in dieser bestimmten Methode übergeben würde.
Mark S. Rasmussen

1
Was Sie mit einer statischen Klasse wirklich tun, ist die Rückkehr zu rohem, nicht objektorientiertem C (wenn auch speicherverwaltet) - alle Funktionen befinden sich in einem globalen Raum, es gibt keine this, Sie müssen den globalen Status verwalten usw. Also Wenn Sie prozedurale Programmierung mögen, tun Sie dies. Aber schätzen Sie, dass Sie dann viele der strukturellen Vorteile von OO verlieren.
Ingenieur

1
Die meisten dieser Gründe haben mit einer schlechten Codeverwaltung zu tun, nicht mit der Verwendung statischer Methoden. In F # und anderen funktionalen Sprachen verwenden wir überall statische Methoden (Funktionen ohne Instanzstatus) und haben diese Probleme nicht. Das heißt, F # bietet ein besseres Paradigma für die Verwendung von Funktionen (Funktionen sind erstklassig, Funktionen können curry und teilweise angewendet werden), was sie praktikabler macht als in C #. Ein größerer Grund für die Verwendung von Klassen ist, dass C # dafür entwickelt wurde. Alle Abhängigkeitsinjektionskonstrukte in .NET Core drehen sich um Instanzklassen mit deps.
Justin J Stark

89

Ich bevorzuge den statischen Weg. Da die Klasse kein Objekt darstellt, ist es nicht sinnvoll, eine Instanz davon zu erstellen.

Klassen, die nur für ihre Methoden existieren, sollten statisch bleiben.


19
-1 "Klassen, die nur für ihre Methoden existieren, sollten statisch bleiben."
Rookian

8
@Rookian warum bist du damit nicht einverstanden?
jjnguy

6
Ein Beispiel wäre das Repository-Muster. Die Klasse enthält nur Methoden. Wenn Sie Ihrem Ansatz folgen, können Sie keine Schnittstellen verwenden. => Kupplung erhöhen
Rookian

1
@Rook, im Allgemeinen sollten Leute keine Klassen erstellen, die nur für Methoden verwendet werden. In dem Beispiel, das Sie angegeben haben, sind statische Methoden keine gute Idee. Für einfache Dienstprogrammmethoden ist der statische Weg jedoch am besten.
jjnguy

9
Absolut richtig :) Mit Ihrer Bedingung "für einfache Gebrauchsmethoden" stimme ich Ihnen voll und ganz zu, aber Sie haben in Ihrer Antwort eine allgemeine Regel aufgestellt, die imo falsch ist.
Rookian

18

Wenn es keinen Grund gibt, eine Instanz der Klasse erstellen zu lassen, um die Funktion auszuführen, verwenden Sie die statische Implementierung. Warum sollten die Konsumenten dieser Klasse eine Instanz erstellen, wenn eine nicht benötigt wird?


16

Wenn Sie den Status nicht speichern müssen des Objekts müssen, müssen Sie es zunächst nicht instanziieren. Ich würde mit der einzigen statischen Methode gehen, an die Sie Parameter übergeben.

Ich würde auch vor einer riesigen Utils-Klasse warnen, die Dutzende nicht verwandter statischer Methoden hat. Dies kann in Eile unorganisiert und unhandlich werden. Es ist besser, viele Klassen mit jeweils wenigen verwandten Methoden zu haben.


6

Ich würde sagen, das statische Methodenformat wäre die bessere Option. Und ich würde die Klasse auch statisch machen, so dass Sie sich keine Sorgen machen müssen, versehentlich eine Instanz der Klasse zu erstellen.


5

Ich weiß wirklich nicht, wie die Situation hier ist, aber ich würde es als Methode in eine der Klassen einordnen, zu denen arg1, arg2 oder arg3 gehören - Wenn Sie semantisch sagen können, dass eine dieser Klassen die besitzen würde Methode.


4

Ich würde vorschlagen, dass es aufgrund der bereitgestellten Informationen schwer zu beantworten ist.

Mein Bauch ist, dass wenn Sie nur eine Methode haben und die Klasse sofort wegwerfen, Sie sie zu einer statischen Klasse machen, die alle Parameter übernimmt.

Natürlich ist es schwierig, genau zu sagen, warum Sie nur für diese eine Methode eine einzelne Klasse erstellen müssen. Ist es die typische Situation der "Utilities-Klasse", wie die meisten annehmen? Oder implementieren Sie eine Art Regelklasse, von der es in Zukunft möglicherweise mehr geben wird?

Lassen Sie diese Klasse beispielsweise steckbar sein. Dann möchten Sie eine Schnittstelle für Ihre eine Methode erstellen und dann möchten Sie, dass alle Parameter an die Schnittstelle und nicht an den Konstruktor übergeben werden, aber Sie möchten nicht, dass sie statisch ist.


3

Kann Ihre Klasse statisch gemacht werden?

Wenn ja, dann würde ich es zu einer 'Utilities'-Klasse machen, in die ich alle meine Ein-Funktions-Klassen einfügen würde.


2
Ich bin etwas anderer Meinung. In Projekten, an denen ich beteiligt bin, kann Ihre Utilities-Klasse Hunderte von nicht verwandten Methoden enthalten.
Paul Tomblin

3
@ Paul: Allgemein vereinbart. Ich hasse es, Klassen mit Dutzenden (oder ja Hunderten) von völlig unabhängigen Methoden zu sehen. Wenn Sie diesen Ansatz wählen müssen, teilen Sie diese zumindest in kleinere, verwandte Gruppen von Dienstprogrammen auf (z. B. FooUtilities, BarUtilities usw.).
John Rudy

9
eine Klasse - eine Verantwortung.
Andreas Petersson

3

Wenn diese Methode zustandslos ist und Sie sie nicht weitergeben müssen, ist es am sinnvollsten, sie als statisch zu definieren. Wenn Sie die Methode weitergeben müssen, können Sie einen Delegaten anstelle eines Ihrer anderen vorgeschlagenen Ansätze verwenden.


3

Für einfache Anwendungen und internalHelfer würde ich eine statische Methode verwenden. Für Anwendungen mit Komponenten liebe ich das Managed Extensibility Framework . Hier ist ein Auszug aus einem Dokument, das ich schreibe, um die Muster zu beschreiben, die Sie in meinen APIs finden.

  • Dienstleistungen
    • Durch eine I[ServiceName]ServiceSchnittstelle definiert.
    • Exportiert und importiert nach dem Schnittstellentyp.
    • Die einzelne Implementierung wird von der Hostanwendung bereitgestellt und intern und / oder von Erweiterungen verwendet.
    • Die Methoden auf der Serviceschnittstelle sind threadsicher.

Als erfundenes Beispiel:

public interface ISettingsService
{
    string ReadSetting(string name);

    void WriteSetting(string name, string value);
}

[Export]
public class ObjectRequiringSettings
{
    [Import]
    private ISettingsService SettingsService
    {
        get;
        set;
    }

    private void Foo()
    {
        if (SettingsService.ReadSetting("PerformFooAction") == bool.TrueString)
        {
            // whatever
        }
    }
}

2

Ich würde einfach alles im Konstruktor machen. wie so:

new MyClass(arg1, arg2, arg3);// the constructor does everything.

oder

MyClass my_object(arg1, arg2, arg3);

Was ist, wenn Sie etwas anderes als ein Objekt vom Typ MyClass zurückgeben müssen?
Bill the Lizard

13
Ich halte es für eine schlechte Praxis, Ausführungslogik in einen Konstruktor zu integrieren. Bei einer guten Entwicklung geht es um Semantik. Semantisch existiert ein Konstruktor, um ein Objekt zu konstruieren und keine anderen Aufgaben auszuführen.
mstrobl

Rechnung, wenn Sie einen Rückgabewert benötigen, übergeben Sie den Verweis auf den Wert ret: MyClass myObject (arg1, arg2, arg3, retvalue); mstrobl, wenn das Objekt nicht benötigt wird, warum es erstellen? und dieser Trick kann Ihnen in einigen Fällen tatsächlich helfen.

Wenn ein Objekt keinen Staat hat, Vars also nein, dann Instancing es wird nicht jede Zuordnung produzieren - weder auf Heap noch Stapel. Sie können sich Objekte in C ++ als nur C-Funktionsaufrufe mit einem versteckten ersten Parameter vorstellen, der auf eine Struktur verweist.
mstrobl

mstrobl, es ist wie eine AC-Funktion bei Steroiden, weil Sie private Funktionen definieren können, die der CTOR verwenden kann. Sie können auch die Variablen der Klassenmitglieder verwenden, um Daten an die privaten Funktionen zu übergeben.

0

Ein weiteres wichtiges Problem ist, ob das System in einer Multithread-Umgebung ausgeführt wird und ob es statensicher ist, eine statische Methode oder Variablen zu haben ...

Sie sollten auf den Systemstatus achten.


0

Möglicherweise können Sie die Situation insgesamt vermeiden. Versuchen Sie, umzugestalten, damit Sie erhaltenarg1.myMethod1(arg2, arg3) . Tauschen Sie arg1 gegen arg2 oder arg3 aus, wenn dies sinnvoller ist.

Wenn Sie keine Kontrolle über die Klasse von arg1 haben, dekorieren Sie sie:

class Arg1Decorator
    private final T1 arg1;
    public Arg1Decorator(T1 arg1) {
        this.arg1 = arg1;
    }
    public T myMethod(T2 arg2, T3 arg3) {
        ...
    }
 }

 arg1d = new Arg1Decorator(arg1)
 arg1d.myMethod(arg2, arg3)

Der Grund dafür ist, dass in OOP Daten und Methoden, die diese Daten verarbeiten, zusammengehören. Außerdem erhalten Sie alle Vorteile, die Mark erwähnt hat.


0

Ich denke, wenn Eigenschaften Ihrer Klasse oder der Instanz der Klasse nicht in Konstruktoren oder in Ihren Methoden verwendet werden, wird nicht empfohlen, Methoden wie 'statisches' Muster zu entwerfen. Die statische Methode sollte immer als "Hilfe" gedacht werden.


0

Je nachdem, ob Sie nur etwas tun oder etwas zurückgeben möchten, können Sie Folgendes tun:

public abstract class DoSomethingClass<T>
{
    protected abstract void doSomething(T arg1, T arg2, T arg3);
}

public abstract class ReturnSomethingClass<T, V>
{
    public T value;
    protected abstract void returnSomething(V arg1, V arg2, V arg3);
}

public class DoSomethingInt extends DoSomethingClass<Integer>
{
    public DoSomethingInt(int arg1, int arg2, int arg3)
    {
        doSomething(arg1, arg2, arg3);
    }

    @Override
    protected void doSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        // ...
    }
}

public class ReturnSomethingString extends ReturnSomethingClass<String, Integer>
{
    public ReturnSomethingString(int arg1, int arg2, int arg3)
    {
        returnSomething(arg1, arg2, arg3);
    }

    @Override
    protected void returnSomething(Integer arg1, Integer arg2, Integer arg3)
    {
        String retValue;
        // ...
        value = retValue;
    }
}

public class MainClass
{
    static void main(String[] args)
    {
        int a = 3, b = 4, c = 5;

        Object dummy = new DoSomethingInt(a,b,c);  // doSomething was called, dummy is still around though
        String myReturn = (new ReturnSomethingString(a,b,c)).value; // returnSomething was called and immediately destroyed
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.