Trattare con oggetti simili con differenti firme di metodo

1

Sono abbastanza nuovo per la progettazione OO e ho problemi con la progettazione di alcuni software e sto cercando un pattern o una combinazione di pattern che possa aiutarmi a risolvere il mio problema.

Ho un tipo con una collezione di diverse forme geometriche (ad esempio linee, rettangoli e cubi). In realtà ho persino compositi di queste forme. Ogni forma ha uno o più valori. Un valore è una coordinata (tra unidimensionale per le linee e tridimensionale per i cubi).

Tutti i tipi di forme hanno due metodi essenziali. Un metodo per aggiungere valori e un metodo per ottenere tutti i valori di una forma. Per esempio. Cube.AddValue(new Value3D{X=4,Y=7,Z=11}) o Line.AddValue(new Value1D{X=42}) rispettivamente Cube.GetValues() restituirebbe un insieme di Value3D e Line.GetValues() restituirebbe un insieme di Value1D .

Poiché questi metodi hanno firme fondamentalmente diverse, non posso derivare da una singola interfaccia per archiviare tutti questi diversi oggetti in una raccolta. Suddividere la raccolta in più raccolte separate, una per ogni tipo sarebbe possibile, ma non desiderabile poiché appartengono insieme (dominio saggio).

Ho preso in considerazione l'utilizzo di valori tridimensionali per tutte le forme e ho semplicemente ignorato Y / Z per linee / rettangoli, ma non è molto bello e violerebbe in realtà la O e la L in SOLID (è necessaria una quarta dimensione in futuro).

Un'altra possibilità sarebbe probabilmente quella di usare un'interfaccia generica come IShape<T> dove T è Valore * D. Questa interfaccia dovrebbe essere:

public interface IShape<T> {
    public IEnumerable<T> GetValues();
    public void AddValue(T value);
}

Ma ciò richiederebbe un altro, non generico interface INonGenericShape {} perché non posso usare un'interfaccia generica senza specificare il tipo. Vorrei finire con qualcosa di simile:

public class SomeShape{
    private IList<INonGenericShape> _shapes = new List<INonGenericShape>();
}

Ma allora potrei anche usare System.Object , dato che IShape non fornisce alcuna informazione preziosa. Quindi eccomi qui, non sapendo come risolvere questo (archiviare gli oggetti in una singola raccolta per i consumatori di quella classe). Qualcuno può fornire qualche idea o intuizione?

    
posta Ray 06.05.2014 - 15:52
fonte

4 risposte

3

Contrariamente a molti (forse la maggior parte) delle esercitazioni di progettazione orientate agli oggetti, le gerarchie di ereditarietà non dovrebbero necessariamente corrispondere alla tassonomia del mondo reale di un dominio. In altre parole, solo perché tutti gli oggetti sono forme nel mondo reale non significa necessariamente che tutti dovrebbero ereditare da IShape nel programma. Anche se condividono tutti un'interfaccia, solo perché hanno metodi simili non significa che quei metodi devono essere parte di tale interfaccia.

Il trucco è guardare il codice chiamante per vedere quali operazioni sono realmente necessarie a quel punto. Ad esempio:

for shape in shapes:
  shape.doSomething()

Chiamerai davvero AddValue in un ciclo simile? Ne dubito. Più probabilmente, sarà qualcosa di simile a Draw o GetEditControl , e AddValue verrà chiamato prima che sia persino messo in shapes , o da un% -conoscenza EditControl . In altre parole, IShape dovrebbe avere solo metodi a cui non interessa la dimensione e le operazioni che riguardano la dimensione dovrebbero essere gestite da altre interfacce.

    
risposta data 06.05.2014 - 17:58
fonte
3

Le tue forme non dovrebbero contenere i loro valori attraverso l'interfaccia. Le interfacce sono cose che devi essere in grado di fare con loro. Le classi sono i vari modi in cui puoi realizzare le stesse cose.

Se il tuo sforzo è quello di disegnare queste forme sullo schermo, la tua interfaccia non avrà nulla a che fare con valori di qualsiasi tipo direttamente.

public interface IDrawable {
    void Draw(Graphics g);
}

In questo modo puoi inventare idee di forma completamente nuove che non hai mai pensato senza dover cambiare le firme di come interagiscono. Questo è tutto per l'OO. Isolare l'interazione con una singola cosa che funzionerà per tutto. (suggerimento: spesso estrai una funzione, non un valore.)

Ora, quando decidi di dover scrivere queste forme su una memoria dati, puoi aggiungere.

public interface IWritable {
    void Write(Stream s);
}

Ora c'è posto per la digitazione di questi valori, tutto funziona meglio se è digitato. Nota che ora puoi praticamente mettere qualcosa qui, e mescolare e abbinare liberamente.

public class Line : IDrawable , IWritable {
    public Point Start { get; set; }
    public Point End { get; set; }
    public void Draw(Graphics g) { }
    public void Write(Stream s) { }
}

public class ThreePointTriangle : IDrawable , IWritable {
    public Point First { get; set; }
    public Point Second { get; set; }
    public Point Third { get; set; }
    public void Draw(Graphics g) { }
    public void Write(Stream s) { }
}

public class SinRightTriangle : IDrawable , IWritable {
    public Point AngleA { get; set; }
    public Line Opposite { get; set; }
    public Line Hypotenuse { get; set; } 
    public void Draw(Graphics g) { }
    public void Write(Stream s) { }
}

public class Car : IDrawable {
    public void Draw(Graphics g) { }
}
    
risposta data 07.05.2014 - 05:33
fonte
1

La risposta è semplicemente, questi non sono oggetti simili. L'ereditarietà riguarda il comportamento condiviso, non i valori condivisi. Solo perché questi oggetti sono costituiti da punti non significa che siano correlati in termini di comportamento.

Lasciando da parte che getter e setter non sono realmente un comportamento e dovrebbero essere evitati se possibile *, anche in questo caso i getter ei setter stanno facendo cose diverse, come suggerito dalle diverse firme. Questo dovrebbe chiarire, questi oggetti non condividono il comportamento oltre il senso più insignificante di memorizzazione dei punti.

(questo è un buon esempio del perché evitare getter e setter, poiché possono offuscare il vero comportamento di un oggetto e far sembrare che gli oggetti non correlati siano correlati perché hanno tutti metodi getter e setter)

    
risposta data 07.05.2014 - 17:33
fonte
0

Che ne dici di qualcosa di simile:

public interface ICoordinateValue {}

public class Value1D : ICoordinateValue {}
public class Value2D : ICoordinateValue {}
public class Value3D : ICoordinateValue {}


public interface IShape
{
    IEnumerable<ICoordinateValue> GetValues();
    void AddValue(ICoordinateValue value);
}
    
risposta data 06.05.2014 - 17:01
fonte