Was ist eine bessere Praxis - Hilfsmethoden als Instanz oder statisch?

48

Diese Frage ist subjektiv, aber ich war nur neugierig, wie die meisten Programmierer das angehen. Das folgende Beispiel ist in Pseudo-C #, dies sollte jedoch auch für Java, C ++ und andere OOP-Sprachen gelten.

Wenn ich in meinen Klassen Hilfsmethoden schreibe, neige ich dazu, sie als statisch zu deklarieren und die Felder nur dann zu übergeben, wenn die Hilfsmethode sie benötigt. In Anbetracht des folgenden Codes bevorzuge ich beispielsweise den Methodenaufruf Nr. 2 .

class Foo
{
  Bar _bar;

  public void DoSomethingWithBar()
  {
    // Method Call #1.
    DoSomethingWithBarImpl();

    // Method Call #2.
    DoSomethingWithBarImpl(_bar);
  }

  private void DoSomethingWithBarImpl()
  {
    _bar.DoSomething();
  }

  private static void DoSomethingWithBarImpl(Bar bar)
  {
    bar.DoSomething();
  }
}

Mein Grund dafür ist, dass (zumindest für meine Augen) klar wird, dass die Helfer-Methode einen möglichen Nebeneffekt auf andere Objekte hat - auch ohne deren Implementierung zu lesen. Ich stelle fest, dass ich schnell Methoden finden kann, die diese Praxis anwenden und mir so beim Debuggen von Dingen helfen.

Was bevorzugen Sie in Ihrem eigenen Code und was sind Ihre Gründe dafür?

Ilian
quelle
3
Ich würde C ++ aus dieser Frage entfernen: In C ++ würden Sie es natürlich zu einer kostenlosen Methode machen, da es keinen Sinn macht, es willkürlich in einem Objekt zu verstecken: Es deaktiviert ADL.
Matthieu M.
1
@MatthieuM .: Freie Methode oder nicht, es spielt keine Rolle. Die Absicht meiner Frage war zu fragen, ob es irgendwelche Vorteile gibt, Hilfsmethoden als Instanzen zu deklarieren. In C ++ könnte die Wahl also Instanzmethode oder freie Methode / Funktion sein. Sie haben eine gute Vorstellung von ADL. Wollen Sie damit sagen, dass Sie kostenlose Methoden bevorzugen? Vielen Dank!
Ilian
2
Für C ++ werden kostenlose Methoden empfohlen. Sie erhöhen die Kapselung (indirekt, da sie nur auf die öffentliche Schnittstelle zugreifen können) und spielen sich aufgrund von ADL (insbesondere in Vorlagen) immer noch gut. Ich weiß jedoch nicht, ob dies auf Java / C # anwendbar ist. C ++ unterscheidet sich stark von Java / C #, daher erwarte ich, dass sich die Antworten unterscheiden (und daher halte ich es nicht für sinnvoll, nach den drei Sprachen zusammen zu fragen).
Matthieu M.
1
Ohne zu versuchen, jemanden zu ärgern, habe ich noch nie einen guten Grund gehört, keine statischen Methoden anzuwenden. Ich benutze sie, wo immer ich kann, da sie klarer machen, was die Methode bewirkt.
Sridhar Sarnobat

Antworten:

23

Das hängt wirklich davon ab. Wenn die Werte, mit denen Ihre Helfer arbeiten, primitiv sind, sind statische Methoden eine gute Wahl, wie Péter hervorhob.

Wenn sie komplex sind, dann SOLID gilt, insbesondere die S , die ich und die D .

Beispiel:

class CookieJar {
      function takeCookies(count:Int):Array<Cookie> { ... }
      function countCookies():Int { ... }
      function ressuplyCookies(cookies:Array<Cookie>
      ... // lot of stuff we don't care about now
}

class CookieFan {
      function getHunger():Float;
      function eatCookies(cookies:Array<Cookie>):Smile { ... }
}

class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieJar;
      function makeEveryBodyAsHappyAsPossible():Void {
           //perform a lot of operations on jake, jane and the cookies
      }
      public function cookieTime():Void {
           makeEveryBodyAsHappyAsPossible();
      }
}

Hier geht es um Ihr Problem. Sie können machen makeEveryBodyAsHappyAsPossibleeine statische Methode, die in den notwendigen Parametern nehmen. Eine andere Option ist:

interface CookieDistributor {
    function distributeCookies(to:Array<CookieFan>):Array<Smile>;
}
class HappynessMaximizingDistributor implements CookieDistributor {
    var jar:CookieJar;
    function distributeCookies(to:Array<CookieFan>):Array<Smile> {
         //put the logic of makeEveryBodyAsHappyAsPossible here
    }
}
//and make a change here
class OurHouse {
      var jake:CookieFan;
      var jane:CookieFan;
      var cookies:CookieDistributor;

      public function cookieTime():Void {
           cookies.distributeCookies([jake, jane]);
      }
}

Jetzt OurHousemüssen Sie nicht mehr über die Feinheiten der Cookie-Verteilungsregeln Bescheid wissen. Es muss erst jetzt ein Objekt sein, das eine Regel implementiert. Die Implementierung wird in ein Objekt abstrahiert, dessen alleinige Verantwortung darin besteht, die Regel anzuwenden. Dieses Objekt kann isoliert getestet werden. OurHousekann mit einem bloßen Schein der getestet werden CookieDistributor. Und Sie können leicht entscheiden, die Regeln für die Cookie-Verteilung zu ändern.

