Laden...

Guter Stil: Properties oder Konstruktor mit vielen Parametern?

Erstellt von Uwe81 vor 13 Jahren Letzter Beitrag vor 13 Jahren 6.666 Views
U
Uwe81 Themenstarter:in
282 Beiträge seit 2008
vor 13 Jahren
Guter Stil: Properties oder Konstruktor mit vielen Parametern?

Hallo!

Ich stolpere immer wieder über folgendes Problem:

Ein Objekt braucht, um sinnvoll arbeiten zu können, 6 andere Objekte von außen.

Was mache ich nun? Übergebe ich alle Objekte im Konstruktor? Dann habe ich eine laaaange Parameterliste.

Dürfen die Werte als Property gesetzt werden? Schöner bei der Initialisierung, aber man kann leicht was vergessen. Oder später ein Property nochmal setzen, was nicht wirklich sinnvoll sein muss.

Was ist da üblich?

Viele Grüße,
Uwe

3.430 Beiträge seit 2007
vor 13 Jahren

Hallo Uwe81,

das ist ein Stück weit Geschmacksache.
Wobei ich immer soweit wie nötig den Konstruktoren verwende.
Denn manchmal müssen die Objekte schon beim Erstellen vorhanden sein damit das Objekt arbeiten kann.
Und wenn man nur die Properties verwendet dann ist nicht gewährleistet dass auch alle Properties gesetzt wurden.
Somit ist es in so einem Fall praktischer wenn man direkt im Konstruktoren die Parameterliste hat.

Also meine Vorgehensweise: Konstruktoren für alle Pflichtproperties (die es schon zum Erstellen braucht) und für die restlichen (optionalien) Properties gibts die Properties 😃

Gruss
Michael

U
14 Beiträge seit 2010
vor 13 Jahren

Erforderliche oder für das Objekt typische/essentielle Werte im Konstruktor aufführen (ggf. mit Überladungen), dem Rest Standardwerte zuweisen, die später ggf. über Objektinitialisierer initialisiert werden.

3.170 Beiträge seit 2006
vor 13 Jahren

Hallo,

wenn das Objekt die anderen Objekte tatsächlich braucht, um sinnvoll arbeiten zu können, gehört die Übergabe IMHO in den Konstruktor. Die Parameterliste kann ruhig lang sein, das macht ja nix.

Wenn zudem nicht sinnvoll ist, ein Property nachher erneut zu setzen, sollte dieses dann auch keinen Setter haben.

Wenn die Parameterlisten tatsächlich unerträglich lang werden, solltest Du ggf. Deine Architektur nochmal überdenken.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

U
Uwe81 Themenstarter:in
282 Beiträge seit 2008
vor 13 Jahren

Wenn die Parameterlisten tatsächlich unerträglich lang werden, solltest Du ggf. Deine Architektur nochmal überdenken.

Danke für die Aufmunterung.... Diese Parameterlisten kommen aus einem Refactoring, weil ich vorher eigentlich eine Gott-Klasse mit 10 Zuständigkeiten hatte und jedes Objekt bekam die Gottklasse und ein oder zwei weitere Parameter.

Jetzt habe ich die Zuständigkeiten in einzelne Interfaces gepackt, aber nun braucht eben jedes Objekt zwei bis vier der zehn Interfaces plus ein oder zwei Parameter.

Also insgesamt 5 oder 6 Parameter.

U
208 Beiträge seit 2008
vor 13 Jahren

Wenn die Parameterlisten tatsächlich unerträglich lang werden, solltest Du ggf. Deine Architektur nochmal überdenken.

Das war mein aller erster Gedanke... So viele Abhängigkeiten untereinander sind auf jeden Fall nicht gut. Schau dir mal das Konzept der Event-Based-Components an. Habe zwar noch nicht praktisch mit EBCs gearbeitet, aber von der Theorie her klingt das alles (für mich jedenfalls) mehr als cool. 😃
Sollte auf jeden Fall helfen, die ganzen Abhängigkeiten zu entfernen.

