En los patrones de diseño de comportamiento, el patrón de Estado y el patrón de Estrategia son hermanos cercanos; ambos son muy similares. Primero veamos su diagrama de clases general y comparémoslos juntos, como se muestra en la figura:

¿No se parecen mucho? Con solo mirar el UML no podemos distinguir gran cosa. A continuación, combinaremos ejemplos para comparar las diferencias entre ambos. El siguiente ejemplo proviene de "Head First Design Patterns".
Patrón de Estrategia
El patrón de Estrategia define una familia de algoritmos, los encapsula por separado y permite que puedan intercambiarse entre sí. Este patrón hace que los cambios de algoritmos sean independientes de los clientes que los usan.
Una empresa desarrolló un juego de patos en el que aparecen patos con diversas características: pato de cabeza verde, pato de cabeza roja, pato de goma... ¿Cómo implementar estos patos en un programa? Ante este problema de escenario, es fácil pensar en definir primero una superclase Duck para todos los patos, encapsulando en ella los métodos y atributos comunes. Por ejemplo, todos los patos nadan y todos tienen su propia apariencia:
Ejemplo
public void swim(){
System.out.println("All duck can swim!");
}
public abstract void display();
public void fly(){
System.out.println("¡Volando~~~");
}
public void quack(){
System.out.println("¡Cuac, cuac, cuac~");
}
}
Pero pronto descubrimos que la definición de esta superclase tiene problemas. No todos los patos vuelan, no todos los patos hacen "cuac" (supongamos que el pato de goma hace "chirrido"). En cuanto hereden esta superclase, todos los patos volarán y todos harán "cuac".
¿Qué hacer?
Solution 1
La primera solución que se nos ocurre es: que las subclases sobrescriban el método. Es muy simple: las subclases que no vuelan sobrescriben el método fly, ¿no basta con reescribirlo?
Pero la desventaja es evidente: ¿no tendrían que sobrescribir todas las subclases que no vuelan? Supongamos que 50 tipos de patos no vuelan; ¿cuánto trabajo de reescritura y mantenimiento supondría?
Solution 2
Bien, entonces pensamos en un segundo método: seguir diseñando subclases abstractas, por ejemplo, la subclase abstracta FlyNoQuackDuck que vuela pero no grazna, la subclase abstracta QuackNoFlyDuck que grazna pero no vuela, la subclase abstracta NoQuackNoFlyDuck que no grazna ni vuela, la subclase abstracta FlyAndQuackDuck que vuela y grazna...
Escribiendo y escribiendo, descubrimos que este método tampoco funciona; es demasiado inflexible, y cuantas más partes cambien, más subclases abstractas habrá que definir.
Solution 3
¿Cuál es entonces el buen método? Pensamos: el problema anterior surge porque siempre intentamos por costumbre usar la herencia para implementar las partes que cambian. En realidad, podemos extraer las partes que cambian e implementarlas mediante composición.
Aquí se utilizan tres principios de diseño:
- Identifica las partes de la aplicación que pueden necesitar cambios y sepáralas.
- Programa para interfaces, no para implementaciones.
- Favorece la composición sobre la herencia.
Aplicando el primer principio de diseño, separamos los métodos que cambian, fly() y quack(), y los encapsulamos como dos interfaces de clases de comportamiento. Luego, según las diferentes necesidades, diseñamos diferentes clases de comportamiento que implementan las interfaces.

Aplicando el segundo y tercer principio de diseño, en la clase Duck combinamos las interfaces de dos variables miembro y asignamos dinámicamente sus valores en las subclases.

El "diagrama de clases" general es el siguiente:

