2010-11-20 17 views
0

Je suis en train de concevoir une interface pour mon projet et curieux de savoir si l'idée peut devenir réalité ou non. Voici la situation, Lors de l'exécution, je souhaite utiliser un tableau de pointeurs de classe de base pour émettre des commandes à différents objets dérivés. Différents objets dérivés ont une implémentation différente (fonctions virtuelles). Mon problème est le suivant: si ces objets ont différents niveaux de support de l'interface, comment puis-je éviter d'écrire une fonction vide?Design Pattern: interface uniforme pour les classes dérivées partiellement supportées

Par exemple, (mon code actuel)

Class Base { //this is the main interface 
public: 
    virtual void basicFun() = 0; 
    virtual void funA(int input1) {return;} 
    virtual void funB(int input2) {return;} 
    virtual void funC(int input3) {return;} 
} 

Class Derived1 : public Base { //this class support only funA() 
public: 
    void basicFun() {//....} 
    void funA(int input1) {//do something} 
} 

Class Derived2 : public Base { //this class support both funA() funB() 
public: 
    void basicFun() {//....} 
    void funA(int input1) {//do something} 
    void funB(int input2) {//do something} 
} 

Class Derived3 : public Base { //this class support all 
public: 
    void basicFun() {//....} 
    void funA(int input1) {//do something} 
    void funB(int input2) {//do something} 
    void funC(int input3) {//do something} 
} 

Hypothèse: pour un certain objet, la fonction non prise en charge ne serait jamais appelé. c'est-à-dire BasePtr-> funC() ne sera jamais appelé si l'objet pointé par basePtr est Derived1 ou Derived2. Le problème est:

  1. je dois définir une fonction vide soit dans la base ou dérivée si une interface uniforme est souhaitée
  2. Si les fonctions vides sont définis comme ci-dessus, le compilateur ne cesse de me avertir paramètres non référencés (INPUT1 ~ ENTRÉE3) . Bien sûr, je peux l'éteindre, mais je n'aime pas ça.

Alors, existe-t-il un modèle que je peux utiliser pour obtenir une interface uniforme sans définir de fonctions vides? J'y ai réfléchi pendant quelques jours. Cela semble impossible. Parce que funA() funB() et funC() doivent être dans l'interface pour que je puisse utiliser un tableau de pointeurs de base pour contrôler tous les objets, ce qui signifie que Derived1, funB() et funC() doivent être définis.

Merci, et merci beaucoup, et merci de partager vos idées.

Solti

Répondre

1

Les interfaces uniformes sont une bonne chose. Ce qui signifie que vous devez implémenter toutes les méthodes dans une interface, même si cela signifie que vous aurez des méthodes vides. Il n'y a aucun modèle de conception pour ce problème, parce que ce n'est pas un vrai problème en premier lieu. Pensez-y un instant: vous avez une interface Car, avec les méthodes Accelerate() et Brake(). Une classe dérivée de Car doit implémenter toutes les méthodes. Souhaitez-vous qu'un objet dérivé de Car implémente la méthode Accelerate() mais pas la méthode Brake()? Ce serait un étonnamment dangereux Car!

Une interface dans le contexte de la POO doit avoir un contrat bien défini auquel adhèrent les deux parties. En C++, ceci est appliqué dans une certaine mesure en exigeant que tous les virtual purs soient implémentés dans les classes dérivées. Essayer d'instancier une classe avec des méthodes virtuelles non implémentées entraîne des erreurs de compilation, en supposant qu'on n'utilise pas de trucs stupides pour contourner le problème.

Vous vous opposez à la création de méthodes vides car elles provoquent des avertissements du compilateur.Dans votre cas, omettez simplement le nom du paramètre:

void funC(int) // Note lack of parameter name 
{ 
} 

Ou commentaires le nom sur:

void funC(int /*input3*/) 
{ 
} 

Ou encore by using templates!

template<class T> void ignore(const T&) { } 

//... 

void funC(int input3) 
{ 
    ignore(input3); 
} 
+0

Salut, merci de répondre. Je peux comprendre ce que vous voulez dire par une voiture sans frein(). Cependant, si nous développons une sorte de logiciel OEM, nous pourrions utiliser le même code de niveau de bibliothèque mais un code de niveau AP différent. Donc j'espère garder l'interface inchangée et seulement modifier du code dans AP. Donc, pour certains clients, je veux leur donner une voiture sans frein(). et il est garanti voiture-> Frein() n'est jamais nécessaire. (Peut-être que le client fait des crash tests) Deuxièmement, j'ai essayé le nom du paramètre omettant le truc, et ça marche !!! Merci beaucoup! – Solti

0

Voici ce que je l'aurais fait: créer une aide classe de base avec des implémentations par défaut vides de toutes les méthodes virtuelles pures. Vous pouvez ensuite dériver de cette classe de base à la place de l'interface principale, puis sélectionner la méthode à remplacer.

// main interface, everything pure virtual 
struct IBase 
{ 
    virtual ~IBase() {} 
    virtual void basicFun() = 0; 
    virtual void funA(int input1) = 0; 
    virtual void funB(int input2) = 0; 
    virtual void funC(int input3) = 0; 
}; 

// helper base class with default implementations (empty) 
class Base : public IBase 
{ 
    void basicFun() {} 
    void funA(int input1) {} 
    void funB(int input2) {} 
    void funC(int input3) {} 
}; 

class Derived1 : public Base { //this class support only funA() 
    void funA(int input1) {//do something} 
}; 

class Derived2 : public Base { //this class support both funA() funB() 
    void funA(int input1) {//do something} 
    void funB(int input2) {//do something} 
}; 

class Derived3 : public IBase { //this class support all 
    void basicFun() {//....} 
    void funA(int input1) {//do something} 
    void funB(int input2) {//do something} 
    void funC(int input3) {//do something} 
}; 

int main() 
{ 
    // I always program to the interface 
    IBase& b1 = Derived1(); b1.basicFun(); b1.funA(); b1.funB(); b1.funC(); 
    IBase& b2 = Derived2(); b2.basicFun(); b2.funA(); b2.funB(); b2.funC(); 
    IBase& b3 = Derived3(); b3.basicFun(); b3.funA(); b3.funB(); b3.funC(); 
} 
0

Si vous avez vraiment besoin d'interfaces non uniformes (pensez avant), peut-être le modèle des visiteurs est la peine d'essayer:

struct Visitor; 

struct Base 
{ 
    virtual ~Base() {} 
    virtual void accept(Visitor& v) { v.visit(*this); } 
}; 

struct InterfaceA : Base 
{ 
    void accept(Visitor& v) { v.visit(*this); } 
    virtual void MethodA() = 0; 
}; 

struct InterfaceB : Base 
{ 
    void accept(Visitor& v) { v.visit(*this); } 
    virtual void MethodB() = 0; 
}; 

struct InterfaceA2 : InterfaceA 
{ 
    void accept(Visitor& v) { v.visit(*this); } 
    void MethodA(); // Override, eg. in terms of MethodC 
    virtual void MethodC() = 0; 
}; 

// Provide sensible default behavior. Note that the visitor class must be 
// aware of the whole hierarchy of interfaces 
struct Visitor 
{ 
    virtual ~Visitor() {} 
    virtual void visit(Base& b) { throw "not implemented"; } 
    virtual void visit(InterfaceA& x) { this->visit(static_cast<Base&>(x)); } 
    virtual void visit(InterfaceA2& x) { this->visit(static_cast<InterfaceA&>(x)); } 
    virtual void visit(InterfaceB& x) { this->visit(static_cast<Base&>(x)); } 
}; 


// Concrete visitor: you don't have to override all the functions. The unimplemented 
// ones will default to what you expect. 
struct MyAction : Visitor 
{ 
    void visit(InterfaceA& x) 
    { 
     x.MethodA(); 
    } 

    void visit(InterfaceB& x) 
    { 
     x.methodB(); 
    } 
}; 

utilisation:

Base* x = getSomeConcreteObject(); 
MyAction f; 
x->accept(f); 

ce invoqueront soit En fonction du type d'exécution x. Le visiteur tel qu'il est implémenté vous permet de ne pas surcharger de nombreuses fonctions et revient à l'action pour une classe de base si le comportement n'est pas implémenté. Éventuellement, si vous ne parvenez pas à fournir une action dans un visiteur pour une classe, celle-ci sera définie par défaut sur throw "not implemented".

L'exactitude des constantes peut vous obliger à distinguer entre Visitor et ConstVisitor, pour lesquelles toutes les méthodes accept seraient const.