Ansonsten würde ich aber zu der Konstruktor-Variante greifen. Was spricht denn gegen besonders lange Parameterlisten???

F
10.010 Beiträge seit 2004
vor 13 Jahren

Schon mal "Dependency Injection" gehört?
Das ist genau das was Du hier machst, und da das ein ganz normaler std ist, gibt es da haufenweise Ansätze für.

Eine der "einfachsten" ist ein DI Container wie Unity, Structurmap, Ninject oder LightCore.
Letzterer ist von Peter Bucher, und es gibt da ein paar Beispiele hier im Forum.

F
60 Beiträge seit 2010
vor 13 Jahren

effective java item 2:
consider a builder when faced with many construktor parameters

vor allem wenn noch viele optionale parameter dabei sind, kann man gut folgendes machen:


// JavaBeans Pattern
TestObject t = new TestObject();
t.setParam1 = ...;
t.setParam2 = ...;
t.setParam3 = ...;
// ...
// Objekterstellung fertig

allerdings hat dein objekt während der erstellung einen inkonsistenten zustand, dafür gibt es das builder pattern:


 public class Test
    {
        private readonly int req1;
        private readonly int req2;
        private readonly int opt1;
        private readonly int opt2;
        private readonly int opt3;
        private readonly int opt4;

        private Test(int req1, int req2, int opt1, int opt2, int opt3, int opt4)
        {
            this.req1 = req1;
            this.req2 = req2;
            this.opt1 = opt1;
            this.opt2 = opt2;
            this.opt3 = opt3;
            this.opt4 = opt4;
        }

        public class Builder
        {
            // req param
            private readonly int req1;
            private readonly int req2;

            // opt param, werden auf ihre defaults gesetzt
            private int opt1 = 1;
            private int opt2 = 2;
            private int opt3 = 3;
            private int opt4 = 4;

            public Builder(int req1, int req2)
            {
                this.req1 = req1;
                this.req2 = req2;
            }

            public Builder Opt1(int i)
            {
                opt1 = i;
                return this;
            }

            public Builder Opt2(int i)
            {
                opt2 = i;
                return this;
            }

            public Builder Opt3(int i)
            {
                opt3 = i;
                return this;
            }

            public Builder Opt4(int i)
            {
                opt4 = i;
                return this;
            }

            public Test Build()
            {
                return new Test(req1, req2, opt1, opt2, opt3, opt4);
            }
        }
        public static void main(string[] args)
        {
            // Opt4 omitted
            Test t = new Test.Builder(1, 2).Opt1(3).Opt2(4).Opt4(5).Build();
        }
    }

so musst du nicht den konstruktor in x variationen bereit stellen und hältst dein objekt trotzdem konsistent.

// edit: sorry, ich hoff der post ist nicht zu länglich

3.511 Beiträge seit 2005
vor 13 Jahren

Eine Variante die ich gerne nutze ist, das ich für den Konstruktor ein extra Objekt übergebe, in dem die Parameter hängen. Also ungefähr so


public class Dummy
{
  public Dummy(DummySettings settings) 
  {
    // Bla
  }
}

public class DummySettings
{
   public string Foo { get; set; }
   public string Bar { get; set; }
   // Usw...
}

Das hat den großen Vorteil, wenn die Klasse Dummy mal mit einem weiteren Parameter versehen werden muss, bleiben alle vorherigen Aufrufe unberührt und ich muss keinen weiteren Konstruktor erzeugen.

Sowas sieht man auch häufig im .NET Framework selber (z.B. XmlWriterSettings).

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

U
Uwe81 Themenstarter:in
282 Beiträge seit 2008
vor 13 Jahren

Eine Variante die ich gerne nutze ist, das ich für den Konstruktor ein extra Objekt übergebe, in dem die Parameter hängen.

Das wäre vielleicht eine Möglichkeit.

Nochmal etwas zu dem Hintergrund. Es geht eigentlich um einen Presenter zwischen einem Model (der GUI, keine Buissiness-Logik) und eigenen Controls. Das Model war bisher eine Gott-Klasse, die zwar intern an weitere Klassen delegierte aber nah außen ungefähr 10 Verantwortlichkeiten hatte. Diese habe ich aufgeteilt.

