Skip to content

Calidad del Software II: Métricas Asociadas al Acoplamiento

Publicado:
1 min de lectura diseño de software, acoplamiento

Tabla de contenido

Ver contenido

Introducción

Hablemos ahora del acoplamiento en relación a la calidad del software, en una entrega pasada hablamos de la cohesión y una forma de medirla mediante LCOM4. Mediremos ahora la inestabilidad de nuestro sistema mediante las métricas de clases acopladas dentro de un paquete.

Medición de Acoplamiento

El acoplamiento representa la dependencia entre clases. Idealmente, una clase debe:

Inestabilidad

Una medida del acoplamiento es la inestabilidad, que representa la fragilidad de una clase a cambios externos.

Cálculo de Inestabilidad

Inestabilidad = Acoplamiento Eferente / (Acoplamiento Aferente + Acoplamiento Eferente)

Por ejemplo, si una clase Bar tiene un acoplamiento aferente de 1 (una clase depende de ella) y un acoplamiento eferente de 5 (ella depende de cinco clases), su inestabilidad será:

5 / (1 + 5) = 0.83, una inestabilidad bastante alta.

Esto significa que la clase Bares fácil de cambiar, pues tiene poca dependencia de otras clases, mientras que por otro lado tiene muchas dependencias externas.

Cálculo de LCOM4

Ejemplo en Ruby

Revisemos este caso en Ruby (considere que está hecho así por fines didácticos):

La clase Route modela una ruta de transporte en un sistema simulado. Su objetivo es representar una secuencia de estaciones por las que pasa un vehículo (como un autobús), y gestionar tanto el avance en la ruta como la generación de pasajeros.

Y tiene como responsabilidades:

class Route
  def initialize(name, stops, distances, num_stops, generator)
    @name = name
    @stops = stops
    @distances_between = distances
    @num_stops = num_stops
    @generator = generator
    @destination_stop_index = 0
    @destination_stop = nil
  end

  def update
    generate_new_passengers
    @stops.each(&:update)
  end

  def is_at_end?
    @destination_stop_index >= @num_stops
  end

  def next_stop
    @destination_stop_index += 1
    if @destination_stop_index < @num_stops
      @destination_stop = @stops[@destination_stop_index]
    else
      @destination_stop = @stops.last
    end
  end

  def get_destination_stop
    @destination_stop
  end

  def get_total_route_distance
    @distances_between.sum
  end

  def get_next_stop_distance
    @distances_between[@destination_stop_index - 1]
  end

  def generate_new_passengers
    @generator.generate_passengers
  end
end

Al analizar la clase, podemos notar que tiene dos grupos de responsabilidades (métodos):

Si vamos a la definición, podemos notar que el uso de los atributos resaltan bastante bien esta división de responsabilidades.

El resultado de LCOM4 es igual a 2, lo que nos indica que la clase tiene 2 responsabilidades, lo que sugiere que podría refactorizarse.

Propuesta de división en clases

A continuación, refactorizaremos la lógica de la clase Route en dos componentes:

Es clave reconocer que la lógica de update implica modificaciones dinámicas en el estado del atributo @stops (estaciones). Mientras que RouteTracker se enfoca en el seguimiento estructural de la ruta —como el avance y el cálculo de distancias—, es PassengerManager quien asume la responsabilidad de manipular el estado de los pasajeros y en relación a las estaciones.

class RouteTracker
  def initialize(stops, distances, num_stops)
    @stops = stops
    @distances_between = distances
    @num_stops = num_stops
    @destination_stop_index = 0
    @destination_stop = nil
  end

  def is_at_end?
    @destination_stop_index >= @num_stops
  end

  def next_stop
    @destination_stop_index += 1
    @destination_stop = if @destination_stop_index < @num_stops
                          @stops[@destination_stop_index]
                        else
                          @stops.last
                        end
  end

  def get_destination_stop
    @destination_stop
  end

  def get_total_route_distance
    @distances_between.sum
  end

  def get_next_stop_distance
    @distances_between[@destination_stop_index - 1]
  end
end
class PassengerManager
  def initialize(generator, stops)
    @generator = generator
    @stops = stops
  end

  def generate_new_passengers
    @generator.generate_passengers
  end

  def update
    generate_new_passengers
    @stops.each(&:update)
  end
end
class Route
  def initialize(name, stops, distances, num_stops, generator)
    @name = name
    @tracker = RouteTracker.new(stops, distances, num_stops)
    @passenger_manager = PassengerManager.new(generator, stops)
  end

  def update
    @passenger_manager.update
  end

  def is_at_end?
    @tracker.is_at_end?
  end

  def next_stop
    @tracker.next_stop
  end

  def get_destination_stop
    @tracker.get_destination_stop
  end

  def get_total_route_distance
    @tracker.get_total_route_distance
  end

  def get_next_stop_distance
    @tracker.get_next_stop_distance
  end

  def generate_new_passengers
    @passenger_manager.generate_new_passengers
  end
end

Como puedes observar, ahora la clase Route actúa como una API que delega responsabilidades a RouteTracker y PassengerManager, mejorando la testabilidad y la separación de responsabilidades.

Conclusión

El cálculo de métricas asociadas a la cohesión es una herramienta más para tomar decisiones de diseño basadas en datos. No debemos depender exclusivamente de una sola métrica, sino utilizarla como guía complementaria a nuestro criterio.

LCOM4 nos ayuda a identificar una posible violación del principio de responsabilidad única (SRP). En este ejemplo, propusimos una refactorización que separa las responsabilidades de manera más clara. Como toda métrica, su valor está en cómo se interpreta y aplica.



Anterior post
Arquitectura de Software III: Principios de Paquetes
Siguiente post
Calidad del Software I: Métricas Asociadas a la Cohesión