... Como esto nos obliga a crear captadores y establecedores, que en la práctica a menudo son totalmente extraños. ¿Existe alguna buena razón de diseño de lenguaje por la cual las interfaces en la mayoría de los idiomas (¿todos?) No permiten que los campos miembros ayuden a cumplir con las especificaciones de la interfaz?
En la mayoría de los idiomas existentes, los miembros reciben un trato fundamentalmente diferente de los métodos, pero ¿TIENE que ser así? En un lenguaje teórico, ¿no podríamos tener una interfaz que (a) especificara un método de cero argumentos como getter, pero aceptara un miembro legible como su implementador, y (b) especificara un método de un solo argumento como setter, pero aceptado un campo de miembro grabable como su implementador, dado un lenguaje que admite la especificación del control de acceso directo en las variables cuando se declaran? Un lenguaje prototípico que permita la herencia múltiple (para abordar el punto muy válido que algunos encuestados han hecho) sería ideal para algo como esto.
Por favor, infórmeme si ya existe algo como esto.
fuente
Respuestas:
No por ninguna razón teóricamente fundamentada. Creo que las razones por las que no lo ves a menudo son prácticas:
La primera es cómo lidiar con estas cosas en tiempo de ejecución. En Java o C ++, una asignación a un miembro público de tipo primitivo
foo.x = 5
es fácil de manejar: cosas5
en algún registro, averiguar dónde en la memoria vive elx
miembro de lafoo
vida y volcar el registro allí. En un lenguaje como C # donde las clases pueden encapsular las tareas de interceptación a los miembros o calcular previamente cómo evaluarán esos miembros , el compilador no tendrá a prioriconocimiento de si la implementación de una clase disponible en tiempo de ejecución utiliza esa característica. Eso significa que todas las asignaciones de miembros públicos y las evaluaciones de los miembros públicos tienen que diferirse para la implementación. * Las operaciones que involucran mucho acceso a esos miembros terminan teniendo un impacto en el rendimiento porque constantemente están haciendo llamadas de subrutina que podrían manejarse más rápido con regularidad. asignaciones en línea. Dejar la evaluación y la asignación separadas de los captadores y establecedores le da al desarrollador cierto control sobre cómo se manejan en una situación dada.El segundo es la edad. Con la posible excepción de F #, ninguno de los idiomas de uso muy amplio en la actualidad tiene menos de una década, e incluso tan recientemente como en aquel entonces, los ciclos adicionales necesarios para hacer posible la evaluación interceptada y la asignación pueden no haber sido aceptables.
El tercero es la inercia. Creo que hasta cierto punto, todavía hay algún estigma asociado con la asignación directa a los miembros públicos, incluso si el lenguaje permite que la clase los manipule y la gente todavía piense en términos de que los captadores y los establecedores son mejores. Eso desaparecerá con el tiempo, tal como sucedió con todas las personas que tenían sus narices en el aire sobre las personas que escribieron programas en idiomas de nivel superior en lugar de asamblea.
* Hay una compensación que viene con esto: un lenguaje tiene que ir por completo con aplazar evaluaciones / asignaciones a la clase o rechazar en tiempo de ejecución si el código que usa evaluaciones / asignaciones en línea para una clase determinada intenta vincularse con una implementación que los intercepta . Personalmente, creo que esta última es una mala idea porque obliga a recompilar el código que usa una API que no ha cambiado.
fuente
Si lo hace.
Los campos de miembros son datos por instancia. Los datos deben existir en algún lugar de la memoria para cada instancia; debe haber una dirección o secuencia de direcciones reservadas para ello. En Java, .NET y muchos otros lenguajes OO, estos datos van en el montón.
Los métodos son datos por clase. Son punteros a una tabla. Así es como los métodos obtienen el despacho virtual. En realidad, solo hay una instancia asignada, por clase.
Una interfaz es esencialmente un tipo especial de clase que solo contiene métodos, no datos de instancia. Eso es parte de la definición de interfaz. Si contuviera algún dato, ya no sería una interfaz, sería una clase abstracta. Ambas opciones están bien, por supuesto, dependiendo de qué tipo de patrón de diseño estés tratando de implementar. Pero si agrega datos de instancia a las interfaces, eliminaría todo lo que las convierte en interfaces.
Bien, entonces ¿por qué un método de interfaz no puede apuntar a un campo miembro? Porque no hay nada a nivel de clase a lo que señalar. Una vez más, una interfaz no es más que una tabla de métodos. El compilador solo puede generar una tabla, y cada método debe apuntar a la misma dirección de memoria. Por lo tanto, es imposible que los métodos de interfaz apunten a campos de clase, porque el campo de clase es una dirección diferente para cada instancia.
Quizás el compilador podría aceptar esto de todos modos y generar un método getter para que señale la interfaz, pero eso se convierte esencialmente en una propiedad . Si quieres saber por qué Java no tiene propiedades, bueno ... esa es una historia muy larga y tediosa en la que preferiría no entrar aquí. Pero si usted está interesado, es posible que desee echar un vistazo a otros lenguajes orientados a objetos, que hacen aplicar propiedades, tales como C # o Delphi. La sintaxis es realmente fácil:
Sí, eso es todo lo que hay que hacer. Importante: Estos no son campos, la "barra int" en la
Foo
clase es una propiedad automática , y el compilador está haciendo exactamente lo que acabo de describir anteriormente: generar automáticamente captadores y establecedores (así como el campo del miembro de respaldo).Entonces, para recapitular la respuesta a tu pregunta:
fuente
Esto es el resultado de la ocultación de información, es decir, esos miembros no deberían ser visibles como campos sino como propiedades que pueden o no almacenarse / almacenarse en caché
Un ejemplo es el código hash que cambia cuando el objeto cambia, por lo que es mejor calcularlo cuando se solicita y no cada vez que cambia el objeto.
Además, la mayoría de los lenguajes son de herencia única + implementos múltiples y permitir que los campos formen parte de interfaces lo coloca en un territorio de herencia múltiple (lo único que falta es la implementación predeterminada de funciones), que es un campo minado de casos extremos para acertar
fuente
Las variables miembro son la implementación de la clase, no la interfaz. Es posible que desee cambiar la implementación, por lo que no se debe permitir que otras clases hagan referencia a esa implementación directamente.
Considere una clase con los siguientes métodos: sintaxis de C ++, pero eso no es importante ...
Parece bastante obvio que habrá una variable miembro llamada
mode
om_mode
similar que almacena directamente ese valor de modo, haciendo más o menos lo que una "propiedad" haría en algunos idiomas, pero eso no es necesariamente cierto. Hay muchas formas diferentes de manejar esto. Por ejemplo, el método Set_Mode podría ...Una vez hecho esto, muchos otros métodos de la clase de ejemplo podrían llamar métodos apropiados de esa clase interna a través del puntero, obteniendo un comportamiento específico del modo sin necesidad de verificar cuál es el modo actual.
El punto aquí es menos que hay más de una forma posible de implementar este tipo de cosas, y mucho más que en algún momento puede cambiar de opinión. Tal vez comenzaste con una variable simple, pero todas las declaraciones de cambio para identificar el modo en cada método están teniendo problemas. O tal vez comenzaste con el puntero de instancia, pero resultó ser demasiado pesado para tu situación.
Resulta que cosas como esta pueden suceder para los datos de cualquier miembro. Es posible que necesite cambiar las unidades en las que se almacena un valor de millas a kilómetros, o puede encontrar que su enumeración de identificación única del caso ya no puede identificar de manera única todos los casos sin considerar alguna información adicional, o lo que sea.
Esto también puede suceder con los métodos: algunos métodos son exclusivamente para uso interno, dependen de la implementación y deberían ser privados. Pero muchos métodos son parte de la interfaz y no será necesario cambiarles el nombre o lo que sea solo porque la implementación interna de la clase ha sido reemplazada.
De todos modos, si su implementación está expuesta, otro código inevitablemente comenzará a depender de él, y se le bloqueará para mantener esa implementación. Si su implementación está oculta de otro código, esa situación no puede suceder.
El bloqueo del acceso a los detalles de implementación se denomina "ocultación de datos".
fuente
Como otros han señalado, una interfaz describe lo que hace una clase, no cómo lo hace. Es esencialmente un contrato que las clases heredadas deben cumplir.
Lo que usted describe es ligeramente diferente: una clase que proporciona alguna implementación pero quizás no toda, como una forma de reutilización. Estos se conocen generalmente como clases abstractas y se admiten en varios lenguajes (por ejemplo, C ++ y Java). Estas clases no pueden ser instanciadas por derecho propio, solo pueden ser heredadas por clases concretas (o más abstractas).
Nuevamente, como ya se señaló, las clases abstractas tienden a ser de mayor valor en los lenguajes que admiten herencia múltiple, ya que pueden usarse para proporcionar piezas de funcionalidad adicional a una clase más grande. Sin embargo, esto a menudo se considera un mal estilo, ya que generalmente una relación de agregación ("has-a") sería más apropiada
fuente
Las propiedades Objective-C proporcionan este tipo de funcionalidad con sus descriptores de acceso sintetizados. Una propiedad de Objective-C es esencialmente una promesa de que una clase proporciona accesores, típicamente de la forma
foo
para el getter ysetFoo:
para el setter. La declaración de propiedad especifica ciertos atributos de los accesores relacionados con la gestión de la memoria y los comportamientos de subprocesamiento. También hay una@synthesize
directiva que le dice al compilador que genere accesores e incluso la variable de instancia correspondiente, por lo que no tiene que molestarse en escribir getters y setters o declarar un ivar para cada propiedad. También hay algo de azúcar sintáctico que hace que el acceso a la propiedad parezca acceder a los campos de una estructura, pero en realidad resulta en llamar a los accesores de la propiedad.El resultado de todo eso es que puedo declarar una clase con propiedades:
y luego puedo escribir código que parece que accede a ivars directamente, pero de hecho usa métodos de acceso:
Ya se ha dicho que los accesores le permiten separar la interfaz de la implementación, y la razón por la que desea hacerlo es preservar su capacidad de cambiar la implementación en el futuro sin afectar el código del cliente. El inconveniente es que debes ser lo suficientemente disciplinado para escribir y usar los accesores solo para mantener abiertas tus opciones futuras. Objective-C hace que generar y usar accesores sea tan fácil como usar ivars directamente. De hecho, debido a que se encargan de gran parte del trabajo de gestión de la memoria, el uso de las propiedades es en realidad mucho más fácil que acceder a los ivars directamente. Y cuando llegue el momento en que desee cambiar su implementación, siempre puede proporcionar sus propios accesores en lugar de dejar que el compilador los genere por usted.
fuente
¿"La mayoría" de los idiomas? ¿Qué idiomas has investigado? En lenguajes dinámicos no hay interfaces. Solo hay tres o cuatro lenguajes OO estáticamente tipados con alguna popularidad: Java, C #, Objective-C y C ++. C ++ no tiene interfaces; en su lugar, usamos clases abstractas, y una clase abstracta puede contener un miembro de datos público. C # y Objective-C son compatibles con las propiedades, por lo que no hay necesidad de getters y setters en esos lenguajes. Eso deja solo Java. Presumiblemente, la única razón por la que Java no admite propiedades es porque ha sido difícil introducirlas de una manera compatible con versiones anteriores.
fuente
Algunas de las respuestas mencionan 'ocultación de información' y 'detalles de implementación' como la razón por la cual los métodos de interfaz son preferibles a los campos. Creo que la razón principal es más simple que eso.
Las interfaces son una característica del lenguaje que le permite cambiar el comportamiento haciendo que múltiples clases implementen una interfaz. El comportamiento de una clase se define por sus métodos. Los campos, por otro lado, solo reflejan el estado de un objeto.
Por supuesto, los campos o propiedades de una clase pueden ser parte del comportamiento, pero no son la parte importante del contrato definido por la interfaz; son solo un resultado. Es por eso que, por ejemplo, C # le permite definir propiedades en una interfaz, y en Java usted define un captador en la interfaz. Aquí es donde entra en juego un poco de ocultación de información, pero no es el punto principal.
Como dije antes, las interfaces le permiten cambiar el comportamiento, pero echemos un vistazo a la parte de intercambio. Una interfaz le permite cambiar la implementación subyacente creando una nueva clase de implementación. Por lo tanto, tener una interfaz que consta de campos no tiene mucho sentido, porque no se puede cambiar mucho sobre la implementación de los campos.
La única razón por la que desea crear una interfaz de solo campo es cuando quiere decir que alguna clase es un cierto tipo de objeto. He enfatizado la parte 'es a', porque deberías usar herencia en lugar de interfaces.
Nota al margen: Sé que la composición que prefería sobre la herencia y la composición generalmente solo se puede lograr mediante el uso de interfaces, pero usar interfaces solo para 'etiquetar' una clase con algunas propiedades es igual de malo. La herencia es excelente cuando se usa apropiadamente, y siempre y cuando cumpla con el Principio de Responsabilidad Única, no debería tener ningún problema.
Quiero decir, nunca he trabajado con una biblioteca de control de IU, por ejemplo, que no usa herencia. La única área donde las interfaces entran en juego aquí es en el aprovisionamiento de datos, por ejemplo,
ITableDataAdapter
para vincular datos a un control de tabla. Este es exactamente el tipo de cosa que las interfaces están destinadas a tener un comportamiento intercambiable .fuente
En Java, la razón es que las interfaces solo permiten especificar métodos .
Por lo tanto, si desea algo con un campo en el contrato de interfaz, debe hacerlo como método getter y setter.
También vea esta pregunta: ¿ Cuándo se justifican los captadores y establecedores?
fuente
El objetivo de una interfaz es especificar las funciones que debe implementar para afirmar que admite esta interfaz.
fuente
Los miembros no están permitidos en las interfaces porque esos lenguajes tendrían que lidiar con el mal horrendo que es, la herencia múltiple.
fuente