¿Qué significa "programa para interfaces, no implementaciones"?

Respuestas:

148

Las interfaces son solo contratos o firmas y no saben nada sobre implementaciones.

Codificación contra medios de interfaz, el código del cliente siempre contiene un objeto de interfaz que es suministrado por una fábrica. Cualquier instancia devuelta por la fábrica sería del tipo Interfaz que cualquier clase de candidato de fábrica debe haber implementado. De esta manera, el programa cliente no está preocupado por la implementación y la firma de la interfaz determina lo que se pueden hacer todas las operaciones. Esto se puede usar para cambiar el comportamiento de un programa en tiempo de ejecución. También le ayuda a escribir programas mucho mejores desde el punto de vista del mantenimiento.

Aquí hay un ejemplo básico para ti.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

[STAThread]
static void Main()
{
    //This is your client code.
    ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
    speaker.Speak();
    Console.ReadLine();
}

public interface ISpeaker
{
    void Speak();
}

public class EnglishSpeaker : ISpeaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : ISpeaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak German.");
    }

    #endregion
}

public class SpanishSpeaker : ISpeaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    #endregion
}

texto alternativo

Este es solo un ejemplo básico y la explicación real del principio está más allá del alcance de esta respuesta.

EDITAR

He actualizado el ejemplo anterior y agregué una Speakerclase base abstracta . En esta actualización, agregué una función a todos los altavoces a "SayHello". Todos los oradores hablan "Hola Mundo". Entonces esa es una característica común con una función similar. Consulte el diagrama de clase y encontrará que Speakerla ISpeakerinterfaz de implementación de clase abstracta marca el Speak()resumen como lo que significa que cada implementación de Speaker es responsable de implementar el Speak()método, ya que varía de Speakera Speaker. Pero todos los oradores dicen "Hola" por unanimidad. Entonces, en la clase abstracta Speaker definimos un método que dice "Hola Mundo" y cada Speakerimplementación derivará el SayHello()método.

Considere un caso en el SpanishSpeakerque no se puede decir hola, por lo que en ese caso puede anular el SayHello()método para hispanohablantes y generar una excepción adecuada.

Tenga en cuenta que no hemos realizado ningún cambio en Interface ISpeaker. Y el código del cliente y SpeakerFactory tampoco se verán afectados. Y esto es lo que logramos mediante Programación a interfaz .

Y podríamos lograr este comportamiento simplemente agregando un orador de clase abstracta base y algunas modificaciones menores en cada implementación, dejando el programa original sin cambios. Esta es una característica deseada de cualquier aplicación y hace que su aplicación sea fácilmente mantenible.

public enum Language
{
    English, German, Spanish
}

public class SpeakerFactory
{
    public static ISpeaker CreateSpeaker(Language language)
    {
        switch (language)
        {
            case Language.English:
                return new EnglishSpeaker();
            case Language.German:
                return new GermanSpeaker();
            case Language.Spanish:
                return new SpanishSpeaker();
            default:
                throw new ApplicationException("No speaker can speak such language");
        }
    }
}

class Program
{
    [STAThread]
    static void Main()
    {
        //This is your client code.
        ISpeaker speaker = SpeakerFactory.CreateSpeaker(Language.English);
        speaker.Speak();
        Console.ReadLine();
    }
}

public interface ISpeaker
{
    void Speak();
}

public abstract class Speaker : ISpeaker
{

    #region ISpeaker Members

    public abstract void Speak();

    public virtual void SayHello()
    {
        Console.WriteLine("Hello world.");
    }

    #endregion
}

public class EnglishSpeaker : Speaker
{
    public EnglishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        this.SayHello();
        Console.WriteLine("I speak English.");
    }

    #endregion
}

public class GermanSpeaker : Speaker
{
    public GermanSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak German.");
        this.SayHello();
    }

    #endregion
}

public class SpanishSpeaker : Speaker
{
    public SpanishSpeaker() { }

    #region ISpeaker Members

    public override void Speak()
    {
        Console.WriteLine("I speak Spanish.");
    }

    public override void SayHello()
    {
        throw new ApplicationException("I cannot say Hello World.");
    }

    #endregion
}

texto alternativo

esta. __curious_geek
fuente
19
La programación en la interfaz no se trata solo del tipo de la variable de referencia. También significa que no utiliza ninguna suposición implícita sobre su implementación. Por ejemplo, si usa a Listcomo tipo, aún podría estar asumiendo que el acceso aleatorio es rápido al llamar repetidamente get(i).
Joachim Sauer
16
Las fábricas son ortogonales a la programación de interfaces, pero creo que esta explicación hace que parezca que son parte de ella.
T.
@Toon: de acuerdo contigo. Quería proporcionar un ejemplo muy básico y simple para la programación a la interfaz. No quería confundir al interlocutor implementando la interfaz IFlyable en pocas clases de aves y animales.
esto. __curious_geek
@esta. si en cambio uso una clase abstracta o un patrón de fachada, ¿se seguirá llamando "programa a una interfaz"? ¿o tengo que usar explícitamente una interfaz e implementarla en una clase?
never_had_a_name
1
¿Qué herramienta uml estabas usando para crear las imágenes?
Adam Arold
29