Nur braucht halt jeder Presenter etwa 3 bis 4 dieser Verantwortlichkeiten (nicht immer dieselben).

Vermutlich werde ich nun alle Klassen, die diese Verantwortlichkeiten haben, in einen Container packen. Dann gibt es in jedem Presenter zwei Konstruktoren: Einer, der wirklich alle Objekte einzeln übergeben bekommt (gut zum Testen und für spezielle Fälle) und einen, der den Container bekommt und sich selbst das Wesentliche rauszieht.

Viele Grüße,
Uwe

F
10.010 Beiträge seit 2004
vor 13 Jahren

Meine Antwort überlesen?

DI-Container machen die Arbeit für dich.

S
489 Beiträge seit 2007
vor 13 Jahren

Meine Antwort überlesen?

DI-Container machen die Arbeit für dich.

Dem stimme ich absolut zu. Du wirst Dich irgendwann sehr ärgern (@Khalid: Du ganz sicher), wenn Du Dir jetzt nicht erstmal Dependency Injection und IoC anschaust bevor Du auch nur ein Stückchen weiter machst. Ein Beispiel hierzu wäre Spring.NET IoC.

Mit dem Spring.NET IoC kannste dann auch wählen ob Du Property Injection oder Constructor Injection nutzen willst.

U
Uwe81 Themenstarter:in
282 Beiträge seit 2008
vor 13 Jahren

Meine Antwort überlesen?

DI-Container machen die Arbeit für dich.

Nein, nicht überlesen. Bin mir nur aus verschiedenen Gründen unsicher, ob ich das tun sollte (kann aber auch daran liegen, dass ich über DI-Container einiges gelesen aber sie noch nicht verwendet habe).

Problem 1: Es ist nur eine GUI-Komponente, die von anderen Verwendet wird. Jeder, der diese Komponente verwendet, hat also eine Abhängigkeit auf den DI-Container. Würde ich aber akzeptieren, wenn das ansonsten wirklich vorteilhaft ist.

Problem 2: Mir ist nicht ganz klar, wie ich mir über einen DI-Container eine konkrete Instanz hole. Wenn meine Klasse als Parameter einen IFoo und einen IBar bekommt, dann geht es nicht darum, dass er eine neue Instanz vom Typ IFoo bekommt und nur den konkreten Typ nicht kennen soll. Sondern der Parameter wird ein konkretes bereits existierendes Objekt sein. Dieses muss aber kein Singleton sein, es kann also sein, dass ich zur Laufzeit drei verschiedene Objekte vom Typ IFoo habe. Irgendwie muss ich dem Objekt also sagen, welches IFoo er sich holen soll.

3.511 Beiträge seit 2005
vor 13 Jahren

@Khalid: Du ganz sicher

Unser gesamtes Framework setzt auf IoC/DI. Es gibt aber immer Stellen, wo man trotzdem anders verfahren muss. Nur weil man eine Technik einsetzt, muss man diese nicht zwingend verwenden...

"Jedes Ding hat drei Seiten, eine positive, eine negative und eine komische." (Karl Valentin)

F
10.010 Beiträge seit 2004
vor 13 Jahren

@Uwe81:
Man kann einen Container auch zur not in 15 Zeilen nachbauen.
http://www.kenegozi.com/Blog/2008/01/17/its-my-turn-to-build-an-ioc-container-in-15-minutes-and-33-lines.aspx

Wie SeboStone schon sagte, wirst du dich ärgern, wenn Du es nicht tust, und später dann siehst wieviel arbeit du dir erspart hättest.

Für das Problem mit der Auswahl des Containers gibt es das CommonServiceLocator Interface.
Für jeden einigermaßen vernünftigen DI Container gibt es Implementationen für das Interface.

Der SL wird dann ggf von dir benutzt um die Objekte anzulegen oder Services zu holen.

