Interfaces: ¿cuál es el punto?

269

La razón de las interfaces realmente se me escapa. Por lo que entiendo, es una especie de solución alternativa para la herencia múltiple inexistente que no existe en C # (o eso me dijeron).

Todo lo que veo es que usted predefine algunos miembros y funciones, que luego deben redefinirse nuevamente en la clase. Haciendo así que la interfaz sea redundante. Simplemente se siente sintáctico ... bueno, basura para mí (Por favor, no te ofendas. Basura como en cosas inútiles).

En el ejemplo que se muestra a continuación, tomado de un hilo de interfaces de C # diferente en el desbordamiento de la pila, simplemente crearía una clase base llamada Pizza en lugar de una interfaz.

ejemplo sencillo (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
    }
}
Nebelhom
fuente
Tengo la sensación de que hay duplicados de esta pregunta aquí en SO, pero todos parecen explicar la parte del contrato de una interfaz, por lo que no estoy seguro de que se apliquen.
Lasse V. Karlsen
77
tratando de ser un usuario agradable y ordenado, tiendo a buscar mi respuesta primero en varios foros antes de publicar algo. Desafortunadamente, la mayoría de ellos comenzaron en una etapa posterior y el resto no ayudó. Ya estaba luchando con el básico "¿Por qué hacerlo?" ya que me pareció innecesaria complicarlo demasiado. Por cierto. Gracias a todos por las respuestas muy rápidas. Tengo que digerirlos todos primero, pero creo que ahora tengo una idea razonablemente buena del punto de ellos. Parece que siempre lo he mirado desde un ángulo diferente. Muchas gracias por tu ayuda.
Nebelhom
1
También las interfaces ayudan a establecer la herencia como para los structtipos.
ja72
3
Hmm, el OP preguntaba "Por lo que entiendo, la interfaz es una especie de solución alternativa para la herencia múltiple inexistente que no existe en C #. (Aparte de eso, en el ejemplo de pizza de libro de texto citado) Yo simplemente usar una clase base en lugar de una interfaz ". Y luego la mayoría de las respuestas dieron un ejemplo que puede ser implementado por una clase base (abstracta), o dieron un ejemplo para mostrar cómo la interfaz es necesaria para el escenario de herencia múltiple. Esas respuestas son todas buenas, pero ¿no están simplemente reiterando algo que el OP ya sabe? No es de extrañar que OP haya elegido una respuesta sin ejemplos. LOL
RayLuo

Respuestas:

176

El punto es que la interfaz representa un contrato . Un conjunto de métodos públicos que cualquier clase de implementación 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 eso 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 usarla IPizzadonde sea que maneje una instancia de un tipo de pizza desconocido. Se IPizzagarantiza que cualquier instancia cuyo tipo hereda es ordenable, ya que tiene un Order()método.

Python no está estáticamente tipado, por lo tanto, los tipos se guardan y se buscan en tiempo de ejecución. Por lo tanto, puede intentar llamar a un Order()método en cualquier objeto. El tiempo de ejecución es feliz siempre que el objeto tenga dicho método y probablemente solo se encoja de hombros y diga »Meh.« Si no lo tiene. No es así en C #. El compilador es responsable de hacer las llamadas correctas y si solo tiene algo aleatorio, objectel compilador aún no sabe 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. (Puede hacer tales cosas con reflexión o con la dynamicpalabra clave, pero eso está yendo un poco lejos en este momento, supongo).

También tenga en cuenta que una interfaz en el sentido habitual no necesariamente tiene que ser un C # interface, también podría ser una clase abstracta o incluso una clase normal (que puede ser útil si todas las subclases necesitan compartir algún código común, en la mayoría de los casos , sin embargo, es interfacesuficiente).

