Wzorzec projektowy stan (ang. state)

Wzorzec projektowy strategia (ang. strategy)

Cel

  • umożliwienie zmiany zachowania obiektu w momencie zmiany jego stanu
  • pozorna zmiana klasy obiektu

Wzorce State i Strategy zostaną omówione wspólnie, ponieważ mają identyczną strukturę i zbliżone cele. Dotyczą one funkcjonalnej zmiany zachowania obiektu w trakcie wykonywania programu. W ten sposób pozornie obiekt ten zmienia klasę, do której należy. W przypadku wzorca State celem jest zmiana zachowania obiektu w zależności od stanu, w jakim obiekt się znajduje.

Wzorzec Strategy służy do modelowania algorytmu realizacji pewnej czynności, który może zostać określony i zmieniony w trakcie wykonywania programu.

Struktura Stan

Stan jest obiektem. Zmiana stanu oznacza zmianę obiektu go reprezentującego. Delegowane do niego metody są wywoływane polimorficznie.

W przypadku wzorca State centralnym obiektem jest Context. Jego metody wywoływane przez klientów delegują żądania do skojarzonego z nim relacją kompozycji obiektu typu State, reprezentującego jego stan. Metody obiektu State są polimorficzne, czyli wraz ze zmianą tego obiektu zmienia się też ich funkcjonalność. W ten sposób, gdy zachodzi zmiana skojarzonego z obiektem Context obiektu State, zmieniają się też zachowanie metod kontekstu. Pozornie zatem obiekt Context zmienia klasę, do której należy. Wzorzec Strategy stosuje podobne rozwiązanie, tylko na nieco większą skalę. Obiekt Context realizuje pewien algorytm, którego poszczególne kroki mogą zmieniać się w zależności od wyboru konkretnego algorytmu. Z obiektem tym skojarzony jest (także za pomocą kompozycji) obiekt algorytmu, którego metody implementują zmieniające się kroki. Zmiana obiektu algorytmu powoduje zmianę zachowania obiektu Context. W obu przypadkach najważniejszą zaletą jest możliwość zmiany skojarzonego obiektu (stanu lub algorytmu) w trakcie działania programu, bez potrzeby jego rekompilacji.

uczestnicy

  • Context
    • posiada referencję do obiektu reprezentującego bieżący stan
  • State
    • definiuje interfejs pozwalający hermetyzować zachowanie związane z każdym stanem
  • Concrete State
    • definiuje własne metody implementujące zachowanie specyficzne dla tego stanu

Obiekt Context posiada referencję do obiektu typu State, wskazującą na bieżący stan. W obiekcie State zdefiniowane są wszystkie metody, których zachowanie zależy od stanu obiektu Context.

konsekwencje

  • Podział zachowania obiektu wg stanów
    • kod związany ze jednym stanem jest zapisany w jednym obiekcie
  • zmiana stanu jest realizowana przez zmianę obiektu stanu na inny
  • ochrona przed stanem niespójnym
  • możliwość współdzielenia obiektów State
    • obiekty State zwykle definiują tylko zachowanie
    • obiekty State zwykle są bezstanowe

Zastosowanie wzorca pozwala modyfikować zachowanie obiektów tak jakby zmieniała się ich klasa ? i to jest najważniejszy cel i konsekwencja tego wzorca. Istnieje natomiast grupa efektów pośrednich, ale o dość interesujących właściwościach. Hermetyzacja stanu w postaci niezależnych klas pozwala na jednorazową, niepodzielną zmianę tego stanu, bez wprowadzania stanów niespójnych czy nieoznaczonych. Jeżeli obiekty State nie przechowują informacji (w większości przypadków może ona być zapamiętana w obiekcie Context, ponieważ ona nie ulega zmianie), a jedynie definiują zachowanie, wówczas - paradoksalnie - obiekty te, reprezentujące stan, są bezstanowe i mogą być współdzielone między wiele obiektów Context.

State: przykład

public class Account {
	private int balance = 0;
	private String owner = null;
	private boolean isOpen = false;
	public Account(String owner, int balance) {
		this.owner = owner;
		this.balance = balance;
		this.isOpen = true;
	}
	public void credit(int amount) {
		if (isOpen) {
			balance += amount;
		} else {
			alert("Konto nieaktywne!");
		}
	}
}

Przykładem będzie rachunek bankowy. Tym razem, obok stanu biznesowego, przechowującego informacje związane z rachunkiem (saldo, właściciel), posiada on także zmienną isOpen, określającą, czy rachunek jest aktywny, czy nie. Zmienna ta ma wpływ na działanie niektórych metod biznesowych: wykonanie operacji credit() nie jest mo?liwe, jeżeli zmienna isOpen ma wartość false.

public interface AccountState {
	public void credit(Account acc, int amount);
}
public class AccountOpen implements AccountState {
	public void credit(Account acc, int amount) {
		acc.balance += amount;
	}
}
public class AccountClosed implements AccountState {
	public void credit(Account acc, int amount) {
		alert("The account is closed!");
	}
}

Aby zastosować wzorzec State w tym przypadku, należy zdefiniować interfejs AccountState oraz klasy reprezentujące stan aktywności i nieaktywności rachunku. Ten interfejs i implementujące go klasy posiadają metodę credit(), której zachowanie jest różne w zależności od klasy: AccountOpen realizuje tę metodę bezwarunkowo, natomiast AccountClosed ? również bezwarunkowo ją blokuje.

public class Account {
	private int balance = 0;
	private String owner = null;
	private AccountState state = null;

	public Account(String owner, int balance) {
		this.owner = owner;
		this.balance = balance;
		this.state = new AccountOpen();
	}
	public void credit(int amount) {
		this.state.credit(this, amount); // delegacja
	}
	public void close() {
		this.state = new AccountClosed();
	}
}

W klasie Account pole isOpen jest zastąpione poprzez referencję typu AccountState wskazującą na obiekt reprezentujący bieący stan, przy czym domyślnym stanem początkowym jest stan aktywności (AccountOpen). Metoda credit() w klasie Account jest delegowana do obiektu stanu, dzięki czemu zmiana tego obiektu spowoduje inną obsługę tego komunikatu.
Metoda close() powoduje zmianę bieążcego obiektu stanu na AccountClosed ? od tego momentu metoda credit() jest zablokowana.

Struktura Strategia

Sorter zleca operację wybranej strategii sortowania. Każda strategia to jeden algorytm. Zmiana strategii nie wpływa na obiekt Sorter.

Drugi przykład dotyczy wzorca Strategy. Klasa Sorter wykonuje sortowanie wewnętrznej kolekcji. Ponieważ istnieją różne algorytmy sortowania, dlatego realizacja metody sort() jest delegowana do aktywnego algorytmu, stanowiącego implementację klasy SortingStrategy. Metody tej klasy to kroki algorytmu. Każdy algorytm może realizować je w charakterystyczny dla siebie sposób. Zmiana algorytmu sortowania jest realizowana wyłącznie przez zmianę obiektu reprezentującego ten algorytm: jest przezroczysta z punktu widzenia obiektu Sorter.