Patrones de diseño de Java (1/23): Patrones de estrategia

definición

El modo Estrategia define una familia de algoritmos y los encapsula por separado para que puedan ser reemplazados entre sí.Este modo permite que los cambios del algoritmo sean independientes de los clientes que utilizan el algoritmo.

inserte la descripción de la imagen aquí

Caso: Simular aplicación de pato

al principio

inserte la descripción de la imagen aquí

Nuevo requisito: el programa de simulación necesita un pato volador

Agregue un nuevo método fly() a la clase principal.
inserte la descripción de la imagen aquí


Desventaja de esto : no todas las subclases de Duck pueden volar, como el pato de goma.

inserte la descripción de la imagen aquí

Cuando se trata de mantenimiento , el uso de la herencia con fines de reutilización no termina bien.


Un remedio es anular el método fly() en la clase de pato de goma.

inserte la descripción de la imagen aquí


Nuevo problema : se agregó la clase DecoyDuck, que es un pato falso que no puede volar ni ladrar.

inserte la descripción de la imagen aquí

Aprovechar la herencia para proporcionar el comportamiento de Duck genera los siguientes inconvenientes:

  1. El código se repite en varias subclases.
  2. El comportamiento en tiempo de ejecución no se puede cambiar fácilmente.
  3. Es difícil conocer el comportamiento completo de todos los patos.
  4. El cambio puede afectar todo el cuerpo, provocando cambios que otros patos no quieren.

Es una pesadilla interminable verse obligado a verificar y posiblemente anular fly() y quark() cada vez que aparece una nueva subclase de pato .


Una mejora : fly() se puede sacar y poner en una interfaz Flyable . De esta forma, solo los patos voladores implementan esta interfaz. De la misma manera, también se puede utilizar para diseñar una interfaz Quackable , porque no todos los patos pueden ladrar.

inserte la descripción de la imagen aquí

Aunque Flyable y Quackable pueden resolver algunos de los problemas (ya no habrá más patitos de goma voladores), hacen que el código no sea reutilizable , lo que solo puede considerarse como saltar de una pesadilla a otra. Incluso, en un pato que puede volar, puede haber muchas variaciones de la acción de volar...

Esto significa: cada vez que necesite modificar un comportamiento, debe rastrear y modificar cada clase que define este comportamiento, y si no tiene cuidado, puede crear nuevos errores.

(MyNote: la clave predeterminada de Java8 puede tener un código de implementación en la clase de interfaz).

Una hermosa visión : si existe una manera de crear software de modo que cuando necesitemos cambiar el software, sea fácil pasar menos tiempo rehaciendo el código y dejar que el programa haga más con el menor impacto en el código existente. Que bueno debe ser...

Una verdad inmutable del desarrollo de software: el cambio dura para siempre

No importa qué tan bien diseñado esté el software, después de un tiempo, siempre necesita crecer y cambiar, o el software morirá .

Hay muchos factores que impulsan el cambio, tales como:

  • El cliente o usuario necesita algo más, o quiere una nueva función.
  • La empresa decidió utilizar otro producto de base de datos y también compró datos de otro proveedor, lo que resultó en formatos de datos incompatibles.

Principio de diseño: separación de variaciones

Averigüe dónde se pueden necesitar cambios en su aplicación, aíslelos y no los mezcle con código que no necesita cambiar.

Saque y encapsule las partes que cambiarán para que las otras partes no se vean afectadas. Después de los cambios de código, las sorpresas se reducen y el sistema se vuelve más resistente .

(MyNote: Centrarse en el cambio.)

En otras palabras, si cada vez que llega un nuevo requisito, cambiará a un cierto aspecto del código , entonces puede estar seguro de que esta parte del código debe extraerse y diferenciarse de otro código no visto.

Aquí hay otra forma de pensar sobre este principio: sacar y encapsular la parte que cambia para que pueda extenderse fácilmente más adelante sin afectar otras partes que no necesitan cambiar .

Tal concepto es simple y es el espíritu detrás de casi todos los patrones de diseño . Todos los patrones proporcionan una forma de cambiar una parte del sistema sin afectar otras partes .

Simulación de separación de variaciones en programas Duck

Fly() y quack() en la clase Duck cambiarán de pato a pato.

Para separar estos dos comportamientos de la clase Duck, los sacaremos de la clase Duck y crearemos un nuevo conjunto de clases que representan cada comportamiento.

inserte la descripción de la imagen aquí

Diseño pato dos comportamientos

Queremos que todo sea flexible, también queremos poder asignar comportamientos a instancias de patos, digamos que queremos generar instancias de ánades reales y asignarles tipos específicos de comportamientos de vuelo.