Joey
fuente
1
+1, aunque no diría que la interfaz (en el sentido de un contrato) puede ser una clase abstracta o normal.
Groo
3
Agregaría que no puede esperar comprender las interfaces en un par de minutos. Creo que no es razonable entender las interfaces si no tienes años de experiencia en programación orientada a objetos. Puede agregar algunos enlaces a libros. Sugeriría: Inyección de dependencias en .NET, que es realmente lo profundo que es el agujero de rabit, no solo una introducción suave.
knut
2
Ah, está el problema de que no tenga ni idea de DI. Pero supongo que el principal problema del autor de la pregunta fue por qué son necesarios cuando en Python todo funciona sin él. Eso es lo que trató de proporcionar el párrafo más grande de mi respuesta. No creo que sea necesario profundizar en cada patrón y práctica que usa interfaces aquí.
Joey
1
Bueno, entonces su pregunta es: "¿Por qué usar lenguajes tipados estáticamente cuando los tipeados dinámicamente son más fáciles de programar?". Aunque no soy el experto para responder esa pregunta, puedo aventurarme a decir que el rendimiento es el tema decisivo. Cuando se realiza una llamada a un objeto python, el proceso debe decidir durante el tiempo de ejecución si ese objeto tiene un método llamado "Orden", mientras que si realiza la llamada a un objeto C #, ya está establecido que implementa ese método, y Se puede hacer una llamada a dicha dirección.
Boluc Papuccuoglu
3
@BolucPapuccuoglu: más allá de eso, con un objeto estáticamente tipado si uno conoce fooimplementos IWidget, entonces un programador que vea una llamada foo.woozle()puede mirar la documentación IWidgety saber qué se supone que debe hacer ese método . Es posible que el programador no tenga forma de saber de dónde vendrá el código para la implementación real, pero cualquier tipo que cumpla con el IWidgetcontrato de interfaz se implementará foode manera consistente con ese contrato. En un lenguaje dinámico, por el contrario, no habría un punto de referencia claro de lo que foo.woozle()debería significar.
supercat
440

Nadie realmente ha explicado en términos simples cómo las interfaces son útiles, así que voy a intentarlo (y robaré un poco la idea de la respuesta de Shamim).

Tomemos la idea de un servicio de pedido 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 tiene que prepararse, pero cada pizza se prepara de manera diferente . Por ejemplo, cuando se ordena una pizza de masa rellena, el sistema probablemente tiene que verificar que ciertos ingredientes estén disponibles en el restaurante y dejar de lado aquellos que no son necesarios para las pizzas de plato hondo.

