2011-12-29 16 views
41

Estoy leyendo "Código limpio" y tengo problemas para entender cómo mantener algunas de mis funciones (generalmente constructores) en un MÁXIMO de 3 parámetros.Reducción del número de argumentos a un constructor

A menudo mis objetos necesitan una gran cantidad de información para trabajar: ¿se supone que debo hacer un pequeño constructor y luego usar funciones de mutador para darles toda la información? Esto no parece ser mejor que simplemente usar un gran constructor.

Como ejemplo, tengo una clase "MovablePatch". Permite al usuario arrastrar un cuadrado en una ventana. Necesita varios parámetros, incluidos Radius, Color, Renderer, InitialPosition y Visibility. Actualmente Colecciono todo esto de mi interfaz gráfica de usuario y luego llamo:

MovablePatch(int radius, Renderer* renderer, Color color, Position initial, bool visibility) 

Estas son sólo algunas de las cosas que necesito en esta clase. ¿Alguien puede sugerir de qué otra forma puedo empaquetar esta información para pasarla al constructor? No veo ningún obvio "dividirlo en clases más pequeñas" que aparecen aquí.

+7

Busque el patrón del generador. – Bashwork

+1

¿Tienes que seguir esa línea guía? De lo contrario, diría que lo ignoro donde no es obvio cómo aplicarlo. Imo si las modificaciones para seguir una directriz no son intuitivas, generalmente no vale la pena refactorizar el código para garantizar que se siga la pauta sin importar qué. Para una solución única que puede conducir fácilmente a un código más complicado (y por lo tanto bugprone) que ni siquiera tiene una mejor legibilidad (un ejemplo para esto es el pequeño constructor mencionado seguido de llamadas a función de mutador o el patrón de generador, que hacen que sea fácil olvida establecer algún valor). – Grizzly

+6

Usando una simple 'struct' para que se pasen los atributos relacionados, ya que se ha hecho un parámetro desde antes de que yo naciera, estoy bastante seguro. – AJG85

Respuesta

31

Usted podría tener

MovablePatch(Renderer* renderer, CircleAppearance circleAppearance) 

donde CircleAppearance recoge la otra información.

Sin embargo, el código limpio y otros libros que generalizan sobre cómo debe ser el buen código, apuntan al 80 por ciento del código que existe. Su código parece estar "más cerca del metal" que la variedad típica de LoB (línea de negocio). Como tal, puede toparse con lugares donde ciertos ideales de codificación no son aplicables.

¡Lo más importante es que estás pensando en ello y tratando de mantener las cosas limpias y ordenadas! :)

20

Algunas de las cosas que está pasando podrían resumirse en una construcción más grande. Por ejemplo, visibility, color y radius, podría tener sentido colocarse en un objeto que usted defina. Entonces, una instancia de esta clase, llámala ColoredCircle, podría pasarse al constructor de MovablePatch. A ColoredCircle no le importa dónde está o qué renderizador está usando, pero sí un MovablePatch.

Mi punto principal, es que desde una perspectiva OO, radius no es realmente un número entero, es un radio. Desea evitar estas largas listas de constructores porque es desalentador comprender el contexto de estas cosas. Si los recopila en una clase más grande, algo así como lo que ya tiene con Color y Position, puede tener menos parámetros pasados ​​y hacer que sea más fácil de entender.

+2

¿Esto no solo cambia el problema? También tiene que inicializar la clase 'ColoredCircle' de alguna manera, y todavía quedan cuatro parámetros ... – MartinStettner

+2

@MartinStettner, sí, primero tiene que inicializar' ColoredCircle', pero deben ser cuatro de los parámetros. Eso significa que 'MovablePatch' solo tomaría un' ColoredCircle' y un 'Renderer *'. –

+1

¿Debería almacenarse 'Position' en el Patch o en el Circle? Creo que el parche tiene la posición, no el círculo. –

8

Una buena opción es usar un patrón de generador, donde cada método "setter" devuelve la propia instancia, y puede encadenar los métodos como lo necesite.

En su caso, obtendrá una nueva clase MovablePatchBuilder.

El enfoque es muy útil y puede encontrarlo en muchos marcos e idiomas diferentes.

Consulte here para ver algunos ejemplos.

12

El Named Parameter Idiom es útil aquí.En su caso, es posible que tenga

class PatchBuilder 
{ 
public: 
    PatchBuilder() { } 
    PatchBuilder& radius(int r) { _radius = r; return *this; } 
    PatchBuilder& renderer(Renderer* r) { _renderer = r; return *this; } 
    PatchBuilder& color(const Color& c) { _color = c; return *this; } 
    PatchBuilder& initial(const Position& p) { _position = p; return *this; } 
    PatchBuilder& visibility(bool v) { _visibility = v; return *this; } 

private: 
    friend class MovablePatch; 
    int _radius; 
    Renderer* _renderer; 
    Color _color; 
    Position _position; 
    bool _visibility; 
}; 

class MovablePatch 
{ 
public: 
    MovablePatch(const PatchBuilder& b) : 
     _radius(b._radius); 
     _renderer(b._renderer); 
     _color(b._color); 
     _position(b._position); 
     _visibility(b._visibility); 
    { 

    } 

private: 
    int _radius; 
    Renderer* _renderer; 
    Color _color; 
    Position _position; 
    bool _visibility; 
}; 

entonces que lo utilice como tal

int 
main() 
{ 
    MovablePatch foo = PatchBuilder(). 
     radius(1.3). 
     renderer(asdf). 
     color(asdf). 
     position(asdf). 
     visibility(true) 
    ; 
} 

excesivamente simplificada, pero creo que se pone el punto a través. Si se requieren ciertos parámetros que pueden ser incluidos en el PatchBuilder constructor:

class PatchBuilder 
{ 
public: 
    PatchBuilder(const Foo& required) : _foo(required) { } 
    ... 
}; 

Obviamente este patrón degenera en el problema original si se requiere que todos los argumentos, en cuyo caso el idioma parámetro llamado no es aplicable. El punto es que esta no es una solución única para todos, y como Adam describe en el comentario a continuación, hay costos adicionales y algunos gastos indirectos al hacerlo.

+1

Me tomé la libertad de agregar las declaraciones 'return * this' a los métodos de' PatchBuilder';) ... – MartinStettner

+11

Esta nueva fijación fluida del generador es peligrosa. Debe tener un control de tiempo de ejecución para ver si todo estaba ocupado. No recomendaría esto. Complejidad innecesaria que va a costar mantener. –

+1

@ AdamDymitruk He actualizado la respuesta con algunas palabras basadas en su sugerencia. –

28

No tome máximas como "no tendrá más de 3 parámetros en sus constructores" al valor nominal. Si tienes la más mínima posibilidad de hacer que un objeto sea inmutable, hazlo; y si es inmutable significa que va a tener un constructor con 50 parámetros, que así sea; ve a por ello; ni lo pienses dos veces.

Incluso si el objeto va a ser mutable, debe pasar su constructor tantos parámetros como sea necesario para que inmediatamente después de la construcción esté en un estado válido y significativo. En mi libro, es absolutamente inadmisible tener que saber cuáles son los métodos mágicos de los mutatores que deben llamarse (a veces incluso en el orden correcto) antes de que se pueda invocar cualquier otro método, bajo pena de segfault.

Dicho esto, si realmente desea reducir el número de parámetros a un constructor, o a cualquier función, simplemente pase este método una interfaz que pueda invocar para obtener de él las cosas que necesita para poder trabajo.

+7

+1 para esto. La inmutabilidad es mucho más beneficiosa que las listas de parámetros pequeños. – vocaro

Cuestiones relacionadas