Uno se topa con esta frase cuando lee sobre patrones de diseño.
Pero no lo entiendo, ¿podría alguien explicarme esto?
Uno se topa con esta frase cuando lee sobre patrones de diseño.
Pero no lo entiendo, ¿podría alguien explicarme esto?
Respuestas:
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
}
Este es solo un ejemplo básico y la explicación real del principio está más allá del alcance de esta respuesta.
He actualizado el ejemplo anterior y agregué una Speaker
clase 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 Speaker
la ISpeaker
interfaz 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 Speaker
a 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 Speaker
implementación derivará el SayHello()
método.
Considere un caso en el SpanishSpeaker
que 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
}
List
como tipo, aún podría estar asumiendo que el acceso aleatorio es rápido al llamar repetidamente get(i)
.
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:
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 SOLID
principios.
Un ejemplo en .NET sería codificar con en IList
lugar de List
o Dictionary
, por lo que podría usar cualquier clase que se 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 clase base (BCL) es la ProviderBase
clase 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.
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.
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.
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.
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,
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