Al escribir esto en código, técnicamente podrías simplemente hacer

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 hondo (en términos de C #) pueden requerir diferentes propiedades para establecer Prepare() método que la corteza rellena, y por lo tanto terminas con muchas propiedades opcionales, y la clase no escala bien (¿qué pasa si agregas nuevas 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. Entonces, si tiene 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. Solo 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, solo sabe que se está pidiendo pizzas y, por lo tanto, cada llamada a Preparepreparará automáticamente cada pizza correctamente según su tipo, incluso si la colección tiene varios tipos de pizzas.

KallDrexx
fuente
11
+1, me gustó el ejemplo de usar una Lista para mostrar el uso de una interfaz.
danielunderwood
21
Buena respuesta, pero quizás enmendarla para aclarar por qué una interfaz en este caso es mejor que simplemente usar una clase abstracta (para un ejemplo tan simple, ¿una clase abstracta puede ser mejor?)
Jez
3
Esta es la mejor respuesta. Hay un pequeño error tipográfico o puede que solo copie y pegue el código. En la interfaz C # no declaras modificadores de acceso como public. Entonces, dentro de la interfaz debería ser solovoid Prepare();
Dush
26
No veo cómo esto responde la pregunta. Este ejemplo podría haberse hecho fácilmente con una clase base y un método abstracto, que es exactamente lo que señalaba la pregunta original.
Jamie Kitson
44
Las interfaces realmente no entran en su derecho hasta que hay muchas de ellas dando vueltas. Solo puede heredar de una sola clase, abstracta o no, pero puede implementar tantas interfaces diferentes en una sola clase como desee. De repente no necesitas una puerta en el marco de una puerta; cualquier cosa que sea IOpenable hará, ya sea una puerta, una ventana, un buzón, lo que sea.
mtnielsen
123

Para mí, al comenzar, el punto a estos solo se hizo 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 varios 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 la 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 al introducir un acoplamiento suelto entre su front-end y su implementación de back-end.

Para empezar, podría 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 marketing decide que también necesita Orcos, ya que leyeron sobre ellos en Twitter, por lo que tendría que 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 complicarse. Puede usar una interfaz aquí para que su front-end se escriba una vez y (aquí está el bit importante) probado, y luego puede conectar más elementos de back-end según sea necesario:

interface ICreature
{
    void Walk(int distance)
}

public class Troll : ICreature
public class Orc : ICreature 

//etc

El extremo frontal es entonces:

void SpawnCreature(creatureType)
{
    ICreature creature;

    switch(creatureType)
    {
         case Orc:

           creature = new Orc();

          case Troll:

            creature = new Troll();
    }

    creature.Walk();
}

El front end 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 mirar esto desde este punto de vista es que también podrías haber usado fácilmente una clase de criatura abstracta, y desde esta perspectiva, esto tiene el mismo efecto.

Y podría 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 nuestro front end se convertiría en:

CreatureFactory _factory;

void SpawnCreature(creatureType)
{
    ICreature creature = _factory.GetCreature(creatureType);

    creature.Walk();
}

El front end 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.

B: Digamos 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

El front end 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

La mayoría de los marcos de inyección de dependencia son más fáciles de trabajar cuando hay un acoplamiento muy flojo entre el código de front-end y la implementación de back-end. Si tomamos nuestro ejemplo de fábrica anterior y hacemos que nuestra fábrica implemente una interfaz:

public interface ICreatureFactory {
     ICreature GetCreature(string creatureType);
}

Nuestro front end podría entonces tener esto inyectado (por ejemplo, un controlador MVC API) a través del constructor (típicamente):

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 tiempo de ejecución se cree una instancia de CreatureFactory siempre 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 unitaria para nuestro controlador, podemos proporcionar una ICreatureFactory simulada (por ejemplo, si la implementación concreta requiere acceso a la base de datos, no queremos que nuestras pruebas unitarias 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 de 'legado' no están bien estructurados, y A tiene una referencia a B.

Luego encontrará funcionalidad en B que necesita llamar a un método que ya está en A. No puede hacerlo utilizando implementaciones concretas a medida que obtiene una referencia circular.

Puede tener una interfaz declarada en B que la clase en A luego implementa. Su método en B puede pasar una instancia de una clase que implementa la interfaz sin problemas, incluso si el objeto concreto es de un tipo en A.

Arrozal
fuente
66
¿No es molesto cuando encuentras una respuesta que intentaba decir lo que eras, pero lo hace mucho mejor? Stackoverflow.com/a/93998/117215
Paddy
18
La buena noticia es que ahora es una página muerta y la tuya no lo es :). ¡Buen ejemplo!
Sealer_05
1
Tu discusión en C me perdió un poco. Pero, me gustan sus discusiones A y B porque ambas explican esencialmente cómo se pueden usar las interfaces para proporcionar tipos comunes de funcionalidad en múltiples clases. El área de las interfaces que todavía me resulta confusa es cómo se usan las interfaces para un acoplamiento más flexible. ¿Quizás eso es lo que estaba abordando su discusión C? Si es así, creo que necesito un ejemplo más detallado :)
user2075599
1
Me parece en su ejemplo que ICreature sería más adecuado para ser una clase base abstracta (¿Criatura?) Para Troll y Orc. Entonces, cualquier lógica común entre criaturas podría implementarse allí. Lo que significa que no necesita la interfaz ICreature en absoluto ...
Skarsnik
1
@Skarsnik - bastante, de eso se trata esta nota: "Un punto importante a tener en cuenta al mirar esto desde este punto de vista es que también podrías haber usado fácilmente una clase de criatura abstracta, y desde esta perspectiva, esto tiene el mismo efecto."
Paddy
33

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
    }
}
Shamim Hafiz
fuente
27

Los ejemplos anteriores no tienen mucho sentido. Puede realizar 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 */ }
}

Obtiene el mismo comportamiento que con la interfaz. Puede crear List<Food>ay repetir eso 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 MenuItemy no preocuparse por cómo manejan cada llamada al 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();
    }
}
Aleksandar Toplek
fuente
2
Tuve que desplazarme hasta aquí para encontrar una respuesta que realmente responda a la pregunta "¿por qué no usar una clase abstracta en lugar de una interfaz?". Parece que realmente es solo para manejar la falta de herencia múltiple de c #.
SyntaxRules
2
Sí, esta es la mejor explicación para mí. El único que explica claramente por qué la interfaz puede ser más útil que una clase abstracta. (es decir: una pizza es un elemento de menú Y también es comida, mientras que con una clase abstracta solo puede ser una u otra pero no ambas)
Maxter
25