Piense en una interfaz como un contrato entre un objeto y sus clientes. Es decir, la interfaz especifica las cosas que puede hacer un objeto y las firmas para acceder a esas cosas.

Las implementaciones son los comportamientos reales. Digamos, por ejemplo, que tiene un método sort (). Puede implementar QuickSort o MergeSort. Eso no debería importarle al ordenamiento del código del cliente siempre que la interfaz no cambie.

Las bibliotecas como la API de Java y .NET Framework hacen un uso intensivo de las interfaces porque millones de programadores usan los objetos provistos. Los creadores de estas bibliotecas deben tener mucho cuidado de no cambiar la interfaz de las clases en estas bibliotecas porque afectará a todos los programadores que usan la biblioteca. Por otro lado, pueden cambiar la implementación tanto como quieran.

Si, como programador, codifica contra la implementación, tan pronto como cambie, su código dejará de funcionar. Así que piense en los beneficios de la interfaz de esta manera:

  1. oculta las cosas que no necesita saber para que el objeto sea más fácil de usar.
  2. proporciona el contrato de cómo se comportará el objeto para que pueda confiar en eso
Vincent Ramdhanie
fuente
Significa que debe ser consciente de lo que está contratando para hacer el objeto: en el ejemplo, siempre que solo esté contratando un tipo, no necesariamente un tipo estable.
pingüino
De manera similar a cómo la documentación de la biblioteca no menciona la implementación, son solo descripciones de las interfaces de clase incluidas.
Joe Iddon el
17

Significa que debe intentar escribir su código para que use una abstracción (clase abstracta o interfaz) en lugar de la implementación directamente.

Normalmente, la implementación se inyecta en su código a través del constructor o una llamada a un método. Por lo tanto, su código conoce la interfaz o la clase abstracta y puede llamar a cualquier cosa que esté definida en este contrato. Como se usa un objeto real (implementación de la interfaz / clase abstracta), las llamadas operan en el objeto.

Este es un subconjunto de Liskov Substitution Principle(LSP), la L de los SOLIDprincipios.

Un ejemplo en .NET sería codificar con en IListlugar de Listo Dictionary, por lo que podría usar cualquier clase que se implemente IListindistintamente en su código:

// myList can be _any_ object that implements IList
public int GetListCount(IList myList)
{
    // Do anything that IList supports
    return myList.Count();
}

Otro ejemplo de la Biblioteca de clase base (BCL) es la ProviderBaseclase abstracta: esto proporciona cierta infraestructura y, lo que es más importante, significa que todas las implementaciones de proveedores se pueden usar indistintamente si se codifica contra ella.

Oded
fuente
pero, ¿cómo puede un cliente interactuar con una interfaz y usar sus métodos vacíos?
never_had_a_name
1
El cliente no interactúa con la interfaz, pero a través de la interfaz :) Los objetos interactúan con otros objetos a través de métodos (mensajes) y una interfaz es una especie de lenguaje, cuando sabe que cierto objeto (persona) implementa (habla) inglés (IList) ), puede usarlo sin necesidad de saber más sobre ese objeto (que él también es italiano), porque no es necesario en ese contexto (si desea pedir ayuda no necesita saber que él también habla italiano si entiendes ingles).
Gabriel Ščerbák
Por cierto. En mi humilde opinión, el principio de sustitución de Liskov se trata de la semántica de la herencia y no tiene nada que ver con las interfaces, que también se pueden encontrar en idiomas sin herencia (Ir de Google).
Gabriel Ščerbák
5

Si tuviera que escribir una Clase de automóvil en la era de Combustión de automóviles, entonces hay una gran posibilidad de que implemente oilChange () como parte de esta Clase. Pero, cuando se introducen los autos eléctricos, estaría en problemas ya que no hay cambio de aceite involucrado para estos autos, ni implementación.

La solución al problema es tener una interfaz performMaintenance () en la clase Car y ocultar detalles dentro de la implementación adecuada. Cada tipo de automóvil proporcionaría su propia implementación para performMaintenance (). Como propietario de un automóvil, todo lo que tiene que hacer es realizar mantenimiento () y no preocuparse por adaptarse cuando hay un CAMBIO.

class MaintenanceSpecialist {
    public:
        virtual int performMaintenance() = 0;
};

class CombustionEnginedMaintenance : public MaintenanceSpecialist {
    int performMaintenance() { 
        printf("combustionEnginedMaintenance: We specialize in maintenance of Combustion engines \n");
        return 0;
    }
};

class ElectricMaintenance : public MaintenanceSpecialist {
    int performMaintenance() {
        printf("electricMaintenance: We specialize in maintenance of Electric Cars \n");
        return 0;
    }
};

