2013-04-14 12 views
7

Ho un semplice programma che disegna figure geometriche basate su dati del mouse forniti dall'utente. Ho una classe che gestisce il tracciamento del mouse (ottiene la lista con la cronologia dei movimenti del mouse) e una classe astratta chiamata Shape. Da questa classe deraglio alcune forme extra, come Circle, Rectangle, ecc. - e ognuna di esse sovrascrive la funzione Abstract Draw().Superamento del principio Open-Closed

Funziona tutto bene, ma il problema si presenta quando voglio che l'utente sia in grado di cambiare manualmente la forma desiderata . Ottengo i dati del mouse e so quale forma dovrei disegnare. Il problema è come fare in modo che il codice "sappia" quale oggetto dovrebbe creare e passare i parametri appropriati per il costruttore al parametro . A questo punto è anche impossibile aggiungere nuovi derivati ​​di forma, che è assolutamente sbagliato.

mi obiously non voglio uscire con un codice simile:

List<Shape> Shapes = new List<Shape>(); 
// somwhere later 

if(CurrentShape == "polyline"){ 
    Shapes.Add(new Polyline(Points)); 
} 
else if (CurrentShape == "rectangle"){ 
    Shapes.Add(new Rectangle(BeginPoint, EndPoint)); 
} 
// and so on. 

Il codice di cui sopra chiaramente vilates Open-Closed Principle. Il problema è che non ho alcuna idea su come superarlo. Il problema principale è che le diverse forme hanno costruttori con parametri diversi, il che rende molto più problematico.

Sono abbastanza sicuro che questo è un problema comune, ma non so come superarlo. Hai un'idea?

+2

Questo non è il "principio aperto-chiuso". Questo è solo polimorfismo –

+1

Bene, voglio che il codice della classe Shape sia chiuso per l'edizione e aperto per le estensioni, quindi penso che corrisponda al problema OCP. –

+0

Per quanto tu desideri che sia vero, non lo è! –

risposta

3

Chiede una fabbrica, ma non solo la fabbrica ma la fabbrica con i lavoratori iniettabili.

public class Context { 
    public Point BeginPoint; 
    public Point EndPoint; 
    public List Points; 

    whatever else 
} 

public class ShapeFactory { 

    List<FactoryWorker> workers; 

    public Shape CreateShape(string ShapeName, Context context) 
    { 
     foreach (FactoryWorker worker in workers) 
     if (worker.Accepts(ShapeName)) 
      return worker.CreateShape(context); 
    } 

    public void AddWorker(FactoryWorker worker) { 
     workers.Add(worker); 
    } 
} 

public abstract class FactortWorker { 
    public abstract bool Accepts(string ShapeName); 
    puboic Shape CreateShape(Context context); 
} 

public class PolyLineFactoryWorker : FactoryWorker { 

    public override bool Accepts(string ShapeName) { 
     return ShapeName == "polyline"; 
    } 

    public Shape CreateShape(Context context) { ... } 

} 

In questo modo il codice è aperto per le estensioni: i nuovi operai possono essere creati liberamente e aggiunti alla fabbrica.

+0

Ciao. Mi piacerebbe sapere, come è meglio dell'altra risposta estendere 'ShapeFactory' invece di iniettare lavoratori, se l'altra risposta usava anche un parametro' String ShapeName'? – Blueriver

+0

@Blueriver: questo renderebbe entrambe le risposte equivalenti. Ma non c'è una fabbrica che si estende nell'altra risposta. I produttori da lì sono malamente lavoratori da qui. La fabbrica stessa non dovrebbe mai essere modificata in quanto interromperà la parte chiusa dell'OCP. –

+0

Vedo. Grazie! – Blueriver

6

Quando è necessario creare oggetti che derivano tutti da una singola classe o implementare la stessa interfaccia, un approccio comune consiste nell'utilizzare uno factory. Nel tuo caso, tuttavia, una fabbrica semplice potrebbe non essere sufficiente, perché la fabbrica stessa deve essere estensibile.

Un modo per implementare è la seguente:

interface IShapeMaker { 
    IShape Make(IList<Point> points); 
} 
class RectMaker : IShapeMaker { 
    public Make(IList<Point> points) { 
     // Check if the points are good to make a rectangle 
     ... 
     if (pointsAreGoodForRectangle) { 
      return new Rectangle(...); 
     } 
     return null; // Cannot make a rectangle 
    } 
} 
class PolylineMaker : IShapeMaker { 
    public Make(IList<Point> points) { 
     // Check if the points are good to make a polyline 
     ... 
     if (pointsAreGoodForPolyline) { 
      return new Polyline(...); 
     } 
     return null; // Cannot make a polyline 
    } 
} 

Con queste Maker classi in mano, si può fare un registro di produttori (un semplice List<IShapeMaker>) passare attraverso i creatori passando loro i punti, e l'arresto quando torni una forma non nulla.

Questo sistema rimane estensibile, perché è possibile aggiungere un paio di NewShape e NewShapeMaker, e "loro collegare" in quadro esistente: una volta NewShapeMaker ottiene nel Registro di sistema, il resto del sistema diventa subito pronto a riconoscere e utilizzare il tuo NewShape.

+2

C'è uno svantaggio di questa implementazione - dal momento che lo stesso insieme di punti potrebbe essere accettato da più produttori, improvvisamente l'ordine dei produttori conta - cambiando l'ordine farebbe in modo che la fabbrica restituisca una forma diversa. L'implementazione originale è esente da questo problema, in quanto il client factory rende esplicita la forma desiderata. Questo sarebbe facilmente risolto passando questo parametro addizionale al metodo factory (vedi la mia risposta). –

+0

@WiktorZychla Il parametro extra fornisce un controllo esplicito al chiamante, ma rende anche necessario modificare il chiamante ogni volta che estendi il sistema aggiungendo una nuova forma (ovvero il chiamante deve "imparare" per passare il nome del chiamante nuova forma ogni volta che una nuova forma diventa disponibile). La mia lettura della domanda è che l'OP vuole evitare quel parametro extra. – dasblinkenlight

+0

Scrive "Conosco i dati del mouse e la forma che dovrei disegnare". Da questo, direi che il chiamante conosce la forma esatta. Credo che abbia una cassetta degli attrezzi di tutte le forme disponibili o qualcosa di simile. –

Problemi correlati