Explicación simple con analogía

El problema a resolver: ¿Cuál es el propósito del polimorfismo?

Analogía: Así que soy el encargado de una obra de construcción.

Los comerciantes caminan por el sitio de construcción todo el tiempo. No sé quién va a cruzar esas puertas. Pero básicamente les digo qué hacer.

  1. Si es carpintero, digo: construya andamios de madera.
  2. Si es un plomero, digo: "Prepara las tuberías"
  3. Si se trata de un electricista, le digo: "Saque los cables y reemplácelos con cables de fibra óptica".

El problema con el enfoque anterior es que tengo que: (i) saber quién entra por esa puerta y, según quién sea, tengo que decirles qué hacer. Eso significa que tengo que saber todo sobre 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()a BuildScaffold()(es decir, un ligero cambio de nombre), también tendré que cambiar la clase de llamada (es decir, la Forepersonclase), tendrá que hacer dos cambios en el código en lugar de (básicamente ) solo uno. Con el polimorfismo, (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 ... ¿quién eres? ok haz eso ... polimorfismo - SECA ese código, y es muy efectivo en ciertas situaciones:

  • Con 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 SÓLIDO: principio de apertura-cierre).

La solución

Imagine un escenario en el que, sin importar quién entra por la puerta, puedo decir: "Trabajar ()" y hacen sus respetuosos trabajos en los que se especializan: el plomero se ocuparía de las tuberías y el electricista se ocuparí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 persona y que pueden trabajar, y en segundo lugar , (ii) no necesito saber nada sobre ese comercio en particular. El tradie se encargará de eso.

Entonces, 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()!!

Cual es el beneficio?

El beneficio es que si cambian los requisitos de trabajo específicos del carpintero, etc., el capataz no necesitará cambiar su código, no necesita saberlo ni preocuparse. Lo único que importa es que el carpintero sepa lo que 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 es si el trabajador de la construcción (.eg Welder, Glazier, Tiler, etc.) puede hacer un poco de trabajo ().


Problema ilustrado y solución (con y sin interfaces):

Sin interfaz (Ejemplo 1):

Ejemplo 1: sin una interfaz

Sin interfaz (Ejemplo 2):

Ejemplo 2: sin una interfaz

Con una interfaz:

Ejemplo 3: los beneficios de usar una interfaz

Resumen

Una interfaz le permite hacer que la persona haga el trabajo que se le asigna, sin que usted sepa exactamente quiénes son o los detalles de lo que pueden hacer. Esto le permite agregar fácilmente nuevos tipos (de comercio) sin cambiar su código existente (bueno, técnicamente 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 comprende nada de lo anterior o si no está claro, pregunte en un comentario e intentaré mejorar la respuesta.

BKSpurgeon
fuente
44
Más uno para el chico que llega a trabajar con una sudadera con capucha.
Yusha
11

En ausencia de pato escribiendo como puede usarlo en Python, C # se basa en interfaces para proporcionar abstracciones. Si las dependencias de una clase fueran todos tipos concretos, no podría pasar ningún otro tipo; utilizando interfaces, puede pasar cualquier tipo que implemente la interfaz.

Vidrio roto
fuente
3
+1 Eso es correcto, si tienes pato escribiendo, no necesitas interfaces. Pero sí imponen una seguridad de tipo más fuerte.
Groo
11

El ejemplo de Pizza es malo porque deberías estar usando una clase abstracta que maneja los pedidos, y las pizzas deberían anular el tipo de pizza, por ejemplo.

Usas interfaces cuando tienes una propiedad compartida, pero tus clases heredan de diferentes lugares, o cuando no tienes ningún código común que puedas usar. Por ejemplo, se usan cosas que se pueden eliminar IDisposable, sabes que se eliminará, simplemente no sabes qué sucederá cuando se elimine.

Una interfaz es solo un contrato que le dice algunas cosas que puede hacer un objeto, qué parámetros y qué tipos de retorno esperar.

bevacqua
fuente
10

Considere el caso en el que no controla ni posee las clases base.

Tome los controles visuales, por ejemplo, en .NET para Winforms, todos ellos heredan del control de clase base, que está completamente definido en el marco .NET.

Supongamos que está en el negocio de crear controles personalizados. Desea crear nuevos botones, cuadros de texto, vistas de lista, cuadrículas, etc., y desea que todos tengan ciertas características exclusivas de su conjunto de controles.

Por ejemplo, es posible que desee una forma común de manejar la temática 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 relacionado 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 crear un código que diga "para todos los controles en el formulario que son míos, establezca el tema en X".

Ingresar interfaces.

Las interfaces son una forma de mirar un objeto, para determinar que el objeto se adhiere a un determinado contrato.

Crearía "YourButton", descendería de Button y agregaría soporte para todas las interfaces que necesita.

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, sino que 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 ....
}
Lasse V. Karlsen
fuente
Sí, lo sé, no deberías usar "es" y luego lanzarlo, déjalo a un lado.
Lasse V. Karlsen
44
suspiro, lo sé, pero eso es incidental aquí.
Lasse V. Karlsen
7

