2008-08-23 6 views

Respuesta

9

¿Qué tal si todavía no está seguro acerca de la interfaz y no quiere ningún otro código según la interfaz actual? [Esa es la parte superior de mi cabeza, pero yo estaría interesado en otras razones también!]

Editar: Un poco de google dieron los siguientes: http://codebetter.com/blogs/patricksmacchia/archive/2008/01/05/rambling-on-the-sealed-keyword.aspx

Citando:

Hay tres razones por las que una clase sellada es mejor que una clase sin sellar:

  • de versiones: Cuando una clase se sella originalmente, se puede cambiar a sin sellar en el f uture sin romper la compatibilidad. (...)
  • Rendimiento:. (...) si el compilador JIT ve una llamada a un método virtual utilizando un tipo sellados, el compilador JIT puede producir código más eficiente mediante una llamada al método no virtualmente (...)
  • Seguridad y Previsibilidad: Una clase debe proteger su propio estado y no permitir que se corrompa. Cuando sea abierto una clase, una clase derivada puede acceder y manipular el estado de la clase base, si cualquiera de los campos de datos o métodos que manipulan internamente los campos son accesibles y no privado. (...)
+0

Meh. Estoy totalmente en desacuerdo. ¿Por qué deberían las clases preocuparse por sus descendientes? ¿Cómo ayuda el sellado de una clase a administrar la integridad del objeto? ¿Obligando a un programador a reinventar la clase nuevamente? El malentendido de los principios básicos de OOP es bastante obvio a partir de expresiones como "manipular el estado de la clase base". Uno no manipula clase, sino que manipula una instancia de la clase. Sellar una clase no es garantía de seguridad. Eso es una tontería perfecta. – PJK

21

Debido a clases de escritura que se substitutably extendidos se malditamente difícil y requiere que haga predicciones precisas de cómo los futuros usuarios querrán extender lo que usted ha escrito.

Sellar su clase les obliga a utilizar la composición, que es mucho más robusta.

1

Esto puede no aplicarse a su código, pero muchas clases dentro del framework .NET están selladas a propósito para que nadie intente crear una subclase.

Existen ciertas situaciones en las que las partes internas son complejas y requieren que ciertas cosas se controlen muy específicamente para que el diseñador decida que nadie debe heredar la clase para que nadie accidentalmente rompa la funcionalidad al usar algo incorrectamente.

0

Porque siempre quiere que le den una referencia a la clase y no a una derivada por varias razones:
i. invariantes que tiene en alguna otra parte de su código
ii. seguridad
etc

Además, debido a que es una apuesta segura con respecto a la compatibilidad con versiones anteriores, nunca podrá cerrar esa clase por herencia si se libera.

O quizás no tuvo tiempo suficiente para probar la interfaz que la clase expone para asegurarse de que pueda permitir que otros hereden de ella.

O tal vez no tiene sentido (que vea ahora) tener una subclase.

O no desea informes de errores cuando las personas intentan crear una subclase y no logran obtener todos los detalles esenciales: reducir los costos de soporte.

1

@jjnguy

Otro usuario lo desea, puede volver a utilizar el código de sub-clasificar a su clase. No veo una razón para detener esto.

Si quieren usar la funcionalidad de mi clase, pueden lograr eso con contención, y tendrán un código mucho menos quebradizo como resultado.

Parece que la composición a menudo se pasa por alto; con demasiada frecuencia la gente quiere subirse al carro de la herencia. ¡No deberian! La sustituibilidad es difícil. Predeterminado para la composición; me lo agradecerás a la larga.

+0

Depende de si había una interfaz implementada por la superclase. Si no hay una interfaz cercana a la funcionalidad que desea extender, entonces la composición no funcionará. –

0

A veces, su interfaz de clase simplemente no debe ser cargada. La interfaz pública simplemente no es virtual y mientras que alguien podría anular la funcionalidad que está en su lugar, sería simplemente incorrecto. Sí, en general, no deben anular la interfaz pública, pero puede asegurarse de que no lo hagan al hacer que la clase no sea heredable.

El ejemplo que se me ocurre en este momento son las clases personalizadas contenidas con clones profundos en .Net. Si heredas de ellos, pierdes la capacidad de clonación profunda. [Soy un tanto confuso en este ejemplo, hace mucho tiempo que no trabajo con IClonable]. Si tienes una verdadera clase de singelton, probablemente no quieras formas heredadas de alrededor, y una capa de persistencia de datos no es normalmente el lugar que desea una gran cantidad de herencia.

8

Quiero darle este mensaje de "código completo":

Herencia - subclases - tiende a trabajo en contra de la principal técnica imperativo que tiene como programador, es la gestión de la complejidad. Por el bien de controlar la complejidad, debes mantener un fuerte sesgo contra la herencia.

1

Estoy de acuerdo con jjnguy ... Creo que las razones para sellar una clase son pocas y distantes. Muy por el contrario, he estado en la situación más de una vez en la que quiero extender una clase, pero no pude porque estaba sellada.

