2012-09-04 14 views
6

Así que decidí usar el Patrón de diseño de fábrica junto con la Inyección de dependencias.Demasiadas matrices de constructores para el patrón de diseño de inyección/herencia de dependencias

class ClassA 
{   
    Object *a, *b, *c; 
    public: 
    ClassA(Object *a, Object *b, Object *c) : 
    a(a), b(b), c(c) {} 
}; 

class ClassB : public ClassA 
{   
    Object *d, *e, *f; 
    public: 
    ClassB(Object *a, Object *b, Object *c, Object *d, Object *e, Object *f) : 
    ClassA(a, b, c), d(d), e(e), f(f) {} 
}; 

Ahora, el problema es que classB tiene demasiados argumentos para el constructor. Este es un único ejemplo de capa de herencia, pero cuando las capas de herencia comienzan a hacerse más profundas, y cuando cada clase de capa necesita más objetos para construirse, el constructor en la capa superior termina requiriendo demasiados argumentos para poder hacerlo.

Sé que podría usar setters en lugar del constructor, pero ¿hay alguna otra manera?

+12

Mi enfoque general es evitar la herencia tanto como sea posible, y tratar de mantener cada clase enfocada en una sola responsabilidad. ¿Podrías construir 'ClassA' por separado, y luego inicializar' ClassB' con una referencia a eso, en lugar de unirlos estrechamente a través de la herencia? –

+2

Mike Seymour tiene razón. Prefiere una relación has-a (composición) a la relación más estrechamente acoplada: una relación (herencia). Tal vez si explica lo que quiere hacer, podemos decirle cómo hacerlo mejor. – metal

+2

No veo ningún problema importante con la herencia. Debe usarse en todas partes donde se necesite. No debe ser abusado como todo lo demás. –

Respuesta

1

Este es uno de los problemas de C++ (si esto se puede llamar un problema). No tiene otra solución que tratar de mantener el número de parámetros del ctor mínimo.

Uno de los métodos es el uso de los puntales estructura gustaría:

struct PropsA 
{ 
    Object *a, *b, *c; 
}; 

class ClassA 
{ 
    ClassA(PropsA &props, ... other params); 
}; 

Esto parece obvio, pero yo no utilizar esto varias veces. En muchos casos resulta que algún grupo de params está relacionado. En este caso, tiene sentido definir una estructura para ellos.

Mi peor pesadilla de este tipo fue con las clases de envoltura delgada. Se puede acceder directamente a los métodos y campos de datos de la base, mientras que todos los ctors se tienen que duplicar. Cuando hay más de 10 ctors, la creación de un contenedor comienza a ser un problema.

+0

En la mayoría de los casos, tendría sentido hacer de 'PropsA' una clase de pleno derecho mediante el uso de métodos relacionados. De lo contrario, terminas con una estructura de datos que no está muy orientada a objetos. – casablanca

+1

Puse los requisitos de la aplicación, la velocidad de desarrollo y la claridad del código MUY LEJOS por delante de las cosas sofisticadas de ser "orientado a objetos". –

+1

OOP no es "elegante", pero es una opción que tomas o dejas. Estoy de acuerdo con que alguien lo haga por procedimientos, utilizando estructuras y funciones, pero si planeas usar clases, vale la pena esforzarte para hacerlo bien, de lo contrario terminas en un lío donde la mitad de tu código está orientado a objetos y el resto es de procedimiento; no creo que eso conduzca a mucha claridad. – casablanca

6

Setter no se recomienda para tales cosas, ya que dan como resultado un objeto parcialmente construido que es muy propenso a errores. Un patrón común para construir un objeto que requiere muchos parámetros es el uso de un generador. La responsabilidad de ClassBBuilder es crear objetos ClassB. Haces que el constructor de ClassB sea privado y permitas que solo el constructor lo llame usando una relación de amigos. Ahora, el constructor puede mirar alguna manera como esto

ClassBBuilder { 
    public: 
    ClassBBuilder& setPhoneNumber(const string&); 
    ClassBBuilder& setName(consg string&); 
    ClassBBuilder& setSurname(const string&); 
    ClassB* build(); 
} 

y utiliza el constructor le gusta esto:

ClassB* b = ClassBBuilder().setName('alice').setSurname('Smith').build(); 

construcción cheques() método que se establecen todos los parámetros necesarios y entonces devuelve construyen adecuadamente objeto o NULO. Es imposible crear un objeto parcialmente construido. Todavía tiene un constructor con muchos argumentos, pero es privado y se llama solo en un solo lugar. Los clientes no lo verán Los métodos de compilación también documentan muy bien lo que significa cada parámetro (cuando ves ClassB ('foo', 'bar') necesitas verificar el constructor para descubrir qué parámetro es un nombre y cuál es un apellido).

+0

Esto puede ser un poco más agradable que los setters, pero está efectivamente retrasando la verificación de suficiente información está presente para construir un objeto para el tiempo de ejecución. No considero una mejora sobre un constructor simple tomar algunos argumentos. –

+0

Si desea cumplir el tiempo de compilación, a veces puede beneficiarse del constructor. Este es el caso cuando un constructor de clase toma muchos parámetros opcionales con valores predeterminados. Luego, puede usar los ajustadores del constructor para establecer estos parámetros opcionales y el método build() toma todos los parámetros requeridos. Esto le daría cumplimiento de tiempo de compilación para que se pasen todos los parámetros necesarios, pero será más legible y menos propenso a errores que una llamada a un constructor que toma muchos parámetros, algunos de ellos requieren que algunos de ellos sean opcionales con valores predeterminados. –

+1

Me gusta este enfoque. El código para un cliente que construye el objeto es mucho más limpio. Aunque es cierto que pierde la verificación del tiempo de compilación, la función build() aún permite una verificación de validación en su código. –

1

Creo que lo que usted está describiendo no es un problema en C++ - de hecho, C++ refleja las dependencias expresada por su diseño bastante bien:

  1. Para construir un objeto de tipo ClassA, es necesario tener tres instancias Object (a, b y c).
  2. Para construir un objeto del tipo ClassB, también necesita tener tres instancias Object (d, e y f).
  3. Cada objeto del tipo ClassB se puede tratar como un objeto del tipo ClassA.

Esto significa que para la construcción de un objeto de tipo ClassB es necesario proporcionar tres Object objetos que son necesarios para la implementación de la interfaz ClassA, y luego otros tres para la implementación de la interfaz ClassB.

Creo que el problema real aquí está su diseño. Se podría considerar diferentes enfoques para resolver este:

  1. No deje ClassB hereda ClassA. Puede o no ser una opción según si necesita acceso homogéneo a objetos de cualquier tipo (por ejemplo, porque tiene una colección de ClassA* y esta colección también puede contener punteros al ClassB).
  2. Busca objetos que siempre aparecen juntos. Me gusta: quizás los primeros dos objetos pasados ​​a cualquiera de los constructores (a y b o d y e) representan algún tipo de par. Tal vez un identificador de objeto o similar? En este caso, puede ser beneficioso introducir un resumen dedicado (leer: tipo) para esto.