patterns pattern patrones first diseño book oop design-patterns interface software-design ooad

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



head first design patterns (7)

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

Codificando 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 de tipo Interfaz que cualquier clase de candidato de fábrica debe haber implementado. De esta forma, el programa del cliente no se preocupa por la implementación y la firma de la interfaz determina qué operaciones se pueden realizar. Esto se puede usar para cambiar el comportamiento de un programa en tiempo de ejecución. También te 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 http://ruchitsurati.net/myfiles/interface.png

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

EDITAR

Actualicé el ejemplo anterior y agregué una clase base abstracta de altavoces. En esta actualización, agregué una función a todos los Spakers para "SayHello". Todos los oradores hablan "Hola mundo". Entonces esa es una característica común con funciones similares. Consulte el diagrama de clases y verá que la clase abstracta de altavoces implementa la interfaz ISpeaker y marca el Speak () como resumen, lo que significa que cada implementación de Speaker es responsable de implementar el método Speak, ya que varía de Speaker a Speaker. Pero todos los oradores dicen "Hola" por unanimidad. Entonces, en la clase de Orador, definimos un método que dice "Hola mundo" y cada implementación del Orador derivará el método SayHello.

Considere un caso en el que SpanishSpeaker no puede decir Hello, por lo que en ese caso puede anular el método SayHello para el hablante de español y presentar una excepción adecuada.

Tenga en cuenta que no hemos realizado cambios en Interface ISpeaker. Y el código de cliente y SpeakerFactory tampoco se verán afectados. Y esto es lo que conseguimos mediante Programming-to-Interface .

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

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 http://demo.ruchitsurati.net/myfiles/interface1.png

Uno se tropieza con esta frase cuando lee sobre patrones de diseño.

Pero no lo entiendo, ¿podría alguien explicar esto por mí?


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

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

1) Prueba.

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

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

WorkerClass -> IDAL -> DALClass.

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

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

WorkerClass -> IDAL -> IFakeDAL.

2) Reutilización

Siguiendo el ejemplo anterior, digamos que queremos pasar de SQL Server (que nuestra DALClass concreta usa) a MonogoDB. Esto requeriría un gran trabajo, pero NO si lo hemos programado en una interfaz. En ese caso, solo escribimos la nueva clase DB y cambiamos (a través de la fábrica)

WorkerClass -> IDAL -> DALClass

a

WorkerClass -> IDAL -> MongoDBClass


Esta declaración es sobre el acoplamiento. Una posible razón para usar programación orientada a objetos es la reutilización. Entonces, por ejemplo, puede dividir su algoritmo entre dos objetos colaborativos A y B. Esto podría ser útil para la posterior creación de otro algoritmo, que podría reutilizar uno u otro de los dos objetos. Sin embargo, cuando esos objetos se comunican (enviar mensajes, métodos de llamada), crean dependencias entre ellos. Pero si desea usar uno sin el otro, debe especificar qué debería hacer algún otro objeto C para el objeto A si reemplazamos B. Esas descripciones se llaman interfaces. Esto permite que el objeto A se comunique sin cambios con diferentes objetos que dependen de la interfaz. La afirmación que mencionaste dice que si planeas reutilizar alguna parte de un algoritmo (o más generalmente un programa), debes crear interfaces y confiar en ellas, por lo que puedes cambiar la implementación concreta en cualquier momento sin cambiar otros objetos si usas el interfaz declarada.


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

Las implementaciones son los comportamientos reales. Digamos, por ejemplo, que tienes un método sort (). Puede implementar QuickSort o MergeSort. Eso no debería importar al código de llamada del cliente, siempre y cuando 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 proporcionados. 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 en contra de la implementación, tan pronto como cambie su código deja de funcionar. Así que piense en los beneficios de la interfaz de esta manera:

  1. oculta las cosas que no necesita saber para simplificar el uso del objeto.
  2. proporciona el contrato de cómo se comportará el objeto para que pueda depender de eso

Si tuviera que escribir una Clase de Automóviles en la era del Coche de Combustión, entonces hay una gran posibilidad de que implemente OilChange () como parte de esta Clase. Pero, cuando se introducen los autos eléctricos, estarías en problemas ya que no hay cambios de aceite para estos autos, y no hay 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 coche proporcionaría su propia implementación para performMaintenance (). Como propietario de un automóvil, todo lo que tiene que hacer es ejecutar el mantenimiento () y no preocuparse por la adaptación cuando haya 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. Usted crea el servicio que desea tercerizar. 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 salen con un mecanismo para proporcionar el servicio.
  3. No desea preocuparse por asociar el tipo de automóvil con el proveedor de servicios. Solo debe especificar cuándo desea programar el mantenimiento e invocarlo. La empresa de servicios apropiada debe intervenir y realizar el trabajo de mantenimiento.

    Enfoque alternativo.

  4. Identifica el trabajo (puede ser una nueva Interfaz de interfaz) que vale para todos sus automóviles.
  5. Usted sale con un mecanismo para proporcionar el servicio. Básicamente, vas a proporcionar la implementación.
  6. Usted invoca el trabajo y hágalo usted mismo. Aquí va a hacer el trabajo de un trabajo de mantenimiento apropiado.

    ¿Cuál es el inconveniente del segundo enfoque? Puede que no sea el experto en encontrar la mejor forma de realizar el mantenimiento. Tu trabajo es conducir el auto y disfrutarlo. No estar en el negocio de mantenerlo.

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


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 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 están operando en el objeto.

Este es un subconjunto del Liskov Substitution Principle (LSP), el L de los principios SOLID .

Un ejemplo en .NET sería codificar con IList lugar de List o Dictionary , por lo que podría usar cualquier clase que implemente IList indistintamente 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 clases base (BCL) es la clase abstracta ProviderBase : proporciona algo de infraestructura, y lo que es más importante significa que todas las implementaciones de proveedores se pueden usar indistintamente si se codifica en contra de ella.


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