Precondition Methoden sind Gift für die API

Ich sehe öfters eine API, bei der ich vor dem eigentlichen Aufruf der gewünschten Methode bzw. nach dem Erzeugen des Objekts zuerst eine Initialize oder Configure Methode aufrufen muss.

   1: var foo = new Foo();

   2: foo.Initialize(Bar bar);

   3: foo.Execute();

Das finde ich persönlich unschön, schließlich muss der Aufrufer somit die Komponente gut kennen. Besser wäre, wenn nach der Erzeugung nur die Initialize Methode zur Verfügung stünde. Erst nach dem Aufruf selbiger sollte es möglich sein die Execute Methode aufzurufen.

Hier zwei Beispiele, wie sich das technisch lösen lässt:

   1: public class Foo

   2: {

   3:     private Foo() {}

   4:  

   5:     public static Foo Initialize(Bar bar)

   6:     {

   7:         //do your stuff

   8:         return new Foo();

   9:     }

  10:  

  11:     public void Execute()

  12:     {

  13:         //do something

  14:     }

  15: }

Auf Foo wird dann wie folgt zugegriffen (beachte: this ist in diesem Fall eine Instanz von Bar):

   1: var foo = Foo.Initialize(this);

   2: foo.Execute();

 

Hier eine Lösung für die Verwendung in einem IoC Container. Statt Foo und Bar mit kleinen Änderungen.

Interface1:

   1: public interface ISetupParallelization

   2: {

   3:     IRunParallelization SetupWith(ParallelizationConfig config);

   4: }

Interface2:

   1: public interface IRunParallelization

   2: {

   3:     ParallelizationStatus Run(Queue<dynamic> jobs);       

   4: }

Implementierung beider Interfaces in einer Klasse:

   1: public class CalculationParallelizer : ISetupParallelization, IRunParallelization

   2: {

   3:     public CalculationParallelizer()

   4:     {

   5:     }

   6:  

   7:     public ParallelizationStatus Run(Queue<dynamic> jobs)

   8:     {

   9:         //do something        

  10:     }

  11:  

  12:     public IRunParallelization SetupWith(ParallelizationConfig config)

  13:     {

  14:         _config = config;

  15:         return this;

  16:     }    

  17: }

 

Im vorliegenden Fall würde im Container nur der Service ISetupParallelization registriert werden. Wenn dir der Beitrag gefallen hat, dann vote dafür und/oder hinterlasse einen Kommentar. Hier geht es zu Videos zum Thema von IoC.

Mit Tag(s) versehen: ,