class Car {
    public:
        MaintenanceSpecialist *mSpecialist;
        virtual int maintenance() {
            printf("Just wash the car \n");
            return 0;
        };
};

class GasolineCar : public Car {
    public: 
        GasolineCar() {
        mSpecialist = new CombustionEnginedMaintenance();
        }
        int maintenance() {
        mSpecialist->performMaintenance();
        return 0;
        }
};

class ElectricCar : public Car {
    public: 
        ElectricCar() {
             mSpecialist = new ElectricMaintenance();
        }

        int maintenance(){
            mSpecialist->performMaintenance();
            return 0;
        }
};

int _tmain(int argc, _TCHAR* argv[]) {

    Car *myCar; 

    myCar = new GasolineCar();
    myCar->maintenance(); /* I dont know what is involved in maintenance. But, I do know the maintenance has to be performed */


    myCar = new ElectricCar(); 
    myCar->maintenance(); 

    return 0;
}

Explicación adicional: usted es propietario de un automóvil que posee varios automóviles. Esculpe el servicio que desea externalizar. En nuestro caso, queremos externalizar el trabajo de mantenimiento de todos los automóviles.

  1. Identifica el contrato (interfaz) que es válido para todos sus automóviles y proveedores de servicios.
  2. Los proveedores de servicios cuentan con un mecanismo para proporcionar el servicio.
  3. No debe preocuparse por asociar el tipo de automóvil con el proveedor de servicios. Simplemente especifique cuándo desea programar el mantenimiento e invocarlo. La compañía de servicio apropiada debe participar y realizar el trabajo de mantenimiento.

    Enfoque alternativo.

  4. Identifica el trabajo (puede ser una nueva interfaz de interfaz) que se adapta a todos sus automóviles.
  5. Usted sale con un mecanismo para proporcionar el servicio. Básicamente vas a proporcionar la implementación.
  6. Invocas el trabajo y lo haces tú mismo. Aquí va a hacer el trabajo de mantenimiento apropiado.

    ¿Cuál es la desventaja del segundo enfoque? Es posible que no sea el experto en encontrar la mejor manera de realizar el mantenimiento. Su trabajo es conducir el automóvil y disfrutarlo. No estar en el negocio de mantenerlo.

    ¿Cuál es la desventaja del primer enfoque? Existe la sobrecarga de encontrar una empresa, etc. A menos que sea una empresa de alquiler de automóviles, puede que no valga la pena el esfuerzo.

Raghav Navada
fuente
4

Esta afirmación trata sobre el acoplamiento. Una razón potencial para usar la programación orientada a objetos es la reutilización. Entonces, por ejemplo, puede dividir su algoritmo entre dos objetos colaboradores A y B. Esto podría ser útil para la creación posterior de otro algoritmo, que podría reutilizar uno u otro de los dos objetos. Sin embargo, cuando esos objetos se comunican (envían mensajes - métodos de llamada), crean dependencias entre sí. Pero si desea usar uno sin el otro, debe especificar qué debe hacer algún otro objeto C para el objeto A si reemplazamos B. Esas descripciones se denominan interfaces. Esto permite que el objeto A se comunique sin cambios con diferentes objetos que dependen de la interfaz. La declaración que mencionó dice que si planea reutilizar alguna parte de un algoritmo (o más generalmente un programa), debe crear interfaces y confiar en ellas,

Gabriel Ščerbák
fuente
2

Como han dicho otros, significa que su código de llamada solo debe saber sobre un padre abstracto, NO sobre la clase de implementación real que hará el trabajo.

Lo que ayuda a entender esto es el POR QUÉ siempre debe programar en una interfaz. Hay muchas razones, pero dos de las más fáciles de explicar son

1) Prueba.

Digamos que tengo mi código de base de datos completo en una clase. Si mi programa conoce la clase concreta, solo puedo probar mi código ejecutándolo realmente en esa clase. Estoy usando -> para significar "habla con".

WorkerClass -> DALClass Sin embargo, agreguemos una interfaz a la mezcla.

WorkerClass -> IDAL -> DALClass.

Entonces, DALClass implementa la interfaz IDAL, y la clase trabajadora SOLO llama a través de esto.

Ahora, si queremos escribir pruebas para el código, podríamos crear una clase simple que simplemente actúe como una base de datos.

WorkerClass -> IDAL -> IFakeDAL.

2) reutilizar

Siguiendo el ejemplo anterior, digamos que queremos pasar de SQL Server (que utiliza nuestro DALClass concreto) a MonogoDB. Esto requeriría mucho trabajo, pero NO si lo hemos programado para una interfaz. En ese caso, simplemente escribimos la nueva clase DB y cambiamos (a través de la fábrica)

WorkerClass -> IDAL -> DALClass

a

WorkerClass -> IDAL -> MongoDBClass

Mathieson
fuente
1

Las interfaces describen las capacidades. cuando escriba código imperativo, hable sobre las capacidades que está utilizando, en lugar de tipos o clases específicos.

rektide
fuente