En este caso, podría (y probablemente lo haría) simplemente definir una clase base de Pizza y heredar de ellos. Sin embargo, hay dos razones por las cuales las interfaces le permiten hacer cosas que no se pueden lograr de otras maneras:

  1. Una clase puede implementar múltiples interfaces. Simplemente define las características que debe tener la clase. Implementar una gama de interfaces significa que una clase puede cumplir múltiples funciones en diferentes lugares.

  2. Una interfaz se puede definir en un ámbito más amplio que la clase o la persona que llama. 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 esto en otro lugar.

Una implicación de 2 es que puede cambiar la clase que se está utilizando, simplemente requiriendo que implemente la interfaz adecuada.

Gato Schroedingers
fuente
6

Considere que no puede usar la herencia múltiple en C # y luego vuelva a mirar su pregunta.

Claus Jørgensen
fuente
4

Si estoy trabajando en una API para dibujar formas, es posible que quiera usar DirectX o llamadas gráficas, o OpenGL. Entonces, crearé una interfaz, que abstraerá mi implementación de lo que llamas.

Así que llama a un método de fábrica: MyInterface i = MyGraphics.getInstance(). Luego, tiene un contrato, para saber en qué funciones puede esperar MyInterface. Entonces, puedes llamar i.drawRectangleoi.drawCube y saber que si intercambia una biblioteca por otra, las funciones son compatibles.

Esto se vuelve más importante si está utilizando la Inyección de dependencias, ya que puede, en un archivo XML, intercambiar implementaciones.

Entonces, puede tener una biblioteca de cifrado que se puede exportar para uso general, y otra que está a la venta solo para empresas estadounidenses, y la diferencia está en que cambia un archivo de configuración y el resto del programa no cambiado

Esto se usa mucho con colecciones en .NET, ya que solo debe usar, por ejemplo, List variables, y no se preocupe si era una ArrayList o LinkedList.

Mientras codifique en la interfaz, el desarrollador puede cambiar la implementación real y el resto del programa no se modifica.

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á inactiva por mantenimiento o no.

James Black
fuente
4

Una interfaz es realmente un contrato que las clases implementadoras deben seguir, de hecho, es la base de casi todos los patrones de diseño que conozco.

En su ejemplo, la interfaz se crea porque entonces todo lo que ES una pizza, lo que significa que implementa la interfaz de Pizza, se garantiza que se ha implementado

public void Order();

Después de su código mencionado, 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 lo único que le importa es que sus objetos respondan al orden ().

Oscar Gomez
fuente
4

Hice una búsqueda de la palabra "composición" en esta página y no la vi ni una vez. Esta respuesta es muy adicional a las respuestas mencionadas anteriormente.

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 de decorador" de Derek Banas (que, curiosamente, también usa pizza como ejemplo) es una ilustración que vale la pena:

https://www.youtube.com/watch?v=j40kRwSm4VE

Jay Edwards
fuente
2
Estoy realmente sorprendido de que esta no sea la mejor respuesta. Las interfaces en toda su utilidad, son de mayor uso en la composición.
Maciej Sitko
3

Las interfaces son para aplicar la conexión entre diferentes clases. por ejemplo, tienes una clase para auto 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 quemar. entonces simplemente haces;

public class Car : IBurnable
{
public void Burn() { ... }
}

