Łańcuch konstruktorów
Koziołek
1 Informacje podstawowe
2 Omówienie szczegółowe
2.1 Wstęp
2.2 Opis problemu
2.3 Implementacja
2.4 Koszty i Problemy
3 Powiązane wzorce
4 Przykładowe implementacje
4.5 Java
Informacje podstawowe
Nazwa: Chain Construktors (Łańcuch konstruktorów)
Klasyfikacja: Wzorzec refakoryzacyjny - Refactoring to Patterns - Joshua Kerievsky
Omówienie szczegółowe
Wstęp
Łańcuch konstruktorów jest odpowiedzią na problem tworzenia obiektów za pomocą konstruktorów z różną ilością parametrów i powtarzanie się kodu. Pozwala na tworzenie kodu zgodnego z zasadą DRY (ang. Don't Repeat Yourself - nie powtarzaj się). Kod taki charakteryzuje się dużą odpornością na błędy wynikające z powtórzeń oraz łatwością modyfikacji.
Opis problemu
Załóżmy, że mamy pewną klasę, która ma kilka pól:
package eu.runelord.programmers.io.dp.chainconstructors;
public class MyObject {
private Integer a;
private Long b;
private String c;
public MyObject() {
}
public MyObject(Integer a) {
this.a = a;
}
public MyObject(Integer a, Long b) {
this.a = a;
this.b = b;
}
public MyObject(Integer a, Long b, String c) {
this.a = a;
this.b = b;
this.c = c;
}
}
Jak widać wywołanie pierwszych trzech konstruktorów może (dużo zależy od języka) doprowadzić do stanu nieustalonego dla obiektu. Będzie to spowodowane nieustawieniem niektórych pól obiektu. W znacznie gorszej sytuacji możemy się znaleźć jeżeli nasz kod będzie przypominał ten:
package eu.runelord.programmers.io.dp.chainconstructors;
public class MyObject2 {
private Integer a;
private Long b;
private String c;
public MyObject2() {
}
public MyObject2(Integer a) {
this.a = a;
SomeObserver.register(this);
}
public MyObject2(Integer a, Long b) {
this.a = a;
this.b = b;
SomeObserver.register(this);
}
public MyObject2(Integer a, Long b, String c) {
this.a = a;
this.b = b;
this.c = c;
SomeObserver.register(this);
}
}
Jak widać w konstruktorze domyślnym nie wywołano procedury odpowiedzialnej za rejestrację tworzonego obiektu w obiekcie SomeObserver. Taki błąd pojawia się wszędzie tam gdzie powtarzany jest kod.
Chcąc uniknąć tego problemu należy przenieść powtarzający się kod w jedno miejsce. Dodatkowo zyskujemy możliwość łatwiejszej modyfikacji kodu ponieważ zmiany dokonujemy tylko w jednym miejscu.
Implementacja
Implementacja tego wzorca opiera się w znacznej mierze na refaktoryzacj istniejącego kodu. Jeżeli tworzymy nową klasę należy zadbać o prawidłową implementację już w trakcie jej tworzenia.
Na początek przyjrzyjmy się konstruktorom i wyszukajmy powtarzający się kod. Zazwyczaj konstruktory różnią się tylko ilością ustawianych pól. Jeżeli wykonywane są jakieś inne niezbędne czynności to ich kod zazwyczaj powtarza się we wszystkich konstruktorach. Jeżeli któryś z konstruktorów wykonuje dodatkowe czynności to odseparujmy je od wspólnego kodu przenosząc poza ten konstruktor. Następnie usuńmy powtarzający się kod z konstruktorów. Nie usuwamy go z konstruktora o największej liczbie argumentów. W miejsce tego kodu wstawmy wywołane konstruktora o największej liczbie parametrów podając wartości domyślne w przypadku braku któregoś z parametrów w danym konstruktorze. Powinniśmy otrzymać kod podobny do tego:
package eu.runelord.programmers.io.dp.chainconstructors;
public class ChainConstructors {
private Integer a;
private Long b;
private String c;
public ChainConstructors() {
this(null, null, null);
}
public ChainConstructors(Integer a) {
this(a, null, null);
System.out.println("konstruktor jednoargumentowy");
}
public ChainConstructors(Integer a, Long b) {
this(a, b, null);
}
public ChainConstructors(Integer a, Long b, String c) {
this.a = a;
this.b = b;
this.c = c;
}
}
Jak widać uzyskaliśmy w ten sposób spójny kod. Ponad to w konstruktorze o jednym argumencie wykonujemy dodatkowe czynności, któe nie mają wpływu na inne wywołania.
Koszty i Problemy
Zastosowanie tego wzorca nie powinno sprawiać problemów nawet początkującym programistom. Nie ma on wpływu na inne komponenty ponieważ nie zmienia API klasy. Jedynym czynnikiem, który nas ogranicza jest czasochłonność. Może się też okazać, że ilość kodu zmiennego w konstruktorach jest bardzo duża, ale oznaczać może to tylko, że klasa ma za dużą odpowiedzialność i należy rozbić ją na mniejsze, bardziej wyspecjalizowane pod klasy.
Powiązane wzorce
Przykładowe implementacje
Java
package eu.runelord.programmers.io.dp.chainconstructors;
public class ChainConstructors {
private Integer a;
private Long b;
private String c;
public ChainConstructors() {
this(null, null, null);
}
public ChainConstructors(Integer a) {
this(a, null, null);
System.out.println("konstruktor jednoargumentowy");
}
public ChainConstructors(Integer a, Long b) {
this(a, b, null);
}
public ChainConstructors(Integer a, Long b, String c) {
this.a = a;
this.b = b;
this.c = c;
}
}
W języku java należy pamiętać, że wywołanie innego konstruktora musi być pierwszą operacją w konstruktorze. Nie można też łączyć wywołania wielu konstruktorów w tym wywołania konstruktora super().