Aber das ist eigentlich die falsche Denkweise.
Normalerweise legst du beim entwickeln deiner Objekte fest, welche Daten und Services es braucht, und der Aufrufer ist zuständig für das setzen dieser Daten.

Controller/Presenter benötigen solche SL, aber nicht deine GUI Componente, insofern stellt sich die frage für dich doch eigentlich garnicht.

Deine Kunden sind dafür zuständig dir die Daten zu liefern und nicht dein Control.

U
Uwe81 Themenstarter:in
282 Beiträge seit 2008
vor 13 Jahren

Ich verstehe glaub ich wirklich nicht, wie mir ein DI-Container hier helfen könnte.

Angehängt ist mal ein Beispiel. Wie könnte das mit DI-Containern besser werden.


interface IFoo { }
interface IBar { }
interface IBaz { }

class Foo1 : IFoo { }
class Foo2 : IFoo { }
class Bar : IBar { }
class Baz : IBaz { }

class MyClass {
    public MyClass(IFoo foo1, IFoo foo2, IBar bar, IBaz baz1, IBaz baz2) {
    }
}

class MyOtherClass {
    Foo1 foo1;
    Foo2 foo2A;
    Foo2 foo2B;
    Bar barA;
    Bar barB;
    Baz bazA;
    Baz bazB;
    Baz bazC;

    MyClass CreateMyClass() {
        return new MyClass(foo1, foo2B, barB, bazC, bazA);
    }
}

Aber das ist eigentlich die falsche Denkweise.
Normalerweise legst du beim entwickeln deiner Objekte fest, welche Daten und Services es braucht, und der Aufrufer ist zuständig für das setzen dieser Daten.

Controller/Presenter benötigen solche SL, aber nicht deine GUI Componente, insofern stellt sich die frage für dich doch eigentlich garnicht.

Deine Kunden sind dafür zuständig dir die Daten zu liefern und nicht dein Control.

Naja, ich entwickel nicht ein Control sondern eine Sammlung von Controls, die miteinander interagieren. Allerdings muss diese Sammlung Anwendungsspezifisch erweiterbar sein. Dennoch sind die Schichten "Model" und "Presenter" bereits vorhanden, um die Interaktion der einzelnen Controls zu steuern.

Die Presenter (die derzeit die lange Parameterliste haben) werden i.d.R. in einer Factory erzeugt werden. Es ist also eher interne Struktur, als das der User wirklich damit in Berührung käme. Aber auch die interne Struktur sollte halt "schön" sein.

Viele Grüße,
Uwe

F
10.010 Beiträge seit 2004
vor 13 Jahren

Na was denn nun?
Erst ist es ein UserControl und jetzt eher ein Framework.

Und so abstracte Fragen kann man nicht beantworten, denn es ist überall so, wenn man beim Design der Architektur etwas Vergessen/anders angedacht hat, kann man nachträglich schlecht eine komplett andere Architektur dranbasteln.
Du müsstest erstmal verstehen wie IOC/DI wirklich funktioniert und danach dann die ganze Architektur neu aufbauen.

Deine Factory würde dann z.b. vom container übernommen, Du müsstest je nach Container auch die Unterscheidungen machen, wann Foo1 oder Foo2 als IFoo genommen werden ( warum überhaupt?).

Meist ergibt sich nach einer Analyse mit der anderen Sichtweise eine deutlich einfachere Architektur.

U
Uwe81 Themenstarter:in
282 Beiträge seit 2008
vor 13 Jahren

Na was denn nun?
Erst ist es ein UserControl und jetzt eher ein Framework.

Oben stand:

Nochmal etwas zu dem Hintergrund. Es geht eigentlich um einen Presenter zwischen einem Model (der GUI, keine Buissiness-Logik) und eigenen Controls. Das Model war bisher eine Gott-Klasse, die zwar intern an weitere Klassen delegierte aber nah außen ungefähr 10 Verantwortlichkeiten hatte. Diese habe ich aufgeteilt.

