2012-02-16 19 views
5

Estaba leyendo un ejemplo de herencia simple y encontré la idea básica de que un cuadrado es un tipo base y un rectángulo derivado de un cuadrado.Pobre ejemplo de herencia en C#

El ejemplo para establecer las dimensiones cuadradas usaba una propiedad llamada Size. El ejemplo para el rectángulo pasó a usar Width y Height.

Esto no tenía sentido en mi cabeza, así que lo codifiqué.

El problema parece ser que al acceder a rectangle, siempre habrá una propiedad confusa llamada 'Size' presente.

¿Tengo este derecho? ¿O hay alguna manera de ocultar otras clases para que no vean Size al mirar rectangle?

public class square 
{ 
    public int Size { get; set; } 
    public square(int size) 
    { 
     this.Size = size; 
    } 
} 

public class rectangle : square 
{ 
    public int Width { get { return base.Size; } set { base.Size = value; } } 

    public int Height { get; set; } 

    public rectangle(int width, int height) 
     : base(width) 
    { 
     Height = height; 
    } 
} 
+7

Debería ser al revés: cuadrado debe derivar de un rectángulo. Un cuadrado también tiene un ancho y una altura, simplemente son iguales para el cuadrado. Un rectángulo puede, por supuesto, tener diferentes valores. – xxbbcc

+1

No es el primer ejemplo pobre de herencia que encontrará en su estudio, o su carrera en ese sentido. Felicitaciones por detectarlo, el código google huele alguna vez ... –

+0

@Tod El artículo 35 es "Considerar alternativas a las funciones virtuales". Mi respuesta no contiene una sola función virtual (de hecho, a menos que obtenga otra clase de 'Cuadrado' o 'Rectángulo', no puede mutar un objeto de ningún tipo), así que preocúpese de comentar cómo responde mi respuesta " ¿completamente equivocado?" –

Respuesta

13

Estás 100% en lo cierto de que esto es una herencia retrógrada. En su lugar, debe tener una clase Square heredera de una clase Rectangle, ya que un cuadrado es un tipo especial de rectángulo.

A continuación, se obtiene algo así como

public class Rectangle 
{ 
    public int Width { get; private set; } 
    public int Height { get; private set; } 

    public Rectangle(int width, int height) 
    { 
     if (width <= 0 || height <= 0) 
      throw new ArgumentOutOfRangeException(); 
     Width = width; 
     Height = height; 
    } 
} 

public class Square : Rectangle 
{ 
    public int Size 
    { 
     get 
     { 
      return Width; 
     } 
    } 

    public Square(int size) 
     : base(size, size) 
    { 
    } 
} 
+5

Tener la subclase Square no es necesariamente una buena idea, ya que, aunque todos los cuadrados son rectángulos, diseñar su jerarquía de herencia de esta manera es una manera rápida de violar el principio de sustitución de Liskov ... –

+3

@ReedCopsey Es perfectamente legítimo tener 'Square' subclase 'Rectangle' si está minimizando la mutabilidad, como lo hago en el código publicado. –

+1

Es cierto: escribí esto antes de su edición. Sin embargo, si permite la mutación de los tipos (como las clases de ejemplo del OP), entonces es peligroso. –

8

El problema es que un rectángulo es no un cuadrado - Tampoco se puede incluso decir que una Square es una Rectangle porque esto causa muchos problemas también desde un Rectangle (por lo general) ofrece métodos para configurar de forma independiente anchura y altura - esto sería constreñidos por un Square - esto es una violación de la clásica Liskov substition principle. - citar de Wikipedia:

Un ejemplo típico que infringe LSP es una clase Square que deriva de una clase Rectangle, suponiendo que existen métodos getter y setter para ancho y alto. La clase Square siempre asume que el ancho es igual a la altura. Si se utiliza un objeto Square en un contexto donde se espera un rectángulo, puede producirse un comportamiento inesperado porque las dimensiones de un cuadrado no pueden (o más bien no deberían) modificarse de forma independiente. Este problema no se puede solucionar fácilmente: si podemos modificar los métodos setter en la clase Square para que conserven el invariante Square (es decir, mantener las dimensiones iguales), estos métodos debilitarán (violarán) las condiciones posteriores para el Rectángulo setters, que establecen que las dimensiones se pueden modificar de forma independiente. Las violaciones de LSP, como esta, pueden o no ser un problema en la práctica, dependiendo de las postcondiciones o invariantes que en realidad son esperadas por el código que usa clases que violan LSP. La capacidad de reproducción es un problema clave aquí. Si Square y Rectangle solo tenían métodos getter (es decir, eran objetos inmutables), entonces no podría ocurrir una violación de LSP.

2

creo que el problema es el geométrico :-)

Un rectángulo no es un cuadrado. Un cuadrado ES un rectángulo.

Debe invertir la herencia y descubrirá que el rectángulo solo tiene Ancho y Alto y el cuadrado tiene una propiedad adicional llamada Tamaño.

adiós, Marco

3

encontré con la idea básica de que un cuadrado es un tipo de base y un rectángulo se deriva de un cuadrado.

El problema es que esto es falso -

Un cuadrado es un tipo específico de rectángulo, un rectángulo, pero no es un cuadrado.

Dicho esto, hacer que el cuadrado herede el rectángulo también es peligroso, ya que terminas con una situación inesperada. Se trata de un common violation of the Liskov Substitution Principle, por lo que a menudo es mejor tener tanto Square y Rectangle implementar una clase base común, tales como Shape, que sólo contienen propiedades compartidas por ambas clases, como cosas como Area o Bounds, etc.

0

Un cuadrado es siempre un rectángulo, pero un rectángulo no siempre es un cuadrado; de ahí que Rectanglesea el padre de y no al revés.

0

Geometría 101: Un cuadrado es un rectángulo, pero un rectángulo no es un cuadrado.

0

Cuadrado y rectángulo son un ejemplo clásico de una violación del Liskov substitution principle. En este principio, ninguno está directamente relacionado entre sí, pero podría estar relacionado con una forma, por ejemplo.

Por ejemplo:

public class Rectangle 
{ 
    public int width {get;set;} 
    public int height {get;set;} 
    public int Area() { return width * height; } 
} 

public class Square : Rectangle 
{ 
    public override int width 
    { 
     get { return base.width; } 
     set { base.height = value; base.width = value; } 
    } 

    //... etc ... 
} 

[Test] 
public void Rectangles_area_should_equal_length_times_width() 
{ 
    Rectangle r = new Square(); 
    r.height = 10; 
    r.width = 15; 

    Assert.That(r.Area() == 150); // Fails 
} 

ve usted el problema? Al hacer referencia al cuadrado desde su clase base, el comportamiento cambia de forma inesperada. La solución correcta es que Square y Rectangle sean hermanos, (hijos de Shape), no padres/hijos.

0

Refiriéndose a su pregunta acerca de cómo ocultar los miembros heredados, puede utilizar el modificador new:

public class Rectangle : Square { 
    private new int Size { get; set; } 
    ... 
}