public class Tree : IBurnable
{
public void Burn() { ... }
}
hakan
fuente
2
La pregunta que me persigue en ejemplos como este es: ¿Por qué ayuda esto? ¿Puedo pasar ahora el argumento de tipo IBurnable a un método y todas las clases con la interfaz IBurnable se pueden manejar? Igual que el ejemplo de Pizza que encontré. Es bueno que pueda hacer esto, pero no veo el beneficio, como tal. ¿Podrías extender el ejemplo (porque soy realmente grueso en este momento) o dar un ejemplo que no funcionaría con él (nuevamente, porque me siento realmente grueso en este momento)? Muchas gracias.
Nebelhom
1
De acuerdo. Interfaz = "Puede hacer". Clase / Resumen Calss = "Is A"
Peter.Wang
3

Obtendrá interfaces, cuando las necesite :) Puede estudiar ejemplos, pero necesita ¡Ajá! efecto para realmente conseguirlos.

Ahora que sabe qué son las interfaces, simplemente codifique sin ellas. Tarde o temprano se encontrará con un problema, donde el uso de interfaces será lo más natural.

sventevit
fuente
3

Me sorprende que no muchas publicaciones contengan la razón más importante para una interfaz: patrones de diseño . Es la imagen más amplia en el uso de contratos, y aunque es una decoración de sintaxis para el código de máquina (para ser sincero, 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.

Expandamos la analogía de la pizza para decir una comida completa de 3 platos. Todavía tendremos la Prepare()interfaz principal para todas nuestras categorías de alimentos, pero también tendremos declaraciones abstractas para las selecciones de cursos (entrante, principal, postre) y diferentes propiedades para los tipos de alimentos (salados / dulces, vegetarianos / no vegetarianos, sin gluten, etc.).

Con base en estas especificaciones, podríamos implementar el patrón Abstract Factory para conceptualizar todo el proceso, pero usar interfaces para asegurar que solo los cimientos sean concretos. Todo lo demás podría volverse flexible o alentar el polimorfismo, pero mantener la encapsulación entre las diferentes clases Courseque implementan la ICourseinterfaz.

Si tuviera más tiempo, me gustaría elaborar un ejemplo completo de esto, o alguien puede extender esto por mí, pero en resumen, una interfaz C # sería la mejor herramienta para diseñar este tipo de sistema.

ShaunnyBwoy
fuente
1
¡Esta respuesta merece más puntos! Las interfaces brillan cuando se usan en patrones diseñados, por ejemplo, The State Pattern. Consulte plus.google.com/+ZoranHorvat-Programming para obtener información adicional
Alex Nolasco,
3

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 la altura del objeto.

Ahora definamos un método que funcione 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 SwimmingPoolque 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 Houseque 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, puede llamar al Areamé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.

dharmatech
fuente
3

Una interfaz define un contrato entre el proveedor de una determinada funcionalidad y los consumidores correspondientes. Desacopla la implementación del contrato (interfaz). Debe echar un vistazo a la arquitectura y el diseño orientados a objetos. Es posible que desee comenzar con wikipedia: http://en.wikipedia.org/wiki/Interface_(computing)

hogar
fuente
3

Qué ?

Las interfaces son básicamente un contrato que todas las clases que implementan la interfaz deben seguir. Parecen una clase pero no tiene implementación.

En la C#interfaz, los nombres por convención se definen al prefijar una 'I', por lo que si desea tener una interfaz llamada formas, debe declararla comoIShapes

Ahora por qué ?

Improves code re-usability

Digamos que usted quiere dibujar Circle, Triangle. puede agruparlos juntos y llamarlos Shapesy tienen métodos para dibujar Circley Triangle Pero tener aplicación concreta sería una mala idea porque mañana es posible que decida tener 2 más Shapes Rectangley Square. Ahora, cuando los agrega, existe una gran posibilidad de que pueda romper otras partes de su código.

Con Interface, aísla las diferentes implementaciones del Contrato


Escenario en vivo Día 1

Se te pidió que crearas una aplicación para dibujar círculos y triángulos

interface IShapes
{
   void DrawShape();
   
 }

class Circle : IShapes
{
    
    public void DrawShape()
    {
        Console.WriteLine("Implementation to Draw a Circle");
    }
}

Class Triangle: IShapes
{
     public void DrawShape()
    {
        Console.WriteLine("Implementation to draw a Triangle");
    }
}
static void Main()
{
     List <IShapes> shapes = new List<IShapes>();
        shapes.Add(new Circle());
        shapes.Add(new Triangle());

        foreach(var shape in shapes)
        {
            shape.DrawShape();
        }
}

Escenario en vivo Día 2

Si se le solicitó agregar Squarey agregar Rectangle, todo lo que tiene que hacer es crear la implementación para ello class Square: IShapesy Mainagregarlo a la listashapes.Add(new Square());

Clint
fuente
¿Por qué está agregando una respuesta a una pregunta de 6 años con docenas de otras respuestas votadas cientos de veces? Aquí no hay nada que no se haya dicho.
Jonathon Reinhart
@JonathonReinhart, sí, pensé que sí, pero estaba pensando en este ejemplo y la forma en que se explica ayudará a relacionarse mejor con algunos cuerpos que con los demás.
Clint
2

Aquí hay muchas buenas respuestas, pero me gustaría intentarlo desde una perspectiva ligeramente diferente.

Puede estar familiarizado con los principios SÓLIDOS del diseño orientado a objetos. En resumen:

S - Principio de responsabilidad única O - Principio abierto / cerrado L - Principio de sustitución de Liskov I - Principio de segregación de interfaz D - Principio de inversión de dependencia

Seguir los principios SÓLIDOS ayuda a producir código limpio, bien factorizado, cohesivo y poco acoplado. Dado que:

"La gestión de dependencias es el desafío clave en el software en todas las escalas" (Donald Knuth)

entonces cualquier cosa que ayude con la gestión de dependencias es una gran victoria. Las interfaces y el principio de inversión de dependencias realmente ayudan a desacoplar el código de las dependencias de clases concretas, por lo que el código puede escribirse y razonarse en términos de comportamientos en lugar de implementaciones. Esto ayuda a dividir el código en componentes que se pueden componer 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 ser componente en una colección de servicios, y cada servicio es descrito por una interfaz. Los servicios se pueden "inyectar" en clases en tiempo de ejecución pasándolos como un parámetro constructor. Esta técnica realmente se vuelve crítica si comienzas a escribir pruebas unitarias y usas un desarrollo basado en pruebas. ¡Intentalo! Comprenderá rápidamente cómo las interfaces ayudan a separar el código en fragmentos manejables que se pueden probar individualmente de forma aislada.

Tim Long
fuente
1

El objetivo 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 se desacople y permita la capacidad de expansión.

Samir Adel
fuente
1

Therese se preguntan ejemplos realmente geniales.

Otro, 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, a partir de ahí cada pizza se encarga de su propia lógica.

Esto ayuda a reducir el acoplamiento y la complejidad ciclomática. Todavía tiene que implementar la lógica, pero habrá menos de lo que tenga que estar pendiente en la imagen más amplia.

Para cada pizza, puede realizar un seguimiento de la información específica de esa pizza. Lo que otras pizzas tienen no importa porque solo las otras pizzas necesitan saberlo.

tam
fuente
1

La forma más sencilla de pensar en las interfaces es reconocer lo que significa herencia. Si la clase CC hereda la clase C, significa que:

  1. La clase CC puede usar cualquier miembro público o protegido de la clase C como si fueran propios, y por lo tanto solo necesita implementar cosas que no existen en la clase principal.
  2. Se puede pasar o asignar una referencia a un CC a una rutina o variable que espera una referencia a un C.

Esas dos funciones de la herencia son, en cierto sentido, independientes; aunque la herencia se aplica a ambos 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 varios 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 de heredar cualquier clase deseada o implementar cualquier otra interfaz.

Una buena característica de esto (infrautilizado en el marco .net, en mi humilde opinión) es que permiten indicar de manera declarativa las cosas que puede hacer un objeto. Algunos objetos, por ejemplo, querrán un objeto de fuente de datos del que 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 con 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 se pueda pasar tanto a las cosas que esperan un ReadableByIndex como a las 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 desagradable. Un enfoque más limpio es tener interfaces para los diferentes propósitos, y luego hacer que los objetos implementen interfaces para las cosas que realmente pueden hacer. Si uno tuviera una interfaz IReadableByIndex y otra interfaz IAppendable,

Super gato
fuente
1

Las interfaces también se pueden conectar en cadena para crear otra interfaz más. Esta capacidad para 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 SÓLIDOS)

