0

Recently after deriving from an abstract class, I realized I started having a lot of similar classes that only changed one method. I researched ways to avoid this and reached the conclussion that the Decorator pattern was probably the best way to avoid it.

So I must derive from the following class:

public abstract class SlowLoadableComponent<T> : LoadableComponent<T>
        where T : SlowLoadableComponent<T>

which in turn derives from:

public abstract class LoadableComponent<T> : ILoadableComponent
    where T : LoadableComponent<T>
{
    ...

    protected virtual void HandleLoadError(WebDriverException ex)
    {
    }

    protected abstract void ExecuteLoad();

    protected abstract bool EvaluateLoadedStatus();
    
    ...
}

So when deriving, we need to define ExecuteLoad() and EvaluateLoadedStatus(). Example:

public abstract class ExampleSlowLoadableComponent<T> : SlowLoadableComponent<T>
    where T : ExampleSlowLoadableComponent<T>
{
    protected IWebDriver driver;

    protected ExampleSlowLoadableComponent(IWebDriver driver, TimeSpan timeout, IClock clock)
        : base(timeout, clock)
    {
        this.driver = driver;
    }

    

    protected override bool EvaluateLoadedStatus()
    {
        // evaluate loaded status logic
    }

    protected override void ExecuteLoad()
    {
        // execute load logic
    }
}

The problem I am having is that in some classes ExecuteLoad() must be changed, while in others, EvaluateLoadedStatus() is the one that has to be changed, maybe both must be changed.

However I am at a total loss of how to do this.

For example, lets imagine we need to derive an abstract class from the previous ExampleSlowLoadableComponent a derived class that needs a IWebElement type defined in the class, and that ExecuteLoad() changes to use this IWebElement:

public abstract class SlowLoadableComponentWithButton<T> : ExampleSlowLoadableComponent<T>
    where T : SlowLoadableComponentWithButton<T>
{

    protected SlowLoadableComponentWithButton(IWebDriver driver, TimeSpan timeout, IClock clock)
        : base(driver, timeout, clock)
    {
    }

    protected abstract IWebElement Button { get; }    

    protected override bool EvaluateLoadedStatus()
    {
        // evaluate loaded status logic
    }

    protected override void ExecuteLoad()
    {
        Button.Click();
    }
    
}

And now, we need a new abstract class deriving from the previous one, but this time the IWebElement must be injected in the class constructor:

public abstract class SlowLoadableComponentWithButtonInjected<T> : SlowLoadableComponentWithButton<T>
    where T : SlowLoadableComponentWithButtonInjected<T>
{

    protected SlowLoadableComponentWithButtonInjected(
         IWebDriver driver,
         IWebElement button,
         TimeSpan timeout,
         IClock clock) : base(driver, timeout, clock)
    {
        Button = button;
    }

    protected override IWebElement Button { get; }    

    protected override bool EvaluateLoadedStatus()
    {
        // evaluate loaded status logic
    }
}

As you can see, the changes from one class to another are very small, usually just adding one step from the previous one, however I am not sure how to change this pattern into Decorator pattern?

Any help is appreciated.

elgato
  • 506
  • 1
  • 5
  • 20
  • 1
    Hard to tell from the code but at a guess you should be using "composition over inheritance" here. A tall inheritance hierarchy is rarely a good design. – Ian Mercer Jan 26 '21 at 23:33
  • I'd recommend focusing on the design here and remove the Autofac part from the equation (ie, remove it from the question/tags/title). While you might be _using_ Autofac, it's a distraction/red herring for the real issue here, which is how to design the classes. – Travis Illig Jan 27 '21 at 14:19
  • @TravisIllig you are right, I edited the question. – elgato Jan 27 '21 at 14:40
  • The Liskov substitution principle (https://stackoverflow.com/a/584732/8695782). I see it as: "only use inheritance when you want substitutability of implementations" or "you can't promise more or less than the base". I found that while some other SOLID principles can be negotiated/compromised against each other, this one leads to very weird issues in real cases when not respected. Inheriting Dog and Table from "FourLegged" is probably wrong. imho Decorator would only deepen inheritance explosion. Work on refining your abstractions first, then maybe Strategy? – Alexandru Clonțea Jan 28 '21 at 12:32

0 Answers0