lundi 21 novembre 2016

Design pattern to apply Customization Filter

Lets say there is a class Foo, which defines its behavior using F1, F2, F3 functions. There are few clients which extends behavior of Foo and customizes it as per their need.

For such scenario its straight forward to implement it as

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 }
}

Right? But this is possible only if my library exposes Class Foo for other clients to create CustomFoo1 and CustomFoo2. I don't want to expose my concrete implementation, rather I would use interface which others can use. So I can change my implementation as:

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;
} 

Same will be done for CustomFoo2.

But here if you see, I am able to hide my concrete implementation behind interface, but that caused my clients to actually implement whole interface and delegate call to default implementation from library if customized classes do not want to override anything from default behavior. So I don't think this is good solution either.

This makes me think about next possible solution. If you take close look at it, clients of library are really looking for customizing only F1(). So I am declaring another interface say IFooCustomizePolicy

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

I will modify IFoo as:

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

Update Concrete implementation as :

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; 
}

And other clients will be modified as:

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

Now if you see, with this approach I am able to hide my concrete class, yet able to provide default behavior to client along with way to customize it.

Now lets say, requirements changes and new client comes and asks for new method to be customizable. In this case, I would have to update Policy Interface, which is okay, that's acceptable. But along with that what I would need to do is to update Foo's corresponding method to respect policy and add below logic to methods everytime they are added to policy.

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

If you see here, what I really want is kind of filter, If my object have policy then filter should call policy method and should not continue with default implementation. But I am not sure what pattern to apply and how?

Aucun commentaire:

Enregistrer un commentaire