2011-09-07 7 views
6

Sto lavorando a un'applicazione basata su slide in C++. Ogni diapositiva ha una collezione slide-oggetti che può includere elementi come didascalia, pulsante, rettangolo, eccCome dovrei progettare un set di classi correlate dove solo alcune supportano una determinata operazione?

Solo alcuni di questi elementi supportano riempimento, mentre altri non lo fanno .

Qual è il modo migliore per implementare il riempimento per gli elementi della diapositiva in questo caso? Qui ci sono due modi in cui ho pensato di:

  1. creare un'interfaccia Fillable e implementare questa interfaccia per gli elementi di scorrimento che supportano riempire, mantenendo tutte le proprietà relative a riempire nell'interfaccia. Quando si scorre l'elenco degli elementi della diapositiva, dynamic_cast li in Fillable e, in caso di esito positivo, eseguire l'operazione relativa al riempimento.

  2. Fare una classe fill. Crea un puntatore fill una parte della classe elemento della diapositiva, assegna l'oggetto fill al puntatore fill per quegli oggetti che supportano il riempimento e per il resto lo mantengono nullo. Dare una funzione GetFill, che restituirà il fill per gli elementi se esiste altrimenti restituisce NULL.

Qual è l'approccio migliore per questo? Sono interessato alle prestazioni e alla manutenibilità.

risposta

3

Vorrei fare una combinazione dei due. Crea l'interfaccia Fillable e scegli il tipo di ritorno per il tuo metodo GetFill. Questo è meglio dell'approccio del cast dinamico. L'uso del cast dinamico per interrogare l'interfaccia richiede che l'oggetto della diapositiva reale implementa l'interfaccia se deve supportarlo. Con un metodo di accesso come GetFill, tuttavia, è possibile fornire un riferimento/puntatore a un altro oggetto che implementa l'interfaccia.È anche possibile restituire this se l'interfaccia viene effettivamente implementata da questo oggetto. Questa flessibilità può aiutare a evitare il gonfiarsi della classe e promuovere la creazione di oggetti componenti riutilizzabili che possono essere condivisi da più classi.

Modifica: Questo approccio funziona anche bene con il modello di oggetto nullo. Invece di restituire un puntatore nullo per gli oggetti che non supportano Fillable, è possibile restituire un oggetto no-op semplice che implementa l'interfaccia. Quindi non devi preoccuparti di controllare sempre i puntatori nulli nel codice client.

0

creare una classe di base SlideItem:

class SlideItem { 
    public: 
     virtual ~SlideItem(); 
     virtual void fill() = 0; 
}; 

Poi fare un'implementazione vuota per coloro che non si può riempire:

class Button : public SlideItem { 
    public: 
     void fill() { } 
}; 

E un'implementazione di riempimento adeguato per gli altri:

class Rectangle : public SlideItem { 
    public: 
     void fill() { /* ... fill stuff ... */ } 
}; 

e mettili tutti in un contenitore .. se vuoi riempirli basta chiamare tutti ... facile da m aintain .. e chi se ne importa delle prestazioni :)


Se hai davvero bisogno di codice veloce, la tua prima soluzione è sicuramente buona. Ma se lo fai in quel modo, assicurati di non doverlo lanciare ogni volta che vuoi riempirlo. Lanciali una volta e metti i puntatori in un contenitore fillabile. Quindi scorrere su questo contenitore fillable se si deve riempire.

Quindi, ancora una volta, IMHO ci metti troppo impegno in questo, senza un guadagno di prestazioni ragionevole. (Naturalmente non conosco la tua domanda, potrebbe essere giustificato .. ma di solito non lo è)

+2

Si chiama interfaccia fat, ed è un antipattern. –

+0

@Armen Tsirunyan: Ok, giusto. Se c'è un antipattern, deve esserci anche lo schema giusto per questo. Puoi certamente spiegarmelo :) Oh e se la tua soluzione contiene il doppio delle classi (adattatori o quant'altro) non mi interessa .. – duedl0r

+0

@ duedl0r: Temo di aver letto tutto il paragrafo sbagliato, scusa. –

0

Sembra che quello che stai cercando è vicino allo Capability Pattern. Il tuo n. 2 è vicino a questo modello. Ecco cosa farei:

