it-swarm.dev

Welches ist eine bessere Vorgehensweise - Hilfsmethoden als Instanz oder statisch?

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

Wie auch immer, wenn ich Hilfsmethoden in meinen Klassen schreibe, neige ich dazu, sie als statisch zu deklarieren und die Felder nur zu übergeben, wenn die Hilfsmethode sie benötigt. In Anbetracht des folgenden Codes bevorzuge ich beispielsweise die Verwendung von 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 es (zumindest für meine Augen) klar macht, dass die Hilfsmethode möglicherweise Nebenwirkungen auf andere Objekte hat - auch ohne ihre Implementierung zu lesen. Ich finde, dass ich schnell Methoden entwickeln kann, die diese Praxis verwenden, und mir so beim Debuggen von Dingen helfen kann.

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

48
Ilian Pinzon

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

Wenn sie komplex sind, gilt FEST , genauer gesagt die S , die - I und das 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 makeEveryBodyAsHappyAsPossible zu einer statischen Methode machen, die die erforderlichen Parameter übernimmt. Eine weitere 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 muss OurHouse 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. OurHouse kann mit einem bloßen Mock des CookieDistributor getestet werden. Und Sie können ganz einfach entscheiden, die Regeln für die Cookie-Verteilung zu ändern.

Achten Sie jedoch darauf, dass Sie es nicht übertreiben. Zum Beispiel ist es nicht wirklich sinnvoll, ein komplexes System von 30 Klassen als Implementierung von CookieDistributor zu haben, bei dem jede Klasse nur eine winzige Aufgabe erfüllt. Meine Interpretation des SRP ist, dass es nicht nur vorschreibt, dass jede Klasse nur eine Verantwortung haben darf, sondern dass eine einzige Verantwortung von einer einzigen Klasse wahrgenommen werden sollte.

Bei Grundelementen oder Objekten, die Sie wie Grundelemente verwenden (z. B. Objekte, die Punkte im Raum, Matrizen oder ähnliches darstellen), sind statische Hilfsklassen sehr sinnvoll. Wenn Sie die Wahl haben und dies wirklich sinnvoll ist, können Sie der Klasse, die die Daten darstellt, tatsächlich eine Methode hinzufügen, z. Es ist sinnvoll, dass ein Point eine add -Methode hat. Übertreiben Sie es nicht.

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

23
back2dos

Es ist eine bekannte Redewendung, Methoden von Dienstprogrammklassen static zu deklarieren, daher müssen solche Klassen niemals instanziiert werden. 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 # spöttische Frameworks gibt, die selbst dies erreichen können, aber sie sind nicht alltäglich und zumindest einige von ihnen sind kommerziell). Also wenn eine Hilfsmethode eine externe Abhängigkeit hat (z. B. eine DB), die es - also ihre Aufrufer - schwierig macht, einen Unit-Test durchzuführen, ist es besser, sie als nicht statisch zu deklarieren. Dies ermöglicht die Abhängigkeitsinjektion, wodurch die Aufrufer der Methode für Unit-Tests einfacher werden.

Aktualisieren

Erläuterung: In den obigen Abschnitten werden Dienstprogrammklassen behandelt, die nur Hilfsmethoden auf niedriger Ebene und (normalerweise) keinen Status enthalten. Hilfsmethoden in zustandsbehafteten 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 hauptsächlich mit einer Instanz der Klasse B arbeitet, hat möglicherweise tatsächlich einen besseren Platz in der Klasse B. Wenn ich mich jedoch entscheide, sie dort zu belassen, wo sie ist, bevorzuge ich Option 1, da es einfacher und leichter zu lesen ist.

18
Péter Török

Was möchten Sie lieber in Ihrem eigenen Code tun?

Ich bevorzuge # 1 (this->DoSomethingWithBarImpl();), es sei denn natürlich, die Hilfsmethode benötigt keinen Zugriff auf die Daten/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 viel schwieriger zu lesen, wenn ich mich für # 2 entscheiden würde. Mit # 1 habe ich immer die Mitglieder und die Instanz zur Verfügung. Im Vergleich zu vielen OO-basierten Implementierungen neige ich dazu, viel Kapselung und sehr wenige Mitglieder pro Klasse zu verwenden. Was die Nebenwirkungen betrifft - ich bin damit einverstanden, gibt es oft eine Zustandsvalidierung, die erweitert die Abhängigkeiten (zB Argumente, die man übergeben müsste).

# 1 ist viel einfacher zu lesen, zu warten und hat gute Leistungsmerkmale. 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 # 2 eine gute gemeinsame Lösung (z. B. die Standardeinstellung) in einer Codebasis wäre, wäre ich besorgt über die Struktur des Designs: Tun die Klassen zu viel? Gibt es nicht genug Kapselung oder nicht genug Wiederverwendung? Ist die Abstraktionsebene hoch genug?

schließlich scheint # 2 redundant zu sein, wenn Sie OO Sprachen verwenden, in denen Mutationen unterstützt werden (z. B. eine const -Methode).

4
justin