2009-06-24 5 views
7

Je voudrais construire une classe de base (abstraite) (appelons-la type::base) avec une fonctionnalité courante et une interface fluide, le problème auquel je fais face est le type de retour de toutes ces méthodesInterface fluide et héritage en C++

class base { 
    public: 
     base(); 
     virtual ~base(); 

     base& with_foo(); 
     base& with_bar(); 
    protected: 
     // whatever... 
    }; 

maintenant, je pourrais faire des sous-types, par exemple:

class my_type : public base { 
    public: 
     myType();   
     // more methods... 
    }; 

Le problème vient lorsque vous utilisez ces sous-types comme ceci:

my_type build_my_type() 
{ 
    return my_type().with_foo().with_bar(); 
} 

Ceci ne sera pas compilé car nous retournons la base au lieu de my_type.

Je sais que je pouvais:

my_type build_my_type() 
{ 
    my_type ret; 
    ret.with_foo().with_bar(); 

    return ret; 
} 

Mais je pensais comment puis-je mettre en œuvre, et je ne l'ai pas trouvé des idées valables, une suggestion?

+0

J'ai supprimé l'espace de nommage parce qu'il n'a rien (pour autant que je peux voir) à faire avec la question –

Répondre

4

Ce problème de "perdre le type" peut être résolu avec des modèles - mais c'est plutôt compliqué.

Par exemple.

class Pizza 
{ 
    string topping; 
public: 
    virtual double price() const; 
}; 

template <class T, class Base> 
class FluentPizza : public Base 
{ 
    T* withAnchovies() { ... some implementation ... }; 
}; 

class RectPizza : public FluentPizza<RectPizza, Pizza> 
{ 
    double price() const { return length*width; :) } 
}; 

class SquarePizza : public FluentPizza<SquarePizza, RectPizza> 
{ 
    ... something else ... 
}; 

Vous pouvez alors écrire

SquarePizza* p=(new SquarePizza)->withAnchovies(); 

Le modèle est qu'au lieu de

class T : public B 

vous écrire

class T : public Fluent<T, B> 

Une autre approche pourrait être de ne pas utiliser interface fluide sur les objets, mais sur les pointeurs au lieu:

class Pizza { ... }; 
class RectPizza { ... }; 
class SquarePizza { ... whatever you might imagine ... }; 

template <class T> 
class FluentPizzaPtr 
{ 
    T* pizza; 
public: 
    FluentPizzaPtr withAnchovies() { 
    pizza->addAnchovies(); // a nonfluent method 
    return *this; 
    } 
}; 

Utilisez comme ceci:

FluentPizzaPtr<SquarePizza> squarePizzaFactory() { ... } 

FluentPizzaPtr<SquarePizza> myPizza=squarePizzaFactory().withAnchovies(); 
+0

Pourriez-vous donner un exemple? Il pourrait être intéressant. – liori

+0

cf. l'édition. Cela peut être intéressant, je ne sais pas si je l'utiliserais personnellement, cependant ... – jpalecek

+0

Donc, vous passez réellement aux pointeurs. Ensuite, vous pouvez simplement utiliser un polymorphisme simple, sans CRTP, et retourner la base *. – liori

-2

La façon dont je le ferais en C#, et je crois qu'il travaillerait en C++ aussi est de fournir une implémentation par défaut pour with_foo() et with_bar() ... Pardonnez mon C#, mais:

class base { 
    virtual base with_foo() 
    { throw new NotImplementedException(); } 
    virtual base with_bar(); 
    { throw new NotImplementedException(); } 
} 
+0

Mais cela ne résout pas le problème - le problème est une incompatibilité de type, ce qui est hors de propos de la question . – jpalecek

-1

En C++ vous devriez conserver des pointeurs ou des références plutôt que des valeurs. Aussi, vous pourriez vouloir expliquer ce que vous entendez par "interfaces fluentes".

+2

http://en.wikipedia.org/wiki/Fluent_interface – liori

+1

Désolé - dès que je vois le nom "Martin" mon détecteur bullsh * t s'éteint à plein volume et je ne peux plus lire. Cela ne semble pas important si c'est un prénom ou un nom de famille. –

+0

Quel est le problème avec Martin Fowler? – Tobias

0

Une solution fonctionnerait comme ceci:

return *static_cast<my_type*>(&my_type().with_foo().with_bar()); 

L'utilisation static_cast dit essentiellement le compilateur «Je sais ce que je fais ici.

5

Vous devriez retournerez références/pointeurs, et vous ne devriez pas besoin de garder les informations de type.

class base { 
    public: 
    base(); 
    virtual ~base(); 

    base &with_foo(); 
    base &with_bar(); 
    protected: 
    // whatever... 
}; 

class my_type : public base { 
    public: 
    my_type();   
    // more methods... 
}; 

base *build_my_type() 
{ 
    return &new my_type()->with_foo().with_bar(); 
} 

Vous avez déjà un destructeur virtuel. Vraisemblablement, vous avez d'autres fonctions virtuelles.Accédez à tout via le type de base et les fonctions virtuelles qui y sont déclarées.

+0

Le problème est que je ne veux pas perdre le type. – blaxter