2016-11-22 4 views
0

Disons qu'il existe une classe Foo, qui définit son comportement à l'aide des fonctions F1, F2, F3. Il y a peu de clients qui étendent le comportement de Foo et le personnalise selon leurs besoins.Motif de conception à appliquer Filtre de personnalisation

Pour ce scénario avant son droit de mettre en œuvre comme

class Foo 
{ 
public: 
virtual void F1() { // default behavior } 
virtual void F2() { // default behavior } 
virtual void F3() { // default behavior } 
} 


class CustomFoo1 : public Foo 
{ 
public: 
void F1() override { // customized behavior } 
void F11() { // CustomFoo1's own methods } 
} 


class CustomFoo2 : public Foo 
{ 
public: 
void F1() override { // customized behavior } 
void F21() { // CustomFoo2's own methods } 
void F22() { // CustomFoo2's own methods } 
} 

droit? Mais cela n'est possible que si ma bibliothèque expose Class Foo pour d'autres clients pour créer CustomFoo1 et CustomFoo2. Je ne veux pas exposer ma mise en œuvre concrète, mais plutôt utiliser une interface que d'autres peuvent utiliser. Donc, je peux changer mon implémentation comme:

interface IFoo // Public interface 
{ 
    public: 
    virtual void F1() = 0; 
    virtual void F2() = 0; 
    virtual void F3() = 0; 
} 

Class Foo : public IFoo // Internal to library 
{ 
    public: 
    virtual void F1() override { // default behavior } 
    virtual void F2() override { // default behavior } 
    virtual void F3() override { // default behavior } 
} 

static IFoo* CreateFoo() { return new Foo(); } 

class CustomFoo1 : public IFoo 
{ 
public: 
    void F1() override { // Custom Behavior } 
    void F2() override { pFoo->F2(); // Need default so delegate call to default implementation } 
    void F3() override { pFoo->F3(); // Need default so delegate call to default implementation }  
    void F11() { // personal own implementation } 
private: 
IFoo* pFoo; 
} 

Même chose sera faite pour CustomFoo2. Mais ici, si vous voyez, je peux cacher mon implémentation concrète derrière l'interface, mais cela a amené mes clients à implémenter l'interface entière et à déléguer l'appel à l'implémentation par défaut de la bibliothèque si les classes personnalisées ne veulent pas remplacer comportement. Donc, je ne pense pas que ce soit une bonne solution non plus.

Cela me fait penser à la prochaine solution possible. Si vous regardez de plus près, les clients de la bibliothèque cherchent vraiment à personnaliser uniquement F1(). donc je déclare une autre interface dire IFooCustomizePolicy

interface IFooCustomizePolicy 
{ 
public: 
virtual void F1Customized() = 0; 
} 

Je modifierai IFoo comme:

interface IFoo // Public interface 
{ 
    public: 
    virtual void F1() = 0; 
    virtual void F2() = 0; 
    virtual void F3() = 0; 
    virtual void RegisterCustomizationPolicy(IFooCustomizePolicy* pPolicy) = 0; 
} 

Mise à jour la mise en œuvre du béton comme:

Class Foo : public IFoo // Internal to library 
{ 
    public: 
    void F1() override 
    { 
      if(pPolicy != nullptr) 
      pPolicy->F1Customized(); 
      else 
      // default behavior 
    } 
    void F2() override { // default behavior } 
    void F3() override { // default behavior } 
    void RegisterCustomizationPolicy(IFooCustomizePolicy* pPolicy) override 
     { this->pPolicy = pPolicy; } 

private: 
IFooCustomizePolicy* pPolicy; 
} 

et d'autres clients sera modifié comme suit:

class CustomFoo1 : public IFooCustomizePolicy 
{ 
public: 
    void F1Customized() override { // customized behavior } 
    void F11() { //CustomFoo1's own methods } 
} 

Maintenant, si vous voyez, avec cette approche, je suis capable de cacher ma classe concrète, mais capable de fournir un comportement par défaut au client avec la façon de le personnaliser. Maintenant, disons que les exigences changent et qu'un nouveau client arrive et demande que la nouvelle méthode soit personnalisable. Dans ce cas, je devrais mettre à jour Policy Interface, ce qui est acceptable, c'est acceptable. Mais avec ce que je devrais faire est de mettre à jour la méthode correspondante de Foo pour respecter la politique et ajouter la logique ci-dessous aux méthodes chaque fois qu'ils sont ajoutés à la politique.

if(policy present) then call policy customized method 
else continue with default behavior 

Si vous voyez ici, ce que je veux vraiment est une sorte de filtre, Si mon objet a la politique puis filtrer devrait appeler la méthode de la politique et ne doit pas poursuivre la mise en œuvre par défaut. Mais je ne suis pas sûr quel modèle appliquer et comment?

+0

Je pense qu'un modèle de conception de pont peut bien fonctionner ici afin que l'interface puisse être séparée de l'implémentation. – seccpur

Répondre

0

Ce que vous penchez vers «la composition plutôt que l'héritage» - ce qui est souvent une bonne chose. La douleur que vous rencontrez avec les méthodes par défaut est parce que vous ne l'avez pas prise assez loin.

Vous pouvez éviter de mettre les méthodes par défaut dans Foo par toujours ayant un F1Policy, mais la configuration initiale à une stratégie par défaut.

class Foo : public IFoo { 
public: 
    void F1() override { 
    f1Policy->f(); 
    } 
    void F2() override { 
    f2Policy->f(); 
    } 

    void setF1Policy(const shared_ptr<F1Policy> &newPolicy) { 
    f1Policy = newPolicy; 
    } 

protected: 
    shared_ptr<F1Policy> f1Policy(new DefaultF1Policy); 
    shared_ptr<F2Policy> f2Policy(new DefaultF2Policy); 
    ... 
}; 

Maintenant, vous ne stockez généralement pas d'état dans la stratégie par défaut, ce qui vous permet de réutiliser une instance. Ainsi, la construction va devenir quelque chose comme:

shared_ptr<F1Policy> f1Policy = DefaultF1Policy.instance; 
shared_ptr<F1Policy> f2Policy = DefaultF2Policy.instance; 

La prochaine chose que vous pourriez rencontrer est que les politiques ont besoin de leur f() pour pouvoir faire appel dans le F2() etc de la classe Foo. Vous pouvez y parvenir en passant un IFoo supplémentaire en tant qu'argument à leurs fonctions f().

struct F1Policy { 
    virtual void f(IFoo * foo)=0; 
}; 

struct DefaultF1Policy : public F1Policy { 
    void f(IFoo * foo) override { 
     foo->F2(); 
    } 
}; 

void Foo::F1() { 
    f1Policy->f(this); 
} 
0

Au lieu d'avoir les clients à mettre en œuvre la délégation eux-mêmes, vous pouvez fournir une autre classe de base pour vos clients à hériter de, qui transmet par défaut l'appel à la mise en œuvre appropriée. De cette façon, vos clients n'ont qu'à remplacer les fonctions dont ils ont besoin. Regardez ci-dessous

#include <memory> 
#include <iostream> 

// expose this to your clients 
struct interface { 

    virtual void f1() = 0; 
    virtual void f2() = 0; 
    virtual void f3() = 0; 

    virtual ~interface() = default; 

}; 

// this is your default implementation 
struct default_implementation 
: public interface { 

    void f1() override {std::cout << "default f1" << std::endl;} 
    void f2() override {std::cout << "default f2" << std::endl;} 
    void f3() override {std::cout << "default f3" << std::endl;} 
}; 

// this is a way for your client to create a pointer to your default implementation 
std::shared_ptr<interface> create_default_implementation() { 
    return std::make_shared<default_implementation>(); 
} 

// expose this to your clients too 
// this is the class your clients will inherit from 
// this essentially uses the decorator pattern and ensures that by default all f1, f2, f3 delegate to the appropriate implementation 
class client_base 
: public interface { 

    std::shared_ptr<interface> m_impl; 

public: 

    client_base(std::shared_ptr<interface> default_impl = create_default_implementation()) 
    : m_impl(default_impl){} 

    void f1() override { 
     m_impl->f1(); 
    } 

    void f2() override { 
     m_impl->f2(); 
    } 

    void f3() override { 
     m_impl->f3(); 
    } 
}; 

// now your clients can override whichever functions they feel like 
class client_foo1 
: public client_base { 

public: 
    using client_base::client_base; 

    void f1() override {std::cout << "foo1 f1" << std::endl;} 
    void f11() {/* put whatever here */ } 
}; 


class client_foo2 
: public client_base { 

public: 
    using client_base::client_base; 

    void f2() override {std::cout << "foo2 f2" << std::endl;} 
    void f12() {/* put whatever here */ } 
}; 

int main() { 

    client_foo1 foo1; 
    client_foo2 foo2; 

    foo1.f1(); // prints foo1 f1 
    foo1.f2(); // prints default f2 
    foo1.f3(); // prints default f3 

    foo2.f1(); // prints default f1 
    foo2.f2(); // prints foo2 f2 
    foo2.f3(); // prints default f3 

}