【问题标题】:Best way to eliminate inheritance "magic" in C# winforms?在 C# winforms 中消除继承“魔法”的最佳方法?
【发布时间】:2013-10-07 11:47:10
【问题描述】:

我正在开发一个遗留应用程序,由于继承而存在一些缺陷,但我正在努力正确解决它。

目前 WinForms 的结构如下所示:

  • BaseForm
  • ListViewForm : BaseForm
  • ListViewFormReadOnly : ListViewForm
  • ListViewFormWithDetailForm : ListViewForm
  • DetailForm : BaseForm
  • ConcreteForm : ListViewFormWithDetailForm

BaseForm 中有一个方法叫做 protected virtual void InitializeMyStuff(),它会在继承的实例中被覆盖。

例如

public class BaseForm {
    public BaseForm() {
       //.. do stuff

       //.. do other stuff like initialize DB connection or read app.config values and initialize properties..
}

public virtual void InitializeMyStuff() {
        throw new NotImplementedException();
    }
}

public class ListViewForm : BaseForm {
    protected BindingSource GridBindingSource { get; set; }

    public ListViewForm {
       //do special stuff like adding the grid and some buttons
    }
}
public class ConcreteForm : ListViewForm {
public override void InitializeMyStuff() {
        GridBindingSource = my_bindingSource;
        SomeOtherUsefulProperty = myValue;
        Foo = new Bar();
        // etc.
    }
}

//Usage:
var myForm = new ConcreteForm();
myForm.InitializeMyStuff();

您可以想象,这会产生一些问题,例如: -“此时我必须设置哪些内容才能使表单正常工作” - “什么东西可能还没有初始化?” - “我可以使用哪些属性和方法调用” 以及关于那个魔法黑匣子中可能发生的其他一些有趣的想法。

我怎样才能重构它,以便更清楚地知道发生了什么?请记住,这是一个包含大约 150 个或更多具体表格的项目。

我最初的想法是将GridBindingSource 之类的神奇属性封装到一个对象中(例如FormConfiguration),并在BaseForm 中将其设为私有。

例如类似的东西

public class BaseForm {
    private FormConfigObject _formConfig = new FormConfigObject();

    protected override void OnLoad()
    {
        InitializeMyStuff(_formConfig);
    }

    protected virtual void InitializeMyStuff(FormConfigObject config)
    {}
}

我在这里遇到的问题是:ListForm 的 FormConfig 对象必须具有其他属性,例如 GridBindingSource 但我不能只将派生类中的签名更改为 ListFormConfigObject 而不是FormConfigObject..

任何人都可以提出可能的解决方案来摆脱这种困境吗?

// 编辑:将代码理顺到实际发生的情况,并消除构造函数违规中的虚拟调用。

【问题讨论】:

  • 这也违反了CA2214 规则:msdn.microsoft.com/en-us/library/ms182331.aspx - 不应在构造函数中调用虚方法。
  • 你有什么好的理由来改变它吗?起初我的想法是完全删除 InitializeMyStuff,在 ctor 中进行虚拟调用是个坏主意,但这是很多工作。
  • 这个问题可能更适合codereview.stackexchange.com
  • 是的,虚拟调用实际上隐藏在其他方法的层次结构中,实际应用程序要复杂得多。对不起,我的示例代码中的那个错误:/
  • BaseForm 实际上是 Form(或 Control)类型吗?如果是这样,您或许可以将对InitialiseMyStuff() 的调用移动到OnCreateControl() 的实现中,这样可以避免在构造函数中调用虚方法的问题,这也意味着您可以确保所有包含的控件都已创建到那时。

标签: c# .net winforms design-patterns inheritance


【解决方案1】:

主要问题是:BaseForm 内部是否有任何对象:

  • 需要在BaseForm的构造函数中初始化
  • 取决于子类的具体实现

如果存在这样的对象,那么它们可能应该被设为多态,并从子类传递到BaseForm 的构造函数中。

一个简单的例子,关于许多可能的场景:

abstract class RandomPicture
{
    public RandomPicture()
    {
        shapes = new List<Shape>();
        InitializeRandomShapes();

        // do some initial drawing calculations
    }

    protected abstract void InitializeRandomShapes();

    protected List<Shape> shapes;
}

//... subclasses initialize the shapes

这可以改成:

abstract class RandomPicture
{
    public RandomPicture(AbstractShapeCollection shapeCollection)
    {
        shapes = shapeCollection;

        // do some initial drawing calculations
    }

    private AbstractShapeCollection shapes;
}

现在子类通过抽象对象提供所需的信息,因此基类可以继续它的任务。

像这样将信息拆分为各种对象是一个很好的重构开始,因为您创建了更多更小的对象,这些对象更易于测试和管理,并揭示了您遇到的混乱的底层结构。它还有助于减少违反Single Responsibility Principle 的次数。

【讨论】:

  • 我编辑了我的问题,以整理出编写示例时所犯的一些错误。构造函数中实际上没有虚拟调用,因为初始化是从外部调用的......:/
  • @bberger 我已从答案中删除了有关规则的信息。但我认为它几乎保持不变——重构为更小的对象。 OnLoad 类似于构造函数,所以原则上我会建议相同的场景。
  • 是的,当我可以从头开始时,这就是我会做的事情 - 但这对于公司中没有人会批准的重构来说太过分了:/我需要另一个解决方案:/
  • @bberger 从头开始​​完全重构几乎不被推荐。在执行新功能时一次提取一个对象。当您出于业务原因需要对其进行修改时,在本地修复代码。重写所有工作而没有任何其他目的的东西,而不是重写它以“看起来更好”是没有意义的。
  • 完全同意这一点。但是将代码重构为您建议的内容几乎是对整个项目的完整重构,因为有太多的依赖关系导致“魔法”继续。在不完全破坏“架构”的情况下,将我发布的部分重构为更易于维护的解决方案将是我的目标#1,因为至少应用程序的某些部分会变得更加清晰,并分成更小的部分,以便在必要时处理。..跨度>
猜你喜欢
  • 1970-01-01
  • 2018-06-01
  • 2012-01-11
  • 2013-04-25
  • 2020-03-29
  • 2021-02-05
  • 1970-01-01
  • 2016-04-03
  • 2021-06-15
相关资源
最近更新 更多