Principio de segregación de interfaz

Buenas prácticas,Desarrollo

Interface Segregation – SOLID IV

24 Nov , 2014  

Interface Segregation, diseñando interfaces a la medida de las necesidades de los clientes

Este principio, desarrollado inicialmente por Robert C. Martin, reza lo siguiente:

Los clientes de un programa dado sólo deberían conocer de éste aquellos métodos que realmente utilizan, y no aquellos que no necesitan utilizar.

Robert C. Martin como consultor para Xerox

Cuando Robert trabajaba en Xerox observó que un sistema de impresora nuevo creado por la empresa que ejecutaba varias tareas como grapar o enviar faxes tenía un software creado desde cero con una clase base llamada Job que se utilizaba prácticamente en todas las tareas. El problema de este diseñó es que como todas las tareas estabas interconectadas entre sí al final la tarea de grapado tenía acceso a las tareas de impresión, por ejemplo. La solución consistió en crear interfaces separadas para las tareas de impresión y de grapado, implementándolas en la clase Job.

Espera… ¿Interfaces?

Vaya, nos encontramos con un pequeño problema. Desde el principio de esta serie de artículos se ha utilizado Ruby como lenguaje de programación en el que ver y aplicar los principios SOLID pero… Ruby no tiene interfaces. Sin embargo, podemos mostrar un ejemplo similar de violación del ISP con una calculadora de tarifas:

class FeeCalculator
  def calculate_fee(product, user, vat)
    # Calculation code...
  end
end

class ProductController
  def show_fee
    @fee = FeeCalculator.new.calculate_fee(product, user, vat)
  end
end

Hasta ahora no parece haber ningún problema, tenemos nuestra clase calculadora de tarifas con su método para realizar el cálculo y un controlador que llama a este método.

Pero entonces otro desarrollador nos informa que va a añadir un nuevo controlador para guardar órdenes de compra y necesita realizar el cálculo de la tarifa guardando además el resultado en la base de datos. Y es entonces cuando muchos desarrolladores llegan a la siguiente conclusión:

class FeeCalculator
  def calculate_fee(product, user, var, store_result)
    # Calculation code...
    
    if store_result
      # Store result into DB
    end
  end
end

class ProductController
  def show_fee
    @fee = FeeCalculator.new.calculate_fee(product, user, vat, false)
  end
end

class OrderController
  def create_order
    @fee = FeeCalculator.new.calculate_fee(product, user, vat, true)
  end
end

Añadimos un nuevo argumento a nuestro método existente, metemos el código adicional… Y listo, ¡todo funcionando!.

Un momento…

Si nos paramos detenidamente a ver esta solución, el desarrollador que ha confeccionado ProductController se ve obligado a modificar su llamada original para enviar un parámetro que no tiene ningún sentido para él ya que nunca le ha interesado guardar el cálculo de la tarifa. Entonces, ¿por qué le obligamos a depender de un código que no está utilizando?

Si leemos el enunciado del principio de segregación de interfaces y traducimos interfaz por parámetros del método veremos que estamos violando este principio.

Separando los métodos

No pases más de cuatro parámetros en un método. Las opciones de hash son parámetros.

Sandi Metz

Una vez visto el problema podríamos proceder a resolverlo de diferentes formas. Esta es una aproximación:

class FeeCalculator
  def calculate_fee(product, user, vat)
    # Calculation code...
  end
  
  def save(fee)
    # Store result into DB
  end
end

class ProductController
  def show_fee
    @fee = FeeCalculator.new.calculate_fee(product, user, vat, false)
  end
end

class OrderController
  def create_order
    fee_calculator = FeeCalculator.new
    fee = fee_calculator.calculate_fee(product, user, vat, true)
    fee_calculator.save(fee)
  end
end

Simplemente hemos dividido nuestro método original en dos métodos y así cada cliente utiliza el código que realmente necesita utilizar.

Más información

Visita el resto de la serie de artículos

, ,


Deja un comentario

A %d blogueros les gusta esto: