Konstruktor durch Fabrikmethode ersetzen

Một phần của tài liệu AW refactoring improving the design of existing code (Trang 337 - 341)

Sie wollen bei der Erzeugung eines Objekts mehr als nur eine einfache Konstruk- tion vornehmen.

Ersetzen Sie den Konstruktor durch eine Fabrikmethode.

10.12.1 Motivation

Sie werden Konstruktor durch Fabrikmethode ersetzen am ehesten dann verwenden, wenn Sie einen Typenschlüssel durch Unterklassen ersetzen. Sie haben ein Ob- jekt, das bisher mit einem Typenschlüssel erzeugt wurde, nun aber Unterklassen benửtigt. Die genaue Unterklasse họngt vom Typenschlỹssel ab. Ein Konstruktor kann aber nur ein Objekt seiner Klasse liefern. Deshalb müssen Sie den Konstruk- tor durch eine Fabrikmethode ersetzen [Gang of Four].

Sie kửnnen Fabrikmethoden auch in anderen Situationen einsetzen, in denen Konstruktoren zu beschrọnkt sind. Fabrikmethoden sind wesentlich fỹr Wert durch Referenz ersetzen (179). Sie kửnnen auch dazu dienen, verschiedene Arten der Objekterzeugung auszudrücken, die über die Anzahl und den Typ der Parame- ter hinausgehen.

Employee (int type) { _type = type;

}

static Employee create(int type) { return new Employee(type);

}

10.12.2 Vorgehen

• Erstellen Sie eine Fabrikmethode. Rufen Sie in ihrem Rumpf den Konstruktor auf.

• Ersetzen Sie alle Aufrufe des Konstruktors durch Aufrufe der Fabrikmethode.

• Wandeln Sie nach jeder Ersetzung um und testen Sie.

• Deklarieren Sie den Konstruktor als privat.

• Wandeln Sie um.

10.12.3 Beispiel

Ein schnelles, aber ermüdendes und überstrapaziertes Beispiel liefert ein Mitarbei- tergehaltssystem. Ich habe folgende Klasse Employee (Mitarbeiter):

class Employee { private int _type;

static final int ENGINEER = 0;

static final int SALESMAN = 1;

static final int MANAGER = 2;

Employee (int type) { _type = type;

}

Ich mửchte Unterklassen von Employee fỹr die verschiedenen Typenschlỹssel bil- den. Also muss ich eine Fabrikmethode erstellen:

static Employee create(int type) { return new Employee(type);

}

Dann lasse ich alle Clients des Konstruktors die neue Methode verwenden und deklariere den Konstruktor als privat:

client code...

Employee eng = Employee.create(Employee.ENGINEER);

class Employee...

private Employee (int type) { _type = type;

}

10.12.4 Beispiel: Unterklasse mit einem String erzeugen

Bis jetzt habe ich nicht viel gewonnen; der grửòte Vorteil besteht darin, dass ich den Empfọnger des Erzeugungsaufrufs von der Klasse des erzeugten Objekts ge- trennt habe. Wenn ich spọter Typenschlỹssel durch Unterklassen ersetzen (227) an- wende, um die Schlüssel durch Unterklassen von Employee zu ersetzen, kann ich diese Klassen vor den Clients verbergen, indem ich die Fabrikmethode verwende:

static Employee create(int type) { switch (type) {

case ENGINEER:

return new Engineer();

case SALESMAN:

return new Salesman();

case MANAGER:

return new Manager();

default:

throw new IllegalArgumentException("Incorrect type code value");

} }

Das Traurige dabei ist, dass ich nun einen switch-Befehl habe. Sollte ich eine neue Unterklasse hinzufügen, so muss ich daran denken, diesen switch-Befehl zu aktu- alisieren, und ich neige zur Vergesslichkeit.

Eine gute Mửglichkeit, dies zu vermeiden, besteht darin Class.forName zu verwen- den. Als Erstes muss der Typ des Parameters geọndert werden, im Wesentlichen eine Variante von Methode umbenennen (279). Ich erstelle eine neue Methode, die einen String als Argument erhọlt:

static Employee create (String name) { try {

return (Employee) Class.forName(name).newInstance();

} catch (Exception e) {

throw new IllegalArgumentException ("Unable to instantiate" + name);

} }

Nun kann ich die Methode create mit dem ganzzahligen Parameter die neue Me- thode verwenden lassen:

class Employee {

static Employee create(int type) { switch (type) {

case ENGINEER:

return create("Engineer");

case SALESMAN:

return create("Salesman");

case MANAGER:

return create("Manager");

default:

throw new IllegalArgumentException("Incorrect type code value");

} }

Ich kann dann in den Aufrufern von create Befehle wie:

Employee.create(ENGINEER) durch

Employee.create("Engineer")

ersetzen. Wenn ich damit fertig bin, kann ich die Version der Methode mit dem ganzzahligen Parameter entfernen.

Das Schửne an diesem Ansatz ist, dass er es mir erspart, die create-Methode zu ọn- dern, wenn ich eine neue Unterklasse von Employee einfüge. Diesem Ansatz fehlt aber die Sicherheit der Typenprüfung zur Umwandlungszeit: Ein Schreibfehler führt zu einem Laufzeitfehler. Wenn das ein wichtiger Punkt ist, verwende ich eine explizite Methode (siehe unten), aber dann muss ich jedes Mal, wenn ich eine Un- terklasse hinzufüge, auch eine neue Methode schreiben. Sie müssen hier Flexibili- tọt gegen Typsicherheit abwọgen. Treffe ich die falsche Entscheidung, so kann ich glücklicherweise entweder Methode parametrisieren (289) oder Parameter durch expli- zite Methode ersetzen (292) verwenden, um die Entscheidung zu korrigieren.

Ein anderer Grund, mit Class.ForName vorsichtig umzugehen, besteht darin, dass so die Namen von Unterklassen den Clients bekannt werden. Das ist nicht so schlimm, weil Sie andere Strings verwenden und anderes Verhalten in der Fabrik- methode ausfỹhren kửnnen. Es ist ein guter Grund, Methode integrieren (114) nicht anzuwenden, um die Fabrikmethode zu entfernen.

10.12.5 Unterklasse mit expliziten Methoden erzeugen

Ich kann einen anderen Ansatz verwenden, um Unterklassen hinter expliziten Methoden zu verbergen. Dies ist nützlich, wenn ich nur wenige Unterklassen habe, die sich nicht ọndern. So kửnnte ich eine abstrakte Klasse Person mit Unter- klassen Male und Female haben. Ich beginne damit, in der Oberklasse eine Fabrik- methode für jede Unterklasse zu definieren:

class Person...

static Person createMale(){

return new Male();

}

static Person createFemale() { return new Female();

}

Nun kann ich Aufrufe der Art Person kent = new Male();

durch

Person kent = Person.createMale();

ersetzen. Damit weiò die Oberklasse weiter von ihren Unterklassen. Wenn ich das vermeiden mửchte, brauche ich ein komplexeres Schema, wie das des Product Traders [Bọumer und Riehle]. Meistens ist diese Komplexitọt aber nicht erforder- lich, und dieser Ansatz funktioniert gut.

Một phần của tài liệu AW refactoring improving the design of existing code (Trang 337 - 341)

Tải bản đầy đủ (PDF)

(468 trang)