Ł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

Metoda tworząca

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().

0 komentarzy