2012-02-16 11 views
5

Im stava leggendo un esempio di ereditarietà semplice e si è imbattuto nell'idea di base che un quadrato è un tipo di base e un rettangolo deriva da un quadrato.Esempio di ereditarietà scadente in C#

L'esempio per l'impostazione delle dimensioni quadrate usato una proprietà chiamata Size. L'esempio per il rettangolo ha quindi utilizzato Width e Height.

Questo non ha senso nella mia testa, quindi l'ho codificato.

Il problema sembra essere che quando si accede a rectangle, ci sarà sempre una proprietà di confusione chiamata 'Size' presente.

Ho capito bene? O c'è un modo per nascondere altre classi dal vedere Size quando si guarda a 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

Dovrebbe essere il contrario: piazza dovrebbe derivare da un rettangolo. Un quadrato ha anche una larghezza e un'altezza, sono semplicemente uguali per il quadrato. Un rettangolo può, ovviamente, avere valori diversi. – xxbbcc

+1

Non è il primo povero esempio di ereditarietà che incontrerai nel tuo studio o la tua carriera. Congratulazioni per avvistarlo, odore google code qualche volta ... –

+0

@Tod L'articolo 35 è "Considera alternative alle funzioni virtuali". La mia risposta non contiene una singola funzione virtuale (infatti, a meno che tu non abbia derivato un'altra classe da 'Square' o' Rectangle' non puoi mutare un oggetto di nessuno dei due tipi), quindi ti prego di commentare come questo rende la mia risposta " morto sbagliato? " –

risposta

13

Hai ragione al 100% che si tratta di eredità inversa. Invece, dovresti avere una classe Square ereditare da una classe Rectangle, poiché un quadrato è un tipo speciale di rettangolo.

Poi, si ottiene qualcosa come

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

Avere Square sottoclasse Rectangle non è necessariamente una buona idea: mentre tutti i quadrati sono rettangoli, progettare la gerarchia ereditaria in questo modo è un modo rapido per violare il Principio di sostituzione di Liskov ... –

+3

@ReedCopsey È perfettamente legittimo avere 'Square' sottoclasse 'Rectangle' se si minimizza la mutabilità, come faccio nel codice postato. –

+1

Vero - Ho scritto questo prima della modifica. Tuttavia, se consenti la mutazione dei tipi (come le classi di esempio dell'OP), allora è pericoloso. –

8

Il problema è che un rettangolo è non una piazza - È inoltre non si può nemmeno dire che un Square è un Rectangle perché questo provoca un sacco di problemi anche dal un Rectangle (di solito) offre metodi per impostare in modo indipendente larghezza e altezza - questo sarebbe costretto da un Square - questo è un classico violazione della Liskov substition principle. - di citare Wikipedia:

Un tipico esempio che viola LSP è una classe Square che deriva da una classe Rectangle, presupponendo che esistono metodi getter e setter per sia larghezza che altezza. La classe Square presuppone sempre che la larghezza sia uguale all'altezza. Se un oggetto quadrato viene utilizzato in un contesto dove è previsto un rettangolo, si può verificare il comportamento imprevisto perché le dimensioni di un quadrato non possono (o meglio non dovrebbe) essere modificati indipendente. Questo problema non può essere risolto facilmente: se siamo in grado di modificare i metodi setter nella classe Square in modo da preservare l'invarianza quadrata Square (ovvero mantenere le dimensioni uguali), allora questi metodi indeboliranno (violano) le postcondizioni per il rettangolo. setter, in cui si afferma che le dimensioni possono essere modificate indipendentemente. Violazioni di LSP, come questo, può o non può essere un problema, in pratica, seconda delle postcondizioni o invarianti che sono effettivamente attesi dal codice che utilizza le classi viola LSP. La mutabilità è un problema chiave qui. Se Square e Rectangle avevano solo metodi getter (ad esempio, erano oggetti immutabili), non si poteva verificare alcuna violazione di LSP.

2

Penso che il problema è quello geometrico :-)

Un rettangolo non è un quadrato. Un quadrato È un rettangolo.

Dovresti invertire l'ereditarietà e scoprirai che il rettangolo ha solo larghezza e altezza e quadrato ha una proprietà aggiuntiva chiamata Dimensione.

Bye, Marco

3

sono imbattuto l'idea di base che una piazza è un tipo di base e un rettangolo è derivato da un quadrato.

Il problema è che questo è falso -

Un quadrato è un tipo specifico di rettangolo, ma un rettangolo non è un quadrato.

Detto questo, anche rendere rettangolo ereditario quadrato è pericoloso, in quanto si arriva a una situazione inaspettata. Si tratta di un common violation of the Liskov Substitution Principle, motivo per cui spesso è meglio avere sia Square e Rectangle implementare una classe base comune, come Shape, che dovrebbe contenere solo le proprietà condivise da entrambe le classi, come ad esempio le cose come Area o Bounds, ecc

0

Un quadrato è sempre un rettangolo, ma un rettangolo non è sempre un quadrato - quindi Rectangledovrebbe essere il genitore di Square e non viceversa.

0

Geometria 101: un quadrato è un rettangolo, ma un rettangolo non è un quadrato.

0

Square e Rectangle sono un classico esempio di violazione dello Liskov substitution principle. In questo principio, nessuno dei due è direttamente correlato l'uno all'altro, ma potrebbe essere correlato a una forma, ad esempio.

Ad esempio:

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 
} 

Non si vede il problema? Quando si fa riferimento al quadrato dalla sua classe base, il comportamento cambia in modo inaspettato. La soluzione corretta è che Square e Rectangle siano fratelli, figli di Shape, non genitori/figli.

0

Riferendosi alla sua domanda di nascondere i membri ereditati, è possibile utilizzare il modificatore di new:

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