Como ejemplo perfecto, hace poco estaba creando un pequeño paquete (Java, no C#, pero los mismos principios) para ajustar la funcionalidad de la herramienta memcached. Quería una interfaz, así que en las pruebas podía burlarme de la API cliente que estaba utilizando y también podía cambiar de cliente si surgía la necesidad (hay 2 clientes enumerados en la página principal de Memcached). Además, quería tener la oportunidad de reemplazar la funcionalidad por completo si surgiera la necesidad o el deseo (por ejemplo, si los servidores de memcached están caídos por alguna razón, podríamos intercambiarlos en caliente con una implementación de caché local).

Expongo una interfaz mínima para interactuar con la API del cliente, y hubiera sido increíble ampliar la clase API del cliente y luego simplemente agregar una cláusula de implementaciones con mi nueva interfaz. Los métodos que tenía en la interfaz que coincidían con la interfaz real no necesitarían más detalles y, por lo tanto, no tendría que implementarlos explícitamente. Sin embargo, la clase estaba sellada, así que tuve que sustituir las llamadas a una referencia interna a esta clase. El resultado: más trabajo y mucho más código sin ninguna buena razón.

Dicho esto, creo que hay momentos en los que es posible que desee hacer una clase sellada ...y lo mejor que puedo pensar es una API que invocará directamente, pero permitirá que los clientes la implementen. Por ejemplo, un juego donde puedes programar contra el juego ... si tus clases no fueron selladas, entonces los jugadores que están agregando características podrían potencialmente explotar la API para su beneficio. Sin embargo, este es un caso muy limitado, y creo que cada vez que tiene un control total sobre la base de código, realmente hay pocas razones, si es que hay alguna, para hacer una clase sellada.

Esta es una de las razones por las que realmente me gusta el lenguaje de programación Ruby ... incluso las clases principales están abiertas, no solo para extender, sino también para AGREGAR Y CAMBIAR funcionalidad dinámicamente, ¡A LA CLASE MISMA! Se llama monkeypatching y puede ser una pesadilla si se abusa de él, ¡pero es muy divertido jugar con él!

0

No todo lo que es importante en una clase se afirma fácilmente en el código. Puede haber semántica y relaciones presentes que se rompen fácilmente heredando y reemplazando métodos. Anular un método a la vez es una manera fácil de hacerlo. Usted diseña una clase/objeto como una única entidad significativa y luego alguien viene y piensa que si uno o dos métodos fueran 'mejores', no causarían daño. Eso puede o no ser cierto. Quizás pueda separar correctamente todos los métodos entre privado y no privado o virtual y no virtual, pero aún así puede no ser suficiente. La herencia exigente de todas las clases también impone una enorme carga adicional al desarrollador original para prever todas las formas en que una clase heredera podría arruinar las cosas.
No conozco una solución perfecta. Soy comprensivo con la prevención de la herencia, pero eso también es un problema porque dificulta las pruebas unitarias.

+0

La herencia no debe afectar al autor de la clase original; él no es el que se meterá en problemas con una clase heredada que arruina las cosas. Si el desarrollador puede prever tales preocupaciones potenciales, entonces la clase original no estaba bien diseñada en primer lugar / –

0

Expuse una interfaz mínima para interactuar con la API del cliente, y hubiera sido increíble ampliar la clase API del cliente y luego simplemente agregar una cláusula de implementaciones con mi nueva interfaz. Los métodos que tenía en la interfaz que coincidían con la interfaz real no necesitarían más detalles y, por lo tanto, no tendría que implementarlos explícitamente. Sin embargo, la clase estaba sellada, así que tuve que sustituir las llamadas a una referencia interna a esta clase. El resultado: más trabajo y mucho más código sin ninguna buena razón.

Bueno, hay una razón: su código ahora está algo aislado de los cambios en la interfaz de memcached.

0

Rendimiento:. (...) si el compilador JIT ve una llamada a un método virtual utilizando un tipo sellados, el compilador JIT puede producir código más eficiente mediante una llamada al método no virtualmente (...)

Esa es una gran razón de hecho. Por lo tanto, para las clases de rendimiento crítico, sealed y amigos tienen sentido.

Todas las otras razones que he visto mencionar hasta ahora se reducen a "¡nadie toca mi clase!". Si está preocupado de que alguien malinterprete su funcionamiento interno, hizo un mal trabajo al documentarlo. No puede saber que no hay nada útil que agregar a su clase, o que ya conoce todos los casos de uso imaginables. Incluso si tiene razón y el otro desarrollador no debería haber usado su clase para resolver su problema, usar una palabra clave no es una gran manera de prevenir ese error. La documentación es Si ignoran la documentación, su pérdida.

2

El único uso legítimo de la herencia es definir un caso particular de una clase base como, por ejemplo, cuando se hereda de Shape para derivar Circle. Para comprobar esta mirada a la relación en dirección opuesta: ¿es una forma una generalización de Círculo? Si la respuesta es sí, entonces está bien usar herencia.

Si tiene una clase para la que no puede haber casos particulares que especialicen su comportamiento, debe sellarse.

También debido a LSP (Principio de sustitución de Liskov) se puede utilizar la clase derivada donde se espera la clase base y esto es realmente impone el mayor impacto del uso de la herencia: el código que utiliza la clase base puede recibir una clase heredada y todavía tiene que funcionar como se esperaba. Para proteger el código externo cuando no hay una necesidad obvia de subclases, se sella la clase y sus clientes pueden confiar en que su comportamiento no cambiará. De lo contrario, el código externo debe diseñarse explícitamente para esperar posibles cambios en el comportamiento de las subclases.

Un ejemplo más concreto sería el patrón Singleton. Debes sellar singleton para asegurarte de que no se puede romper la "singletonness".

1

Desde una perspectiva orientada a objetos, el sellado de una clase documenta claramente la intención del autor sin la necesidad de comentarios. Cuando cierro una clase, trato de decir que esta clase fue diseñada para encapsular algún conocimiento específico o algún servicio específico. No estaba destinado a ser mejorado o subclasificado más.

Esto va bien con el patrón de diseño de Método de plantilla. Tengo una interfaz que dice "Realizo este servicio". Luego tengo una clase que implementa esa interfaz. Pero, ¿qué ocurre si la realización de ese servicio se basa en un contexto del que la clase base no tiene conocimiento (y que no debería conocer)? Lo que ocurre es que la clase base proporciona métodos virtuales, que son protegidos o privados, y estos métodos virtuales son los ganchos para que las subclases proporcionen la información o acción que la clase base no sabe y no puede conocer. Mientras tanto, la clase base puede contener código que es común para todas las clases secundarias. Estas subclases estarían selladas porque están destinadas a lograr esa única implementación concreta del servicio.

¿Se puede argumentar que estas subclases deben subclasificarse para mejorarlas? Yo diría que no porque si esa subclase no pudiera hacer el trabajo en primer lugar, nunca debería haber derivado de la clase base. Si no te gusta, entonces tienes la interfaz original, ve a escribir tu propia clase de implementación.

El sellado de estas subclases también desalienta los niveles profundos de herencia, que funciona bien para los marcos de GUI pero funciona mal para las capas de lógica de negocios.

0

La mayoría de las respuestas (cuando se abstraen) indican que las clases selladas/finalizadas son una herramienta para proteger a otros programadores contra posibles errores. Hay una línea borrosa entre la protección significativa y la restricción sin sentido. Pero mientras el programador sea quien se espera que entienda el programa, no veo ninguna razón para restringirlo de reutilizar partes de una clase. La mayoría de ustedes habla sobre las clases. ¡Pero todo se trata de objetos!

En su primera publicación, DrPizza afirma que diseñar clases heredables significa anticipar posibles extensiones. ¿Entiendo bien que crees que la clase debería ser heredable solo si es probable que se extienda bien? Parece como si estuvieras acostumbrado a diseñar software de las clases más abstractas. Permítanme una breve explicación de cómo pienso al diseñar:

Partiendo de los objetos muy concretos, encuentro las características y [por lo tanto] la funcionalidad que tienen en común y la abstraigo a la superclase de esos objetos particulares. Esta es una forma de reducir la duplicidad de código.

A menos que desarrolle un producto específico como un marco, me preocuparé por mi código, no por otros códigos (virtuales). El hecho de que otros puedan encontrar útil reutilizar mi código es una buena ventaja, no mi objetivo principal. Si deciden hacerlo, es su responsabilidad garantizar la validez de las extensiones. Esto se aplica a todo el equipo. El diseño frontal es crucial para la productividad.

Volviendo a mi idea: sus objetos deberían servir principalmente para sus propósitos, no una posible funcionalidad de shoulda/woulda/coulda de sus subtipos. Tu objetivo es resolver un problema determinado. Los lenguajes orientados a objetos usan el hecho de que muchos problemas (o más probablemente sus subproblemas) son similares y, por lo tanto, el código existente se puede usar para acelerar el desarrollo posterior.

Sellar una clase obliga a las personas que podrían aprovechar el código existente SIN MODIFICAR REALMENTE SU PRODUCTO para reinventar la rueda. (Esta es una idea crucial de mi tesis: ¡Heredar una clase no la modifica! Lo cual parece bastante peatonal y obvio, pero comúnmente se ignora).

Las personas a menudo tienen miedo de que sus clases "abiertas" se tuerzan a algo que no puede sustituir a sus ascendentes. ¿Y qué? ¿Por qué debería importarte? ¡Ninguna herramienta puede evitar que un programador defectuoso genere software defectuoso!

No estoy tratando de denotar las clases heredables como la forma correcta en última instancia de diseño, considere esto más como una explicación de mi inclinación a las clases heredables. Esa es la belleza de la programación: un conjunto virtualmente infinito de soluciones correctas, cada una con sus propios pros y contras. Sus comentarios y argumentos son bienvenidos.

Y finalmente, mi respuesta a la pregunta original: finalizaría una clase para que otros sepan que considero la clase como una hoja del árbol de clases jerárquicas y no veo absolutamente ninguna posibilidad de que pueda convertirse en un nodo padre. (Y si alguien piensa que realmente podría, entonces o estaba equivocado o no me entendieron).

Cuestiones relacionadas