O = "Las clases deben estar abiertas para extensión pero cerradas para modificación"


fuente
1

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 los 100. Pero, con la interfaz, puede colocar el nuevo cambio en una nueva interfaz y simplemente usar esa interfaz donde sea necesario (Seq. De interfaz de SOLID). Además, el uso de memoria parece que sería menor con la interfaz ya que un objeto en el ejemplo de interfaz se usa solo una vez en la memoria a pesar de cuántos lugares implementan la interfaz.

ScottS
fuente
1

Las interfaces se utilizan para impulsar la coherencia, de una manera que está acoplada libremente, lo que la hace diferente a la clase abstracta que está estrechamente acoplada. Es por eso que también se define comúnmente como un contrato. Cuantas clases que implementen la interfaz cumplan con las "reglas / sintaxis" definido por la interfaz y no hay elementos concretos dentro de ella.

Solo daré un ejemplo respaldado por el gráfico a continuación.

Imagine que en una fábrica hay 3 tipos de máquinas: una máquina de rectángulo, una máquina de triángulo y una máquina de polígonos. Los tiempos son competitivos y desea optimizar la capacitación de los operadores. Solo desea capacitarlos en una metodología de arranque y parada de máquinas para que pueda tiene un botón de inicio verde y un botón de detención rojo. Así que ahora en 3 máquinas diferentes tiene una manera consistente de iniciar y detener 3 tipos diferentes de máquinas. Ahora imagine que estas máquinas son clases y las clases necesitan tener métodos de inicio y detención, cómo va a impulsar la coherencia entre estas clases, que puede ser muy diferente? La interfaz es la respuesta.

