0

FYI: I produce useful WinForms and console (C#), and PowerShell apps, but I’m weak on OO design. My professional programming career was exclusively procedural programing. Thru OJT (with heavy help from S.O.) I’ve learned the basics of OOP, .NET, WinForms, C#, and Word interop. As they say, “I know enough to be dangerous”.

I’ve hit a point in my “design-while-coding”, and I’m stuck.

Background and Question

I’m writing a Word VSTO add-in. I’m implementing its UI using Custom Task Panes (CTP). As you may know, for each CTP, you must define at least one User Control (UC) object. For the sake of discussion, my add-in will include 5 User Control objects

  1. Class P (implemented)
  • Derives from the Microsoft UserControl class
  • Hosts various production-related UI options controls
  1. Class T (partially implemented)
  • Derives from the Microsoft UserControl class
  • Hosts various test-related UI options controls
  1. Class F1, F2, and F3 (implemented)
  • Currently, each derives from class P
  • Each is a data-entry form hosting various UI controls relevant to data-entry scenario 1, 2, or 3.
  1. Notes
  • The user can display either the P form or the T form.
  • When the P form is displayed, the user can display and interact with form F1 or F2 or F3.

Here is where I’m stuck:

I’m implementing class T and I realize that like class P, when the user displays form T, they also need to display and interact with form F1 or F2 or F3. The business logic behind the controls on Form F1, F2, and F3 is the same regardless of whether form P or form T us being used.

My knee-jerk is to derive F1, F2, and F3 from both P and T, but C# doesn’t support multiple inheritance

I’ve read enough to know (but not fully understand) that an interface might solve my problem, but in general, it seems a little kludgy.

I’ve looked over the OO design patterns, and the Abstract Factory pattern looks hopeful, but I’m looking for a second opinion before I code myself down a rabbit hole because of this: … employment of this pattern … may result in unnecessary complexity and extra work in the initial writing of code. Additionally, higher levels of separation and abstraction can result in systems that are more difficult to debug and maintain.

Updated Question (7/28/2023):

What is the pattern or implementation approach you’d use to let F1, F2, and F3 inherit from both P and T? at design time or at run time?

Here’s a bonus question: what is the scenario (condition, motivation?) that would compel a programmer to implement a C# interface?

VA systems engineer
  • 2,856
  • 2
  • 14
  • 38
  • Regarding interfaces try Reading about dependency injection – JTIM Jul 18 '23 at 20:57
  • I appreciate the answer, but I intended `"The business logic behind the controls on Form F1, F2, and F3 is the same regardless of whether form P or form T us being used"` to just be a clarifying comment and not the main question. I've edited my post to clarify my question. – VA systems engineer Jul 28 '23 at 11:59

2 Answers2

0

The business logic behind the controls on Form F1, F2, and F3 is the same regardless of whether form P or form T us being used

I believe you can decouple a business logic to a separate class and inject it in your forms such as F1, F2 and F3. After decoupling business logic, it is possible to use MVP pattern in Winforms.

You can decouple and extract business logic by creating an abstraction and its implementation.

This is an abstraction of your business logic. It is kind of a Dependency inversion Principle in a nutshell:

public interface IMySharedBusinessLogic
{
    void SharedBusinessBehavour_1();

    void SharedBusinessBehavour_2();

    void SharedBusinessBehavour_3();
}

And this is the concrete implementation of the abstrasction:

public class MySharedBusinessLogic : IMySharedBusinessLogic
{
    public void SharedBusinessBehavour_1()
    {
        throw new NotImplementedException();
    }

    public void SharedBusinessBehavour_2()
    {
        throw new NotImplementedException();
    }

    public void SharedBusinessBehavour_3()
    {
        throw new NotImplementedException();
    }
}

And then just inject your dependencies like this:

//using Microsoft.Extensions.DependencyInjection;
public partial class Form1 : Form
{
    private readonly IMySharedBusinessLogic mySharedBusinessLogic;

    public Form1(IMySharedBusinessLogic mySharedBusinessLogic)
    {
        InitializeComponent();
        this.mySharedBusinessLogic = mySharedBusinessLogic;         
        MessageBox.Show(
            mySharedBusinessLogic.SharedBusinessBehavour_1());
    }
}
StepUp
  • 36,391
  • 15
  • 88
  • 148
0

The general solution to avoid a multi-inheritance scenario is called composition.
And because you are the author of all participating types you can even consider to refactor your types to the relevant types P and T into one inheritance tree (not recommended).

We can generally say that it is not natural that a type must inherit from more than one superclass. While multi-inheritance is primarily not supported because of compiler issues (compilers are usually required to be deterministic), multi-inheritance is plainly wrong in terms of semantics.
Fore example, TypeX should inherit from Type1 and TypeA: if TypeX is Type1 and TypeX is also TypeA then both Type1 and TypeA should already be children of the same inheritance tree. Usually they are not because they have nothing in common.
It is semantically wrong to inherit from both Car and Submarine to create a Spaceship just because you need particular functionality of each.
It's semantically wrong even when multi-inheritance would be allowed. Inheritance is not always the solution to reuse functionality.
Inheritance is always a technique primarily meant to add specialization to a generalized super type and not to reuse functionality.
While Car is the most generalized type a RaceCar represents a specialization. It's therefore semantically correct to let RaceCar derive from Car because a RaceCar is really a Car.

But how to design the aforementioned Spaceship when you need particular functionality of the Car e.g. the cockpit, and functionality of the Submarine e.g. the air conditioning system? ==> Composition.

Although every beginner believes inheritance is the core and must-use feature of OO programming, composition is the more natural and intuitive technique.
In fact most objects in nature are composed of other objects.

For example, a hot air balloon is composed of a basket (gondola), some rigging and the bag that holds the air, and a source of heat that burns propane gas. The air itself is composed of molecules. A molecule is composed of two or more atoms. Each atom is composed of the nucleus, that consists of protons and neutrons and is surrounded by a cloud of electrons. You can do this with every object. Object are composed of other objects that themselves are compositions.

To compose our Spaceship wen have to create the objects we can compose it of. This means, we must extract the cockpit from the Car and create a Cockpit class. Now Car and Spaceship can both use a Cockpit. Same applies to the air conditioning system: we extract the functionality to a dedicated AirConditioningSystem class and enable both the Submrine and the Spaceship to use it. Spaceship can now use the best of both types without being forced to inherit from them (i.e. create a very unnatural relationship).

It's difficult to tell which of the two solutions (merge types into a common inheritance tree vs composition) is the better for your case as you haven't shared enough details.
Composition expresses a has a relationship: House has a Basement and has a Stairway. Inheritance expresses a is a relationship: Warehouse is a House. You have to decide which technique expresses the relationship between your types best.

Usually you will always use a mix of composition and inheritance. A subclass inherits functionality from a superclass and at the same time adds more functionality by using other objects.

Solution 1: Composition

Your intent is to let F1, F2 and F3 extend P and T. This means that F1, F2 and F3 use functionality of P AND T.

Applying the spaceship example to your problem would lead us to extract the reusable functionality into a new class or even classes.
In your scenario we will end up with at least two classes: Q, that contains the extracted functionality of P that we want to reuse, and U, that contains the extracted functionality of T.

The final class design is as follows:

Q.cs
The reusable functionality extracted from P.

class Q
{
  // Functionality that was previously implemented in P
  public void DoSomething()
  {}
}

U.cs
The reusable functionality extracted from T.

class U
{
  // Functionality that was previously implemented in T
  public void DoSomething()
  {}
}

P.cs
Derives from the Microsoft UserControl class and is composed of Q.

class P : UserControl
{
  private Q QFunctionality { get; }

  // Use the constructor to actually compose the instance
  // either by creating the dependencies locally 
  // or by requesting the via constructor parameters
  public P()
    => this.QFunctionality = new Q();

  public void ExecuteSomeExternalFunctionality()
    => this.QFunctionality.DoSomething();
}

T.cs
Derives from the Microsoft UserControl class and is composed of U.

class T : UserControl
{
  private U UFunctionality { get; }

  // Use the constructor to actually compose the instance
  // either by creating the dependencies locally 
  // or by requesting the via constructor parameters
  public T()
    => this.UFunctionality = new U();

  public void ExecuteSomeExternalFunctionality()
    => this.UFunctionality.DoSomething();
}

F1.cs
Example code for F1, F2 and F3.

class F1
{
  // Functionality extracted from P
  private Q QFunctionality { get; }

  // Functionality extracted from T
  private U UFunctionality { get; }

  // Use the constructor to actually compose the instance
  // either by creating the dependencies locally 
  // or by requesting the via constructor parameters
  public F1()
  {
    this.QFunctionality = new Q();
    this.UFunctionality = new U();
  }

  public void ExecuteSomeQExternalFunctionality()
    => this.QFunctionality.DoSomething();

  public void ExecuteSomeUExternalFunctionality()
    => this.UFunctionality.DoSomething();
}

Solution 2: Inheritance

Although it appears that Pand T are not related, we can still use inheritance by merging P and T into a common type hierarchy. This eliminates the multi-inheritance scenario.

Without knowing more details about your classes I'm pretty sure that inheritance is the wrong choice, semantically and contextually.
In other words F1is not P and F1is not T. Furthermore, P is not T. As a result F1 would therefore inherit functionality it is not supposed to have.

If inheritance would reflect the correct relationship you had already used it intuitively.

But because there are cases where this transformation makes sense I will continue to act like your scenario is one of those just to show how to convert multi-inheritance to single-inheritance (in cases where you have full access to the sources in order to refactor the type hierarchy).

This example also shows why composition is the better solution. If you compare both solutions, you will instantly see that inheritance for the only purpose to provide functionality to unrelated types is just wrong.

This example, the ugliness of the class design in particular, shows that inheritance is much more than simply making functionality available.
Again, inheritance is about specialization of a type hierarchy, from the root to the leafs of the inheritance tree, from the generalized type to the most specialized types.
And semantics help to ensure that we are not violating the specialization. The Liskov Substitution principle (LSP) is another good principle that helps to identify when inheritance must be avoided.

P.cs
Derives from the .NET UserControl.

class P : UserControl
{
  // Additional P functionality
}

T.cs
Now, T derives from the .NET UserControl class by extending P.
This is where the design starts to smell.

class T : P
{
  // Additional T functionality
}

F1.cs
By letting F1, F2 and F3 derive from T it inherits the desired functionality from P and T - but also from UserControl. Additionally, F1, F2 and F3 potentially inherits a lot of functionality from P and T that they are not supposed to have. At this point the wrong semantics have become obvious.

This is where the code smell (or design smell) becomes obnoxious.

class F1 : T
{
}

To answer your question regarding interfaces:

"what is the scenario (condition, motivation?) that would compel a programmer to implement a C# interface?"

"Implementing an interface" means to provide or write a class that implements the contract, the public members defined by the interface. If this is really what you mean, then you must know that interfaces are like abstract classes: you can't create instances of abstract classes or interfaces. In order to create an instance you must implement an interface or extend the abstract class.

If you meant why to use interfaces, I recommend to look up the SOLID principles. The letters "I" (Interface Segregation principle) and "D" (Dependency Inversion principle) give you good and important idea how to use interfaces. Interfaces are an alternative to inheritance and can be used like base types in order to enable polymorphism. Interfaces are like abstract classes except they don't allow for virtual and protected members because they are not allowed to define implementations (except for default members staring with C# 8.0), constructors or derive from classes.