En el lenguaje Java, la clase abstracta y la interfaz son dos mecanismos que admiten la definición de clases abstractas. Es precisamente debido a la existencia de estos dos mecanismos que Java obtiene poderosas capacidades orientadas a objetos. Existen grandes similitudes entre las clases abstractas y las interfaces en términos de soporte para la definición de clases abstractas, e incluso pueden reemplazarse entre sí. Por lo tanto, muchos desarrolladores parecen ser más informales a la hora de elegir la clase abstracta y la interfaz al definir clases abstractas. De hecho, todavía existe una gran diferencia entre los dos. Su elección refleja incluso la comprensión de la naturaleza del dominio del problema y si la comprensión de la intención del diseño es correcta y razonable. Este artículo analizará las diferencias entre ellos e intentará proporcionar a los desarrolladores una base para elegir entre los dos.
Comprender las clases abstractas
La clase abstracta y la interfaz se utilizan para definir clases abstractas en el lenguaje Java (la clase abstracta en este artículo no se traduce de una clase abstracta, representa un cuerpo abstracto y la clase abstracta se usa para definir clases abstractas en el lenguaje Java) A método, preste atención a la distinción), entonces, ¿qué es una clase abstracta y qué beneficios puede traernos el uso de clases abstractas?
En el concepto orientado a objetos, sabemos que todos los objetos están representados por clases, pero lo contrario no es cierto. No todas las clases se utilizan para describir objetos. Si una clase no contiene suficiente información para describir un objeto específico, dicha clase es una clase abstracta. Las clases abstractas se utilizan a menudo para representar conceptos abstractos que derivamos del análisis y diseño de áreas problemáticas. Son abstracciones de una serie de conceptos específicos que parecen diferentes pero que son esencialmente iguales. Por ejemplo: si desarrollamos un software de edición de gráficos, encontraremos que hay algunos conceptos específicos como círculos y triángulos en el dominio del problema. Son diferentes, pero todos pertenecen al concepto de forma. en el dominio del problema, si existe, es un concepto abstracto. Precisamente porque los conceptos abstractos no tienen conceptos concretos correspondientes en el dominio del problema, no se pueden crear instancias de las clases abstractas utilizadas para representar conceptos abstractos.
En el campo orientado a objetos, las clases abstractas se utilizan principalmente para ocultar tipos. Podemos construir una descripción abstracta de un conjunto fijo de comportamientos, pero este conjunto de comportamientos puede tener cualquier cantidad de implementaciones concretas posibles. Esta descripción abstracta es la clase abstracta, y este conjunto de posibles implementaciones concretas está representado por todas las clases derivadas posibles. Los módulos pueden operar sobre un cuerpo abstracto. Debido a que un módulo se basa en una abstracción fija, no se puede modificar al mismo tiempo; el comportamiento de este módulo también se puede ampliar derivando de esta abstracción; Los lectores que estén familiarizados con OCP deben saber que para implementar OCP (principio abierto-cerrado), uno de los principios básicos del diseño orientado a objetos, las clases abstractas son la clave.
Mirando la clase abstracta y la interfaz desde el nivel de definición gramatical
A nivel gramatical, el lenguaje Java proporciona diferentes métodos de definición para clases e interfaces abstractas. El siguiente es un ejemplo de cómo definir una clase abstracta denominada Demo para ilustrar esta diferencia.
La forma de definir la clase abstracta Demo usando una clase abstracta es la siguiente:
demostración de clase abstracta {
método vacío abstracto1();
método vacío abstracto2();
…
}
La forma de definir la clase abstracta Demo usando la interfaz es la siguiente:
demostración de interfaz {
método nulo1();
método vacío2();
…
}
En el método de clase abstracta, Demo puede tener sus propios miembros de datos o métodos de miembros no abstractos. En el método de interfaz, Demo solo puede tener miembros de datos estáticos que no se pueden modificar (es decir, deben ser datos finales estáticos). los miembros generalmente no están definidos en la interfaz) y todos los métodos de los miembros son abstractos. En cierto sentido, la interfaz es una forma especial de clase abstracta.
Desde una perspectiva de programación, tanto la clase abstracta como la interfaz se pueden utilizar para implementar la idea de "diseño por contrato". Sin embargo, todavía existen algunas diferencias en el uso específico.
En primer lugar, la clase abstracta representa una relación de herencia en el lenguaje Java, y una clase solo puede usar la relación de herencia una vez (porque Java no admite herencia múltiple - nota de transferencia). Sin embargo, una clase puede implementar múltiples interfaces. Quizás esta sea una consideración de compromiso por parte de los diseñadores del lenguaje Java al considerar el soporte de Java para la herencia múltiple.
En segundo lugar, en la definición de clase abstracta, podemos asignar el comportamiento predeterminado del método. Pero en la definición de interfaz, los métodos no pueden tener un comportamiento predeterminado. Para eludir esta restricción, se deben usar delegados, pero esto agregará cierta complejidad y, a veces, causará muchos problemas.
Hay otro problema grave al no poder definir el comportamiento predeterminado en una clase abstracta, y es que puede causar problemas de mantenimiento. Porque si luego desea modificar la interfaz de la clase (generalmente representada por una clase o interfaz abstracta) para adaptarla a nuevas situaciones (por ejemplo, agregar nuevos métodos o agregar nuevos parámetros a métodos ya utilizados), será muy problemático. puede llevar mucho tiempo (especialmente si hay muchas clases derivadas). Pero si la interfaz se implementa a través de una clase abstracta, es posible que solo necesite modificar el comportamiento predeterminado definido en la clase abstracta.
De manera similar, si el comportamiento predeterminado no se puede definir en una clase abstracta, la misma implementación del método aparecerá en cada clase derivada de la clase abstracta, violando el principio de "una regla, un lugar", lo que resultará en la duplicación de código, lo que también es perjudicial para el futuro. Por tanto, tenga mucho cuidado al elegir entre clase abstracta e interfaz.
Mirando la clase abstracta y la interfaz desde el nivel del concepto de diseño
Lo anterior analiza principalmente la diferencia entre clase abstracta e interfaz desde la perspectiva de la definición gramatical y la programación. Las diferencias en estos niveles son de nivel relativamente bajo y no esenciales. Esta sección analizará la diferencia entre clase abstracta e interfaz desde otro nivel: los conceptos de diseño reflejados en las dos. El autor cree que sólo analizando desde este nivel podemos comprender la esencia de los dos conceptos.
Como se mencionó anteriormente, la clase abstracta encarna una relación de herencia en el lenguaje Java. Para que la relación de herencia sea razonable, debe existir una relación "es-a" entre la clase principal y la clase derivada, es decir, la clase principal y la clase derivada. la clase derivada tiene el mismo concepto. Esencialmente debería ser el mismo. Este no es el caso de las interfaces. No se requiere que el implementador de la interfaz y la definición de la interfaz sean conceptualmente consistentes, sino que solo implementen el contrato definido por la interfaz. Para que la discusión sea más fácil de entender, a continuación se ilustrará un ejemplo sencillo.
Considere un ejemplo de este tipo. Supongamos que hay un concepto abstracto sobre la puerta en nuestro dominio del problema. La puerta tiene dos acciones: abrir y cerrar. En este momento, podemos definir un tipo que represente el concepto abstracto a través de una clase o interfaz abstracta. Los métodos son los siguientes:
Utilice una clase abstracta para definir la puerta:
puerta de clase abstracta {
vacío abstracto abierto();
cierre vacío abstracto();
}
Definir puerta usando el método de interfaz:
puerta de interfaz {
apertura nula();
cierre vacío();
}
Otros tipos de puerta específicos pueden ampliar la puerta definida mediante el método de clase abstracta o implementar la puerta definida mediante el método de interfaz. Parece que no hay gran diferencia entre usar una clase abstracta y una interfaz.
Ahora se requiere que la puerta tenga una función de alarma. ¿Cómo deberíamos diseñar la estructura de clases para este ejemplo (en este ejemplo, es principalmente para mostrar la diferencia en los conceptos de diseño entre clase abstracta e interfaz, y otras cuestiones irrelevantes se han simplificado o ignorado)? Las posibles soluciones se enumeran a continuación y estas diferentes opciones se analizan desde un nivel de concepto de diseño.
Solución uno:
Simplemente agregue un método de alarma a la definición de Puerta, de la siguiente manera:
puerta de clase abstracta {
vacío abstracto abierto();
cierre vacío abstracto();
alarma nula abstracta();
}
o
puerta de interfaz {
apertura nula();
cierre vacío();
anular alarma();
}
Entonces la AlarmDoor con función de alarma se define de la siguiente manera:
clase AlarmDoor extiende la puerta {
apertura nula(){…}
cierre vacío(){…}
anular alarma(){…}
}
o
clase AlarmDoor implementa Puerta{
apertura nula(){…}
cierre vacío(){…}
anular alarma(){…}
}
Este método viola el ISP (Principio de segregación de interfaz), un principio central en el diseño orientado a objetos. En la definición de Puerta, el método de comportamiento inherente del concepto de Puerta en sí se mezcla con el método de comportamiento de otro concepto de "alarma". Un problema causado por esto es que los módulos que solo se basan en el concepto de Puerta cambiarán debido a cambios en el concepto de "alarma" (por ejemplo, modificando los parámetros del método de alarma), y viceversa.
Solución dos:
Dado que abrir, cerrar y alarma pertenecen a dos conceptos diferentes, según el principio ISP, deben definirse en clases abstractas que representen estos dos conceptos. Los métodos de definición son: ambos conceptos se definen mediante un método de clase abstracta; ambos conceptos se definen mediante un método de interfaz; un concepto se define mediante un método de clase abstracta y el otro concepto se define mediante un método de interfaz.
Obviamente, dado que el lenguaje Java no admite herencia múltiple, no es factible definir ambos conceptos utilizando clases abstractas. Los dos últimos métodos son factibles, pero su elección refleja la comprensión de la esencia del concepto en el dominio del problema y si el reflejo de la intención del diseño es correcto y razonable. Analicemos y expliquemos uno por uno.
Si ambos conceptos se definen utilizando el método de interfaz, entonces refleja dos problemas: 1. Es posible que no comprendamos claramente el dominio del problema. ¿AlarmDoor es esencialmente una puerta o una alarma? 2. Si no hay ningún problema con nuestra comprensión del dominio del problema, por ejemplo: a través del análisis del dominio del problema, encontramos que AlarmDoor es conceptualmente consistente con Door, entonces no podremos revelar correctamente nuestra intención de diseño al implementarlo. , porque Las definiciones de estos dos conceptos (ambos definidos mediante el método de interfaz) no reflejan el significado anterior.
Si nuestra comprensión del dominio del problema es: AlarmDoor es conceptualmente esencialmente una puerta y también tiene la función de alarmar. ¿Cómo deberíamos diseñarlo e implementarlo para reflejar claramente nuestro significado? Como se mencionó anteriormente, la clase abstracta representa una relación de herencia en el lenguaje Java, y la relación de herencia es esencialmente una relación "es-a". Entonces, para el concepto de Puerta, deberíamos usar el método de clase abstracta para definirlo. Además, AlarmDoor tiene una función de alarma, lo que significa que puede completar el comportamiento definido en el concepto de alarma, por lo que el concepto de alarma se puede definir a través de la interfaz. Como se muestra a continuación:
puerta de clase abstracta {
vacío abstracto abierto();
cierre vacío abstracto();
}
interfazAlarma{
anular alarma();
}
clase Alarma La puerta extiende La puerta implementa la alarma{
apertura nula(){…}
cierre vacío(){…}
anular alarma(){…}
}
Básicamente, este método de implementación puede reflejar claramente nuestra comprensión del dominio del problema y revelar correctamente nuestras intenciones de diseño. De hecho, la clase abstracta representa la relación "es-a" y la interfaz representa la relación "como-a". Puede utilizarla como base al elegir. Por supuesto, esto se basa en la comprensión del dominio del problema. Ejemplo: Si AlarmDoor es esencialmente una alarma en concepto y tiene la función de una puerta, entonces la definición anterior debe invertirse.
resumen
1.La clase abstracta representa una relación de herencia en el lenguaje Java, y una clase solo puede usar la relación de herencia una vez. Sin embargo, una clase puede implementar múltiples interfaces.
2. En la clase abstracta, puede tener sus propios miembros de datos y también puede tener métodos de miembros que no sean abstractos, pero en la interfaz, solo puede tener miembros de datos estáticos que no se pueden modificar (es decir, deben ser estáticos finales, pero en la interfaz Los miembros de datos generalmente no están definidos) y todos los métodos de los miembros son abstractos.
3.La clase abstracta y la interfaz reflejan diferentes conceptos de diseño. De hecho, la clase abstracta representa la relación "es-un" y la interfaz representa la relación "como-a".
4. Las clases que implementan clases e interfaces abstractas deben implementar todos los métodos que contienen. Las clases abstractas pueden tener métodos no abstractos. No puede haber métodos de implementación en la interfaz.
5. Las variables definidas en la interfaz son públicas estáticas finales por defecto y se debe dar su valor inicial, por lo que no se pueden redefinir en la clase de implementación ni se pueden cambiar sus valores.
6. Las variables en clases abstractas son amigables de forma predeterminada y sus valores se pueden redefinir en subclases o reasignar.
7. Los métodos en la interfaz son de tipo público y abstracto por defecto.
en conclusión
La clase abstracta y la interfaz son dos formas de definir clases abstractas en el lenguaje Java y son muy similares. Sin embargo, su selección a menudo refleja la comprensión de la esencia del concepto en el dominio del problema y si el reflejo de la intención del diseño es correcto y razonable, porque expresan diferentes relaciones entre conceptos (aunque todos pueden lograr las funciones requeridas). En realidad, este es un tipo de uso idiomático del lenguaje. Espero que los lectores puedan entenderlo con atención.