winforms 你使用什么模式或实现方法来实现多重继承?

6yoyoihd  于 2023-08-07  发布在  其他
关注(0)|答案(2)|浏览(94)
  • 仅供参考:我生产有用的WinForms和控制台(C#),以及PowerShell应用程序,但我在OO设计方面很弱。我的专业编程生涯完全是过程式编程。通过OJT(在S.O.的大力帮助下),我学会了OOP,.NET,WinForms,C#和Word互操作的基础知识。就像他们说的,“我知道的足够危险了”。

我已经达到了我的“边编码边设计”的一个点,我卡住了。

背景及问题

我正在编写Word VSTO加载项。我使用自定义任务窗格(CTP)实现其UI。您可能知道,对于每个CTP,必须至少定义一个用户控制(UC)对象。为了便于讨论,我的加载项将包括5个用户控件对象

  1. P级(已实施)
  • 从Microsoft UserControl类派生
  • 承载各种与生产相关的UI选项控件
  1. T级(部分实施)
  • 从Microsoft UserControl类派生
  • 承载各种与测试相关的UI选项控件
  1. F1、F2和F3类(已实现)
  • 目前,每个都派生自类P
  • 每一个都是一个数据输入窗体,承载与数据输入方案1、2或3相关的各种UI控件。

1.注记

  • 用户可以显示P表单或T表单。
  • 当显示P表单时,用户可以显示表单F1或F2或F3并与表单F1或F2或F3交互。
    这就是我被困住的地方

我正在实现类T,我意识到,像类P一样,当用户显示表单T时,他们也需要显示表单F1或F2或F3并与之交互。无论使用的是表单P还是表单T,表单F1、F2和F3上的控件背后的业务逻辑都是相同的。
我的直觉是从P和T派生F1、F2和F3,但C#不支持多重继承
我已经读了足够多的书,知道(但不是完全理解)一个接口可能会解决我的问题,但总的来说,它似乎有点笨拙。
我已经看过了OO设计模式,抽象工厂模式看起来很有希望,但在我把自己编码进兔子洞之前,我正在寻找第二种意见,因为:… 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.

更新问题(7/28/2023):
你会使用什么模式或实现方法来让F1、F2和F3从P和T继承?在设计时还是在运行时?

这里有一个额外的问题:什么是情景(条件、动机?),这将迫使程序员实现一个C#接口?

4ktjp1zp

4ktjp1zp1#

无论使用的是表单P还是表单T,表单F1、F2和F3上的控件背后的业务逻辑都是相同的
我相信你可以将decouple a business logic注入到一个单独的类中,并将其注入到你的表单中,比如F1F2F3。在解耦业务逻辑之后,可以使用MVP pattern in Winforms
您可以通过创建抽象及其实现来解耦和提取业务逻辑。
这是业务逻辑的抽象。简单地说,它是一种Dependency inversion Principle

public interface IMySharedBusinessLogic
{
    void SharedBusinessBehavour_1();

    void SharedBusinessBehavour_2();

    void SharedBusinessBehavour_3();
}

字符串
这是抽象的具体实现:

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


然后是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());
    }
}

w41d8nur

w41d8nur2#

