2010-09-29 9 views
12

Ich hatte immer das Gefühl, dass die Hauptarbeit einer Klasse im Allgemeinen in ihren Instanzmethoden geleistet werden sollte, während der Konstruktor die Instanz nur in einen gebrauchsfähigen Anfangszustand bringen sollte.Jedes Problem mit der Hauptarbeit einer Klasse in ihrem Konstruktor?

Aber ich finde, dass es in der Praxis Situationen gibt, in denen es sinnvoller scheint, im Wesentlichen die gesamte tatsächliche Arbeit in den Konstruktor zu stellen.

Ein Beispiel: Ich muss einige DBMS-spezifische Informationen aus der Datenbank abrufen. Der natürlichste Weg schien mir eine Klasse DBMSSpecInfo zu haben, mit einem Konstruktor:

public DBMSSpecInfo(java.sql.Connection conn) throws SQLException{ 
    // ... retrieve info from DBMS 
} 

/** @returns max size of table in kiB */ 
public int getMaxTableSize() {//...} 

/** @returns max size of index in kiB */ 
public int getMaxIndexSize() {//...} 

/** @returns name of default schema */ 
public String getDefaultSchema() {//...} 

Sie einmal, um die Klasse zu konstruieren, würde der Konstrukteur alle Daten holen, dann können Sie verschiedene Getter verwenden, um die Informationen, die Sie abrufen müssen .

Natürlich könnte ich die Methode woanders setzen und nur DBMSSpecInfo für den Rückgabewert verwenden (im Wesentlichen mit DBMSSpecInfo nur als Werthalter), aber es fühlt sich hässlich an, eine Klasse nur für die Rückgabe von Werten aus einer einzigen Funktion zu erstellen.

Also was denkst du? Gibt es Probleme beim Ausführen der Hauptarbeit im Konstruktor? Ist es in Java "un-idiomatisch"? Oder ist es eine akzeptable (wenn auch möglicherweise ungewöhnliche) Übung?

Antwort

4

In Ihrem Beispiel ich eine statische Methode GetDBMSSpecInfo (java.sql.Connection conn), die eine Instanz von DBMSSpecInfo Objekt oder null zurück, wenn etwas schief geht (im Fall machen würde Sie nicht werfen wollen Ausnahmen).

Das DBMSSpecInfo Objekt für mich sollte nicht nichts mehr als Eigenschaften erhalten enthält: MaxIndexSize, MaxTableSize, DefaultSchema usw.

Und ich würde den Konstruktor dieses Objekt privat machen, so dass Instanzen können nur aus dem statischen erstellt werden Methode.

+0

+1 In der Tat, das ist genau das, was ich getan habe. Ich habe gerade den Konstruktor von DBMSSpecInfo public gemacht, aber da es nur ein Wert containert (nur 'public final' Felder + Konstruktor) ist, ist es in anderen Instanzen nicht schädlich. Ein öffentlicher Konstruktor hilft auch, wenn Sie Instanzen mit vorgegebenen Werten für Unit-Tests anderer Methoden benötigen. – sleske

+0

Dies ist der vernünftigste Kompromiss - zerstört die Verkapselung nicht, erlaubt aber immer noch relativ einfache Gerätetests. Aber ich denke immer noch, dass Dinge nur verspottet werden sollten. –

+0

Ja, wie Sie erwähnen, wenn Sie Instanzen mit Standardwerten für Komponententests benötigen, ist es in Ordnung, einen öffentlichen Konstruktor zu haben. Das hat aber auch mit der Funktionalität zu tun, die Sie anwenden möchten. In vielen Fällen möchten Sie sicher sein, dass eine bestimmte Objektinstanz mit allen korrekten Informationen erstellt wurde. Denken Sie zum Beispiel an DataRow. In diesem Fall würde ich privaten oder internen Konstrukteuren wärmstens empfehlen. – Dummy01

10

Das Hauptproblem in der Praxis ist das Testen von Einheiten - Sie können das Objekt nicht instanziieren, ohne tatsächlich zu arbeiten. (Oder Sie müssten alle Klassen, die an dieser Arbeit teilnehmen, verspotten).

Verwandte Diskussion: OO Design for testability. Es gibt Beispiele dafür, warum die Arbeit in Konstruktoren schlecht für Komponententests ist.

+0

+1 sehr guter Punkt, von allen anderen Beantwortern (einschließlich mir) verpasst. –

+0

Guter Punkt. Wenn der Konstruktor jedoch der * einzige * Teil ist, der funktioniert, ist der Punkt strittig. – sleske

+0

Also ich denke die Lektion wäre: Arbeite entweder im Konstruktor oder in der Instanz Methode, aber nicht in beiden (da dann kann man nicht jeden Teil einzeln testen). – sleske

0

Meiner Meinung nach sollte der Konstruktor leichtgewichtig sein und keine Ausnahmen auslösen. Ich würde eine Art von Load() -Methode implementieren, um Daten aus der Datenbank zu retreien oder lazy loading zu implementieren.

+5

Hm, würde eine 'Load()' Methode zweistufige Konstruktion implizieren (erstes Konstrukt, dann load()). Ich bin der festen Überzeugung, dass die zweistufige Konstruktion schlecht ist (und Sie können die Klasse nicht unveränderlich machen). – sleske

+1

Können Sie Ihre Meinung mit einer Stiftung begründen? –

+0

Nein, es würde nicht immer eine zweistufige Konstruktion implizieren, vielleicht brauchen Sie den Datenbankzugriff nicht. Ich würde teures Zeugs im Konstruktor vermeiden, da es nicht jedes Mal benötigt wird, wenn Sie die Klasse instanziieren. – elsni

6

Ich würde in solchen Fällen lieber den Erstellungscode von der Klasse selbst trennen. Es könnte in eine statische Factory-Methode oder eine separate Factory-Klasse (die auch eine öffentliche statische innere Klasse sein kann) eingefügt werden. Die Wahl hängt von der Komplexität des Codes und dem Designkontext ab (den wir in diesem Fall nicht kennen).

Dies würde Ihnen auch erlauben, Optimierungen zu machen, wie Caching und Wiederverwendung der Klasseninstanz (en).

+0

Guter Punkt. Vielleicht sollte ich wirklich eine separate "Werthalter" -Klasse schaffen. – sleske

5

Ich bin groß auf Pragmatismus. Wenn es funktioniert, mach es! Aber im Namen von Reinheit und Güte möchte ich einen Design-Vorschlag machen:

Diese Klasse verwirrend den Dateninhalt mit dem Mechanismus zum Abrufen. Das Objekt, das Sie am Ende verwenden, ist nur für die darin enthaltenen Daten interessant. Die "saubere" Aufgabe wäre also, eine andere Klasse zu haben, um die Informationen auszugraben und dann Instanzen dieses Eigenschaftenobjekts zu erstellen.

Diese andere Klasse könnte eine längere Lebensdauer haben, da Sie normalerweise eine Methode zum Ausführen der Arbeit aufrufen, nicht den Konstruktor. Der Konstruktor von DBMSSpecInfo könnte am Ende eine Reihe von Eigenschaften zuweisen, aber nicht viele fehlerbehaftete DB-Zugriffsarbeiten ausführen.

+0

+1 scheint Péter Török und du stimmst zu :-) – sleske

3

Ich denke nicht, dass es eine gute Idee ist, die Hauptarbeit in einem Konstruktor zu tun, da es keinen Rückgabewert hat. So macht es die Fehlerverarbeitung komplizierter IMO, da Sie gezwungen sind, Ausnahmen zu verwenden.

+4

Ich bin fest davon überzeugt, dass die Fehlerverarbeitung, abgesehen von sehr ungewöhnlichen Umständen, * immer * Ausnahmen verwenden sollte. Also würde ich das als ein Pluspunkt betrachten :-). – sleske

2

Ein Nachteil der Arbeit im Konstruktor ist, dass Konstruktoren nicht überschrieben werden können (und nicht an überschreibbare Methoden delegieren sollten).

Ein anderer ist, dass ein Konstruktor alles oder nichts ist. Wenn das Objekt Daten enthält, deren Initialisierungen unabhängige Fehler aufweisen, berauben Sie sich selbst die Fähigkeit, zu verwenden, welche Daten erfolgreich beschafft werden konnten. Ebenso muss die Initialisierung des gesamten Objekts, selbst wenn Sie nur einen Teil davon benötigen, die Leistung beeinträchtigen.

Auf der anderen Seite ermöglicht das Ausführen im Konstruktor, dass der Initialisierungsstatus (hier: die Verbindung zur Datenbank) freigegeben und früher freigegeben wird.

Wie immer sind verschiedene Ansätze in verschiedenen Situationen vorzuziehen.

+0

Vielleicht. OHOH Es ist wahrscheinlich, dass die SQL-Abfrage, die im Konstruktor im OP-Beispiel ausgeführt wurde, in einer einzigen SQL-Abfrage ausgeführt werden könnte, was ein Szenario mit allen Fehlern oder gar keinem Fehler wäre. –

+0

Ich stimme zu. Aber für andere Beispiele können sie ein Deal-Breaker sein - und ich möchte eine Überallgenerierung von Design-Ratschlägen vermeiden, wo ich kann. – meriton

2

Die ganze Arbeit im Konstruktor kann zur "Überlastungshölle" führen. Sie möchten immer mehr Funktionen hinzufügen und anstatt nur eine neue Methode hinzuzufügen, wie Sie es bei der normalen objektorientierten Entwicklung tun würden, fügen Sie immer mehr überladene Konstruktoren hinzu. Schließlich können die Konstruktoren so viele Überladungen und Parameter erzeugen, dass sie unhandlich werden.

+0

+1 Ich bin noch nicht an diesem Punkt, aber es ist etwas zu beachten. – sleske

0

Kein Problem. JDK hat viele Klassen, die Netzwerk-IO in Konstruktoren ausführt.

+0

Hm, die Tatsache, dass die JDK-Klassen es tun, bedeutet nicht, dass es eine gute Übung ist. Einige würden sogar argumentieren, dass es genau das Gegenteil ist ...: -/ – sleske

+0

wie was? Ich würde argumentieren, dass es ein schlechter Entwurf ist, IO in einem Konstruktor zu machen. Kannst du auf eine jdk-Klasse hinweisen, die das tut? – bsautner

+0

@bsautner: Ein Beispiel ist 'java.net.Socket', wo einige Konstruktoren eine Verbindung initiieren. Es gibt jedoch auch einen Konstruktor, der das nicht tut, also haben Sie die Wahl. – sleske

1

Seien Sie vorsichtig, dass das Objekt nicht geklont/deserialisiert ist. Auf diese Weise erstellte Instanzen verwenden den Konstruktor nicht.

+0

Nun, ich habe noch nie ein Objekt serialisiert, und ich glaube, Klon ist * böse *. Trotzdem, gut, das im Hinterkopf zu behalten ... – sleske

Verwandte Themen