2012-06-11 12 views
16

He estado buscando una respuesta clara a esta pregunta de diseño sin éxito. No pude encontrar ayuda ni en el ".NET Framework design guidelines" ni en el "C# programing guidelines". básicamente tengo que exponer a un patrón como una API para que los usuarios pueden definir e integrar sus algoritmos en mi marco de esta manera:Delegados (expresiones Lambda) Vs Interfaces y clases abstractas

1)

// This what I provide 
public abstract class AbstractDoSomething{ 
    public abstract SomeThing DoSomething(); 
} 

Los usuarios necesitan la implementación de esta clase abstracta, que que poner en práctica el método DoSomething (que puedo llamar desde dentro de mi marco y utilizarlo)

2)

descubrí que esto también puede acheived mediante el uso de los delegados:

public sealed class DoSomething{ 
    public String Id; 
    Func<SomeThing> DoSomething; 
} 

En este caso, un usuario sólo puede utilizar DoSomething clase de esta manera:

DoSomething do = new DoSomething() 
{ 
    Id="ThisIsMyID", 
    DoSomething = (() => new Something()) 
} 

Pregunta

Qué de estas dos opciones es mejor para un fácil, útil y lo más importantemente comprensible para exponer como una API?

EDITAR

En caso de : El registro se realiza de esta manera (suponiendo MyDoSomething extiende AbstractDoSomething:

MyFramework.AddDoSomething("DoSomethingIdentifier", new MyDoSomething()); 

En caso de : El registro se realiza como esto :

MyFramework.AddDoSomething(new DoSomething()); 
+0

¿Cómo registra el usuario AbstractDoSomethings con su marco? – dtb

+0

@dtb Por favor, vea mi edición (modificó un poco el código también) – GETah

Respuesta

15

¿Cuál de estas dos opciones es mejor para una API fácil de usar, útil y lo más importante de entender?

El primero es más "tradicional" en términos de OOP, y puede ser más comprensible para muchos desarrolladores. También puede tener ventajas en cuanto a permitir que el usuario administre la vida útil de los objetos (es decir, puede dejar que la clase implemente IDisposable y deshacerse de instancias al apagar, etc.), además de ser fácil de extender en futuras versiones de una manera eso no rompe la compatibilidad con versiones anteriores, ya que agregar miembros virtuales a la clase base no romperá la API. Finalmente, puede ser más simple de usar si desea usar algo como MEF para componer esto automáticamente, lo que puede simplificar/eliminar el proceso de "registro" desde el punto de vista del usuario (ya que pueden crear la subclase y soltarla en una carpeta, y la ha descubierto/usado automáticamente).

El segundo es un enfoque más funcional, y es más simple en muchos aspectos. Esto le permite al usuario implementar su API con muchos menos cambios en su código existente, ya que solo necesitan envolver las llamadas necesarias en un lambda con cierres en lugar de crear un nuevo tipo.

Dicho esto, si usted va a tomar el enfoque de utilizar un delegado, que ni siquiera hacer que el usuario crear una clase - sólo tiene que utilizar un método como:

MyFramework.AddOperation("ThisIsMyID",() => DoFoo()); 

Esto hace que sea un poco más claro, en mi opinión, que está agregando una operación al sistema directamente. También elimina por completo la necesidad de otro tipo en su API pública (DoSomething), lo que de nuevo simplifica la API.

+0

+1. Gracias por tu respuesta. El problema con el registro de un 'Func' directamente en mi framework es que no puedo extender esa funcionalidad en el futuro (no puedo agregar comportamiento a' DoSomething') – GETah

+0

@GETah Como mencioné en mi publicación, si espera querer agregar comportamiento, una clase es probablemente mejor. (Consulte la oración sobre cómo agregar miembros virtuales ...) Es decir, OMI, la principal ventaja de usar clases en lugar de delegados. –

+0

Oh, ya veo. Gracias por tus comentarios. – GETah

6

Me gustaría ir con la clase/interfaz abstracta si: se requiere

  • HacerAlgo
  • HacerAlgo normalmente obtendrá muy grande (por lo que la aplicación de HacerAlgo puede Splited en varios métodos privados/protegido)

me gustaría ir con los delegados si:

  • DoSomethi ng puede ser tratada como un evento (OnDoingSomething)
  • HacerAlgo es opcional (por lo que por defecto a un delegado no-op)
1

Como es típico para la mayoría de estos tipos de preguntas, diría que" depende ". :)