7 thoughts on “Precondition Methoden sind Gift für die API

  1. Stephan 20. März 2014 um 12:43 Reply

    Hallo,

    Ich geb Dir auf der einen Seite Recht, auf der anderen muss ich Dir auch widersprechen.
    Ich finde dass das Erzeugen der Komponenten bzw. Klassen und das Initialisieren zwei unterschiedliche Schritte sind. Diese zu vereinen mag zwar in vielen Fällen praktisch sein, kann aber auch zu Problemen zu führen. Ich verwende sehr viele UserControls (Winforms) teilweise auf komplexen Forms. Da erzeuge ich alle Controls über den Designer (also ich leg sie ab), initialisiert werden sie aber erst, wenn z.B. der Reiter auf dem sie sich befinden aktiviert werden.
    Der Preis dafür ist natürlich, dass man die Init.-Routine kennen muss. Ich hab mir jetzt fest angewöhnt so eine Init-Routine für UserControls zu schreiben, wodurch es quasi eine Konvention geworden ist.

    Gefällt mir

    • Uli Armbruster 20. März 2014 um 18:19 Reply

      Hey Stephan, UI ist für mich auch immer ein eigenes Süppchen. Da gelten oft andere Konventionen und Guidelines als in der Infrastruktur oder der Business Logik. Da bin ich also voll bei dir!

      Gefällt mir

  2. Norbert Eder 20. März 2014 um 14:17 Reply

    Aus meiner Sicht ist die Abhandlung zu allgemein gehalten. Grundsätzlich empfiehlt es sich, die Initialisierung über einen parametrisierten Konstruktor zu realisieren. In komplexeren Fällen kann auch schon mal ein Builder zum Einsatz kommen.

    Gefällt mir

    • Uli Armbruster 20. März 2014 um 18:24 Reply

      Hey Norbert, danke fürs Feedback. Parameterisierter Konstruktor hat 2 Nachteile:
      – Funktioniert nicht beim Einsatz von DI Containern, wenn die Daten zur Initialisierung erst zur Laufzeit zur Verfügung stehen.
      – Der Konstruktor macht die API nicht gerade sprechend. Aussagekräftige Methoden können da gerade denen, die nicht viel mit der API arbeiten, helfen.

      Last but not least wäre zu klären, wann die Aufgabe eines Konstruktors erledigt ist und bereits neues Verhalten beginnt, denn ein Konstruktor sollte ja nur das Objekt in einen initialen, sauberen Zustand zu versetzen. Er soll darüber hinaus keine weitere Logik enthalten. Von berechtigten Ausnahmen mal abgesehen.

      Nutzt ihr bei euch einen DI Container?

      Gefällt mir

      • Norbert Eder 20. März 2014 um 18:34 Reply

        Wenn wir das Thema DI vorerst weg lassen:

        Vielleicht muss man hinterfragen, was den eine Initialisierung genau tun soll. In meinem Verständnis reden wir hier notwendigen Informationen, wie beispielsweise Host und Port unter dem ein Server zu laufen hat. Ohne diese Information kann er nicht gestartet werden (wobei natürlich Standard-Einstellungen herangezogen werden könnten). Hierfür ist es durchaus legitim den Konstruktor zu verwenden. In meinen Augen würde dies eine API sogar vereinfachen, da beispielweise das Erstellen einer Instanz nur unter Angabe der hierfür notwendigen Einstellungen möglichen ist.

        Wenn es hierbei nicht nur um das Setzen einiger Einstellungen, sondern vielleicht um Logik geht, dann stellt sich die Frage, ob dies in dieser Klasse richtig aufgehoben ist und nicht vielleicht überhaupt etwas á la Builder-Pattern zum Einsatz kommt. Hierfür müsstest du etwas mehr über den Kontext deines Posts erzählen.

        Bezüglich DI:
        Ja, verwende ich aber nicht in allen Projekten. Eigentlich versuche ich ohne auszukommen, da sehr viele Dinge dadurch eigentlich erschwert werden.

        Ich glaube dass zur Klärung ein konkretes Beispiel notwendig wäre.

        Gefällt mir

        • Uli Armbruster 21. März 2014 um 11:59 Reply

          Hey Nobert, denke auch, dass das von konkreten Beispielen abhängt. Ein Beispiel würde jetzt auch nur einen Fall abdecken. Im Post ging es mir primär darum technische Möglichkeiten aufzuzeigen.

          Trotzdem noch drei Gedanken von mir dazu:
          Fall1: Wenn du kein DI Container einsetzt, dann erzeugst du dir die Objekte ja selbst im Code. Damit ist vom zeitlichen Ablauf her die Erstellung meistens sowieso dann, wenn auch die benötigten „Init-Informationen“ vorliegen. Beispielsweise die Konfiguration. Und dann würde ich es – wie du schon sagst – auf jeden Fall so handhaben, dass die Konfiguration im Konstruktor übergeben werden muss, weil das der eigentlichen Aufgabe des Konstruktors entspricht. Übrigens lässt sich das auch mit einem DI Container abbilden, indem die Konfiguration im Container registriert wird.

          Fall2: Wenn der Fall anders herum liegt, dass du das Objekt erzeugst, irgendwo hinein reichst und dort erst die Daten der Konfiguration ermittelt werden, dann wäre das durch das Interface Segregation Principle, wie es in Pattern zwei verwendet wird, genau das richtige.

          Fall3: Wenn es Standardwerte gibt, beispielsweise bei einem SMTP Objekt, dann ist das Überschreiben mit speziellen Werten sowieso eine optionale Geschichte, hat damit nichts im Konstruktor verloren. Dann wäre die Init-Methode zum einen schlecht benannt (besser wäre: UseDedicatedSmptSettings(SmtpSettings settings), zum anderen handelt es sich dann nicht mehr um eine Precondition Method.

          Gefällt 1 Person

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: