Объект, который проявляет себя в изменении своего класса.
Указание: условный код почти для всех методов.
Шаблон Состояние(State) переключает с одной реализации на другую во время жизненного цикла суррогата для того, чтобы воспроизвести различное поведение при вызове одинаковых методов. Есть способ улучшить реализацию вашего кода, когда вы сделаете много циклов тестирования каждого вашего метода прежде, чем решите что нужно сделать с тем или иным методом. Например, сказка о царевне-лягушке содержит объект (живое существо), которое ведет себя различным образом в зависимости от своего состояния. Вы должны реализовать это, используя булевскую переменную для проверки:
//: state:KissingPrincess.java
package state;
import junit.framework.*;
class Creature {
private boolean isFrog = true;
public void greet() {
if (isFrog)
System.out.println("Ribbet!");
else
System.out.println("Darling!");
}
public void kiss() {
isFrog = false;
}
}
public class KissingPrincess extends TestCase {
Creature creature = new Creature();
public void test() {
creature.greet();
creature.kiss();
creature.greet();
}
public static void main(String args[]) {
junit.textui.TestRunner.run(KissingPrincess.class);
}
} // /:~
Однако, метод greet() и любой другой метод, который должен проверять переменную isFrog прежде, чем выполнит свою операцию, будет заключать в себе неуклюжий код. При делегировании операции объекту State, объект может быть изменен, что упростит код.
//: state:KissingPrincess2.java
package state;
import junit.framework.*;
class Creature {
private interface State {
String response();
}
private class Frog implements State {
public String response() {
return "Ribbet!";
}
}
private class Prince implements State {
public String response() {
return "Darling!";
}
}
private State state = new Frog();
public void greet() {
System.out.println(state.response());
}
public void kiss() {
state = new Prince();
}
}
public class KissingPrincess2 extends TestCase {
Creature creature = new Creature();
public void test() {
creature.greet();
creature.kiss();
creature.greet();
}
public static void main(String args[]) {
junit.textui.TestRunner.run(KissingPrincess2.class);
}
} // /:~
Кроме того, изменения в State везде происходят автоматически, и не требуют редактирования во всех методах класса, чтобы изменения вступили в силу.
Вот основная структура State:
//: state:StateDemo.java
// Simple demonstration of the State pattern.
package state;
import junit.framework.*;
interface State {
void operation1();
void operation2();
void operation3();
}
class ServiceProvider {
private State state;
public ServiceProvider(State state) {
this.state = state;
}
public void changeState(State newState) {
state = newState;
}
// Передача вызовов методов в реализацию:
public void service1() {
// ...
state.operation1();
// ...
state.operation3();
}
public void service2() {
// ...
state.operation1();
// ...
state.operation2();
}
public void service3() {
// ...
state.operation3();
// ...
state.operation2();
}
}
class Implementation1 implements State {
public void operation1() {
System.out.println("Implementation1.operation1()");
}
public void operation2() {
System.out.println("Implementation1.operation2()");
}
public void operation3() {
System.out.println("Implementation1.operation3()");
}
}
class Implementation2 implements State {
public void operation1() {
System.out.println("Implementation2.operation1()");
}
public void operation2() {
System.out.println("Implementation2.operation2()");
}
public void operation3() {
System.out.println("Implementation2.operation3()");
}
}
public class StateDemo extends TestCase {
static void run(ServiceProvider sp) {
sp.service1();
sp.service2();
sp.service3();
}
ServiceProvider sp = new ServiceProvider(new Implementation1());
public void test() {
run(sp);
sp.changeState(new Implementation2());
run(sp);
}
public static void main(String args[]) {
junit.textui.TestRunner.run(StateDemo.class);
}
} // /:~
В функции main( ) вы можете увидеть, что сначала используется первая реализация, замет происходит переключение на вторую реализацию, которая тоже используется.
Существует несколько деталей, из которых вы должны выбрать в соответствии с требованиями вашей собственной реализации, такие как: что фактически вы предоставляете клиенту, использующему Состояние, и как будут сделаны переключения состояния. Иногда (как в свинговом менеджере компоновки) клиент может передать объект напрямую, но в KissingPrincess2.java фактически Состояние использовалось так, что клиент его не видел. Кроме того, механизм изменения состояния может быть простым или сложным - в Машине Состояний, описанной далее в этой книге, есть большое число состояний и рассмотрены различные механизмы переключения.
Свинговый менеджер компоновки, упомянутый выше в качестве примера, - это интересный пример, потому что он показывает поведения и Стратегии (Strategy) и Состояния (State).
Различие между Proxy и Состоянием (State) состоит в проблемах, которые они решают. Proxy чаще всего решает следующие проблемы, как указано в Design Patterns:
-
Удаленный прокси. Эти прокси предназначены для объектов, расположенных в разном адресном пространстве. Удаленный прокси создается для вас автоматически при использовании RMI компилятором rmic, когда он создает заглушки и скелеты.
-
Виртуальный прокси. Этот прокси обслуживает "ленивую инициализацию" для создания дорогостоящих объектов по требованию.
-
Защитный прокси. Используется, когда вы не хотите, чтобы клиентский программист имел полный доступ к проксируемому объекту.
-
Умная ссылка. Для добавления дополнительных операций при доступе к проксируемому объекту. Например, для отслеживания и хранения количества ссылок, которые удерживает определенный объект, чтобы реализовать идиому копирование при записи и предотвратить наложение объектов. Более простым примером является отслеживание количества вызовов определенного метода.
Вы можете рассматривать ссылку, как некоторого рода защитный прокси, так как она управляет доступом к реальному объекту в куче (и проверяет, например, что вы не используете null-ссылку).
[Переписать это: В Design Patterns Proxy и State не выглядят связанными друг с другом, потому что два этих шаблона (как я полагаю) даны с различными структурами. State, в частности, использует раздельную иерархию реализаций, а мне кажется, что в этом нет необходимости, до тех пор, пока реализация не выйдет из под вашего контроля (конечно это возможно, но если этот код полностью принадлежит вам, то я не вижу причин не получить выгоду из элегантности и полезности единственного базового класса). Кроме того, Прокси должен использовать тот же базовый класс для своей реализации, что и проксируемый объект, доступ к которому вы контролируете. Независимо от специфики, и Пркси и Состояние являются суррогатами, передающими вызов методов реализуемому объекту.]
Состояние может быть найдено повсеместно, поскольку это достаточно фундаментальная идея. For example, in Builder, the "Director" uses a backend Builder object to produce different behaviors.
← | Proxy: выдвижение вперед другого объекта | Итераторы (Iterator): отделение алгоритмов от контейнеров | → |