避免多重继承情况的一般解决方案称为组合。
由于您是所有参与类型的作者,因此您甚至可以考虑将您的类型重构为相关类型PT,并将其合并到一个继承树中(不推荐)。
我们一般可以说,一个类型必须从多个超类继承是不自然的。虽然由于编译器的问题(通常要求编译器具有确定性),多重继承主要不受支持,但就语义而言,多重继承显然是错误的。
例如,TypeX应该继承自Type1TypeA:如果TypeXType1并且TypeX也是TypeA,则Type1TypeA都应该已经是同一继承树的子。通常情况下,他们不是,因为他们没有任何共同点。
从语义上讲,从CarSubmarine继承来创建Spaceship是错误的,因为您需要每一个的特定功能。
即使允许多重继承,这在语义上也是错误的。继承并不总是重用功能的解决方案。
继承始终是一种技术,主要是为了将 specialization 添加到通用超类型,而不是重用功能。
虽然Car是最一般化的类型,但RaceCar表示一个专门化。因此,让RaceCarCar派生在语义上是正确的,因为RaceCar实际上是Car
但是,当您需要Car的特定功能(例如,驾驶舱,以及Submarine的功能,例如空调系统吗==>成分。
虽然每个初学者都相信继承是OO编程的核心和必须使用的特性,但是组合是更自然和直观的技术。
事实上,自然界中的大多数物体都是由其他物体组成的。
例如,一个热气球由一个吊篮(吊篮)、一些索具和装空气的袋子以及一个燃烧丙烷气体的热源组成。空气本身是由分子组成的。一个分子由两个或多个原子组成。每个原子都是由原子核组成的,原子核由质子和中子组成,周围是电子云。可以对每个对象执行此操作。对象是由其他对象组成的,这些对象本身就是组合。
为了组成我们的Spaceship,我们必须创建可以组成它的对象。这意味着,我们必须从Car中提取驾驶舱,并创建一个Cockpit类。现在,CarSpaceship都可以使用Cockpit。同样适用于空调系统:我们将该功能提取到专用的AirConditioningSystem类中,并使SubmrineSpaceship都能够使用它。Spaceship现在可以使用这两种类型中最好的一种,而不必强制继承它们(即创建非常不自然关系)。
很难判断这两种解决方案(将类型合并到一个公共继承树中还是组合)中哪一种更适合您的情况,因为您没有分享足够的细节。
作文表达了一种 * 有 * 的关系:House有一个Basement,还有一个Stairway.继承表示一个 * 是一个 * 关系:Warehouse是一个House。你必须决定哪种技术最能表达你的类型之间的关系。
通常,您将始终混合使用组合和继承。子类继承了超类的功能,同时通过使用其他对象添加了更多的功能。

解决方案1:组成

您的意图是让F1F2F3延伸PT。这意味着F1F2F3使用P AND T的功能。
将太空船的例子应用到您的问题中,将引导我们将可重用的功能提取到一个新的类或甚至类中。
在您的方案中,我们将至少使用两个类:Q,其中包含我们要重用的P的提取功能;以及U,其中包含T的提取功能。
最终的类设计如下所示:

数量cs

P中提取的可重用功能。

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

字符串

美国cs

T中提取的可重用功能。

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

邮政编码

衍生自Microsoft UserControl类别,并由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

衍生自Microsoft UserControl类别,并由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文件

F1F2F3的示例代码。

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

解决方案2:继承权

虽然PT看起来并不相关,但是我们仍然可以通过将PT合并到一个公共的类型层次结构中来使用继承。这就消除了多重继承的情况。
在不了解更多关于类的细节的情况下,我很确定继承在语义和上下文上都是错误的选择。

换句话说,F1不是PF1也不是TP不是T。因此,F1将继承它不应该具有的功能。
如果继承反映了正确的关系,那么您已经直观地使用了它。
但是因为有些情况下这种转换是有意义的,我将继续表现得像你的场景一样,只是为了展示如何将多继承转换为单继承(在你可以完全访问源代码以重构类型层次结构的情况下)。
这个例子也说明了为什么组合物是更好的解决方案。如果你比较这两种解决方案,你会立刻发现,继承的唯一目的是为不相关的类型提供功能是错误的。
这个例子,特别是类设计的丑陋,表明继承不仅仅是简单地使功能可用。
同样,继承是关于类型层次结构的专门化,从继承树的根到叶子,从一般化的类型到最专门化的类型。
语义有助于确保我们没有违反专门化。Liskov替换原则(LSP)是另一个很好的原则,它有助于确定何时必须避免继承。

邮政编码

从.NET UserControl派生。

class P : UserControl
{
  // Additional P functionality
}

*T.cs

现在,T通过扩展P从.NET UserControl类派生而来。
这就是设计开始发臭的地方。

class T : P
{
  // Additional T functionality
}

F1.cs文件

通过让F1F2F3T派生,它继承了PT的所需功能,但也继承了UserControl的功能。此外,F1F2F3可能会从PT继承许多原本不应该有的功能。在这一点上,错误的语义已经变得明显。
这就是代码气味(或设计气味)变得令人讨厌的地方。

class F1 : T
{
}


要回答有关接口的问题,请执行以下操作:

  • “什么是情景(条件,动机?),这将迫使程序员实现一个C#接口?“*

“实现一个接口”意味着提供或编写一个实现合约的类,即接口定义的公共成员。如果这真的是你的意思,那么你必须知道接口就像抽象类:你不能创建抽象类或接口的示例。为了创建一个示例,你必须实现一个接口或者扩展抽象类。
如果你想知道为什么要使用接口,我建议你查阅SOLID原则。字母“I”(接口隔离原则)和“D”(依赖反转原则)为您提供了如何使用接口的良好而重要的想法。接口是继承的替代方法,可以像基类型一样使用,以启用多态性。接口类似于抽象类,除了它们不允许虚拟和受保护的成员,因为它们不允许定义实现(除了从C# 8.0开始的默认成员),构造函数或从类派生。

相关问题