Es geht um folgendes:
Wir betreiben mehrdimensionale Optimierung, da kommen dann eine große Menge von Lösungen (mehrere zehn bis einige tausend) raus. Nun muss innerhalb dieser Lösungsmenge navigiert werden. Dazu gibt es verschiedene Navigatoren:

  • RangeSlider, bei denen eine aktuelle Lösung ausgewählt werden kann und eben Bereiche eingeschränkt werden können.
  • CheckedComboBoxen, Bei denen anhand nominaler Dimensionen Lösungen aktiviert und deaktiviert werden können.
  • Ein Ansicht in einem Kooridnatensystem
  • Farbskalen samt vorschau
  • Außerdem kann es Anwendungen geben, in denen während der Navigation neue Lösungen interpoliert werden.
  • Potentiell für jede Anwendung weitere Ansichten und Restriktoren (z.B. mit der man einstellen kann, dass nur Lösungen aktiv sind, die eine bestimmte "weiche" Nebenbedingung erfüllen

Es ist also eine Sammlung von Cotnrols, die aber miteinander interagieren und anwendungsspezifisch erweiterbar sein müssen.

Die oben beschriebenen Probleme treten bei den Presentern auf.

In der bisherigen Version gibt es ein Model, was eigentlich alles kennt und hat. Es weiß, welche Dimensionen meine Daten haben, was die aktuelle Farbskala ist, verwaltet welche Punkte enabled und disabled sind etc. Kurzum: Es hat etwa Verantwortlichkeiten.

So bekommt der Presenter für den RangeSlider ein Modell, in dem alle Dimensionen existieren (er braucht aber nur eine einzige), in dem es Farben gibt (er braucht keine), das weiß, welche Dimensionen im Koordinatenansicht angezeigt werden (was ihn nicht interessiert) etc.

Dann bekam ein Presenter eigentlich nur zwei Parameter: Das ViewModel und das Control, für das er zuständig ist.

Jetzt habe ich das ViewModel aufgedröselt in seine Verantwortlichkeiten. Alles eigene Interfaces. Aber nun braucht halt jeder Presenter 3 bis 4 von den 10 verschiedenen Verantwortlichkeiten, zusätzlich das Control.

Aber es ist vermutlich schwierig, größere Architekturdisskusion in einem Forum zu führen. Da fehlt Papier um was aufzuzeichnen und die Möglichkeit der unmittelbaren Rückfrage. Es ist schwierig, in TextForm die Probleme und Ideen der derzeitigen Architektur erklären (und die Beispiele sind eben immer zu abstrakt) und umgekehrt sind die Lösungsvorschläge halt eher nicht konkret die Designfrage beantworten (was vermutlich nicht geht), sondern alternative Vorschläge sind (deren Vorteil und Anwendbarkeit ich nicht überblicken kann).

Ich bin für diesen Fall mit der Lösung von Khalid recht zufrieden (die meisten Aufrufe werden kurz, habe aber für die paar Fälle wo ich es brauche Flexibilität), werde mir aber in absehbarer Zeit mal DI-Container zu Gemüte führen. Vielleicht kommt dann der Aha-Moment, wo ich Denke "Hättest du da früher drauf gehört".

Vielen Dank und Viele Grüße,
Uwe

F
10.010 Beiträge seit 2004
vor 13 Jahren

Jetzt habe ich das ViewModel aufgedröselt in seine Verantwortlichkeiten. Alles eigene Interfaces. Aber nun braucht halt jeder Presenter 3 bis 4 von den 10 verschiedenen Verantwortlichkeiten, zusätzlich das Control.

Diese Diskusion hatten wir schon mal woanders.
Es ist schlichtweg ein suboptimaler weg zu versuchen VM's so zu bauen das sie wiederverwendbar sind.
VM's sind der Presenter/Controller, also ist jedes VM anders und macht genau das was es soll, und kennt auch genau das vom Model was gerade gebraucht wird.

Wenn du versuchst jetzt diese VM's aufzuteilen und dann wieder einen Presenter zwischenschaltest verkomplizierst du das ganze unnötig.