¿Qué quieren decir con un patrón de diseño de eventos?
Lo más probable es que se refieran a una implementación del patrón de observador, que es un constructo de lenguaje central en C #, expuesto como ' eventos '. Es posible escuchar eventos conectando un delegado a ellos. Como señaló Yam Marcovic, EventHandler
es el tipo de delegado base convencional para eventos, pero se puede usar cualquier tipo de delegado.
¿Cómo resulta fácil la composición si se usa un delegado?
Esto probablemente solo se refiere a la flexibilidad que ofrecen los delegados. Puede 'componer' fácilmente cierto comportamiento. Con la ayuda de lambdas , la sintaxis para hacer esto también es muy concisa. Considere el siguiente ejemplo.
class Bunny
{
Func<bool> _canHop;
public Bunny( Func<bool> canHop )
{
_canHop = canHop;
}
public void Hop()
{
if ( _canHop() ) Console.WriteLine( "Hop!" );
}
}
Bunny captiveBunny = new Bunny( () => IsBunnyReleased );
Bunny lazyBunny = new Bunny( () => !IsLazyDay );
Bunny captiveLazyBunny = new Bunny( () => IsBunnyReleased && !IsLazyDay );
Hacer algo similar con las interfaces requeriría que use el patrón de estrategia o una Bunny
clase base (abstracta) desde la que extienda conejitos más específicos.
Si hay un grupo de métodos relacionados a los que se puede llamar, utilice la interfaz. ¿Qué beneficio tiene?
Nuevamente, usaré conejitos para demostrar cómo sería más fácil.
interface IAnimal
{
void Jump();
void Eat();
void Poo();
}
class Bunny : IAnimal { ... }
class Chick : IAnimal { ... }
// Using the interface.
IAnimal bunny = new Bunny();
bunny.Jump(); bunny.Eat(); bunny.Poo();
IAnimal chick = new Chick();
chick.Jump(); chick.Eat(); chick.Poo();
// Without the interface.
Action bunnyJump = () => bunny.Jump();
Action bunnyEat = () => bunny.Eat();
Action bunnyPoo = () => bunny.Poo();
bunnyJump(); bunnyEat(); bunnyPoo();
Action chickJump = () => chick.Jump();
Action chickEat = () => chick.Eat();
...
Si una clase solo necesita una implementación del método, utilice la interfaz: ¿cómo se justifica en términos de beneficios?
Para esto, considere el primer ejemplo con el conejito nuevamente. Si solo se necesita una implementación (nunca se requiere una composición personalizada), puede exponer este comportamiento como una interfaz. Nunca tendrá que construir las lambdas, solo puede usar la interfaz.
Conclusión
Los delegados ofrecen mucha más flexibilidad, mientras que las interfaces lo ayudan a establecer contratos sólidos. Por lo tanto, encuentro el último punto mencionado, "Una clase puede necesitar más de una implementación del método". , con mucho, el más relevante.
Una razón adicional para usar delegados es cuando desea exponer solo una parte de una clase desde la que no puede ajustar el archivo fuente.
Como ejemplo de tal escenario (máxima flexibilidad, no es necesario modificar las fuentes), considere esta implementación del algoritmo de búsqueda binaria para cualquier posible colección simplemente pasando dos delegados.
1) Patrones de eventos, bueno, el clásico es el patrón Observer, aquí hay un buen enlace de Microsoft Microsoft talk Observer
El artículo al que se vinculó no está muy bien escrito y hace las cosas más complicadas de lo necesario. El negocio estático es de sentido común, no puede definir una interfaz con miembros estáticos, por lo que si desea un comportamiento polimórfico en un método estático, usaría un delegado.
El enlace de arriba habla sobre los delegados y por qué son buenos para la composición, por lo que podría ayudar con su consulta.
fuente
En primer lugar, buena pregunta. Admiro su enfoque en la utilidad en lugar de aceptar ciegamente las "mejores prácticas". +1 por eso.
He leído esa guía antes. Debe recordar algo al respecto: es solo una guía, principalmente para los recién llegados a C # que saben programar pero no están tan familiarizados con la forma en que C # hace las cosas. No es tanto como una página de reglas, sino que es una página que describe cómo se suelen hacer las cosas. Y dado que ya se han hecho de esta manera en todas partes, puede ser una buena idea mantenerse constante.
Iré al grano y responderé tus preguntas.
En primer lugar, supongo que ya sabes qué es una interfaz. En cuanto a un delegado, es suficiente decir que es una estructura que contiene un puntero escrito a un método, junto con un puntero opcional al objeto que representa el
this
argumento para ese método. En el caso de métodos estáticos, el último puntero es nulo.También hay delegados de multidifusión, que son como los delegados, pero pueden tener varias de estas estructuras asignadas a ellos (lo que significa que una sola llamada a Invocar en un delegado de multidifusión invoca todos los métodos en su lista de invocación asignada).
¿Qué quieren decir con un patrón de diseño de eventos?
Significan usar eventos en C # (que tiene palabras clave especiales para implementar sofisticadamente este patrón extremadamente útil). Los eventos en C # son alimentados por delegados de multidifusión.
Cuando define un evento, como en este ejemplo:
El compilador realmente transforma esto en el siguiente código:
Luego se suscribe a un evento haciendo
Que se compila a
Entonces eso está sucediendo en C # (o .NET en general).
¿Cómo resulta fácil la composición si se usa un delegado?
Esto se puede demostrar fácilmente:
Supongamos que tiene una clase que depende de un conjunto de acciones que se le pasarán. Puede encapsular esas acciones en una interfaz:
Y cualquiera que quisiera pasar acciones a su clase primero tendría que implementar esa interfaz. O podría facilitarles la vida dependiendo de la siguiente clase:
De esta forma, las personas que llaman solo tienen que crear una instancia de RequiredMethods y vincular métodos a los delegados en tiempo de ejecución. Esto suele ser más fácil.
Esta forma de hacer las cosas es extremadamente beneficiosa en las circunstancias correctas. Piénselo: ¿por qué depender de una interfaz cuando lo único que realmente le importa es que le pasen una implementación?
Beneficios de usar interfaces cuando hay un grupo de métodos relacionados
Es beneficioso usar interfaces porque las interfaces normalmente requieren implementaciones explícitas en tiempo de compilación. Esto significa que crea una nueva clase.
Y si tiene un grupo de métodos relacionados en un solo paquete, es beneficioso que ese paquete sea reutilizable por otras partes del código. Entonces, si pueden simplemente crear una instancia de una clase en lugar de crear un conjunto de delegados, es más fácil.
Beneficios de usar interfaces si una clase solo necesita una implementación
Como se señaló anteriormente, las interfaces se implementan en tiempo de compilación, lo que significa que son más eficientes que invocar a un delegado (que es un nivel de indirección per se).
"Una implementación" podría significar una implementación que existe en un solo lugar bien definido.
De lo contrario, una implementación podría provenir de cualquier parte del programa que simplemente se ajuste a la firma del método. Eso permite una mayor flexibilidad, porque los métodos solo necesitan ajustarse a la firma esperada, en lugar de pertenecer a una clase que implemente explícitamente una interfaz específica. Pero esa flexibilidad puede tener un costo y, de hecho, rompe el principio de sustitución de Liskov , porque la mayoría de las veces desea ser explícito, ya que minimiza la posibilidad de accidentes. Al igual que la escritura estática.
El término también podría referirse a los delegados de multidifusión aquí. Los métodos declarados por las interfaces solo se pueden implementar una vez en una clase de implementación. Pero los delegados pueden acumular múltiples métodos, que se llamarán secuencialmente.
Así que, en general, parece que la guía no es lo suficientemente informativa y simplemente funciona como lo que es: una guía, no un libro de reglas. Algunos consejos pueden sonar un poco contradictorios. Depende de usted decidir cuándo es correcto aplicar qué. La guía parece solo darnos un camino general.
Espero que sus preguntas hayan sido respondidas a su satisfacción. Y de nuevo, felicitaciones por la pregunta.
fuente
Si mi memoria de .NET aún se mantiene, un delegado es básicamente una función ptr o functor. Agrega una capa de indirección a una llamada de función para que las funciones puedan sustituirse sin que el código de llamada tenga que cambiar. Esto es lo mismo que hace una interfaz, excepto que una interfaz empaqueta varias funciones juntas, y un implementador tiene que implementarlas juntas.
Un patrón de eventos, en términos generales, es aquel en el que algo responde a eventos de otras partes (como mensajes de Windows). El conjunto de eventos suele ser abierto, pueden presentarse en cualquier orden y no están necesariamente relacionados entre sí. Los delegados trabajan bien para esto, ya que cada evento puede llamar a una sola función sin necesidad de una referencia a una serie de objetos de implementación que también pueden contener numerosas funciones irrelevantes. Además (aquí es donde mi memoria .NET es vaga), creo que se pueden adjuntar múltiples delegados a un evento.
La composición, aunque no estoy muy familiarizado con el término, consiste básicamente en diseñar un objeto para que tenga múltiples subpartes o hijos agregados a los que se pasa el trabajo. Los delegados permiten que los niños se mezclen y se combinen de una manera más ad-hoc donde una interfaz puede ser excesiva o causar demasiado acoplamiento y la rigidez y fragilidad que conlleva.
El beneficio de una interfaz para métodos relacionados es que los métodos pueden compartir el estado del objeto de implementación. Las funciones de delegado no pueden compartir de manera tan limpia, o incluso contener, el estado.
Si una clase necesita una única implementación, una interfaz es más adecuada, ya que dentro de cualquier clase que implemente la colección completa, solo se puede hacer una implementación y obtendrá los beneficios de una clase de implementación (estado, encapsulación, etc.). Si la implementación puede cambiar debido al estado de tiempo de ejecución, los delegados funcionan mejor porque pueden cambiarse por otras implementaciones sin afectar los otros métodos. Por ejemplo, si hay tres delegados que tienen dos implementaciones posibles, necesitaría ocho clases diferentes que implementen la interfaz de tres métodos para dar cuenta de todas las combinaciones posibles de estados.
fuente
El patrón de diseño de "evento" (más conocido como el Patrón de Observador) le permite adjuntar múltiples métodos de la misma firma al delegado. Realmente no puedes hacer eso con una interfaz.
Sin embargo, no estoy convencido de que la composición sea más fácil para un delegado que una interfaz. Esa es una declaración muy extraña. Me pregunto si quiere decir porque puedes adjuntar métodos anónimos a un delegado.
fuente
La mayor aclaración que puedo ofrecer:
Asi que:
fuente
Su pregunta sobre eventos ya ha sido bien cubierta. Y también es cierto que una interfaz puede definir varios métodos (pero en realidad no es necesario), mientras que un tipo de función solo impone restricciones a una función individual.
Sin embargo, la verdadera diferencia es:
Claro, pero ¿qué significa esto?
Tomemos este ejemplo (el código está en haXe ya que mi C # no es muy bueno):
Ahora, el método de filtro hace que sea fácil pasar convenientemente solo una pequeña pieza de lógica, que desconoce la organización interna de la colección, mientras que la colección no depende de la lógica que se le da. Excelente. Excepto por un problema:
La colección no dependen de la lógica. La colección inherentemente asume que la función pasada está diseñada para probar un valor contra una condición y devolver el éxito de la prueba. Tenga en cuenta que no todas las funciones, que toman un valor como argumento y devuelven un valor booleano, son en realidad meros predicados. Por ejemplo, el método remove de nuestra colección es tal función.
Supongamos que llamamos
c.filter(c.remove)
. El resultado sería una colección con todos los elementos dec
whilec
se queda vacio. Esto es desafortunado, porque naturalmente esperaríamosc
que sea invariante.El ejemplo está muy construido. Pero el problema clave es que el código que llama
c.filter
con algún valor de función como argumento no tiene forma de saber si ese argumento es adecuado (es decir, en última instancia conservará la invariante). El código que creó el valor de la función puede o no saber que se interpretará como un predicado.Ahora cambiemos las cosas:
¿Que ha cambiado? Lo que ha cambiado es que cualquier valor que se le
filter
haya dado ahora ha firmado explícitamente el contrato de ser un predicado. Por supuesto, los programadores maliciosos o extremadamente estúpidos crean implementaciones de la interfaz que no están libres de efectos secundarios y, por lo tanto, no son predicados.Pero lo que ya no puede suceder es que alguien vincula una unidad lógica de datos / código, que se interpreta erróneamente como un predicado debido a su estructura externa.
Entonces, para reformular lo que se dijo anteriormente en pocas palabras:
La ventaja de las relaciones explícitas es que puede estar seguro de ellas. La desventaja es que requieren la sobrecarga de lo explícito. Por el contrario, la desventaja de las relaciones implícitas (en nuestro caso, la firma de una función que coincide con la firma deseada), es que realmente no puede estar 100% seguro de que puede usar las cosas de esta manera. La ventaja es que puede establecer relaciones sin todos los gastos generales. Puedes simplemente juntar cosas rápidamente, porque su estructura lo permite. Eso es lo que significa composición fácil.
Es un poco como LEGO: simplemente puedes enchufar una figura LEGO de Star Wars en un barco pirata LEGO, simplemente porque la estructura exterior lo permite. Ahora puede sentir que eso está terriblemente mal, o podría ser exactamente lo que desea. Nadie te va a detener.
fuente
int IList.Count { get { ... } }
) o implícitamente (public int Count { get { ... } }
). Esta distinción no es relevante para esta discusión, pero merece mención para evitar confundir a los lectores.fuente