El **Principio Abierto/Cerrado** (Open/Closed Principle, OCP) establece que **una clase debe estar abierta para su extensión, pero cerrada para su modificación**. Esto significa que deberíamos poder agregar nuevas funcionalidades a una clase sin cambiar su código existente, lo cual es esencial para preservar la estabilidad del software y minimizar los errores.
Una forma común de aplicar el OCP en Python es utilizando la **herencia** o la **composición** para extender el comportamiento de las clases sin modificar su código base.
### Ejemplo de una Clase que Viola el OCP
Imaginemos una clase que calcula el descuento para una tienda en línea. La clase DiscountCalculator contiene lógica para aplicar distintos tipos de descuentos. Sin embargo, si necesitamos agregar un nuevo tipo de descuento, tendríamos que modificar la clase, violando así el principio OCP.
class DiscountCalculator:
  def calculate(self, price, discount\_type):
  if discount\_type == "percentage":
  return price \* 0.90 # 10% de descuento
  elif discount\_type == "fixed":
  return price - 20 # Descuento fijo de $20
  else:
  return price
Si se agrega un nuevo tipo de descuento, como un descuento para clientes frecuentes, sería necesario modificar el método calculate, lo que va en contra del principio OCP.
### Solución: Aplicando OCP
Para aplicar el OCP, separamos cada tipo de descuento en clases independientes, que pueden extenderse sin necesidad de modificar la lógica existente. Utilizaremos **polimorfismo** mediante una clase base abstracta DiscountStrategy.
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
  @abstractmethod
  def apply\_discount(self, price):
  pass
class PercentageDiscount(DiscountStrategy):
  def apply\_discount(self, price):
  return price \* 0.90 # 10% de descuento
class FixedDiscount(DiscountStrategy):
  def apply\_discount(self, price):
  return price - 20 # Descuento fijo de $20
class NoDiscount(DiscountStrategy):
  def apply\_discount(self, price):
  return price # Sin descuento
class DiscountCalculator:
  def \_\_init\_\_(self, strategy: DiscountStrategy):
  self.strategy = strategy
  def calculate(self, price):
  return self.strategy.apply\_discount(price)
### Explicación
- **Clase DiscountStrategy**: Es una clase abstracta que define el método apply\_discount, el cual debe ser implementado por cada tipo de descuento.
- **Clases PercentageDiscount, FixedDiscount y NoDiscount**: Cada una implementa una estrategia de descuento específica. Puedes agregar nuevas estrategias sin modificar las clases existentes.
- **Clase DiscountCalculator**: Su método calculate utiliza una instancia de DiscountStrategy para aplicar el descuento. Esta clase no necesita ser modificada al agregar un nuevo tipo de descuento.
### Uso del Código
Podemos usar DiscountCalculator con distintas estrategias de descuento de forma flexible:
\# Crear instancias de estrategias de descuento
percentage\_discount = PercentageDiscount()
fixed\_discount = FixedDiscount()
no\_discount = NoDiscount()
\# Aplicar descuentos
calculator = DiscountCalculator(percentage\_discount)
print(f"Precio con descuento del 10%: ${calculator.calculate(100)}")
calculator.strategy = fixed\_discount
print(f"Precio con descuento fijo de $20: ${calculator.calculate(100)}")
calculator.strategy = no\_discount
print(f"Precio sin descuento: ${calculator.calculate(100)}")
### Ventajas de Aplicar el Principio OCP
1. **Extensibilidad**: Puedes agregar nuevos tipos de descuento sin modificar las clases existentes.
2. **Mantenibilidad**: Al no modificar el código base, reduces el riesgo de introducir errores en el código existente.
3. **Flexibilidad**: Puedes intercambiar estrategias de descuento dinámicamente, haciendo el sistema más adaptable.
### Conclusión
Aplicar el Principio Abierto/Cerrado permite que el sistema sea extensible y flexible. Este enfoque de diseño reduce la posibilidad de errores al minimizar los cambios en el código existente y fomenta la reutilización de componentes a través del polimorfismo.