ingrese la descripción de la imagen aquí

Un ejemplo simple para ayudarlo a visualizar, uno podría preguntarse ¿por qué no usar la clase abstracta? Con una interfaz, los objetos no tienen que estar directamente relacionados o heredados y aún puede generar consistencia en diferentes clases.

public interface IMachine
{
    bool Start();
    bool Stop();
}

public class Car : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Car started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Car stopped");
        return false;
    }
}

public class Tank : IMachine
{
    public bool Start()
    {
        Console.WriteLine("Tank started");
        return true;
    }

    public bool Stop()
    {
        Console.WriteLine("Tank stopped");
        return false;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var car = new Car();
        car.Start();
        car.Stop();

        var tank = new Tank();
        tank.Start();
        tank.Stop();

    }
}
C.Poh
fuente
1
class Program {
    static void Main(string[] args) {
        IMachine machine = new Machine();
        machine.Run();
        Console.ReadKey();
    }

}

class Machine : IMachine {
    private void Run() {
        Console.WriteLine("Running...");
    }
    void IMachine.Run() => Run();
}

interface IMachine
{
    void Run();
}

Permítanme describir esto desde una perspectiva diferente. Creemos una historia de acuerdo con el ejemplo que he mostrado anteriormente;

Program, Machine e IMachine son los actores de nuestra historia. El programa quiere ejecutarse pero no tiene esa habilidad y Machine sabe cómo ejecutarlo. Machine e IMachine son mejores amigos, pero el programa no está en términos de hablar con Machine. Entonces, Program e IMachine llegaron a un acuerdo y decidieron que IMachine le dirá a Program cómo ejecutar mirando la máquina (como un reflector).

Y el programa aprende a ejecutar con la ayuda de IMachine.

La interfaz proporciona comunicación y desarrolla proyectos sueltos.

PD: Tengo el método de clase concreta como privado. Mi objetivo aquí es lograr un acoplamiento flexible evitando el acceso a propiedades y métodos de clase concretos, y dejando solo la posibilidad de llegar a ellos a través de interfaces. (Entonces definí los métodos de las interfaces explícitamente).

Furkan Öztürk
fuente
1

Sé que llego muy tarde ... (casi nueve años), pero si alguien quiere una pequeña explicación, puedes ir por esto:

En palabras simples, utiliza la Interfaz cuando sabe lo que puede hacer un Objeto o qué función vamos a implementar en un objeto. Ejemplo Insertar, Actualizar y Eliminar.

interface ICRUD{
      void InsertData(); // will insert data
      void UpdateData(); // will update data
      void DeleteData(); // will delete data
}

Nota importante: las interfaces son SIEMPRE públicas.

Espero que esto ayude.

noobprogrammer
fuente