2010-09-05 9 views
7

¿Puede proporcionar escenarios cuando heredamos de una clase, funciona por un tiempo, pero luego algo más cambia e introduce un error? me ocurrió con la siguiente situación:¿Cuándo heredar de una clase te duele más tarde?

  • para implementar rectángulo-con-agujero, que heredamos de la clase Rectángulo. En el constructor, comprobamos que el agujero está dentro del rectángulo
  • Más tarde alguien añade un nuevo método de cambio de tamaño para rectángulo clase. No verifican si el agujero todavía está adentro.
  • Después de cambiar el tamaño, podemos tener rectángulo-con-agujeros con agujeros no dentro de los rectángulos, lo cual es un error.

¿Cuáles son los otros problemas que debería tener cuidado si elijo usar herencia de objetos en C#.

+1

No es algo de lo que tenga cuidado en C#, es algo de lo que debe tener cuidado en cualquier lenguaje orientado a objetos. Las clases base nunca deberían * preocuparse por su progenie. – Robaticus

+0

@Robaticus: Dije C# porque en C++ podemos heredar de múltiples clases y es más fácil meternos en problemas. En C#, podemos heredar solo de una clase. –

+0

Todavía estoy de acuerdo con lo que dije. Esto es algo a tener en cuenta independientemente del idioma que se use. No es exclusivo de C#. – Robaticus

Respuesta

5

Hay muchas maneras de un cambio a una clase base pueden afectar el comportamiento de la clase derivada. ¿Qué pasa si el autor de repente decidió hacer la clase sealed por ejemplo?

Como cualquier interfaz, necesita ser estable para que los consumidores no requieran modificaciones.

La "regla" primaria con herencia es el Principio de Substición Liskov. Esto establece que la clase derivada debe ser sustituible por la clase base u otras clases derivadas de ella.

Este caso rompe esta regla en la cara, ya que un rectángulo con un agujero no es un rectángulo.

Por lo general, es mejor utilizar interfaces para dividir el comportamiento en trozos implementables sensibles. Tales interfaces generalmente se nombran con adjetivos en lugar de nombres. Por ejemplo, tiene sentido que se representen tanto un rectángulo como un rectángulo con un agujero, por lo que una interfaz puede ser IRenderable. Si se puede cambiar el tamaño, puede tener un IResizable, etc. Estos pueden agregarse en un IShape, pero debe tener cuidado de que su definición de Forma defina solo los comportamientos en su dominio problemático.

Derivar directamente de otra clase puede ser peligroso ya que entonces estás obligado por el comportamiento de esa clase. Si realmente necesita hacerlo, puede ser mejor extraer la implementación que necesita en una clase base común (por ejemplo, Rectangle : RectangleImplementation, IShape).

6

Lo que usted describe es una de las trampas de la herencia.

Otro escollo es una jerarquía de herencia de profundidad. Stackoverflow thread en composition over inheritance.

+0

desearía poder votar esto más. –

0

no llame a un método virtual en un constructor. Vea las publicaciones de Eric Lippert al respecto (part 1 y part 2).

3

Esto se conoce como brittle base class problem.

Otro problema potencial con la herencia en general es cuando desea usar su propia clase base, pero un marco requiere una clase base específica (por ejemplo, ContextBoundObject) en lugar de una interfaz.

1

Es por eso que los proféticos diseñadores del lenguaje C# agregaron palabra clave al idioma. Use juiciosamente.

Y use contratos de código para probar sus invariantes y obtener una advertencia temprana sobre la rotura.

+0

Hans, ¿qué quiere decir con "usar contratos de código para probar sus invariantes para obtener una advertencia temprana sobre la rotura"? –

+0

Enlace: http://devjourney.com/blog/code-contracts-part-4-object-invariants/ –

+0

¡Buen enlace, gracias! –

0

Mejor uso - Patrón de decorador en este tipo de casos. Esto proporciona una mejor forma de extensión.

Esto se puede implementar mediante el siguiente diagrama: alt text

El objeto rectángulo se creará y se envuelve en un objeto HoleDecorator que será responsable de proporcionar el agujero. Cuando se hace el cambio de tamaño del rectángulo, se llamará el cambio de tamaño de HoleDecorator, esto llamará primero al objeto de cambio de tamaño del rectángulo y luego llamará a AddedBehavior para el Hole Decorator que especificará qué hacer con el agujero cuando el componente principal esté redimensionado

+0

¿Puede especificar cómo se vería su implementación de Rectangle-with-hole? –

0

Creo que esto no está relacionado con C# o la herencia. No importa lo que haga, este es un problema inevitable del desarrollo de software.

La mejor y sencilla solución es construir su código diariamente y realizar unit testing.

+0

Siempre utilizamos pruebas unitarias e IC, pero no veo cómo eso podría ayudar en este caso. No creamos pruebas unitarias contra situaciones que aún no existen. No hay forma de que podamos tener una prueba unitaria anticipando un método de Redimensionar antes de que se haya desarrollado. ¿Puedes sugerir cómo podría suceder? –

+0

Las pruebas unitarias generalmente llaman a métodos y comparan entradas y salidas. Estoy haciendo pruebas unitarias basadas en escenarios para problemas similares. Con tu ejemplo; después de creado el rectángulo, el objeto pasó a las pruebas Rectángulo (realiza el cambio de tamaño, mueve etc.) las pruebas y pasa sus pruebas de Rectángulo con Agujero (realiza controles de límites) con el mismo contexto. Depende totalmente de la visión del desarrollador y no es perfecto, pero a veces funciona mejor que las pruebas de método. Por cierto, no reemplaza las pruebas de método, es solo una prueba adicional. – ertan

Cuestiones relacionadas