2011-11-16 17 views
7

¿Hay un nombre para esto:¿Es este un patrón de diseño, devolviéndolo de setters?

class A 
{ 
    A* setA() 
    { 
     //set a 
     return this; 
    } 
    A* setB() 
    { 
     //set b 
     return this; 
    } 
}; 

para que pueda hacer algo como esto:

A* a = new A; 
a->setA()->setB(); 

¿Hay algunas desventajas a usar esto? Ventajas?

+7

principal inconveniente es que usted podría estar regresando '* this' como un' 'A &, y escribiendo' A * a = new A.; a-> setA(). setB() 'o (más importante)' A b; b.setA(). setB(); '. 'b.setA() -> setB();' es un poco basura ;-) –

Respuesta

9

Se le conoce como método de encadenamiento de (FAQ link), y se hace más comúnmente con referencias, no punteros.

método de encadenamiento está fuertemente asociado con el nombre de parámetro Idiom (FAQ link), ya que ahora, después de publicar una primera versión de esta respuesta, veo que Steve Jessop discusses in his answer. La expresión NPI es una forma simple de proporcionar una gran cantidad de argumentos predeterminados sin forzar la complejidad de las llamadas al constructor. Por ejemplo, esto es relevante para la programación de GUI.

Un posible problema con la técnica de encadenamiento de métodos es cuando desea o necesita aplicar el modismo NPI para las clases en una jerarquía de herencia. Luego descubres que C++ no admite métodos covariantes. Qué es eso: cuando dejas que tus ojos miren hacia arriba o hacia abajo por las clases de una cadena de herencia de clase, entonces un método covariante es uno cuya definición involucra algún tipo que a tu ojo errante varía en especificidad de la misma manera que la clase ’ s definido en.

Se trata del mismo problema que en la definición de un método clone, que tiene la misma definición textual en todas las clases, pero debe repetirse laboriosamente en cada clase para obtener los tipos correctos.

Resolver ese problema es difícil sin soporte de idiomas; parece ser un problema inherentemente complejo, una especie de conflicto con el sistema de tipo C++. Mis “How to do typed optional arguments in C++98” blog post enlaces al código fuente relevante para automatizar la generación de definiciones covariantes, y a un artículo que escribí al respecto en Dr. Dobbs Journal. Tal vez lo revise para C++ 11, o en algún momento, porque la complejidad y posible fragilidad pueden aparecer como un costo mayor que ’ s worth & hellip;

Saludos & HTH,

+0

Los enlaces de las FAQ aquí se deben actualizar a ISO CPP. Rechacé incorrectamente la edición de alguien para hacer eso. (http://meta.stackexchange.com/questions/249938/i-incorrectly-rejected-some-edits-what-to-do) –

4

He oído que se llamaba algo así como "method chaining" antes, pero yo no lo llamaría un patrón de diseño. (Algunas personas también hablan de implementar un "fluent interface" usando esto - nunca antes lo había visto así, pero Martin Fowler parece haber escrito sobre esto hace un tiempo)

No pierdes mucho haciendo esto - siempre puedes ignorar el resultado de retorno con bastante alegría si no quieres usarlo así.

En cuanto a que vale la pena hacerlo, estoy menos seguro. Puede ser bastante críptico en algunas circunstancias. Sin embargo, es básicamente requerido para cosas como operator<< para IO basado en flujo. Diría que es una llamada a hacer sobre cómo encaja con el resto del código: ¿es esperado/obvio para las personas que lo leen?

(como Steve Jessop señaló esta casi siempre se hace con referencias, sin embargo, no punteros)

0

Se utiliza comúnmente en, por ejemplo, Boost, pero la mayoría de las veces las funciones devuelve referencias en su lugar:

A &setX() 
{ 
    // ... 
    return *this; 
} 
2

una desventaja es que si se derive una clase de a, dice así:

class Foo : public A 
{ 
public: 
    Foo *setC() 
    { 
    // set C 
    return this; 
    } 
}; 

entonces el orden se llama a los emisores es importante. Tendrá que llamar a todos los emisores de Foo primera: Por ejemplo, esto no funcionará:

Foo f=new Foo(); 
f->setA()->setC(); 

considerando que esto:

Foo f=new Foo(); 
f->setC()->setA(); 
+0

Buen punto: es por eso que no es posible escribir algo como '(std :: ostringstream() <<" hi "<< 0) .str() 'que sería una locución muy práctica si funcionara. – Flexo

+0

@awoodland: puede cocinar algo como 'template std :: ostringstream & operator << (std :: ostringstream & o, const T & t) {static_cast (o) << t; devolver o; } ', o es esa plantilla una coincidencia menos buena que la función existente con' ostream & '? –

+0

@SteveJessop - Un tipo derivado sería una mejor coincidencia que un tipo base, pero parece que no funciona cuando lo probé en este momento. Trataré de averiguar por qué. – Flexo

3

Otro uso común-ish es con "objetos de parámetros ". Sin el método de encadenamiento, son bastante incómodos de configurar, pero pueden ser temporales.

En lugar de:

complicated_function(P1 param1 = default1, P2 param2 = default2, P3 param3 = default3); 

Comentario:

struct ComplicatedParams { 
    P1 mparam1; 
    P2 mparam2; 
    P3 mparam3; 
    ComplicatedParams() : mparam1(default1), mparam2(default2), mparam3(default3) {} 
    ComplicatedParams &param1(P1 p) { mparam1 = p; return *this; } 
    ComplicatedParams &param2(P2 p) { mparam2 = p; return *this; } 
    ComplicatedParams &param3(P3 p) { mparam3 = p; return *this; } 
}; 

complicated_function(const ComplicatedParams &params); 

Ahora se puede llamar así:

complicated_function(ComplicatedParams().param2(foo).param1(bar)); 

Lo que significa que la persona que llama no tiene que recordar el orden de los parámetros .Sin el encadenamiento método que tendría que ser:

ComplicatedParams params; 
params.param1(foo); 
params.param2(bar); 
complicated_function(params); 

También puede llamar:

complicated_function(ComplicatedParams().param3(baz)); 

lo que significa que sin tener que definir una tonelada de sobrecargas, puedo especificar sólo el último parámetro y deja el resto por defecto.

El pellizco obvia última es hacer complicated_function un miembro de ComplicatedParams:

struct ComplicatedAction { 
    P1 mparam1; 
    P2 mparam2; 
    P3 mparam3; 
    ComplicatedAction() : mparam1(default1), mparam2(default2), mparam3(default3) {} 
    ComplicatedAction &param1(P1 p) { mparam1 = p; return *this; } 
    ComplicatedAction &param2(P2 p) { mparam2 = p; return *this; } 
    ComplicatedAction &param3(P3 p) { mparam3 = p; return *this; } 
    run(void); 
}; 

ComplicatedAction().param3(baz).run();