Patrón de diseño de estrategia

Dec 01 2022
encapsular lo que varía
Digamos que tenemos una aplicación que permite a los clientes pagar por comprar un producto o recibir un servicio. La aplicación tiene más de una forma de pago, el cliente puede usar efectivo, tarjeta visa o billetera, podemos hacerlo así: En realidad no, tenemos tres tipos de pago y en cada caso hay una implementación diferente, por lo que tenemos un solo comportamiento "pagar" de diferentes maneras, entonces, ¿qué podría pasar si tomamos lo que varía y lo colocamos en clases separadas y usamos una interfaz común para el comportamiento común?

Digamos que tenemos una aplicación que permite a los clientes pagar por comprar un producto o recibir un servicio.

La aplicación tiene más de una forma de pago, el cliente puede usar efectivo, tarjeta visa o billetera, podemos hacerlo así:

public class Customer {

  public void pay(String paymentType) {
    if (StringUtils.isEmpty(paymentType)) {
      throw new RuntimeException("please add payment method");
    }
      // pay according to payment type
    if (paymentType.equalsIgnoreCase("cash")) {
      // pay in cash steps ( Algorithm )
    } else if (paymentType.equalsIgnoreCase("visa")) {
      // use the visa for payment steps ( Algorithm )
    } else if (paymentType.equalsIgnoreCase("wallet")) {
      // use the wallet for payment steps ( Algorithm )
    } else {
      throw new RuntimeException(paymentType + "payment type is not available");
    }
  }
}

public class Client {
    public static void main(String[] args) {
        Customer customer = new Customer();
        customer.pay("cash");
    }
}

En realidad no,

  1. Si desea agregar un nuevo método de pago, debe cambiar esta clase, y eso hace que su diseño no sea flexible para cualquier cambio nuevo, con esta implementación violamos uno de los principios de diseño SOLID que es: " las entidades de software deben estar abiertas ". por ampliación pero cerrado por modificación
  2. La mayor parte de la lógica en la clase no se usa, una vez que el cliente elige el tipo de pago, por lo que si el cliente desea pagar en efectivo, no se usará el código que maneja la visa y la billetera.
  3. Como puede ver en esta forma de diseño, debe manejar todos los tipos de pago posibles, incluso si no tiene sentido, como un tipo de pago NULL o un tipo de pago vacío, y puede restringir fácilmente al cliente para que elija solo tipos de pago válidos. , mediante el uso de un patrón de estrategia.

Tenemos tres tipos de pago y en cada caso hay una implementación diferente, por lo que tenemos un comportamiento que "paga" con diferentes formas, entonces, ¿qué podría pasar si tomamos lo que varía y lo ponemos en clases separadas y usamos una interfaz común para el comportamiento común? .

Vamos a crear una interfaz llamada Pago . Tendrá un método abstracto, y cada método de pago individual tiene su clase concreta que implementa la interfaz de Pago . El Cliente tendrá la interfaz de pago como una "composición de uso" variable y el tipo de método será decidido por la elección del cliente.

public interface Payment {
    void pay();
}
public class CashPayment implements Payment {
    @Override
    public void pay() {
        // pay in cash steps ( Algorithm )
    }
}
public class VisaPayment implements Payment {
    @Override
    public void pay() {
        // use the visa for payment steps ( Algorithm )
    }
}
public class WalletPayment implements Payment {
    @Override
    public void pay() {
        // use the wallet for payment steps ( Algorithm )
    }
}

public class Customer {
    private Payment payment;

    public Customer(Payment payment) {
        this.payment = payment;
    }

    public void setPaymentType(Payment payment) {
        this.payment = payment;
    }

    public void pay() {
        payment.pay();
    }
}

public class Client {
    public static void main(String[] args) {
        Customer customer = new Customer(new VisaPayment());
        customer.pay();
        // you can change the payment type at runtime
        customer.setPaymentType(new CashPayment());
        customer.pay();
    }
}

Diagrama UML para el ejemplo de pago del cliente

Criterios de diseño

de la discusión anterior podemos concluir cuatro principios de diseño:

  1. Diseño para una interfaz no para implementación
  2. Encapsular lo que varía
  3. Las entidades de software deben estar abiertas para la extensión pero cerradas para la modificación.
  4. Usar composición sobre herencia

La definición del libro para el patrón de estrategia es: Definir una familia de algoritmos, encapsular cada uno y hacerlos intercambiables. La estrategia permite que el algoritmo varíe independientemente de los clientes que lo utilicen.

En nuestro ejemplo, la familia de algoritmos eran los métodos de pago, y encapsulamos cada uno de ellos en la subclase concreta, y son intercambiables por el cliente que puede elegir cualquier tipo de pago incluso en tiempo de ejecución, el algoritmo está separado del Cliente y Cliente. …. y ese es el patrón de estrategia .