Resumen
Esta solución resuelve perfectamente nuestro problema. Utilizando la idea del "patrón de estrategia", se extraen las partes variables y se componen dentro de la clase. Según las diferentes subclases, se pueden "set" diferentes subclases de comportamiento para lograr cambiar el comportamiento dinámicamente.
Implementación del código
Dos interfaces de comportamiento
Ejemplo
public void fly();
}
public interface QuackBehavior {
public void quack();
}
Diferentes clases de comportamiento que implementan la interfaz de vuelo
Ejemplo
public void fly(){
System.out.println("No puedo volar...");
}
}
public class FlyWithWings implements FlyBehavior{
public void fly(){
System.out.println("¡Volando~~~");
}
}
public class FlyWithRocket implements FlyBehavior{
public void fly(){
System.out.println("¡Con un lanzacohetes, a volar~~~");
}
}
Diferentes clases de comportamiento que implementan el graznido
Ejemplo
public void quack(){
System.out.println("¡Cuac, cuac, cuac~");
}
}
public class Squeak implements QuackBehavior{
public void quack(){
System.out.println("¡Squeak, squeak, squeak~");
}
}
public class MuteQuack implements QuackBehavior{
public void quack(){
System.out.println("No puedo graznar...");
}
}
La superclase que integra por composición las interfaces
Ejemplo
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;
public void swim(){
System.out.println("All duck can swim!");
}
public abstract void display();
/**
* Cambiar dinámicamente el comportamiento de vuelo
*/
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
/**
* Cambiar dinámicamente el comportamiento de graznido
*/
public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}
public void performFly(){
flyBehavior.fly();
}
public void performQuack(){
quackBehavior.quack();
}
}
Diferentes clases de pato
Ejemplo
* Pato de cabeza verde
*/
public class MallarDuck extends Duck{
public MallarDuck() {
//Puede volar
flyBehavior = new FlyWithWings();
//Grazna
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("Parece un pato de cabeza verde");
}
}
/**
* Pato de cabeza verde
*/
public class RedHeadDuck extends Duck{
public RedHeadDuck() {
//Puede volar
flyBehavior = new FlyWithWings();
//Grazna
quackBehavior = new Quack();
}
@Override
public void display() {
System.out.println("Parece un pato de cabeza roja");
}
}
/**
* Pato de goma
*/
public class RubberDuck extends Duck{
public RubberDuck() {
//No puede volar
flyBehavior = new FlyNoWay();
//Chirría
quackBehavior = new Squeak();
}
@Override
public void display() {
System.out.println("Parece un pato de goma");
}
}
Patrón de Estado
El patrón de Estado permite que un objeto cambie su comportamiento cuando cambia su estado interno; el objeto parece haber modificado su clase.
El patrón de Estado es muy similar al patrón de Estrategia; también encapsula el "estado" de la clase y realiza la transición automáticamente al ejecutar una acción, logrando así que la misma acción de la clase en diferentes estados muestre resultados diferentes. La diferencia con el patrón de Estrategia es que esta transición es "automática" e "inconsciente".
El diagrama de clases del patrón de Estado es el siguiente:

El diagrama de clases del patrón de Estado es idéntico al del patrón de Estrategia; la diferencia radica en su intención. El patrón de Estrategia controla qué estrategia usa el objeto, mientras que el patrón de Estado cambia de estado automáticamente. Después de ver el siguiente caso, debería quedar claro.
Ahora tienes delante un requisito de una máquina de dulces que debe implementarse en Java.
Analicemos: las funciones de la máquina de dulces se pueden dividir en cuatro acciones y cuatro estados como se muestra en la siguiente figura:

