¿Cómo evito escribir muchas funciones de paso en un contenedor?


13

Tengo una clase, que envuelve otra clase de un tipo base común. Debido a que la interfaz de tipo base es bastante grande, esto implica escribir muchas funciones de paso. Estoy buscando una manera de evitar esto.

Hagamos un ejemplo:

      Car
     /   \
Volvo     VolvoWithTrailer

Ahora tengo que implementar todas y cada una de las funciones en la interfaz del automóvil para VolvoWithTrailer y llamar a la función apropiada en el objeto Volvo envuelto, excepto quizás GetMaxSpeed ​​() que devolverá algo más bajo. Básicamente tendré muchas funciones como

int VolvoWithTrailer::GetNumSeats() {
  return mVolvo.GetNumSeats()
}

Una forma obvia de resolver esto sería hacer de VolvoWithTrailer una subclase de Volvo

    Car
     |
   Volvo
     |
VolvoWithTrailer

pero eso parece violar el principio de favorecer la composición sobre la herencia.

¿De qué otra forma podría evitar escribir todos esos contenedores (el lenguaje es C ++)? O, si esa es su posición, ¿por qué debería escribirlos / simplemente usar la herencia? ¿Hay alguna plantilla de magia que pueda ayudar con esto?


2
No he pensado demasiado en esto, y pensando en Java. ¿Qué pasa con una interfaz 'Trailer'? ¿VolvoWithTrailer ampliaría el Volvo e implementaría la interfaz del remolque?

77
Favorezca la composición sobre la herencia solo cuando tenga sentido hacerlo.
Robert Harvey

@RobertHarvey: +1. Esa es una guía, no un "principio", y ciertamente no es un mandamiento absoluto.
Mason Wheeler

En Python puedes resolver esto con introspección (lo que C # llama reflexión).
user16764

1
¿Su IDE no maneja la generación de código?
Amy Blankenship

Respuestas:


5

Mi opinión es que debe escribir lo que necesite para mejorar la estabilidad y la capacidad de mantenimiento a largo plazo del programa. ¿Cuál es el punto de evitar escribir 20 líneas de código hoy, si cada vez que toca algo que usa este código o regresa para mantener esta Clase, le cuesta 5 minutos o más porque no invirtió el tiempo hoy?

Entonces la pregunta se convierte en "¿qué necesitas escribir?" No creo que usted proporciona suficiente información para ser capaz de dar una respuesta definitiva, así que voy a enumerar algunas cosas que me gustaría pensar en tomar una decisión:

1. ¿Necesita tener el objeto Trailer solo, ahora o en el futuro previsible?
Si es así, tiene un argumento bastante bueno para hacer el envoltorio alrededor de ambos objetos compuestos, ya que tendrá que envolver uno u otro. Bien podría ser consistente.

2. ¿Alguno de los tres tipos (Car, Trailer, CarWithTrailer) se encuentra en un área del código que los desarrolladores utilizan con frecuencia o parece probable que lo sea?
Si es así, tenga mucho cuidado, ya que las ramificaciones de elegir lo incorrecto pueden amortizarse de manera muy costosa cada vez que se toca el código. Si no, puede que no haya ninguna diferencia en lo que usted decida. Simplemente elige una dirección y ve con ella.

3. ¿Qué es más fácil de entender?
¿Un enfoque te llama la atención de que alguien que viene detrás de ti inmediatamente "entenderá" lo que estás tratando de hacer? ¿Sus compañeros de equipo tienen prejuicios particulares que podrían hacer que un enfoque sea más difícil de mantener? Si todas las cosas son iguales, elija la solución que imponga la carga cognitiva más baja.

4. ¿Existen ventajas adicionales al usar un enfoque sobre otro?
Una cosa que me llama la atención es que la idea de envoltura tiene una clara ventaja si la escribe que su CarWithTrailerWrapper puede envolver cualquier automóvil y cualquier remolque en un CarWithTrailer. Luego, termina escribiendo todos sus métodos de transferencia, pero solo los escribe una vez , en lugar de una vez para cada Clase de automóvil.

Esto hace que su inversión inicial en escribir los métodos de transferencia sea mucho más barata a medida que agrega más automóviles y remolques. También ayuda a reducir la tentación de acoplar cosas directamente al Volvo o VolvoWithTrailer, al tiempo que reduce las consecuencias del acoplamiento al CarWithTrailerWrapper, ya que puede hacer mucho más con esa única implementación.

Tal vez hay distintas ventajas que obtendría de forma gratuita mediante el uso del método de extensión, pero no las veo.

Bien, parece que me he convencido de seguir adelante y completar los métodos de transferencia, para aplicaciones no triviales.

No soy un programador de C ++, así que no tengo idea de qué herramientas de generación de código están disponibles para usted. El IDE que uso puede generar métodos a partir de una interfaz, y puedo agregar plantillas de código que harían que la escritura de pasadas sea bastante sencilla. Si no tiene acceso a un IDE que hace esto, puede llegar bastante lejos dejando que Excel escriba el código por usted .


3

Debido a que la interfaz de tipo base es bastante grande, esto implica escribir muchas funciones de paso.

Y ahí está tu problema.

Las interfaces deben manejar solo una responsabilidad. Mantenerlos delgados y enfocados limita la cantidad de trabajo de paso que podría necesitar hacer en caso de un decorador, proxy u otro contenedor (además de hacer que la clase sea más fácil de usar, probar, mantener y extender).


55
¿Cómo? Una clase puede implementar múltiples interfaces, y las interfaces pueden heredarse entre sí. Entonces, incluso si una interfaz es bastante pequeña, eso no tiene nada que ver con si las clases que las implementan necesitarán muchas funciones o funciones de transferencia. Cuanto más pequeño y bien definidas las clases , más probabilidades existen de que se necesitan de paso a través de las funciones.
Amy Blankenship
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.