Achten Sie jedoch darauf, dass Sie es nicht übertreiben. Ein komplexes System von 30 Klassen ist beispielsweise die Implementierung von CookieDistributor, bei der jede Klasse nur eine winzige Aufgabe erfüllt, was nicht wirklich Sinn macht. Meine Interpretation des SRP ist, dass es nicht nur vorschreibt, dass jede Klasse nur eine Verantwortung hat, sondern dass eine einzelne Verantwortung von einer einzelnen Klasse ausgeführt werden sollte.

Bei Primitiven oder Objekten, die Sie wie Primitive verwenden (z. B. Objekte, die Punkte im Raum, Matrizen oder ähnliches darstellen), sind statische Hilfsklassen sehr sinnvoll. Wenn Sie die Wahl haben und es wirklich sinnvoll ist, können Sie der Klasse, die die Daten darstellt, tatsächlich eine Methode hinzufügen, z. B. ist es sinnvoll Point, eine addMethode zu haben . Wieder nicht übertreiben.

Abhängig von Ihrem Problem gibt es verschiedene Möglichkeiten, dies zu tun.

back2dos
quelle
18

Es ist allgemein bekannt, Methoden von Utility-Klassen zu deklarieren static, sodass solche Klassen niemals instanziiert werden müssen. Das Befolgen dieser Redewendung erleichtert das Verständnis Ihres Codes, was eine gute Sache ist.

Es gibt jedoch eine ernsthafte Einschränkung dieses Ansatzes: Solche Methoden / Klassen können nicht leicht verspottet werden (obwohl AFAIK zumindest für C # Spott-Frameworks gibt, die dies auch erreichen können, aber sie sind nicht alltäglich und zumindest einige von ihnen kommerziell). Also , wenn eine Hilfsmethode hat jede externe Abhängigkeit (zB DB) , die es macht - so seine Anrufer - schwer zu Unit - Test, ist es besser, es nicht statisch zu deklarieren . Dies ermöglicht eine Abhängigkeitsinjektion, wodurch die Aufrufer der Methode leichter einem Komponententest unterzogen werden können.

Aktualisieren

Erläuterung: Die obigen Ausführungen beziehen sich auf Dienstprogrammklassen, die nur Hilfsmethoden auf niedriger Ebene und (normalerweise) keinen Status enthalten. Hilfsmethoden in stateful Nicht-Utility-Klassen sind ein anderes Problem. Entschuldigung für die Fehlinterpretation des OP.

In diesem Fall spüre ich einen Codegeruch: Eine Methode der Klasse A, die in erster Linie auf einer Instanz der Klasse B ausgeführt wird, hat möglicherweise einen besseren Platz in Klasse B. Wenn ich sie jedoch dort belasse, wo sie ist, bevorzuge ich Option 1. da es einfacher und leichter zu lesen ist.

Péter Török
quelle
Können Sie die Abhängigkeit nicht einfach an die statische Hilfsmethode übergeben?
Ilian
1
@JackOfAllTrades, wenn der einzige Zweck der Methode darin besteht, mit einer externen Ressource umzugehen, gibt es kaum einen Grund, diese Abhängigkeit einzuschleusen. Andernfalls kann es sich um eine Lösung handeln (obwohl die Methode im letzteren Fall wahrscheinlich gegen das Prinzip der einheitlichen Verantwortung verstößt und somit für ein Refactoring in Frage kommt).
Péter Török
1
Statik wird leicht verspottet - Mit Microsoft Moles oder Fakes (abhängig von VS2010 oder VS2012 +) können Sie eine statische Methode durch Ihre eigene Implementierung in Ihren Tests ersetzen. Macht die alten Spott-Frameworks überflüssig. (Es macht auch traditionelle Verspottungen und kann auch konkrete Klassen verspotten). Es ist frei von MS durch Extension Manager.
gbjbaanb
4

Was bevorzugen Sie in Ihrem eigenen Code zu tun

Ich bevorzuge # 1 ( this->DoSomethingWithBarImpl();), es sei denn, die Hilfsmethode benötigt natürlich keinen Zugriff auf die Daten / die Implementierung der Instanz static t_image OpenDefaultImage().

und was sind deine gründe dafür?

öffentliche Schnittstelle und private Implementierung und Mitglieder sind getrennt. Programme wären weitaus schwieriger zu lesen, wenn ich mich für # 2 entschieden hätte. Mit # 1 habe ich immer die Mitglieder und Instanz zur Verfügung. Im Vergleich zu vielen OO-basierten Implementierungen verwende ich in der Regel viel Encapsualtion und nur sehr wenige Member pro Klasse. Was die Nebenwirkungen angeht - damit bin ich einverstanden. Oft gibt es eine Statusüberprüfung, die die Abhängigkeiten erweitert (z. B. Argumente, die übergeben werden müssten).

Nummer 1 ist viel einfacher zu lesen, zu warten und weist gute Leistungsmerkmale auf. Natürlich wird es Fälle geben, in denen Sie eine Implementierung freigeben können:

static void ValidateImage(const t_image& image);
void validateImages() const {
    ValidateImage(this->innerImage());
    ValidateImage(this->outerImage());
}

Wenn Nr. 2 eine gute gemeinsame Lösung (z. B. die Standardlösung) in einer Codebasis wäre, würde mich die Struktur des Designs interessieren: Tun die Klassen zu viel? Gibt es nicht genug Verkapselung oder nicht genug Wiederverwendung? Ist die Abstraktionsstufe hoch genug?

Schließlich scheint # 2 überflüssig zu sein, wenn Sie OO-Sprachen verwenden, bei denen Mutation unterstützt wird (z const. B. eine Methode).

Justin
quelle