2009-04-15 33 views

Respuesta

18

Es un patrón lo suficientemente útil si hay muchas cosas que se deben establecer en un objeto.

class Foo 
{ 
     int x, y, z; 
public: 
     Foo &SetX(int x_) { x = x_; return *this; } 
     Foo &SetY(int y_) { y = y_; return *this; } 
     Foo &SetZ(int z_) { z = z_; return *this; } 
}; 

int main() 
{ 
     Foo foo; 
     foo.SetX(1).SetY(2).SetZ(3); 
} 

Este modelo sustituye a un constructor que toma tres enteros:

int main() 
{ 
     Foo foo(1, 2, 3); // Less self-explanatory than the above version. 
} 

Es útil si tiene un número de valores que no siempre necesitan ser ajustados.

Como referencia, un ejemplo más completo de este tipo de técnica se denomina "Named Parameter Idiom" en C++ FAQ Lite.

Por supuesto, si está utilizando esto para los parámetros con nombre, es posible que desee echar un vistazo a boost::parameter. O puede que no ...

+0

Últimamente me he sentido intrigado por esta técnica. Será interesante ver si alguien presenta algún inconveniente. –

+0

Interesante. No lo había considerado una alternativa a los constructores complejos. Esta técnica podría reducir un gran número de sobrecargas de constructor. –

+1

Hay algunas cosas que no puede hacer con esto que puede hacer con los constructores (por ejemplo, inicializaciones y referencias) – Eclipse

0

No lo creo. Por lo general, piensas en el objeto 'setter' como haciendo exactamente eso.

Además, si acaba de configurar el objeto, ¿no tiene un puntero de todos modos?

2

No todos los ajustadores, pero algunos de ellos podrían devolver la referencia al objeto para ser útiles.

tipo de

a.SetValues(object)(2)(3)(5)("Hello")(1.4); 

utilicé hace esta vez mucho tiempo para construir generador de expresiones SQL que maneja todos los problemas de fugas y otras cosas.

SqlBuilder builder; 

builder.select(column1)(column2)(column3). 
    where("=")(column1, value1) 
       (column2, value2). 
    where(">")(column3, 100). 
    from(table1)("table2")("table3"); 

No pude reproducir las fuentes en 10 minutos. Entonces la implementación está detrás de las cortinas.

+0

He hecho cosas similares para construir documentos XML en C++. La pregunta aquí parece ser acerca de los generadores de propiedades generales. –

10

Puede devolver una referencia a this si quieres función setter cadena de llamadas entre sí de esta manera:

obj.SetCount(10).SetName("Bob").SetColor(0x223344).SetWidth(35); 

personalmente creo que el código es difícil de leer que la alternativa:

obj.SetCount(10); 
obj.SetName("Bob"); 
obj.SetColor(0x223344); 
obj.SetWidth(35); 
+0

Es una cuestión de diseño. Puedes simplemente escribir la primera parte como la segunda, simplemente omitiendo obj. en todo menos en el primero y; en todo menos en el último. En mi humilde opinión, es tan fácil de leer. –

2

Si su motivación está relacionada con el encadenamiento (por ejemplo, la sugerencia de Brian Ensink), ofrecería dos comentarios:

1. If yo Usted se encuentra frecuentemente configurando muchas cosas a la vez, lo que puede significar que debe producir un struct o class que contenga todas estas configuraciones para que puedan pasar todas a la vez. El siguiente paso podría ser usar struct o class en el objeto en sí ... pero como está utilizando getters y setters, la decisión de cómo representarlo internamente será transparente para los usuarios de la clase de todos modos, por lo que esta decisión se relacionan más con cuán compleja es la clase que cualquier otra cosa.

2. Una alternativa a un colocador es crear un objeto nuevo, cambiarlo y devolverlo. Esto es a la vez ineficiente e inapropiado en la mayoría de los tipos, especialmente los tipos mutables. Sin embargo, es una opción que la gente a veces olvida, a pesar de su uso en la clase de cadena de muchos idiomas.

2

El propósito típico para este estilo está en uso para la construcción de objetos.

Person* pPerson = &(new Person())->setAge(34).setId(55).setName("Jack"); 

en lugar de

Person* pPerson = new Person(34, 55, "Jack"); 

Usando el segundo más estilo tradicional se podría olvidar si el primer valor que se pasa al constructor fue la edad o la identificación? Esto también puede conducir a múltiples constructores basados ​​en la validez de algunas propiedades.

Utilizando el primero de estilo podría olvide de establecer algunas de las propiedades de los objetos y los insectos y puede dar lugar donde los objetos no son 'totalmente' construidos. (Se agrega una propiedad de clase en un punto posterior pero no todas las ubicaciones de construcción se actualizaron para llamar al instalador requerido).

A medida que el código evoluciona me gusta mucho el hecho de que puedo usar el compilador para ayudarme a encontrar todos los lugares donde se crea un objeto al cambiar la firma de un constructor. Por eso, prefiero usar constructores comunes de C++ sobre este estilo.

Este patrón podría funcionar bien en aplicaciones que mantienen su modelo de datos a través del tiempo de acuerdo con reglas similares a los utilizados en muchas aplicaciones de bases de datos:

  • Puede agregar un campo/atributo a una tabla/clase que es NULL por defecto. (Por lo tanto la actualización de los datos existentes requiere sólo una nueva columna NULL en la base de datos.)
  • código que es no cambios debería funcionar lo mismo con este campo NULL añadió.
+0

+1 para seguridad en tiempo de compilación. – Eclipse

+0

Wow. Es una adaptación increíble a la falta de parámetros nombrados de C++ (como Ada lo ha tenido durante 20 años). –

+0

-> no. para los indicadores sin embargo :) –

3

OMI emisores son un olor código que generalmente indica una de dos cosas:

de tomar una Mountian fuera de un grano de arena

Si usted tiene una clase como esta:

class Gizmo 
{ 
public: 
    void setA(int a) { a_ = a; } 
    int getA() const { return a_; } 

    void setB(const std::string & b) { v_ = b; } 
    std::string getB() const { return b_; } 
private: 
    std::string b_; 
    int a_; 
}; 

... y los valores realmente son así de simple, entonces ¿por qué no hacer que los miembros de datos público ?:

class Gizmo 
{ 
public: 
    std::string b_; 
    int a_; 
}; 

... mucho más simple y, si los datos son de simple usted no pierde nada.

Otra posibilidad es que usted podría ser

Hacer un grano de arena fuera de un Mountian

Muchas veces los datos no es así de simple: tal vez usted tiene que cambiar varios valores, hacer algo de computación, notificar algún otro objeto; quién sabe qué. Pero si los datos no son lo suficientemente triviales como para necesitar realmente setters & getters, entonces no es tan trivial como para necesitar el manejo de errores también. Entonces, en esos casos, los usuarios getters & deberían devolver algún tipo de código de error o hacer algo diferente para indicar que algo malo sucedió.

Si está encadenando llamadas entre sí de esta manera:

A.doA().doB().doC(); 

... y DOA() falla, lo que realmente quieres a estar llamando DOB ​​() y Doc() de todos modos? Lo dudo.

+6

RE: "A.doA(). DoB(). DoC();" si doA falla, para eso están las excepciones. – Eclipse

+0

+1, buen punto John. También es bueno señalar a Josh. –

Cuestiones relacionadas