2008-09-19 18 views
53

Mi pregunta está relacionada con el patrón de comandos, donde tenemos la siguiente abstracción (código C#):Patrón de comando: cómo pasar parámetros a un comando?

public interface ICommand 
{ 
    void Execute(); 
} 

Tomemos un simple comando de hormigón, que tiene como objetivo eliminar una entidad de nuestra aplicación. Una instancia de Person, por ejemplo.

Tendré un DeletePersonCommand, que implementa ICommand. Este comando necesita el Person para eliminar como parámetro, para eliminarlo cuando se llama al método Execute.

¿Cuál es la mejor manera de gestionar comandos parametrizados? ¿Cómo pasar parámetros a los comandos antes de ejecutarlos?

+6

Sé que esta pregunta data de hace más de cuatro años, pero Juanma y bloparod en realidad dan la respuesta correcta: make 'ICommand' generic (' ICommand '). El 'TArgs' dado encapsula todos los argumentos (se convierte en [Objeto de parámetro] (http://c2.com/cgi/wiki?ParameterObject)). Deberá crear dos objetos por comando: uno para el mensaje; uno por el comportamiento. Esto suena incómodo al principio, pero cuando lo obtienes, nunca mirarás hacia atrás.[Este artículo] (http://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=91) describe este modelo en detalle. Una lectura obligada para todos los que leen esta pregunta. – Steven

+0

@Steven gracias por el enlace a su publicación de blog. Quizás sería bueno si pudieras aclarar cómo el enfoque que describes en él encaja con la pregunta aquí dado que, según tu propia admisión, "no lo consideras [el] Patrón de Comando". Uno podría tener la idea de que su comentario es simplemente autopromoción. – ardila

Respuesta

55

Tendrá que asociar los parámetros con el objeto de comando, ya sea por el constructor o inyección setter (o equivalente). Tal vez algo como esto:

public class DeletePersonCommand: ICommand 
{ 
    private Person personToDelete; 
    public DeletePersonCommand(Person personToDelete) 
    { 
     this.personToDelete = personToDelete; 
    } 

    public void Execute() 
    { 
     doSomethingWith(personToDelete); 
    } 
} 
+2

Exactamente lo que haría. Para cualquier cosa que no se sepa cuando se construye el comando, pasaría la interfaz a un servicio que obtiene el objeto cuando se ejecuta el comando. Eso podría ser una expresión delegada o lambda u otro objeto. –

4

En el constructor y almacenado como campos.

También deseará eventualmente hacer sus ICommands serializables para la pila de deshacer o la persistencia del archivo.

0

DeletePersonCommand puede tener un parámetro en su constructor o métodos. DeletePersonCommand tendrá Execute() y en el atributo execute can check que Getter/Setter pasará previamente a la llamada de Execute().

0

Agregaría los argumentos necesarios al constructor de DeletePersonCommand. Luego, cuando se llama al Execute(), se usan los parámetros pasados ​​al objeto en el momento de la construcción.

-5

Haga que "Persona" implemente algún tipo de interfaz IDeletable, luego haga que el comando tome la clase base o la interfaz que usen sus entidades. De esta manera, se puede hacer una DeleteCommand, que trata de echar la entidad a un IDeletable, y si funciona, llame .Delete

public class DeleteCommand : ICommand 
{ 
    public void Execute(Entity entity) 
    { 
     IDeletable del = entity as IDeletable; 
     if (del != null) del.Delete(); 
    } 
} 
+1

No creo que esto funcione: el objetivo de ICommand es que cada subclase anule Execute() _exactly_. Esta solución requiere que la persona que llama de Execute() sepa más detalles sobre el tipo de comando que se está llamando. –

+0

Estoy de acuerdo con Matt. Esa clase DeleteCommand ni siquiera compilaría, de todos modos, ya que no implementa void Execute() como lo requiere la interfaz de ICommand. – chadmyers

+0

Al usar la inyección de dependencia, todavía tiene que saber todo sobre el tipo de comando, porque tiene que hacerlo nuevo ¡arriba! al menos de esta manera su código puede ser genérico si solo se ocupa de "Entidad". La respuesta original incluye información sobre cómo cambiar ICommand para incluir la clase/interfaz base. –

10

hay algunas opciones:

Se podía pasar parámetros de propiedades o el constructor.

Otra opción podría ser:

interface ICommand<T> 
{ 
    void Execute(T args); 
} 

y encapsular todos los parámetros de comando en un objeto de valor.

+8

El problema con el código anterior es que comandos diferentes (por ejemplo, CreateSomeThingCommand y DeleteSomethingCommand) pueden requerir diferentes parámetros y no se pueden ejecutar de la misma manera (pensando en la llamada a IEnumerable .Execute()). El patrón de comando se usa para separar la definición de la ejecución ... si pasa parámetros en el tiempo de ejecución, está cambiando/controlando el comportamiento del comando en el tiempo de ejecución en lugar del tiempo de definición. – Beachwalker

+0

Dicho sea de paso: creo que se refería a la ejecución vacía (T args) en lugar de Execute (T args>, porque T ya está definida en ICommand , una segunda función/nivel de método es inútil. Podría crear algo como siguiente, también: interfaz ICommand { void Ejecutar (t1 T1, T2 t2); } (lo que hace más Sene) o interfaz ICommand { void Ejecutar (t2 T2); // usando T1 en otro sitio } – Beachwalker

6

Pasar la persona cuando se crea el objeto de comando:

ICommand command = new DeletePersonCommand(person); 

de modo que cuando se ejecuta el comando, que ya sabe todo lo que necesita saber.

class DeletePersonCommand : ICommand 
{ 
    private Person person; 
    public DeletePersonCommand(Person person) 
    { 
     this.person = person; 
    } 

    public void Execute() 
    { 
     RealDelete(person); 
    } 
} 
4

En este caso, lo que hemos hecho con nuestros objetos de comando es la creación de un objeto de contexto que es esencialmente un mapa. El mapa contiene pares de valores de nombre donde las claves son constantes y los valores son parámetros que utilizan las implementaciones de Comando. Especialmente útil si tiene una Cadena de comandos donde los comandos posteriores dependen de los cambios de contexto de los comandos anteriores.

Así el método real se convierte en

void execute(Context ctx); 
+0

Lo usé en mi diseño, pero 'Contexto' fue un Diccionario . – Jutanium

3

Basado en el patrón en C#/WPF la Interfaz ICommand (System.Windows.Input.ICommand) se define para tomar un objeto como un parámetro en la ejecución, como se así como el método CanExecute.

interface ICommand 
      { 
       bool CanExecute(object parameter); 
       void Execute(object parameter); 
      } 

Esto le permite definir su comando como un campo estático del público, que es una instancia de su objeto de comando personalizado que implementa ICommand.

public static ICommand DeleteCommand = new DeleteCommandInstance(); 

De esta forma, el objeto relevante, en su caso una persona, se transfiere cuando se ejecuta execute. El método Execute puede lanzar el objeto y llamar al método Delete().

public void Execute(object parameter) 
      { 
       person target = (person)parameter; 
       target.Delete(); 
      } 
+3

La forma en que el "patrón" se implementa de esta manera no es más que un "especial" d elegate con validación (CanExecute). Creo que esto pierde la funcionalidad real para la que está hecho el patrón ... desacoplamiento de definición y ejecución de un comando. Pasar en params/podría cambiar la forma de ejecución. De esta forma, la definición del comando se toma desde el constructor del comando hasta el tiempo de creación del parámetro en el lugar. (Sé que M $ usó esto para fines de la GUI, pero no creo que este deba ser el enfoque común para implementar el patrón de comando.) – Beachwalker

1

Debe crear un objeto CommandArgs para que contenga los parámetros que desea utilizar. Inyecte el objeto CommandArgs utilizando el constructor del objeto Command.

+0

¿Por qué no inyectar los parámetros necesarios en sí mismo o el valor por un delegado Func ? – Beachwalker

18

pasar los datos a través de un constructor o trabajos setter, pero requiere el creador del comando para conocer los datos de las necesidades de mando ...

La idea "contexto" es realmente bueno, y yo estaba trabajando en (un marco interno) que lo apalancó hace un tiempo.

Si configura su controlador (componentes de IU que interactúan con el usuario, CLI interpretando comandos de usuario, interpretando parámetros entrantes y datos de sesión, etc.) para proporcionar acceso con nombre a los datos disponibles, los comandos pueden solicitar directamente los datos ellos quieren.

Me gusta mucho la separación que permite una configuración como esta. Piense en capas de la siguiente manera:

User Interface (GUI controls, CLI, etc) 
    | 
[syncs with/gets data] 
    V 
Controller/Presentation Model 
    |     ^
[executes]    | 
    V     | 
Commands --------> [gets data by name] 
    | 
[updates] 
    V 
Domain Model 

Si lo hace este "derecho", los mismos comandos y modelo de presentación se pueden utilizar con cualquier tipo de interfaz de usuario.

Llevando esto un paso más allá, el "controlador" de arriba es bastante genérico. Los controles de la interfaz de usuario solo necesitan conocer el nombre del comando que invocarán; ellos (o el controlador) no necesitan tener ningún conocimiento de cómo crear ese comando o qué datos necesita ese comando. Esa es la verdadera ventaja aquí.

Por ejemplo, puede mantener el nombre del comando para ejecutar en un Mapa. Cada vez que el componente se "activa" (generalmente una acción realizada), el controlador busca el nombre del comando, lo instancia, llama a ejecutar y lo empuja a la pila de deshacer (si usa uno).

6

Mi aplicación sería esto (utilizando el ICommand propuesto por Juanma):

public class DeletePersonCommand: ICommand<Person> 
{ 
    public DeletePersonCommand(IPersonService personService) 
    { 
     this.personService = personService; 
    } 

    public void Execute(Person person) 
    { 
     this.personService.DeletePerson(person); 
    } 
} 

IPersonService podría ser un IPersonRepository, depende de lo que "capa" de su comando está.

+1

Parece ser un mejor caso de uso para el patrón de estrategia en lugar del patrón de comando en su ejemplo. – Beachwalker

Cuestiones relacionadas