En diferentes estados, la misma acción produce resultados diferentes. Por ejemplo, en el estado "se insertaron 25 centavos", "girar la manivela" venderá un dulce; mientras que en el estado "sin 25 centavos", "girar la manivela" indicará que inserte una moneda primero.
Después de pensarlo un poco, escribimos el siguiente código de implementación de la máquina de dulces:
Ejemplo
/*
* Cuatro estados
*/
/**Estado sin moneda*/
private final static int NO_QUARTER = 0;
/**Estado con moneda*/
private final static int HAS_QUARTER = 1;
/**Estado vendiendo dulce*/
private final static int SOLD = 2;
/**Estado sin dulces*/
private final static int SOLD_OUT = 3;
private int state = SOLD_OUT;
private int candyCount = 0;
public NoPatternGumballMachine(int count) {
this.candyCount = count;
if(candyCount > 0)
state = NO_QUARTER;
}
/*
* Cuatro acciones
*/
/**
* Insertar moneda
*/
public void insertQuarter() {
if(NO_QUARTER == state){
System.out.println("Insertar moneda");
state = HAS_QUARTER;
}
else if(HAS_QUARTER == state){
System.out.println("¡No inserte monedas repetidamente!");
returnQuarter();
}
else if(SOLD == state){
System.out.println("Moneda insertada, espere el dulce");
returnQuarter();
}else if(SOLD_OUT == state){
System.out.println("Los dulces se han agotado");
returnQuarter();
}
}
/**
* Devolver moneda
*/
public void ejectQuarter() {
if(NO_QUARTER == state){
System.out.println("No hay moneda, no se puede reembolsar");
}
else if(HAS_QUARTER == state){
returnQuarter();
state = NO_QUARTER;
}
else if(SOLD == state){
System.out.println("No se puede devolver la moneda, se está dispensando el dulce, espere");
}else if(SOLD_OUT == state){
System.out.println("No se ha insertado moneda, no se puede devolver");
}
}
/**
* Girar la manivela para dispensar dulce
*/
public void turnCrank() {
if(NO_QUARTER == state){
System.out.println("Inserte una moneda primero");
}
else if(HAS_QUARTER == state){
System.out.println("Manivela girada, preparando la dispensación del dulce");
state = SOLD;
}
else if(SOLD == state){
System.out.println("Ya se ha girado la manivela, espere");
}else if(SOLD_OUT == state){
System.out.println("Los dulces se han agotado");
}
}
/**
* Dispensar dulce
*/
public void dispense() {
if(NO_QUARTER == state){
System.out.println("No se ha insertado moneda, no se puede dispensar dulce");
}
else if(HAS_QUARTER == state){
System.out.println("this method don't support");
}
else if(SOLD == state){
if(candyCount > 0){
System.out.println("Dispensar un dulce");
candyCount --;
state = NO_QUARTER;
}
else{
System.out.println("Lo sentimos, los dulces se han agotado");
state = SOLD_OUT;
}
}else if(SOLD_OUT == state){
System.out.println("Lo sentimos, los dulces se han agotado");
}
}
/**
* Devolver la moneda
*/
protected void returnQuarter() {
System.out.println("Devolviendo moneda...");
}
}
En el código se puede ver que la máquina de dulces, según el estado actual, hace que la acción correspondiente presente diferentes resultados. Este código ya puede satisfacer nuestras necesidades básicas, pero si lo piensas un poco, notarás que esta implementación parece demasiado compleja, con mala escalabilidad y sin un estilo orientado a objetos.
Supongamos que, debido a nuevos requisitos, hay que añadir un estado; entonces tendríamos que modificar cada método de acción y añadir una sentencia else adicional. Y si los requisitos cambian y la acción en un estado debe modificarse, también tendríamos que cambiar cuatro métodos a la vez. Ese trabajo sería tedioso y abrumador.
¿Qué hacer? Uno de los seis principios de diseño
Identifica las partes de la aplicación que pueden necesitar cambios y sepáralas.
En la máquina de dulces, el estado es la parte que cambia constantemente; diferentes estados tienen diferentes acciones. Podemos extraerlo por completo.