Pero creo que la razón para usar la clase abstracta versus la lambda realmente se reduce al comportamiento. Por lo general, pienso que la lambda se usa como un tipo de funcionalidad de devolución de llamada, en la que desea que ocurra algo personalizado cuando sucede algo más. Lo hago mucho en mi código de cliente: - hacer una llamada de servicio - obtener algunos datos de nuevo - ahora invocar mi devolución de llamada para manejar los datos en consecuencia

Usted puede hacer lo mismo con las lambdas - se son específicos y están dirigidos a situaciones muy específicas.

El uso de la clase abstracta (o interfaz) realmente se reduce a donde el comportamiento de su clase es conducido por el entorno que lo rodea. ¿Qué está pasando, con qué cliente estoy tratando, etc.? Estas preguntas más amplias podrían sugerir que debe definir un conjunto de comportamientos y luego permitir que sus desarrolladores (o consumidores de su API) creen sus propios conjuntos de comportamientos en función de sus requisitos. De acuerdo, podrías hacer lo mismo con lambdas, pero creo que sería más complejo de desarrollar y también más complejo para comunicar claramente a tus usuarios.

Así que, supongo que mi regla general es: - use lambdas para comportamientos personalizados de devolución de llamada o efectos secundarios específicos; - use clases o interfaces abstractas para proporcionar un mecanismo para la personalización del comportamiento de los objetos (o al menos la mayoría del comportamiento principal del objeto).

Lo siento, no puedo darle una definición clara, pero espero que esto ayude. ¡Buena suerte!

1

Algunas cosas a tener en cuenta:

¿Cuántas funciones/delegados diferente tendría que ser anulada? Si puede funciones, inheretance agrupará "juegos" de anulaciones de una manera más fácil de entender. Si tiene una única función de "registro", pero muchas sub porciones se pueden delegar al implementador, este es un caso clásico del patrón "Plantilla", que tiene más sentido para ser heredado.

¿Cuántas implementaciones diferentes de la misma función se necesitarán? Si es solo uno, entonces la inherencia es buena, pero si tiene muchas implementaciones, un delegado podría ahorrar gastos generales.

Si hay múltiples implementaciones, ¿el programa deberá cambiar entre ellas? O solo usará una única implementación. Si se requiere el cambio, los delegados pueden ser más fáciles, pero me gustaría advertir esto, especialmente dependiendo de la respuesta al # 1. Ver el patrón de estrategia.

Si la anulación necesita acceso a cualquier miembro protegido, entonces no es así. Si solo puede confiar en los públicos, delegue.

Otras opciones serían los eventos y los métodos de extensión también.

2

Aunque personalmente, si estuviera en mi mano, siempre usaría Delegate Model. Me encanta la simplicidad y elegancia de las funciones de orden superior. Pero al implementar el modelo, tenga cuidado con las pérdidas de memoria. Los eventos suscritos son una de las razones más comunes de pérdida de memoria en .Net. Esto significa, supongamos que si tiene un objeto que tiene algunos eventos expuestos, el objeto original nunca se eliminará hasta que se anulen todos los eventos, ya que el evento crea una referencia fuerte.

+1

Gracias por su respuesta. El problema al hacer API es, desafortunadamente, lo que amas hacer pero lo que a otros les gusta hacer: p – GETah

Cuestiones relacionadas