Solo deja que el comportamiento del pato cambie dinámicamente por cierto . En otras palabras, deberíamos incluir métodos para establecer el comportamiento en la clase de pato para que podamos cambiar dinámicamente el comportamiento de vuelo del ánade real en tiempo de ejecución .

Principio de diseño: programa para la interfaz, no para la implementación.

Representamos cada comportamiento con una interfaz, por ejemplo, FlyBehavior y QuackBehavior, y cada implementación del comportamiento debe implementar una de estas interfaces. Así que esta vez la clase pato no se encargará de implementar esta interfaz, sino que otras clases implementarán específicamente FlyBehavior y QuackBehavior, estas otras clases se llaman clases de comportamiento. La interfaz de comportamiento es implementada por la clase de comportamiento, no por la clase Duck.

Este enfoque es muy diferente del pasado, el enfoque anterior era:

  1. El comportamiento se deriva de la implementación concreta de la superclase Duck,

  2. Heredar una interfaz e implementarla por subclases.

Ambos enfoques dependen de la implementación y nosotros estamos vinculados a la implementación, sin forma de cambiar el comportamiento (excepto escribiendo más código).

En nuestro nuevo diseño, las subclases de pato usarán el comportamiento representado por las interfaces (FlyBehavior y QuackBehavior), por lo que la implementación real no está vinculada a las subclases de pato. (En otras palabras, el código de implementación específico está en la clase específica que implementa FlyBehavior y QuakcBehavior).

inserte la descripción de la imagen aquí

Principio de diseño: programa para la interfaz, no para la implementación

Programar contra interfaces realmente significa programar contra supertipos .

La llamada interfaz aquí tiene múltiples significados.

  • un concepto,
  • Una construcción de interfaz Java.

Puede programar contra interfaces sin involucrar las interfaces de Java.La clave es el polimorfismo .

Usando polimorfismo , el programa se puede programar para el supertipo, y el comportamiento real se ejecutará de acuerdo con la situación real durante la ejecución, y no estará vinculado al comportamiento del supertipo.

Para la programación de supertipos , se puede establecer más claramente: el tipo declarado de una variable debe ser un supertipo, generalmente una clase abstracta o una interfaz , por lo que siempre que sea un objeto generado por una clase que implemente específicamente este supertipo, puede ser asignado a esta variable; esto también significa que al declarar una clase, ¡no se preocupe por el tipo de objeto real cuando se ejecute más tarde!


Eche un vistazo al siguiente ejemplo de polimorfismo simple : suponga que hay una clase abstracta Animal y hay dos implementaciones concretas (Perro y Gato) que heredan Animal.

inserte la descripción de la imagen aquí

Para la realización de la programación, la práctica es la siguiente:

Dog d = new Dog();
d.bark();  

Declarar la variable d como tipo Perro (que es la implementación concreta de Animal) hará que tengamos que codificar la implementación.

Pero para la programación de interfaz/supertipo , el enfoque sería el siguiente:

Animal animal = new Dog();
animal.makeSound();

Sabemos que el objeto es un perro, pero ahora hacemos llamadas polimórficas usando animal.

Aún mejor, las acciones de creación de instancias de subtipos ya no necesitan estar codificadas en el código, como new Dog(), sino que deben especificar el objeto de implementación concreto en tiempo de ejecución.

a = getAnimal();
a.makeSound();

No sabemos cuál es el subtipo real, solo nos importa que sepa cómo hacer makeSound() correctamente.

(MyNote: ¡No hay nada de malo en escribir una implementación específica al comienzo de la codificación y refactorizar activamente si hay un cambio en la demanda en el medio!)

Implementar comportamiento de pato

Disponemos de dos interfaces, FlyBehavior y QuackBehavior, y sus correspondientes clases, encargadas de implementar comportamientos específicos:

inserte la descripción de la imagen aquí

Este diseño permite que las acciones de volar y croar sean reutilizadas por otros objetos, porque estos comportamientos no tienen nada que ver con las clases de pato.

Y podemos agregar algunos comportamientos, que no afectarán las clases de comportamiento existentes, ni afectarán a las clases de pato que usan el comportamiento de vuelo.

De esta manera, existe el beneficio de reutilización de la herencia sin la carga de la herencia. (MyNote: evita el amor del hermano sin perder el amor de la cuñada)

Integrar el comportamiento del pato

El punto es que los patos ahora delegarán las acciones de volar y croar a otros , en lugar de usar métodos definidos en su propia clase (o subclase).

inserte la descripción de la imagen aquí

Luego implemente performQuack() (similar a performFly())

public class Duck {
    
    
    QuackBehavior quackBehavior;
    
    // 还有更多
    public void performQuack() {
    
    
        //不亲自处理呱呱叫行为,而是委托给quackBehavior对象。
        quackBehavior.quack();
    }
}