La nueva idea de diseño es la siguiente:
Primero, definimos una interfaz State. En esta interfaz, cada acción de la máquina de dulces tiene un método correspondiente.
Luego implementamos clases de estado para cada estado de la máquina; estas clases se encargarán de realizar el comportamiento de la máquina en el estado correspondiente.
Finalmente, debemos deshacernos del antiguo código condicional y, en su lugar, delegar las acciones a las clases de estado.
Definir una interfaz State
Ejemplo
/**
* Insertar moneda
*/
public abstract void insertQuarter();
/**
* Devolver moneda
*/
public abstract void ejectQuarter();
/**
* Girar la manivela para dispensar dulce
*/
public abstract void turnCrank();
/**
* Dispensar dulce
*/
public abstract void dispense();
/**
* Devolver la moneda
*/
protected void returnQuarter() {
System.out.println("Devolviendo moneda...");
}
}
Implementar clases de estado para cada estado de la máquina
Ejemplo
* Estado sin moneda
*/
public class NoQuarterState extends State{
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("Has insertado una moneda");
//Transición al estado con moneda
gumballMachine.setState(gumballMachine.hasQuarterState);
}
@Override
public void ejectQuarter() {
System.out.println("No hay moneda, no se puede reembolsar");
}
@Override
public void turnCrank() {
System.out.println("Inserte una moneda primero");
}
@Override
public void dispense() {
System.out.println("No se ha insertado moneda, no se puede dispensar dulce");
}
}
/**
* Estado con moneda insertada
*/
public class HasQuarterState extends State{
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("¡No inserte monedas repetidamente!");
returnQuarter();
}
@Override
public void ejectQuarter() {
returnQuarter();
gumballMachine.setState(gumballMachine.noQuarterState);
}
@Override
public void turnCrank() {
System.out.println("Manivela girada, preparando la dispensación del dulce");
gumballMachine.setState(gumballMachine.soldState);
}
@Override
public void dispense() {
System.out.println("this method don't support");
}
}
/**
* Estado de venta
*/
public class SoldState extends State{
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println(Moneda insertada, por favor espere el caramelo.);
returnQuarter();
}
@Override
public void ejectQuarter() {
System.out.println(No se puede devolver la moneda, se está dispensando el caramelo, por favor espere.);
}
@Override
public void turnCrank() {
System.out.println(Ya se accionó la manivela, por favor espere.);
}
@Override
public void dispense() {
int candyCount = gumballMachine.getCandyCount();
if(candyCount > 0){
System.out.println(Dispensar un caramelo.);
candyCount--;
gumballMachine.setCandyCount(candyCount);
if(candyCount > 0){
gumballMachine.setState(gumballMachine.noQuarterState);
return;
}
}
System.out.println(Lo sentimos, los caramelos están agotados.);
gumballMachine.setState(gumballMachine.soldOutState);
}
}
/**
* Estado agotado
*/
public class SoldOutState extends State{
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println(Los caramelos están agotados.);
returnQuarter();
}
@Override
public void ejectQuarter() {
System.out.println(No se insertó moneda, no se puede devolver la moneda.);
}
@Override
public void turnCrank() {
System.out.println(Los caramelos están agotados.);
}
@Override
public void dispense() {
System.out.println(Los caramelos están agotados.);
}
}
Delegar las acciones de la máquina de caramelos a la clase de estado.
Ejemplo
public State noQuarterState = new NoQuarterState(this);
public State hasQuarterState = new HasQuarterState(this);
public State soldState = new SoldState(this);
public State soldOutState = new SoldOutState(this);
private State state = soldOutState;
private int candyCount = 0;
public GumballMachine(int count) {
this.candyCount = count;
if(count > 0)
setState(noQuarterState);
}
@Override
public void insertQuarter() {
state.insertQuarter();
}
@Override
public void ejectQuarter() {
state.ejectQuarter();
}
@Override
public void turnCrank() {
state.turnCrank();
}
@Override
public void dispense() {
state.dispense();
}
public void setState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void setCandyCount(int candyCount) {
this.candyCount = candyCount;
}
public int getCandyCount() {
return candyCount;
}
}
Se puede ver que, con este diseño, la máquina de caramelos no necesita conocer los cambios de estado, solo tiene que llamar a los métodos del estado. El cambio de estado ocurre dentro del estado. Esto es el "patrón de estado".
Si en este momento se añade otro estado, la máquina de caramelos no necesita hacer ningún cambio, solo necesitamos añadir otra clase de estado y luego agregar el proceso de transición en los métodos de las clases de estado relacionadas.
¿No lo entiendes? Aquí hay otro artículo sobre el patrón de estado:https://dzone.com/articles/state-pattern-simplified
Enlace original: https://blog.csdn.net/z55887/article/details/73198039
Enlace original: https://blog.csdn.net/z55887/article/details/60608898