Creare una classe di riempimento. Rendi il puntatore di riempimento una parte della classe di elementi della diapositiva, assegna l'oggetto di riempimento a riempire il puntatore solo per quegli oggetti che supportano il riempimento, per il resto lo mantengono nullo. Creare una funzione GetCapability (Capability.Fill), che restituirà il riempimento per gli elementi se esiste altrimenti restituisce NULL. Se alcuni dei tuoi oggetti già implementano un'interfaccia Fillable, puoi invece restituire il cast oggetto a un puntatore Fillable.

+0

Purtroppo hai ancora una volta una classe base grassa ... Questo è abbastanza sfortunato. –

0

Considerare la memorizzazione di Variant voci, ad esempio boost::variant.

È possibile definire un boost::variant<Fillable*,Item*> (è necessario utilizzare i puntatori intelligenti se si dispone di proprietà) e quindi avere un elenco di quelle varianti su cui eseguire iterazioni.

+0

La tua classe grassa diventa la variante ... Non mi piace questo approccio, soprattutto se si prevede che il numero di interfacce fill-like cresca. Ciò che non mi piace particolarmente è che di solito, hai "Fillable" ereditare "Item". Dovrai difenderlo meglio :) –

+0

@Alexandre C: La classe stessa non è mai stata modificata, e quindi i client che non richiedono la proprietà 'Fillable' non vedono mai il metodo' fill'. La classe "grassa" è quindi locale all'uso dell'interfaccia "grossa", e tutto va bene! (e sei anche veloce quanto la classe "grassa" era) Inoltre, il meccanismo può crescere localmente senza che sia necessario "Item" per implementare un'interfaccia completa di Visitor. –

0

La risposta è dipende.

Non vedo il punto nel dover ingombrare l'interfaccia di base con fill/get_fillable_instance/... se non tutti gli oggetti devono gestire il riempimento. Tuttavia, è possibile uscire solo con

struct slide_object 
{ 
    virtual void fill() {} // default is to do nothing 
}; 

ma dipende se si pensa fill dovrebbe apparire nella classe astratta oggetto diapositiva. Raramente dovrebbe tuttavia, a meno che non essere compilabili sia eccezionale.

Il getto dinamico può essere corretto nel caso in cui sia necessario fornire due classi distinte di oggetti (e non più di due), alcuni dei quali sono compilabili e l'altro non ha nulla a che fare con la fillabilità. In questo caso, ha senso avere due sotto gerarchie e utilizzare la trasmissione dinamica dove necessario.

Ho usato questo approccio con successo in alcuni casi ed è semplice e manutenibile, a condizione che la logica di invio non sia dispersa (cioè c'è solo uno o due punti in cui si esegue il cast dinamico).

Se si prevede di avere più di riempimento simile comportamento, allora dynamic_cast è una scelta sbagliata in quanto porterà a

if (auto* p = dynamic_cast<fillable*>(x)) 
    ... 
else if (auto* p = dynamic_cast<quxable*>(x)) 
    ... 

che è male. Se hai bisogno di questo, allora implementa un pattern Visitor.

0

Suggerisco di utilizzare un'interfaccia per le forme, con un metodo che restituisce un riempimento. Ad esempio:

class IFiller { 
public: 
    virtual void Fill() = 0; 

protected: 
    IFiller() {} 
    virtual ~IFiller() {} 
}; 

class IShape { 
public: 
    virtual IFiller* GetFiller() = 0; 

protected: 
    IShape() {} 
    virtual ~IShape() {} 
}; 

class NullFiller : public IFiller { 
public: 
    void Fill() { /* Do nothing */ } 
}; 

class Text : public IShape { 
public: 
    IFiller* GetFiller() { return new NullFiller(); } 
}; 

class Rectangle; 
class RectangleFiller : public IFiller { 
public: 
    RectangleFiller(Rectangle* rectangle) { _rectangle = rectangle; } 
    ~RectangleFiller() {} 

    void Fill() { /* Fill rectangle space */ } 

private: 
    Rectangle* _rectangle; 
}; 

class Rectangle : IShape { 
public: 
    IFiller* GetFiller() { return new RectangleFiller(this); } 
}; 

Trovo questo metodo più facile da mantenere e da estendere, mentre non introduce importanti problemi di prestazioni.

Problemi correlati