1

Problem: Have two classes that already inherit from one another in the Windows.Forms framework (BindingNavigator and ToolStrip), so can't insert inheritance. What are the options, or best option, for extending two different classes with the same functionality (for UI consistency)?

I've had some success with decorator, but that requires casting to the shared interface.

I've managed to get it working using a variation of a strategy pattern, together with the c# 8 default interface. Each extended class has a helper object with all the added functionality. I extracted an interface from the helper class, added a helper parameter to that, and the extended classes implement that. The Helper is passed the parent, while the interface remaps methods so they appear direct. (Add() => Helper.Add()). This way all the functionality shows on the extended class as if it was inherited, and the classes can be completely unrelated.

public class ToolStripCustomUI
{
    private ToolStrip _toolStrip;
    public ToolStripButton ButtonAdd { get; set; } = new();
    public ToolStripButton ButtonDelete { get; set; } = new();
    // more controls
    public ToolStripCustomUI(ToolStrip toolStrip)
    {
        _toolStrip = toolStrip;
        Setup();
    }
    public void Setup() { // do stuff }
    // More methods
}
public interface ICustomToolStrip
{
    public ToolStripButton ButtonAdd 
    { 
        get => CustomUI.ButtonAdd; 
        set => CustomUI.ButtonAdd = value; 
    }        
    public ToolStripButton ButtonDelete 
    { 
        get => CustomUI.ButtonDelete; 
        set => CustomUI.ButtonDelete = value; 
    }
    // more controls
    ToolStripCustomUI CustomUI { get; }
    public void Setup() => CustomUI.Setup();
    // Other methods
}
public class ToolStripDecorator : ToolStrip, ICustomToolStrip
{
    public ToolStripCustomUI CustomUI 
       => new ToolStripCustomUI(this);
    public ToolStripDecorator() 
       => CustomUI.Setup();
}
public class CustomBindingNavigator : BindingNavigator, ICustomToolStrip
{
    public ToolStripCustomUI CustomUI => new 
    ToolStripCustomUI(this);
    public CustomBindingNavigator() : base(true)
    {
        Items.Remove(AddNewItem);
        Items.Remove(DeleteItem);
        CustomUI.Setup();
    }
}

Is there a better more correct approach?

Dan
  • 51
  • 5
  • 1
    It would probably help to show the code you're describing. Have you heard [Prefer composition over inheritance?](https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance) – jaco0646 Feb 17 '22 at 17:46
  • @jaco0646 it almost sounds like OP is using composition with the helper object that is injected? Without the code, it's too hard to know. Also, I don't understand "classes that already inherit from one another" (I assume ONE inherits from the other). – Fuhrmanator Feb 18 '22 at 16:50
  • @Fuhrmanator Yes, a helper object that encapsulates all the extended functionality. I have form subsections each having a toolstrip or bindingnavigator (depending on the need), and wanted to use the decorator pattern to choose which, and finally wrap the section creation in a builder pattern since there's many other such interchangeable components. – Dan Feb 21 '22 at 23:50
  • Seems like a good case for the visitor pattern. https://en.wikipedia.org/wiki/Visitor_pattern – fanuc_bob Mar 14 '22 at 17:39
  • @Kopfs - Please do not post answers in the question itself. If you have an answer post it as such, then mark it as the answer. Answered question hold more weight and it will help future readers. Please post an answer and roll-back the question. – Enigmativity Apr 11 '22 at 03:33

2 Answers2

1

Solution I settled on was not to extend the ToolStrip or BindingNavigator directly at all. Instead, I created a new CustomToolStrip class that has a ToolStrip object as a property, and added all the new functionality to the Toolstrip object. I then created a CustomBindingNavigator class that extends CustomToolStrip, and adds all the BindingNavigator specific functionality. The ToolStrip property I overrode to make it a BindingNavigator.

In CustomToolStrip

private virtual ToolStrip ToolStrip { get; set; } = new ToolStrip();

In CustomBindingNavigator

private override ToolStrip ToolStrip {get; set;} = new BindingNavigator();

The toolstrip could then be injected to become a decorator pattern.

Dan
  • 51
  • 5
0

Here is an approach that uses an extension method that targets your custom interface. Extension methods are a special kind of static methods on a static class that allow you to attach new functionality to any class or interface.

more info on extension methods here

I stripped out any implementation details to demonstrate how the types connect.

public class ToolStrip { }
public class BindingNavigator { }
public static class CustomToolStripExtensions
{
    public static void Setup<T>( this T target )  where T: ICustomToolStrip
    {
        // do setup work here!
    }
}
public interface ICustomToolStrip { }
public class ToolStripDecorator : ToolStrip, ICustomToolStrip
{
    public ToolStripDecorator()
    {
        this.Setup<ToolStripDecorator>();
    }
}
public class CustomBindingNavigator : BindingNavigator, ICustomToolStrip
{
    public CustomBindingNavigator()
    {
        this.Setup<CustomBindingNavigator>();
    }
}
Glenn Ferrie
  • 10,290
  • 3
  • 42
  • 73
  • 1
    I have this working based on the original post, but if time permitted would refactor based on this post. I've had to use some type checking in places which could be replaced with generic extension methods as detailed here. – Dan May 31 '22 at 00:59