que - Interfaces C#- ¿Cuál es el punto?
no se puede crear una instancia de una interfaz (28)
La razón de las interfaces realmente me elude. Por lo que entiendo, es una especie de solución para la herencia múltiple inexistente que no existe en C # (o eso me dijeron).
Todo lo que veo es que predefinen algunos miembros y funciones, que luego deben ser redefinidos en la clase nuevamente. De este modo, la interfaz es redundante. Simplemente se siente como sintáctico ... bueno, basura para mí (por favor, no te ofendas, basura como en cosas inútiles).
En el ejemplo dado a continuación, tomado de un subproceso de interfaces C # diferente en el desbordamiento de pila, solo crearía una clase base llamada Pizza en lugar de una interfaz.
Ejemplo fácil (tomado de una contribución de desbordamiento de pila diferente)
public interface IPizza
{
public void Order();
}
public class PepperoniPizza : IPizza
{
public void Order()
{
//Order Pepperoni pizza
}
}
public class HawaiiPizza : IPizza
{
public void Order()
{
//Order HawaiiPizza
}
}
Explicación simple con analogía.
El problema a resolver: ¿Cuál es el propósito del polimorfismo?
Analogía: Así que soy un foreperson en un sitio de construcción.
Los comerciantes caminan en el sitio de la construcción todo el tiempo. No sé quién va a caminar por esas puertas. Pero básicamente les digo qué hacer.
- Si es un carpintero digo: construir andamios de madera.
- Si es un fontanero, digo: "Coloca las tuberías"
- Si se trata de un electricista, digo: "Extraiga los cables y sustitúyalos por otros de fibra óptica".
El problema con el enfoque anterior es que tengo que: (i) saber quién está caminando por esa puerta y, dependiendo de quién sea, tengo que decirles qué hacer. Eso significa que tengo que saber todo acerca de un comercio en particular. Hay costos / beneficios asociados con este enfoque:
Las implicaciones de saber qué hacer:
Esto significa que si el código del carpintero cambia de:
BuildScaffolding()
aBuildScaffold()
(es decir, un ligero cambio de nombre), también tendré que cambiar la clase de llamada (es decir, la claseForeperson
), también tendrá que hacer dos cambios. al código en lugar de (básicamente) solo uno. Con el polimorfismo usted (básicamente) solo necesita hacer un cambio para lograr el mismo resultado.En segundo lugar, no tendrás que preguntar constantemente: ¿quién eres? ok haz esto ... quien eres ok, haz eso ..... polimorfismo - SECA ese código, y es muy efectivo en ciertas situaciones:
Con el polimorfismo, puede agregar fácilmente clases adicionales de comerciantes sin cambiar ningún código existente. (Es decir, el segundo de los principios de diseño SOLID: principio de apertura-cierre).
La solución
Imagine un escenario en el que, sin importar quién entra por la puerta, puedo decir: "Trabajo ()" y ellos hacen el trabajo de respeto en el que se especializan: el plomero se ocuparía de las tuberías y el electricista se encargaría de los cables.
El beneficio de este enfoque es que: (i) No necesito saber exactamente quién está entrando por esa puerta; todo lo que necesito saber es que serán un tipo de comercio y que pueden trabajar, y en segundo lugar , (ii) no necesito saber nada sobre ese comercio en particular. La tradie se encargará de eso.
Así que en lugar de esto:
If(electrician) then electrician.FixCablesAndElectricity()
if(plumber) then plumber.IncreaseWaterPressureAndFixLeaks()
Puedo hacer algo como esto:
ITradesman tradie = Tradesman.Factory(); // in reality i know it''s a plumber, but in the real world you won''t know who''s on the other side of the tradie assignment.
tradie.Work(); // and then tradie will do the work of a plumber, or electrician etc. depending on what type of tradesman he is. The foreman doesn''t need to know anything, apart from telling the anonymous tradie to get to Work()!!
¿Cuál es el beneficio?
El beneficio es que si los requisitos específicos del trabajo del carpintero, etc. cambian, entonces el foreperson no tendrá que cambiar su código, no necesita saber o preocuparse. Lo único que importa es que el carpintero sepa qué se entiende por Trabajo (). En segundo lugar, si un nuevo tipo de trabajador de la construcción ingresa al lugar de trabajo, entonces el capataz no necesita saber nada sobre el oficio; todo lo que le importa al capataz es si el trabajador de la construcción (.eg Welder, Glazier, Tiler, etc.) puede obtener un trabajo () hecho.
Problema y solución ilustrados (con y sin interfaces):
Sin interfaz (Ejemplo 1):
Sin interfaz (Ejemplo 2):
Con una interfaz:
Resumen
Una interfaz le permite hacer que la persona haga el trabajo al que están asignados, sin que usted tenga el conocimiento de quiénes son exactamente ni los detalles de lo que pueden hacer. Esto le permite agregar fácilmente nuevos tipos (de intercambio) sin cambiar su código existente (bueno, técnicamente sí lo cambia un poquito), y ese es el beneficio real de un enfoque OOP frente a una metodología de programación más funcional.
Si no entiende alguno de los puntos anteriores o si no está claro, pregunte en un comentario e intentaré mejorar la respuesta.
Aquí están sus ejemplos reexplicados:
public interface IFood // not Pizza
{
public void Prepare();
}
public class Pizza : IFood
{
public void Prepare() // Not order for explanations sake
{
//Prepare Pizza
}
}
public class Burger : IFood
{
public void Prepare()
{
//Prepare Burger
}
}
Considere el caso en el que no controla o posee las clases base.
Por ejemplo, los controles visuales, en .NET para Winforms, todos ellos heredan de la clase base Control, que está completamente definida en el marco .NET.
Supongamos que usted está en el negocio de crear controles personalizados. Desea crear nuevos botones, cuadros de texto, vistas de lista, cuadrículas y todo lo demás, y le gustaría que tuvieran ciertas características únicas para su conjunto de controles.
Por ejemplo, es posible que desee una forma común de manejar los temas, o una forma común de manejar la localización.
En este caso, no puede "simplemente crear una clase base" porque si lo hace, debe volver a implementar todo lo que se relaciona con los controles.
En su lugar, descenderá de Button, TextBox, ListView, GridView, etc. y agregará su código.
Pero esto plantea un problema, ¿cómo puede ahora identificar qué controles son "suyos", cómo puede compilar algún código que diga "para todos los controles en el formulario que son míos, establezca el tema en X".
Entrar en las interfaces.
Las interfaces son una forma de mirar un objeto, para determinar que el objeto se adhiere a un contrato determinado.
Debería crear "YourButton", descender de Button y agregar soporte para todas las interfaces que necesite.
Esto le permitiría escribir código como el siguiente:
foreach (Control ctrl in Controls)
{
if (ctrl is IMyThemableControl)
((IMyThemableControl)ctrl).SetTheme(newTheme);
}
Esto no sería posible sin interfaces, en lugar de eso, tendría que escribir código como este:
foreach (Control ctrl in Controls)
{
if (ctrl is MyThemableButton)
((MyThemableButton)ctrl).SetTheme(newTheme);
else if (ctrl is MyThemableTextBox)
((MyThemableTextBox)ctrl).SetTheme(newTheme);
else if (ctrl is MyThemableGridView)
((MyThemableGridView)ctrl).SetTheme(newTheme);
else ....
}
Considere que no puede usar la herencia múltiple en C #, y luego vuelva a mirar su pregunta.
El ejemplo de Pizza es malo porque deberías usar una clase abstracta que maneje el ordenamiento, y las pizzas deberían reemplazar el tipo de pizza, por ejemplo.
Utiliza interfaces cuando tienes una propiedad compartida, pero tus clases se heredan de diferentes lugares o cuando no tienes ningún código común que puedas usar. Por ejemplo, esto se usa para cosas que se pueden desechar como IDisposable
, usted sabe que se desechará, simplemente no sabe qué sucederá cuando se deseche.
Una interfaz es solo un contrato que le indica algunas cosas que puede hacer un objeto, qué parámetros y qué tipos de retorno puede esperar.
El punto es que la interfaz representa un contrato . Un conjunto de métodos públicos que cualquier clase implementadora debe tener. Técnicamente, la interfaz solo gobierna la sintaxis, es decir, qué métodos hay, qué argumentos obtienen y qué devuelven. Por lo general, también encapsulan la semántica, aunque solo por documentación.
Luego puede tener diferentes implementaciones de una interfaz y cambiarlas a voluntad. En su ejemplo, dado que cada instancia de pizza es una IPizza
, puede usar IPizza
donde quiera que maneje una instancia de un tipo de pizza desconocido. Cualquier instancia cuyo tipo herede de IPizza
se garantiza que sea ordenable, ya que tiene un método Order()
.
Python no es de tipo estático, por lo tanto, los tipos se mantienen y se consultan en tiempo de ejecución. Así que puedes intentar llamar a un método Order()
en cualquier objeto. El tiempo de ejecución es feliz siempre que el objeto tenga un método así y, probablemente, solo se encoge de hombros y dice "Meh", si no lo tiene. No es así en C #. El compilador es responsable de hacer las llamadas correctas y si solo tiene algún object
aleatorio, el compilador no sabe aún si la instancia durante el tiempo de ejecución tendrá ese método. Desde el punto de vista del compilador no es válido ya que no puede verificarlo. (Puedes hacer esas cosas con la reflexión o la palabra clave dynamic
, pero supongo que eso va un poco lejos ahora).
También tenga en cuenta que una interfaz en el sentido habitual no necesariamente tiene que ser una interface
C #, podría ser una clase abstracta o incluso una clase normal (lo que puede ser útil si todas las subclases necesitan compartir algún código común, en la mayoría de los casos). casos, sin embargo, la interface
suficiente).
En ausencia de escritura de pato, ya que se puede usar en Python, C # se basa en las interfaces para proporcionar abstracciones. Si las dependencias de una clase fueran todos tipos concretos, no podría pasar en ningún otro tipo, utilizando interfaces que puede pasar en cualquier tipo que implemente la interfaz.
En este caso, usted podría (y probablemente lo haría) simplemente definir una clase base de Pizza y heredarla. Sin embargo, hay dos razones por las que las interfaces le permiten hacer cosas que no se pueden lograr de otras maneras:
Una clase puede implementar múltiples interfaces. Simplemente define las características que la clase debe tener. Implementar un rango de interfaces significa que una clase puede cumplir múltiples funciones en diferentes lugares.
Una interfaz se puede definir en un ámbito más pequeño que la clase o el llamador. Esto significa que puede separar la funcionalidad, separar la dependencia del proyecto y mantener la funcionalidad en un proyecto o clase, y la implementación de esta en otro lugar.
Una implicación de 2 es que puede cambiar la clase que se está utilizando, simplemente requiriendo que implemente la interfaz apropiada.
Hice una búsqueda de la palabra "composición" en esta página y no la vi una vez. Esta respuesta es muy adicional a las respuestas antes mencionadas.
Una de las razones absolutamente cruciales para usar interfaces en un Proyecto Orientado a Objetos es que le permiten favorecer la composición sobre la herencia. Al implementar interfaces, puede desacoplar sus implementaciones de los diversos algoritmos que está aplicando a ellas.
Este excelente tutorial de "Patrón decorador" de Derek Banas (que, curiosamente, también usa pizza como ejemplo) es una ilustración que vale la pena:
Interfaz = contrato, usado para acoplamiento suelto (ver GRASP ).
Las interfaces son para aplicar la conexión entre diferentes clases. por ejemplo, tienes una clase para coche y un árbol;
public class Car { ... }
public class Tree { ... }
desea agregar una funcionalidad grabable para ambas clases. Pero cada clase tiene sus propias formas de quemarse. así que simplemente haces;
public class Car : IBurnable
{
public void Burn() { ... }
}
public class Tree : IBurnable
{
public void Burn() { ... }
}
Los ejemplos anteriores no tienen mucho sentido. Podría lograr todos los ejemplos anteriores utilizando clases (clase abstracta si desea que se comporte solo como un contrato ):
public abstract class Food {
public abstract void Prepare();
}
public class Pizza : Food {
public override void Prepare() { /* Prepare pizza */ }
}
public class Burger : Food {
public override void Prepare() { /* Prepare Burger */ }
}
Obtienes el mismo comportamiento que con la interfaz. Puede crear una List<Food>
e iterar que sin saber qué clase se encuentra en la parte superior.
Un ejemplo más adecuado sería la herencia múltiple:
public abstract class MenuItem {
public string Name { get; set; }
public abstract void BringToTable();
}
// Notice Soda only inherits from MenuItem
public class Soda : MenuItem {
public override void BringToTable() { /* Bring soda to table */ }
}
// All food needs to be cooked (real food) so we add this
// feature to all food menu items
public interface IFood {
void Cook();
}
public class Pizza : MenuItem, IFood {
public override void BringToTable() { /* Bring pizza to table */ }
public void Cook() { /* Cook Pizza */ }
}
public class Burger : MenuItem, IFood {
public override void BringToTable() { /* Bring burger to table */ }
public void Cook() { /* Cook Burger */ }
}
Luego puede usarlos todos como MenuItem
y no importa cómo manejan cada llamada de método.
public class Waiter {
public void TakeOrder(IEnumerable<MenuItem> order)
{
// Cook first
// (all except soda because soda is not IFood)
foreach (var food in order.OfType<IFood>())
food.Cook();
// Bring them all to the table
// (everything, including soda, pizza and burger because they''re all menu items)
foreach (var menuItem in order)
menuItem.BringToTable();
}
}
Nadie ha explicado realmente en términos sencillos cómo son útiles las interfaces, por lo que voy a darle una oportunidad (y robar una idea de la respuesta de Shamim un poco).
Tomemos la idea de un servicio de pedidos de pizza. Puede tener varios tipos de pizzas y una acción común para cada pizza es preparar el pedido en el sistema. Cada pizza debe prepararse, pero cada pizza se prepara de manera diferente . Por ejemplo, cuando se ordena una pizza de masa rellena, es probable que el sistema tenga que verificar que ciertos ingredientes estén disponibles en el restaurante y que no sean necesarios para las pizzas de plato profundo.
Cuando escribes esto en código, técnicamente podrías hacerlo
public class Pizza()
{
public void Prepare(PizzaType tp)
{
switch (tp)
{
case PizzaType.StuffedCrust:
// prepare stuffed crust ingredients in system
break;
case PizzaType.DeepDish:
// prepare deep dish ingredients in system
break;
//.... etc.
}
}
}
Sin embargo, las pizzas de plato profundo (en términos de C #) pueden requerir que se establezcan diferentes propiedades en el método Prepare()
que en la corteza rellena, y por lo tanto usted termina con muchas propiedades opcionales, y la clase no se escala bien (¿y si? se agregan nuevos tipos de pizza).
La forma correcta de resolver esto es usar la interfaz. La interfaz declara que todas las pizzas se pueden preparar, pero cada pizza se puede preparar de manera diferente. Así que si tienes las siguientes interfaces:
public interface IPizza
{
void Prepare();
}
public class StuffedCrustPizza : IPizza
{
public void Prepare()
{
// Set settings in system for stuffed crust preparations
}
}
public class DeepDishPizza : IPizza
{
public void Prepare()
{
// Set settings in system for deep dish preparations
}
}
Ahora su código de manejo de pedidos no necesita saber exactamente qué tipos de pizzas se ordenaron para manejar los ingredientes. Simplemente tiene:
public PreparePizzas(IList<IPizza> pizzas)
{
foreach (IPizza pizza in pizzas)
pizza.Prepare();
}
A pesar de que cada tipo de pizza se prepara de manera diferente, esta parte del código no tiene que preocuparse por el tipo de pizza con la que estamos tratando, simplemente sabe que se está llamando para pizzas y, por lo tanto, cada llamado a Prepare
preparará automáticamente cada pizza. correctamente en función de su tipo, incluso si la colección tiene varios tipos de pizzas.
Obtendrás interfaces cuando las necesites :) Puedes estudiar ejemplos, ¡pero necesitas la Aha! Efecto para realmente conseguirlos.
Ahora que sabes qué son las interfaces, solo codifica sin ellas. Tarde o temprano se encontrará con un problema en el que el uso de interfaces será lo más natural.
Para mí, el punto de estos solo quedó claro cuando dejas de verlos como cosas para hacer que tu código sea más fácil / rápido de escribir, este no es su propósito. Tienen una serie de usos:
(Esto va a perder la analogía de la pizza, ya que no es muy fácil visualizar el uso de esto)
Digamos que estás haciendo un juego simple en pantalla y tendrá criaturas con las que interactúas.
R: Pueden hacer que su código sea más fácil de mantener en el futuro mediante la introducción de un acoplamiento suelto entre el extremo delantero y la implementación del extremo posterior.
Para empezar, puedes escribir esto, ya que solo habrá trolls:
// This is our back-end implementation of a troll
class Troll
{
void Walk(int distance)
{
//Implementation here
}
}
Interfaz:
function SpawnCreature()
{
Troll aTroll = new Troll();
aTroll.Walk(1);
}
Dos semanas después, el departamento de mercadotecnia decide que también necesitas orcos, ya que leen sobre ellos en Twitter, por lo que deberías hacer algo como:
class Orc
{
void Walk(int distance)
{
//Implementation (orcs are faster than trolls)
}
}
Interfaz:
void SpawnCreature(creatureType)
{
switch(creatureType)
{
case Orc:
Orc anOrc = new Orc();
anORc.Walk();
case Troll:
Troll aTroll = new Troll();
aTroll.Walk();
}
}
Y puedes ver cómo esto comienza a ensuciarse. Puede usar una interfaz aquí para que su extremo delantero se escriba una vez y (aquí está el bit importante) probado, y luego puede conectar más elementos de extremo posterior según sea necesario:
interface ICreature
{
void Walk(int distance)
}
public class Troll : ICreature
public class Orc : ICreature
//etc
El extremo delantero es entonces:
void SpawnCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
}
La interfaz de usuario ahora solo se preocupa por la interfaz ICreature; no le preocupa la implementación interna de un troll o un orco, sino solo el hecho de que implementan ICreature.
Un punto importante a tener en cuenta al observar esto desde este punto de vista es que también podría haber utilizado fácilmente una clase de criaturas abstractas, y desde esta perspectiva, esto tiene el mismo efecto.
Y podrías extraer la creación a una fábrica:
public class CreatureFactory {
public ICreature GetCreature(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
return creature;
}
}
Y nuestra parte delantera se convertiría entonces en:
CreatureFactory _factory;
void SpawnCreature(creatureType)
{
ICreature creature = _factory.GetCreature(creatureType);
creature.Walk();
}
El extremo frontal ahora ni siquiera tiene que tener una referencia a la biblioteca donde se implementan Troll y Orc (siempre que la fábrica esté en una biblioteca separada), no necesita saber nada de ellos en absoluto.
B: Supongamos que tiene una funcionalidad que solo algunas criaturas tendrán en su estructura de datos homogénea , por ejemplo,
interface ICanTurnToStone
{
void TurnToStone();
}
public class Troll: ICreature, ICanTurnToStone
La parte delantera podría ser:
void SpawnCreatureInSunlight(creatureType)
{
ICreature creature;
switch(creatureType)
{
case Orc:
creature = new Orc();
case Troll:
creature = new Troll();
}
creature.Walk();
if (creature is ICanTurnToStone)
{
(ICanTurnToStone)creature.TurnToStone();
}
}
C: Uso para inyección de dependencia
Es más fácil trabajar con la mayoría de los marcos de inyección de dependencias cuando hay un acoplamiento muy flojo entre el código del extremo frontal y la implementación del extremo posterior. Si tomamos nuestro ejemplo de fábrica anterior y hacemos que nuestra fábrica implemente una interfaz:
public interface ICreatureFactory {
ICreature GetCreature(string creatureType);
}
Nuestra interfaz podría tener esto inyectado (por ejemplo, un controlador MVC API) a través del constructor (normalmente):
public class CreatureController : Controller {
private readonly ICreatureFactory _factory;
public CreatureController(ICreatureFactory factory) {
_factory = factory;
}
public HttpResponseMessage TurnToStone(string creatureType) {
ICreature creature = _factory.GetCreature(creatureType);
creature.TurnToStone();
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Con nuestro marco DI (por ejemplo, Ninject o Autofac), podemos configurarlos para que en el tiempo de ejecución se cree una instancia de CreatureFactory cada vez que se necesite un ICreatureFactory en un constructor; esto hace que nuestro código sea agradable y simple.
También significa que cuando escribimos una prueba de unidad para nuestro controlador, podemos proporcionar una ICreatureFactory simulada (por ejemplo, si la implementación concreta requería acceso a la base de datos, no queremos que nuestras pruebas de unidad dependan de eso) y probar fácilmente el código en nuestro controlador .
D: Hay otros usos, por ejemplo, tiene dos proyectos A y B que, por razones "heredadas", no están bien estructurados, y A tiene una referencia a B.
A continuación, encontrará la funcionalidad en B que necesita llamar a un método que ya se encuentra en A. No puede hacerlo utilizando implementaciones concretas, ya que obtiene una referencia circular.
Puede tener una interfaz declarada en B que la clase en A implementa. A su método en B se le puede pasar una instancia de una clase que implementa la interfaz sin ningún problema, aunque el objeto concreto sea de un tipo en A.
Si estoy trabajando en una API para dibujar formas, es posible que desee utilizar DirectX o llamadas de gráficos, o OpenGL. Entonces, crearé una interfaz, que abstraerá mi implementación de lo que ustedes llaman.
Así que llama a un método de fábrica: MyInterface i = MyGraphics.getInstance()
. Luego, tiene un contrato, por lo que sabe qué funciones puede esperar en MyInterface
. Por lo tanto, puede llamar a i.drawRectangle
o i.drawCube
y saber que si i.drawCube
una biblioteca por otra, se admiten las funciones.
Esto se vuelve más importante si está usando la inyección de dependencia, ya que entonces, en un archivo XML, puede intercambiar las implementaciones.
Por lo tanto, es posible que tenga una biblioteca criptográfica que se pueda exportar para uso general, y otra que solo se venda a compañías estadounidenses, y la diferencia radica en que usted cambia un archivo de configuración, y el resto del programa no lo hace. cambiado
Esto se usa mucho con las colecciones en .NET, como debería usar, por ejemplo, List
variables, y no se preocupe de si se trata de una ArrayList o LinkedList.
Mientras codifique la interfaz, el desarrollador puede cambiar la implementación real y el resto del programa no se modificará.
Esto también es útil cuando se realizan pruebas unitarias, ya que puede simular interfaces completas, por lo tanto, no tengo que ir a una base de datos, sino a una implementación simulada que solo devuelve datos estáticos, por lo que puedo probar mi método sin preocuparme si La base de datos está fuera de servicio por mantenimiento o no.
Una interfaz es realmente un contrato que deben seguir las clases de implementación, de hecho, es la base de casi todos los patrones de diseño que conozco.
En su ejemplo, la interfaz se crea porque se garantiza que se implementó cualquier cosa que sea IS A Pizza, lo que significa que implementa la interfaz Pizza.
public void Order();
Después de su código mencionado usted podría tener algo como esto:
public void orderMyPizza(IPizza myPizza) {
//This will always work, because everyone MUST implement order
myPizza.order();
}
De esta manera, está utilizando el polimorfismo y todo lo que le importa es que sus objetos respondan al orden ().
Aquí hay muchas respuestas buenas, pero me gustaría intentarlo desde una perspectiva ligeramente diferente.
Puede estar familiarizado con los principios de SOLID del diseño orientado a objetos. En resumen:
S - Principio de responsabilidad única O - Principio abierto / cerrado L - Principio de sustitución Liskov I - Principio de segregación de interfaz D - Principio de inversión de dependencia
Seguir los principios de SOLID ayuda a producir un código limpio, bien factorizado, cohesivo y débilmente acoplado. Dado que:
"La gestión de la dependencia es el desafío clave en el software a todas las escalas" (Donald Knuth)
Entonces, cualquier cosa que ayude con la gestión de la dependencia es una gran victoria. Las interfaces y el principio de inversión de dependencia realmente ayudan a desacoplar el código de las dependencias en clases concretas, por lo que el código se puede escribir y razonar en términos de comportamientos en lugar de implementaciones. Esto ayuda a dividir el código en componentes que pueden componerse en tiempo de ejecución en lugar de en tiempo de compilación y también significa que esos componentes se pueden conectar y desconectar fácilmente sin tener que alterar el resto del código.
Las interfaces ayudan en particular con el Principio de Inversión de Dependencia, donde el código puede dividirse en una colección de servicios, y cada servicio está descrito por una interfaz. Los servicios se pueden "inyectar" en clases en tiempo de ejecución pasándolos como un parámetro de constructor. Esta técnica realmente se vuelve crítica si comienzas a escribir pruebas unitarias y utilizar el desarrollo basado en pruebas. ¡Intentalo! Comprenderá rápidamente cómo las interfaces ayudan a dividir el código en partes manejables que se pueden probar individualmente de forma aislada.
Aquí hay una interfaz para objetos que tienen una forma rectangular:
interface IRectangular
{
Int32 Width();
Int32 Height();
}
Todo lo que exige es que implemente formas de acceder al ancho y alto del objeto.
Ahora definamos un método que funcionará en cualquier objeto que sea IRectangular
:
static class Utils
{
public static Int32 Area(IRectangular rect)
{
return rect.Width() * rect.Height();
}
}
Eso devolverá el área de cualquier objeto rectangular.
Implementemos una clase SwimmingPool
que sea rectangular:
class SwimmingPool : IRectangular
{
int width;
int height;
public SwimmingPool(int w, int h)
{ width = w; height = h; }
public int Width() { return width; }
public int Height() { return height; }
}
Y otra clase House
que también es rectangular:
class House : IRectangular
{
int width;
int height;
public House(int w, int h)
{ width = w; height = h; }
public int Width() { return width; }
public int Height() { return height; }
}
Dado eso, puedes llamar al Area
método en casas o piscinas:
var house = new House(2, 3);
var pool = new SwimmingPool(3, 4);
Console.WriteLine(Utils.Area(house));
Console.WriteLine(Utils.Area(pool));
De esta manera, sus clases pueden "heredar" el comportamiento (métodos estáticos) de cualquier número de interfaces.
El propósito principal de las interfaces es que establece un contrato entre usted y cualquier otra clase que implemente esa interfaz, lo que hace que su código esté desacoplado y permita la capacidad de expansión.
Therese está pidiendo ejemplos realmente grandes.
Otra, en el caso de una declaración de cambio, ya no tiene la necesidad de mantener y cambiar cada vez que quiera que rio realice una tarea de una manera específica.
En su ejemplo de pizza, si desea hacer una pizza, la interfaz es todo lo que necesita, desde allí, cada pizza se encarga de su propia lógica.
Esto ayuda a reducir el acoplamiento y la complejidad ciclomática. Aún tiene que implementar la lógica, pero habrá menos de lo que deba hacer un seguimiento en la imagen más amplia.
Para cada pizza, puede hacer un seguimiento de la información específica de esa pizza. Lo que tienen otras pizzas no importa porque solo las otras tienen que saberlo.
¿Tengo razón, entonces, para echar otro vistazo a Interfaces, la interfaz gráfica (winforms / WPF) es como la interfaz? Muestra solo con lo que el usuario final interactuará. El usuario final no tendrá que saber qué se utilizó en el diseño de la aplicación, pero sí sabría qué hacer con ella, según las opciones disponibles en el formulario. En la vista de POO, la idea es crear una interfaz estructurada que informe a otros usuarios de sus bibliotecas DLL, por ejemplo, lo que está disponible para usar y que es como una garantía / contrato que el campo, los métodos y las propiedades estarán disponibles para usar (heredar en su clases).
Comparto tu sensación de que las interfaces no son necesarias. Aquí hay una cita de Cwalina pg. 80 Pautas de diseño del marco "A menudo aquí la gente dice que las interfaces especifican contratos. Creo que esto es un mito peligroso. Las interfaces por sí mismas no especifican mucho ..." Él y el coautor Abrams lograron 3 versiones de .Net para Microsoft. Continúa diciendo que el "contrato" se "expresa" en una implementación de la clase. En mi humilde opinión, durante décadas, hubo muchas personas que advirtieron a Microsoft que llevar el paradigma de la ingeniería al máximo en OLE / COM puede parecer bueno, pero su utilidad es más directa al hardware. Especialmente en gran medida en los años 80 y 90 se codificaron los estándares de interoperación.En nuestro mundo de Internet TCP / IP hay poca apreciación de la gimnasia de hardware y software que haríamos para “conectar” las soluciones entre mainframes, minicomputadoras y microprocesadores, de los cuales las PC eran solo una pequeña minoría. Así que la codificación de las interfaces y sus protocolos hicieron que la computación funcionara. Y las interfaces gobernadas. Pero, ¿qué tiene en común la resolución de hacer que X.25 funcione con su aplicación con la publicación de recetas para las vacaciones? He estado codificando C ++ y C # durante muchos años y nunca creé uno una vez.¿Tiene 25 en común trabajar con su aplicación publicando recetas para las vacaciones? He estado codificando C ++ y C # durante muchos años y nunca creé uno una vez.¿Tiene 25 en común trabajar con su aplicación publicando recetas para las vacaciones? He estado codificando C ++ y C # durante muchos años y nunca creé uno una vez.
La forma más sencilla de pensar en las interfaces es reconocer qué significa la herencia. Si la clase CC hereda la clase C, significa que:
- La clase CC puede usar cualquier miembro público o protegido de la clase C como si fuera el suyo propio, y por lo tanto solo necesita implementar cosas que no existen en la clase principal.
- Una referencia a un CC se puede pasar o asignar a una rutina o variable que espera una referencia a un C.
Esas dos funciones de la herencia son, en cierto sentido, independientes; Si bien la herencia se aplica simultáneamente, también es posible aplicar el segundo sin el primero. Esto es útil porque permitir que un objeto herede miembros de dos o más clases no relacionadas es mucho más complicado que permitir que un tipo de cosa sea sustituible por múltiples tipos.
Una interfaz es algo así como una clase base abstracta, pero con una diferencia clave: un objeto que hereda una clase base no puede heredar ninguna otra clase. Por el contrario, un objeto puede implementar una interfaz sin afectar su capacidad para heredar cualquier clase deseada o implementar cualquier otra interfaz.
Una buena característica de esto (subutilizado en el marco .NET, IMHO) es que hacen posible indicar de forma declarativa las cosas que puede hacer un objeto. Algunos objetos, por ejemplo, querrán un objeto de origen de datos desde el cual puedan recuperar cosas por índice (como es posible con una Lista), pero no necesitarán almacenar nada allí. Otras rutinas necesitarán un objeto de depósito de datos donde puedan almacenar cosas no por índice (como en Collection.Add), pero no necesitarán leer nada. Algunos tipos de datos permitirán el acceso por índice, pero no permitirán la escritura; otros permitirán la escritura, pero no permitirán el acceso por índice. Algunos, por supuesto, permitirán ambos.
Si ReadableByIndex y Appendable fueran clases base no relacionadas, sería imposible definir un tipo que podría pasarse a cosas que esperan un ReadableByIndex y cosas que esperan un Appendable. Uno podría intentar mitigar esto haciendo que ReadableByIndex o Appendable se deriven del otro; la clase derivada tendría que poner a disposición miembros públicos para ambos propósitos, pero advertir que algunos miembros públicos podrían no funcionar realmente. Algunas de las clases e interfaces de Microsoft hacen eso, pero eso es bastante complicado. Un enfoque más limpio es tener interfaces para los diferentes propósitos, y luego hacer que los objetos implementen interfaces para lo que realmente pueden hacer. Si uno tuviera una interfaz IReadableByIndex y otra interfaz IAppendable, las clases que podrían hacer una u otra podrían implementar las interfaces apropiadas para las cosas que pueden hacer.
Las interfaces también se pueden conectar en cadena para crear otra interfaz. Esta capacidad de implementar múltiples Interfaces le da al desarrollador la ventaja de agregar funcionalidad a sus clases sin tener que cambiar la funcionalidad de la clase actual (Principios de SOLID)
O = "Las clases deben estar abiertas para extensión pero cerradas para modificación"
Me sorprende que no muchas publicaciones contengan la razón más importante para una interfaz: los patrones de diseño . El panorama general es el uso de contratos, y aunque es una decoración de sintaxis del código de máquina (para ser honesto, el compilador probablemente simplemente los ignora), la abstracción y las interfaces son fundamentales para la POO, la comprensión humana y las arquitecturas de sistemas complejos.
Ampliemos la analogía de la pizza para decir una comida completa de 3 platos. Seguiremos teniendo la Prepare()
interfaz central para todas nuestras categorías de alimentos, pero también tendremos declaraciones abstractas para las selecciones de cursos (iniciador, plato principal, postre) y diferentes propiedades para los tipos de alimentos (salados / dulces, vegetarianos / no vegetarianos, sin gluten etc).
Basándonos en estas especificaciones, podríamos implementar el patrón de Abstract Factory para conceptualizar todo el proceso, pero usar interfaces para garantizar que solo las bases fueran concretas. Todo lo demás podría volverse flexible o alentar el polimorfismo, sin embargo, mantener la encapsulación entre las diferentes clases de Course
la ICourse
interfaz que implementan .
Si tuviera más tiempo, me gustaría trazar un ejemplo completo de esto, o alguien me lo puede extender, pero en resumen, una interfaz C # sería la mejor herramienta para diseñar este tipo de sistema.
Para mí, una ventaja / beneficio de una interfaz es que es más flexible que una clase abstracta. Como solo puede heredar 1 clase abstracta pero puede implementar múltiples interfaces, los cambios en un sistema que hereda una clase abstracta en muchos lugares se vuelven problemáticos. Si se hereda en 100 lugares, un cambio requiere cambios en todos los 100. Pero, con la interfaz, puede colocar el nuevo cambio en una nueva interfaz y simplemente usar esa interfaz donde sea necesario (Interface Seq. From SOLID). Además, el uso de la memoria parece que sería menos con la interfaz, ya que un objeto en el ejemplo de la interfaz se usa solo una vez en la memoria a pesar de la cantidad de lugares en que se implementa la interfaz.
Una interfaz define un contrato entre el proveedor de una determinada funcionalidad y los consumidores correspondientes. Desacopla la implementación del contrato (interfaz). Debes echar un vistazo a la arquitectura y el diseño orientados a objetos. Puede comenzar con wikipedia: http://en.wikipedia.org/wiki/Interface_(computing)