Programar una interfaz, no una implementacion.
Es posible que hayas escuchado la
frase "program to an interface, not an implementation", y te hayan
dicho que es esencial seguir este principio si se quiere ser un buen
programador orientado a objetos. ¿Pero qué significa exactamente? En esta publicación
de blog se tratará de explicar este concepto e ilustrarlo con un ejemplo.
En esencia, el propósito de la programación de
una interfaz es fomentar el acoplamiento flexible entre las clases, reduciendo
la dependencia de una clase sobre otra. Esto hace que sea más fácil modificar
el código en una clase sin afectar a las demás, y crea una mayor flexibilidad
en el sistema.
Programando una interfaz
Imagina que eres el gerente de
una empresa de software muy exitosa. Los gerentes de oficina a menudo tienen
sed y necesitan una taza de café. Sin embargo, como gerente, obviamente estás
demasiado ocupado como para hacer uno tú mismo, así que consigues que tu
secretaria te haga uno. En el código, esto podría expresarse como:
class OfficeManager{
public function getCoffee(){
//instanciando una secretaria
$secretary = new Secretary();
//Dile a la secretaria como hacer una taza de café.
$coffee = $secretary->getMug()
->fillWithHotWater()
->addCoffee();
//bebe el café
$this->drink($coffee);
}
}
Ahora, se puede obtener el café así:
$you = new OfficeManager();
$you->getCoffee();
Observando el código de arriba ¿Puede haber algo malo en él? ¿Qué tan
flexible es este enfoque? Veamos.
Imagine que necesita su café, y llama a la secretaria como lo hace
normalmente, pero espera, ¡la secretaria ha llamado y ha dicho que está enferma!
¿Qué se puede hacer? Podrías pedirle al siguiente empleado (llamémosle Eric)
que se encuentra frente a tu oficina y decirle que te traiga un café, pero
¿cómo se podría hacer? El método getCoffee no tiene forma de obtener un café
sin usar una secretaria. No importa cuánto quiere Eric hacerte un café, no
puede, porque el método anterior no es flexible en absoluto. Este es un
excelente ejemplo de programación para una implementación - está confiando en una
clase "concreta" para que el programa funcione e introduce
dependencias no deseadas entre los objetos.
Vamos a refactorizar el método getCoffee para aceptar un objeto empleado
como argumento:
class OfficeManager{
public function getCoffee($employee){
//Dile al empleado como hacer una taza de café.
$coffee = $employee->getMug()
->fillWithHotWater()
->addCoffee();
//bebe café.
$this->drink($coffee);
}
}
Esta es una solución más flexible. Ahora podemos decirle a Eric que prepare
un café:
$you = new OfficeManager();
$eric = new Eric();
$you->getCoffee($eric);
De hecho, si se siente particularmente
sediento, podría crear una gran variedad de empleados para que le traigan café:
$you = new OfficeManager();
$employees = array(
new Eric(),
new Jane(),
new George()
);
foreach($employees as $employee){
$you->getCoffee($employee);
}
Hasta ahora bien, pero hay otro aspecto de nuestro método getCoffee que es
menos que ideal. ¿Qué pasa si en el ejemplo anterior, Eric, Jane y George
hicieron café de diferentes maneras? ¿Qué pasa si Jane prefiere agregar café
antes del agua caliente? ¿Qué pasa si George siempre pone el café en su taza
azul favorita? En la actualidad, nuestro método getCoffee limita a los
empleados a una cierta forma de hacer café. Pero mientras obtenga el café, no
debería importar cómo esté hecho. Tampoco hay garantía de que todos los
empleados contengan los mismos métodos, por lo que el código anterior podría
romperse fácilmente.
Una cosa más: ¿qué pasa si compra
una nueva máquina de café para su oficina? Entonces, una clase completamente no
relacionada, CoffeeMachine, podría implementar esta funcionalidad.
Hay que hacer que este método sea
aún más flexible utilizando una interfaz. Una interfaz define un conjunto de
métodos públicos, un contrato, que todas las clases que implementen esa
interfaz deben contener.
Se comienza creando una interfaz
llamada CoffeeMaker. Esta interfaz contendrá un método público llamado
makeCoffee. Se garantiza que cualquier clase que implemente una interfaz
contenga los métodos definidos en esa interfaz. Cualquier clase que implemente
la interfaz de CoffeeMaker DEBE contener un método público llamado makeCoffee.
interface CoffeeMaker{
public function makeCoffee();
}
Tenga en cuenta que la interfaz
no define cómo debe implementarse el método makeCoffee, solo que debería haber
uno. Ahora definamos algunos empleados que implementan esta interfaz. Hagamos
que Eric implemente esta interfaz y, para asegurarnos de que tenemos más
empleados que nos preparen un café, creemos otro empleado llamado Jane, que
también implementa la interfaz de CoffeeMaker.
class Eric implements CoffeeMaker{
public function makeCoffee(){
$this->getMug();
$this->addCoffee();
$this->addHotWater();
$this->addMilk();
return $this->coffee;
}
}
class Jane implements CoffeeMaker{
public function makeCoffee(){
$this->getMug();
$this->addHotWater();
$this->addMilk();
$this->addCoffee();
return $this->coffee;
}
}
Como puede ver, Eric y Jane
tienen una función pública llamada makeCoffee. Si no lo hicieran, se produciría
un error antes de ejecutar cualquier código, porque DEBEN ajustarse a la
interfaz. Ahora habrá notado que en los ejemplos anteriores, Eric y Jane implementan
el método makeCoffee de forma diferente: Eric agrega el café primero, luego
agrega agua caliente y luego agrega leche. Jane, por otro lado, agrega primero
el agua caliente, luego la leche y luego el café. No importa cómo Eric o Jane
implementen el método makeCoffee, siempre y cuando lo implementen.
Ahora, hay que refactorizar el
método getCoffee de OfficeManager para lograr que los nuevos empleados hagan el
café. En lugar de tomar cualquier objeto como argumento, cambiaremos el método
getCoffee para aceptar un objeto de cualquier clase que implemente la interfaz
de CoffeeMaker. Podemos suponer con seguridad que cualquier empleado que
implemente esta interfaz tendrá un método público llamado makeCoffee.
class OfficeManager{
public function getCoffee(CoffeeMaker $employee){
$coffee = $employee->makeCoffee();
$this->drinkCoffee();
}
}
Entonces, ¿cómo se compara nuestra solución actual con el principio del
"programa una interfaz, no la implementación", y por qué es mejor de
lo que teníamos al principio?
Bueno, aquí hay una lista de ventajas que acabamos de incorporar a nuestro
código:
- Eliminada la dependencia de la clase "concreta" secretaria
- Mayor flexibilidad: ahora se puede usar cualquier clase que implemente nuestra interfaz
- Sin dependencia de cómo se implementa el método getCoffee: podemos cambiar la forma en que los empleados preparan el café sin afectar otros aspectos del programa
Ahora esta solución es mucho más flexible de lo que teníamos que empezar.
Favorecer la composición de objetos sobre la herencia de clase
Empecemos
con la definición: Herencia es cuando un objeto o clase se basa en otro
objeto o clase, usando la misma implementación o comportamiento. Esto es un
mecanismo para la reutilización de código para permitirnos extensiones
independientes del software original mediante clases públicas e interfaces.
Por
otra parte la composición quiere decir que tenemos una instancia de una clase
que contiene instancias de otras clases que implementan las funciones deseadas.
Es decir, estamos delegando las tareas que nos mandan a hacer a aquella pieza
de código que sabe hacerlas. El código que ejecuta esa tarea concreta está sólo
en esa pieza y todos delegan el ella para ejecutar dicha tarea. Por lo tanto
estamos reutilizando código de nuevo.
La
composición de objetos y la herencia son dos técnicas para reutilizar la
funcionalidad en sistemas orientados a objetos. En general, la composición
de objetos debería ser favorecida por sobre la herencia. Promueve clases
más pequeñas y enfocadas y jerarquías de herencia más pequeñas.
Troels
Knak-Nielsen escribió :
La herencia de clase es una mezcla de dos conceptos. La clase que se
extiende hereda la implementación de los padres (funciones / métodos) y hereda
el tipo de los padres. En un lenguaje estático, este último es bastante
importante, ya que no se pueden mezclar libremente tipos. Entonces, si
algún método espera un argumento de un tipo dado, puede usar la herencia para
satisfacer esto. En un lenguaje de tipado dinámico que no es un problema,
simplemente puede implementar el comportamiento esperado y eso es todo. Si
necesita un contrato más explícito, puede documentarlo o, dado que PHP tiene
una especie de punto medio en este asunto, puede usar una interfaz
estáticamente estátizada (por ejemplo, implementa Person, en lugar de extends
Person) para hacer lo mismo. Dado que PHP está tipado dinámicamente, esta
solución (un poco más detallada y restrictiva) es puramente opcional.
El otro uso de
la herencia de clase es reutilizar la implementación. Si su Persona de la
clase abstracta es extendida por una subclase Empleador, usted tendría acceso
al mismo código en el Empleador como lo hace en Persona. También puede
lograr la reutilización de código con la composición, pero requiere un poco más
de trabajo. El empleador tendría que implementar un contenedor que delegue
el control a una instancia de Persona en este caso. P.ej.:
clase
Persona {
function sayHello () {
echo "¡Hola, mundo!";
}
}
Empleador
de clase {
protegido $ persona;
function
__construct () {
$ this->
person = new Person ();
}
function
sayHello () {
$ this->
person-> sayHello ();
}
}
más bien que:
clase Persona {
function sayHello
() {
echo "¡Hola,
mundo!";
}
}
clase Empleador extiende Persona {}
Como
puede ver, hay un poco más de trabajo por hacer, y es por eso que las personas
a menudo usan la herencia en estos casos. El costo, sin embargo, es que la
relación Persona-Empleador ahora es inamovible; No se puede cambiar ni
interceptar en tiempo de ejecución. También está la cuestión de la
claridad. Si bien el código de composición es más detallado, también es
muy claro sobre lo que hace. Puedes mirar el código y saber lo que
hace. Con la versión de herencia, necesita ver la superclase para
descubrir qué hace. Algunas veces hay múltiples niveles de herencia, lo
que hace que rastree arriba y abajo de la cadena para averiguar exactamente qué
código está disponible en la clase concreta. Finalmente, está el problema
de la herencia múltiple. En PHP, no puedes. Solo tiene una
oportunidad para heredar, por lo que si quiere reutilizar el código de dos
lugares, bueno, no tiene suerte.
5 razones para usar composición sobre herencia en Java y OOP
En la
composición, una clase que desea utilizar la funcionalidad de una clase
existente no heredan, sino que contiene una referencia de esa clase en una
variable miembro, por eso la composición del nombre. Las relaciones de
herencia y composición también se conocen como relaciones IS-A y HAS-A. Debido
a la relación IS-A, se puede pasar una instancia de subclase a un método, que
acepta una instancia de superclase. Este es un tipo de Polimorfismo, que
se logra usando Herencia.
Una variable de referencia de superclase puede referirse a una instancia de subclase. Al usar la composición, no obtienes este comportamiento, pero aún ofrece mucho más para modificar el equilibrio de su lado.
1) Una razón para favorecer la composición sobre la herencia en Java es que Java no admite herencia múltiple. Dado que solo puede extender una clase en Java, pero si necesita múltiples funciones como, por ejemplo, para leer y escribir datos de caracteres en archivos, necesita la funcionalidad Lector y Escritor y tenerlos como miembros privados facilita su trabajo. Eso se llama composición. Si sigue la programación para la interfaz más que el principio de implementación, y usa el tipo de clase base como variable miembro, puede usar una implementación diferente de Reader y Writer en diferentes situaciones. No obtendrá esta flexibilidad mediante el uso de herencia, en caso de extender una clase, solo obtendrá instalaciones disponibles en tiempo de compilación.
2) La composición ofrece una mejor capacidad de prueba de una clase que la herencia. Si una clase está compuesta por otra clase, puede crear fácilmente un objeto simulado representando la clase compuesta por el bien de la prueba. La herencia no proporciona este lujo. Para probar la clase derivada, debes necesitar su súper clase. Dado que las pruebas unitarias son una de las cosas más importantes a considerar durante el desarrollo del software, especialmente en el desarrollo impulsado por pruebas, la composición gana sobre la herencia.
3) Muchos patrones de diseño orientados a objetos mencionados por Gang of Four en patrones de diseño clásicos intemporales: elementos de software reutilizable orientado a objetos, favorece la composición sobre la herencia. Ejemplos clásicos de esto es el patrón de diseño de estrategia , donde la composición y la delegación se utilizan para cambiar el comportamiento del contexto, sin tocar el código de contexto. Desde el contexto utiliza la composición para mantener la estrategia, en lugar de obtenerla a través de la herencia, es fácil proporcionar una nueva implementación de la Estrategia en tiempo de ejecución. Otro buen ejemplo del uso de la composición sobre la herencia es el patrón de diseño Decorador. En el patrón Decorator , no se amplía ninguna clase para agregar funcionalidad adicional, sino que conservamos una instancia de la clase que estamos decorando y delegamos la tarea original a esa clase después de hacer la decoración. Esta es una de las mayores pruebas de elegir la composición sobre la herencia, ya que estos patrones de diseño están bien probados y probados en diferentes escenarios y resisten la prueba del tiempo, manteniéndose a la altura.
4) Aunque tanto la Composición como la Herencia le permiten reutilizar el código, una de las desventajas de la herencia es que rompe la encapsulación. Si la subclase depende del comportamiento de la superclase para su funcionamiento, de repente se vuelve frágil. Cuando el comportamiento de la superclase cambia, la funcionalidad en la subclase puede romperse, sin ningún cambio en su parte. Un ejemplo de herencia que hace que el código sea frágil es el método add () y addAll () de HashSet . Supongamos, si addAll () de HashSet se implementa llamando a add ()método y escribe una subclase de HashSet, que cifra el contenido antes de insertarlo en HashSet. Dado que hay solo un método add (), que puede insertar objetos en HashSet, usted anula estos métodos y llama a su método encrypt () anulando add () . Esto también cubre automáticamente addAll () , porque addAll () se implementa mediante add () , se ve muy atractivo. Si miras de cerca verás que esta implementación es frágil, porque se basa en el comportamiento de la clase superior. Si la clase base quiere mejorar el rendimiento e implementa addAll () sin llamar al método add () , el siguiente ejemplo se romperá.
la clase pública EncryptedHashSet extiende HashSet {
Una variable de referencia de superclase puede referirse a una instancia de subclase. Al usar la composición, no obtienes este comportamiento, pero aún ofrece mucho más para modificar el equilibrio de su lado.
1) Una razón para favorecer la composición sobre la herencia en Java es que Java no admite herencia múltiple. Dado que solo puede extender una clase en Java, pero si necesita múltiples funciones como, por ejemplo, para leer y escribir datos de caracteres en archivos, necesita la funcionalidad Lector y Escritor y tenerlos como miembros privados facilita su trabajo. Eso se llama composición. Si sigue la programación para la interfaz más que el principio de implementación, y usa el tipo de clase base como variable miembro, puede usar una implementación diferente de Reader y Writer en diferentes situaciones. No obtendrá esta flexibilidad mediante el uso de herencia, en caso de extender una clase, solo obtendrá instalaciones disponibles en tiempo de compilación.
2) La composición ofrece una mejor capacidad de prueba de una clase que la herencia. Si una clase está compuesta por otra clase, puede crear fácilmente un objeto simulado representando la clase compuesta por el bien de la prueba. La herencia no proporciona este lujo. Para probar la clase derivada, debes necesitar su súper clase. Dado que las pruebas unitarias son una de las cosas más importantes a considerar durante el desarrollo del software, especialmente en el desarrollo impulsado por pruebas, la composición gana sobre la herencia.
3) Muchos patrones de diseño orientados a objetos mencionados por Gang of Four en patrones de diseño clásicos intemporales: elementos de software reutilizable orientado a objetos, favorece la composición sobre la herencia. Ejemplos clásicos de esto es el patrón de diseño de estrategia , donde la composición y la delegación se utilizan para cambiar el comportamiento del contexto, sin tocar el código de contexto. Desde el contexto utiliza la composición para mantener la estrategia, en lugar de obtenerla a través de la herencia, es fácil proporcionar una nueva implementación de la Estrategia en tiempo de ejecución. Otro buen ejemplo del uso de la composición sobre la herencia es el patrón de diseño Decorador. En el patrón Decorator , no se amplía ninguna clase para agregar funcionalidad adicional, sino que conservamos una instancia de la clase que estamos decorando y delegamos la tarea original a esa clase después de hacer la decoración. Esta es una de las mayores pruebas de elegir la composición sobre la herencia, ya que estos patrones de diseño están bien probados y probados en diferentes escenarios y resisten la prueba del tiempo, manteniéndose a la altura.
4) Aunque tanto la Composición como la Herencia le permiten reutilizar el código, una de las desventajas de la herencia es que rompe la encapsulación. Si la subclase depende del comportamiento de la superclase para su funcionamiento, de repente se vuelve frágil. Cuando el comportamiento de la superclase cambia, la funcionalidad en la subclase puede romperse, sin ningún cambio en su parte. Un ejemplo de herencia que hace que el código sea frágil es el método add () y addAll () de HashSet . Supongamos, si addAll () de HashSet se implementa llamando a add ()método y escribe una subclase de HashSet, que cifra el contenido antes de insertarlo en HashSet. Dado que hay solo un método add (), que puede insertar objetos en HashSet, usted anula estos métodos y llama a su método encrypt () anulando add () . Esto también cubre automáticamente addAll () , porque addAll () se implementa mediante add () , se ve muy atractivo. Si miras de cerca verás que esta implementación es frágil, porque se basa en el comportamiento de la clase superior. Si la clase base quiere mejorar el rendimiento e implementa addAll () sin llamar al método add () , el siguiente ejemplo se romperá.
la clase pública EncryptedHashSet extiende HashSet {
.....
public boolean add (Object o) {
return super . agregar (cifrar (o));
}
}
Si has usado Composición a favor de Herencia, no enfrentarás este problema y tu clase habría sido más robusta, porque ya no dependes del comportamiento de la clase superior. En su lugar, está utilizando el método de superclase para la parte de adición y se beneficiará con cualquier mejora en addAll () como se muestra en el siguiente ejemplo:
clase pública EncryptedHashSet implementa Set {
contenedor privado de HashSet;
public boolean add (Object o) {
contenedor de devolución . agregar (cifrar (o));
}
public boolean addAll (Collection c) {
return conatainer. agregar (cifrar (c));
}
.......
}
5.
Otra razón para favorecer la composición sobre la herencia es la
flexibilidad. Si usa composición, es lo suficientemente flexible como para
reemplazar la implementación de la clase compuesta con una versión mejor y
mejorada. Un ejemplo es el uso de la clase
Comparator, que proporciona una funcionalidad de comparación. Si
su objeto Container contiene un Comparador en lugar de extender un Comparador
particular para comparar, es más fácil cambiar la forma en que se realiza la
comparación estableciendo diferentes tipos de Comparador en instancia
compuesta, mientras que en caso de herencia solo puede tener un comportamiento
de comparación en tiempo de ejecución. No puedes cambiarlo en tiempo de
ejecución.
Hay
muchas más razones para favorecer la composición sobre la herencia, que
comenzará a descubrir una vez que comience a usar patrones de diseño. En
pocas palabras, favorecer la Composición da como resultado un código más
flexible y robusto que el uso de Herencia. Aunque hay algunos casos en los
que el uso de herencia tiene mucho sentido, como cuando existe una verdadera
relación padre-hijo, pero la mayoría de las veces tiene sentido favorecer la
composición sobre la herencia para la reutilización del código.
Grupo Python: Roberto Vilchez
Darwin Gonzalez
This comment has been removed by the author.
ReplyDeleteUn aspecto fundamental de las interfaces es separar la especificación de una clase (qué hace) de la implementación (cómo lo hace). Usar una u otra implementación puede dar lugar a diferentes rendimientos de un programa.
ReplyDeleteMuchos lenguajes de programación modernos no tienen una palabra clave llamada "interfaz". JavaScript no tiene una palabra clave de interfaz; Ruby no tiene una palabra clave de interfaz; Scala no tiene una palabra clave de interfaz; y así sucesivamente... Pero aún es posible programar una interfaz en todos esos idiomas.
La interfaz de la palabra clave de idioma se puede encontrar en los idiomas escritos como Java o C #. Permite al programador crear un tipo abstracto con múltiples implementaciones concretas.
Si se desea alcanzar el principio de diseño de la programación en una interfaz, es muy probable que use una construcción de lenguaje llamada interfaz si su lenguaje de programación lo tiene (por ejemplo, Java). Sin embargo, el uso de una palabra clave de interfaz no significa que también esté programando en una interfaz.
Cuando tenemos clases que deben implementar un comportamiento idéntico, el método que implementa en el comportamiento se puede llevar a una clase abstracta .Si una clase va a tener un mismo método pero con distintas implementaciones, estos métodos se pueden definir en una interfaz. En una interfaz todos los métodos son públicos y no se define nunca la implementación de los métodos. En una clase abstracta, se pueden tanto definir la implemetación de métodos como sólo el método a implementar, dejando la implementación a las clases que hereden de la clase abstracta.
Las interfaces nos ayudan a desencajar módulos entre sí, ya que si tenemos una interfaz que explica el comportamiento que el módulo espera para comunicarse con otros módulos, podremos crear una clase que lo implemente de modo que cumpla las condiciones. El módulo que describe la interfaz no tiene que saber nada sobre nuestro código y, sin embargo, nosotros podemos trabajar con él sin problemas. Un uso común de las interfaces es para separar una funcionalidad de su aplicación ya que una interfaz define una funcionalidad, que debería ser notable. Cada clase que implementa esta interfaz define las posibles ejecuciones. Teniendo siempre en mente que los métodos abstractos definen los mensajes que se pueden pasar a un objeto. Se puede usar las clases abstractas para reutilizar el código a través de la herencia y también para enviar mensajes que deben implementarse.
ReplyDeleteAdemás de lo ya mencionado se debe tener en cuenta que para los desarrolladores les será mucho más ventajoso escribir código que sea reutilizable en vez de invertir mas tiempo haciendo mantenimiento al código anterior, programar en una interfaz hace que escribir códigos que sean reutilizables resulte mucho más fácil. Dicho eso como su nombre lo indica programar una interfaz, no una implementación; la interfaz viene siendo la especificación de cómo se usa algo, por ejemplo un contrato, entonces si se hace a un lado eso se tienen la implementación que seria como se cumple ese contrato. No obstante es oportuno llegar a pensar que cual es el objeto de la herencia el las interfaces, pero su uso aquí se debe entender como un medio que permite clasificar comportamientos o rasgos que han sido mostrados por muchas clases no relacionadas de objetos, también podría decirse que el uso de la herencia en la programación de interfaces es como una forma de construcción del lenguaje. Para culminar las interfaces hoy en día también son aplicadas en los patrones de diseño y en las metodologías de desarrollo.
ReplyDeleteProgramar una interface en lugar que una implementación logra que nuestra aplicación sea más fácil de extender y nuestro código funcione con todas las implementaciones de esa interface, hasta con las que no han sido creadas. Una de las grandes ventajas que tienen las interfaces es que definen el prototipo de ciertos métodos, pero no se implementan, sino que sólo se especifican, como un contrato. Posteriormente, las clases que quieran hacer uso de ese interfaz, tendrán que implementarlo exactamente como está definido en el "contrato".
ReplyDeleteEs como programar interfaces en lugar de clases, por ejemplo en java haces que el código sea dependiente entre sí, pues para que corra una clase es necesario de otras más, lo que vuelve el código difícil de sostener. En cambio, sí se programan interfaces lo que se hace es separar la especificación de una clase (qué hace) de la implementación (cómo lo hace). Esto se ha comprobado que da lugar a programas más robustos y con menos errores.
This comment has been removed by the author.
ReplyDeleteDe acuerdo con lo que se ha mencionado anteriormente, considero que la frase *programar una interfaz, no una implementación*, como lo indica su nombre intenta demostrarnos de cierta forma, que en algunos casos resulta más cómodo programar una interfaz, ya que ésta permite que se realicen modificaciones sin afectar el código, establece conexiones entre los módulos, creando cierta flexibilidad sin que se haya creado una dependencia entre los mismos. Programar una interfaz en vez de una implementación te proporciona una aplicación más flexible, permitiendo al código funcionar con las implementaciones de todos sus miembros. Mientras que la implementación que está ligada fuertemente a la herencia (generando extensiones que heredan los métodos o funciones de las clases padre). Cabe destacar que éstas a diferencia de las interfaces se encargan de mostrar los detalles de lo que hace cada método.
ReplyDeleteA cualquiera persona luego de leer esta información posteada se puede preguntar ¿Qué interés tiene implementar una interface del API si no nos proporciona código ninguno? Lo cierto es que una interface puede verse en relación a la programación como una norma urbanística en una ciudad por ejemplo. Si lees la documentación de la interfaz, aunque no proporciona código, sí proporciona instrucciones respecto a características comunes para las clases que la implementen y define qué métodos han de incluirse para cumplir con la interfaz y para qué servirán esos métodos. Si implementamos la interface, lo que hacemos es ajustarnos a la norma. Y si todos los programadores se ajustan a la misma norma, cuando un programador tiene que continuar un programa iniciado por otro no tiene que preguntarse: ¿qué método podré usar para comparar varios objetos de este tipo y ponerlos en orden? Comúnmente por ejemplo los programadores se ciñen a lo establecido por el API de Java: para comparar varios objetos y ponerlos en orden (“orden natural”) se implementa la interfaz Comparable y su método compareTo(). Y además con esto se puede saber qué tipo ha de devolver ese método y cómo ha de funcionar, porque así lo indica la documentación de la interface.
ReplyDeleteSe podría decir que si una interfaz define un tipo (al igual que una clase define un tipo) pero ese tipo no provee de ningún método podemos preguntarnos: ¿qué se gana con las interfaces? La implementación (herencia) de una interfaz no podemos decir que evite la duplicidad de código o que favorezca la reutilización de código puesto que realmente no provee código. En cambio sí podemos decir que reúne las otras dos ventajas de la herencia: favorecer el mantenimiento y la extensión de las aplicaciones. ¿Por qué? Porque al definir interfaces permitimos la existencia de variables polimórficas y la invocación polimórfica de métodos.
Como ya lo han dado entender ustedes por medio de este blog programar una interface en lugar que una implementación, por medio de esta se logra que la aplicación que estemos desarrollando sea más fácil de extender y además de esto que nuestro código funcione con todas las implementaciones de esa interface. Como hemos ido aprendiendo durante el tiempo que se lleva programando sabemos que interfaces proporcionan un mecanismo para abstraer los métodos a un nivel superior, lo que permite simular la herencia múltiple de otros lenguajes, ya que hay lenguajes que no soportan la herencia múltiple como es el caso de JAVA, que una forma de lograr esto es por medio de la interfaces. Mediante el ejemplo que dieron a conocer hubo una parte que dio cierta curiosidad, un pequeño fragmento “No importa cómo Eric o Jane implementen el método makeCoffee, siempre y cuando lo implementen” el cual me dio a entender que no importa cómo se hagan las cosas, lo importante es que se realicen y lleven al mismo resultado, en otras palabras, si se programa las interfaces lo que hace es establecer contratos entre los módulos por así decirlo, no nos interesa el cómo se hacen las cosas, sino nada más nos interesa saber el qué de las cosas. Desde mi punto de vista es mejor programar Interfaces porque en cualquier momento podemos cambiar la implementación sin afectar nuestro código.
ReplyDeleteLa pregunta que la mayoría al empezar a leer de este tema se podría hacer es que "Si una interfaz define un tipo (al igual que una clase define un tipo) pero ese tipo no provee de ningún método podemos preguntarnos: ¿qué se gana con las interfaces en Java?
ReplyDeleteLa implementación (herencia) de una interfaz no podemos decir que evite la duplicidad de código o que favorezca la reutilización de código puesto que realmente no provee código.
En cambio sí podemos decir que reúne las otras dos ventajas de la herencia: favorecer el mantenimiento y la extensión de las aplicaciones. ¿Por qué? Porque al definir interfaces permitimos la existencia de variables polimórficas y la invocación polimórfica de métodos. Por ejemplo los arboles como, vehículos y personas son de tipo Actor, de modo que podemos generar código que haga un tratamiento en común de todo lo que son actores. En otras palabras, podemos necesitar una lista de Actores.
Podemos declarar una variable como de tipo Actor (aunque no puedan existir instancias de Actor) que permita referenciar alternativamente a objetos de las distintas subclases de la interfaz.
Un aspecto fundamental de las interfaces en Java es hacer lo que ya hemos dicho que hace una interfaz de forma genérica: separar la especificación de una clase (qué hace) de la implementación (cómo lo hace).
Esto se ha comprobado que da lugar a programas más robustos y con menos errores
Las interfaces se crean en situaciones en las que sabemos que debe hacer alguna tarea, pero como debe hacerse puede variar. En otras palabras, podemos decir que implementamos interfaces para que nuestra clase comience a comportarse de manera particular. Por otro lado podemos decir que Hay dos formas de reutilizar el código, mediante la composición y mediante la herencia. La composición significa utilizar objetos dentro de otros objetos. Por ejemplo, un applet es un objeto que contiene en su interior otros objetos como botones, etiquetas, etc. Cada uno de los controles está descrito por una clase. Y no me queda mucho más que añadir una conclusión que no deja de ser una opinión personal La herencia, no es mala de hecho puede ser la solución que necesitas en un momento concreto.
ReplyDeleteEl problema es que es muy malo es aplicar mal herencia. Y como en la mayor parte de los sistemas que desarrollamos por su naturaleza de cambio, es muy fácil caer en aplicarla mal. Es muy probable que se vuelva contra ti y los beneficios no sean nada comparados con sus problemas a futuro.
En lenguajes que no soportan herencia múltiple, puede resultar ventajoso la composición.
ReplyDeleteLa composición genera un diseño más desacoplado, que puede ayudar a hacer el testing (o TDD) mas fácil. Se podría decir que conviene favorecer la composición por sobre la herencia, sin embargo esta respuesta no es definitiva, cada uno tiene que identificar la necesidades y el dominio en el cual está trabajando para identificar en casos conviene usar cada metodología.
El diseño de interfaces se hace bastante vistoso cuando nos permite modular mas nuestra aplicacion o desarrollo, haciendo interfaces se separa lo que es el codigo del diseño de la aplicacion, permitiendo asi, tener un programa mas entendible(modularidad) y mas facil de mantener(manejo de errores), simplemente cuando se tiene un error, se busca el modulo correspondiente a ese error y se soluciona o cuando se quiera modificar un segmento del programa se trabaja en el sin afectar a los demas, no es lo mismo tener varios pedazos de codigo que tener uno solo muy grande que sea muy dificil de manipular.
ReplyDeleteAgregando a lo que comento pedro, teniendo una clase padre(implementacion) o diseño, se puede heredar y obtener funciones cuando se necesiten, pero opino que la herencia no es tan dificil de manejar ya que si se tiene un diseño de clases, se podria entender facilmente que es lo que se tiene que heredar o hacer en nuestro desarrollo, es por ello que se debe diseñar antes de desarrollar para que no ocurran estos problemas de no saber a donde vamos dirigidos en la aplicación que estemos realizando, modelar el sistema otorga una vista bastante jugosa y hace que no nos perdamos en lo que debemos hacer, como lo debemos hacer y cuando lo debemos hacer.
Apoyo la metodología de diseñar primero todo el sistema y aplicar las buenas practicas de programación para que nuestros desarrollos sean bastante robustos, seamos reconocidos por como lo hacemos.
La composición es bastante fácil de entender y podemos ver la composición en la vida cotidiana, una mesa tiene patas, una pared está compuesta de bloques, etc., es decir, hacer un total de las partes. La composición tiene ventaja al momento de descomponer un problema complejo en soluciones por módulo, debido a que se maneja en términos de partes y componente, algo que resulta un poco más complicado con la herencia por la abstracción que esta presenta que va más allá de simplemente “reutilizar código” y que se vuelve problemático cuando pasa de ser simple a múltiple. Con esto no quiero decir que la herencia no debería utilizarse ya que en el desarrollo de software hay compensaciones y la preferencia por la composición no es una cuestión de si es mejor o no, sino de lo que es más apropiado para las necesidades que se tengan en un contexto específico.
ReplyDeleteLa interfaz de principio de diseño se refiere a perder acoplamiento entre módulos o sistemas. Cada vez que construyas una pieza más grande de software, tendrás dependencias con otros sistemas. La interfaz de la palabra clave de idioma se puede encontrar en los idiomas escritos como Java o C #. Permite al programador crear un tipo abstracto con múltiples implementaciones concretas., le permite el acoplamiento correcto entre las clases del sistema que esta desarrollando. Uno de los objetivos que buscamos cuando programamos es reutilizar métodos y funcionalidades, para lograr una mayor mantenibilidad. Dentro de los lenguajes convencionales orientados a objetos existen varias formas de hacer esto, las dos más conocidas son: herencia de clases y composición. Podría decir que conviene favorecer la composición por sobre la herencia, sin embargo esta respuesta no es definitiva, cada uno tiene que identificar la necesidades y el dominio en el cual está trabajando para identificar en casos conviene usar cada metodología.
ReplyDeleteLas interfaz en términos sencillos sería entonces la distinción de cómo se emplea algo. Estas en la actualidad son bastante usadas tanto en las metodologías de desarrollo como en los patrones de diseño. Al buscar un propósito esencial con respecto a la programación de una interfaz es importante decir que este hace referencia a promover el ensamblaje flexible entre las distintas clases, con esto se tiene como resultado la reducción de lo que se conoce dependencia de una clase hacia otra. Entonces hace referencia a que se torna más cómoda la codificación en dicha clase, lo que no permite que se vean perturbadas las demás, obteniendo una gran flexibilidad con respecto al sistema. Por otro lado con respecto a la vistosidad del diseño de interfaz esta se hace considerablemente atractiva cuando nos deja modular nuestro desarrollo; aplicando interfaces se aísla lo que es el código con el diseño de dicho desarrollo lo cual nos genera lo que denominamos modularidad que no es más que el tener un programa más entendible y facil de mantener en cuanto a los errores se refiere.
ReplyDeleteNo se puede dejar de mencionar la ventaja de tener un código reutilizable para los desarrolladores lo cual les permite administrar y optimizar su tiempo lo que es genial.
Como muchos comentan, la frase “program to an interface, not an implementation” es muy favorable porque al escribir código que sea reutilizable en vez de invertir mas tiempo haciendo mantenimiento es un beneficio grande. Aun asi, El término "programación en una interfaz" está abierto a mucha interpretación. La interfaz en el desarrollo de software es una palabra muy común. Hay desarrolladores que plantean que la Programación a una interfaz significa cuando sea posible, uno debe referirse a un nivel más abstracto de una clase (una interfaz, clase abstracta, o a veces una clase de superclase), en lugar de referirse a una implementación concreta. Sin embargo, esto no es correcto, bueno, o al menos, no es del todo correcto. El punto más importante proviene de una perspectiva de diseño de programa. Aquí, "programar en una interfaz" significa enfocar su diseño en lo que está haciendo el código, no cómo lo hace. Esta es una distinción vital que impulsa su diseño hacia la corrección y la flexibilidad. Ejemplo, supongamos que tengo una clase (clase A) que usa la funcionalidad de la clase abstracta B. Las clases C y D heredan la clase B: proporcionan una implementación concreta para lo que clase se dice que hacer. Si la clase A usa directamente la clase C o D, se llama 'programación para una implementación', que no es una solución muy flexible, y es lo que trata el tema de este blog. Pero si la clase A usa una referencia a la clase B, que luego puede configurarse para la implementación C o D, hace que las cosas sean más flexibles y mantenibles.
ReplyDeleteCuando somos nuevos programadores siempre nuestro enfoque esta basado hacia ciertas partes del programa que son dificiles de desarrollar por su implementacion, puesto que nos enfocamos solamente en lo que es el fin del programa mas no en su funcionamiento practico. El uso de interfaces permita que el desarrollo modular sea mas efectivo, en cuanto a que al usar interfaces que no agregan codigo a nuestro programa pero que si le da funcionalidad. Tambien se denota la importancia en el manejo de las clases, esta es una parte que pudiera convertirse en algo engorroso cuando no sabemos hacerlo, y es lo que comunmente pasa cuando somos inexpertos. El manejo de errores resulta mas efectivo al utilizar este tipo de enfoque, puesto que los programas se hacen mas entendibles y manejables en ese aspecto. Aunado a todo lo anterior se destaca que el tiempo que se ahorra con estos modelos de desarrollo permite que haya un espacio de tiempo ideal para enfoque hacia el diseño de vistas, haciendo del producto final mas atractivo a los usuarios finales.
ReplyDeleteUna interface es similar a una clase o a una estructura, pero sus miembros son abstractos, es decir, no están definidos, no tienen código. Declara modos de comportamiento, pero deja su definición para las clases que la implementen.
ReplyDeleteCuando una clase implementa una interface, debe implementar todos los métodos de la interface. Hay una gran diferencia entre heredar de una clase abstracta e implementar una interface. Una interface puede heredar cero o más interfaces esto es herencia múltiple de interfaces. A tales interfaces se les llama “interfaces base explícitas”. Además de que una interface no sólo hereda las interface que explícitamente indica, sino también aquéllas heredadas implícitamente, es decir, aquéllas que han heredado las interface de las que hereda.