Las subclases de Duck asignan variables de comportamiento en el constructor.

public class MallardDuck extends Duck {
    
    
    public MallardDuck() {
    
    
        quackBehavior = new Quack();
        flyBehavior = new FlyWithWings();
    }
    public void display() {
    
    
    	System.out.println(I’m a real Mallard duck”);
    }
}

calificación finalizada

clase abstracta de pato

public abstract class Duck {
    
    
	FlyBehavior flyBehavior;
	QuackBehavior quackBehavior;

	public Duck() {
    
    
	}

	public void setFlyBehavior(FlyBehavior fb) {
    
    
		flyBehavior = fb;
	}

	public void setQuackBehavior(QuackBehavior qb) {
    
    
		quackBehavior = qb;
	}

	abstract void display();

	public void performFly() {
    
    
		flyBehavior.fly();
	}

	public void performQuack() {
    
    
		quackBehavior.quack();
	}

	public void swim() {
    
    
		System.out.println("All ducks float, even decoys!");
	}
}
truco de pato
public class DecoyDuck extends Duck {
    
    
	public DecoyDuck() {
    
    
		setFlyBehavior(new FlyNoWay());
		setQuackBehavior(new MuteQuack());
	}
	public void display() {
    
    
		System.out.println("I'm a duck Decoy");
	}
}
pato real
public class MallardDuck extends Duck {
    
    

	public MallardDuck() {
    
    

		quackBehavior = new Quack();
		flyBehavior = new FlyWithWings();

	}

	public void display() {
    
    
		System.out.println("I'm a real Mallard duck");
	}
}
pato de cabeza roja
public class RedHeadDuck extends Duck {
    
    
 
	public RedHeadDuck() {
    
    
		flyBehavior = new FlyWithWings();
		quackBehavior = new Quack();
	}
 
	public void display() {
    
    
		System.out.println("I'm a real Red Headed duck");
	}
}
pato de goma
public class RubberDuck extends Duck {
    
    
 
	public RubberDuck() {
    
    
		flyBehavior = new FlyNoWay();
		quackBehavior = new Squeak();
//		quackBehavior = () -> System.out.println("Squeak");
	}
	
	public RubberDuck(FlyBehavior flyBehavior, QuackBehavior quackBehavior) {
    
    
		this.flyBehavior = flyBehavior;
		this.quackBehavior = quackBehavior; 
	}
 
	public void display() {
    
    
		System.out.println("I'm a rubber duckie");
	}
}

interfaz de comportamiento de vuelo

public interface FlyBehavior {
    
    
	public void fly();
}
comportamiento de no volar
public class FlyNoWay implements FlyBehavior {
    
    
	public void fly() {
    
    
		System.out.println("I can't fly");
	}
}
comportamiento de vuelo
public class FlyWithWings implements FlyBehavior {
    
    
	public void fly() {
    
    
		System.out.println("I'm flying!!");
	}
}

interfaz de comportamiento de llamadas

public interface QuackBehavior {
    
    
	public void quack();
}
clase de comportamiento
public class MuteQuack implements QuackBehavior {
    
    
	public void quack() {
    
    
		System.out.println("<< Silence >>");
	}
}
comportamiento de charlatán
public class Quack implements QuackBehavior {
    
    
	public void quack() {
    
    
		System.out.println("Quack");
	}
}
comportamiento chirriante
public class Squeak implements QuackBehavior {
    
    
	public void quack() {
    
    
		System.out.println("Squeak");
	}
}

ejecutar clase de prueba

public class MiniDuckSimulator {
    
    
 
	public static void main(String[] args) {
    
    
		Duck mallard = new MallardDuck();
		mallard.performQuack();
		mallard.performFly();
   
		System.out.println("---");
		
		Duck model = new RedHeadDuck();
		model.performFly();
        //改变飞行行为
		model.setFlyBehavior(new FlyNoWay());
		model.performFly();
	}
}

resultado de la operación:

I'm Quack
I'm flying!!
---
I'm flying!!
I can't fly

Este ejemplo de retrato familiar de clase e interfaz

inserte la descripción de la imagen aquí

Principio de diseño: use más composición, menos herencia

De acuerdo con este ejemplo, el uso de combinación para construir un sistema tiene una gran flexibilidad, no solo se puede encapsular la familia de algoritmos en una clase, sino que también se puede cambiar dinámicamente el comportamiento en tiempo de ejecución, siempre que el objeto de comportamiento combinado se ajuste al comportamiento correcto. estándar de interfaz.

Referencias

  1. Patrón de diseño de cabeza primero

Supongo que te gusta

Origin blog.csdn.net/u011863024/article/details/119792924
Recomendado
Clasificación