如何避免从基础构造函数调用Viritual方法

WCW*_*din 9 c# inheritance constructor virtual-functions

我在库中有一个抽象类.我正在尝试尽可能简单地正确实现此类的派生.问题是我需要在三个步骤中初始化对象:获取文件,执行一些中间步骤,然后使用该文件.第一步和最后一步特别适用于派生类.这是一个精简的例子.

abstract class Base
{
    // grabs a resource file specified by the implementing class
    protected abstract void InitilaizationStep1();

    // performs some simple-but-subtle boilerplate stuff
    private void InitilaizationStep2() { return; }

    // works with the resource file
    protected abstract void InitilaizationStep3();

    protected Base()
    {
        InitilaizationStep1();
        InitilaizationStep2();
        InitilaizationStep3();
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,麻烦在于构造函数中的虚方法调用.如果他们不能指望派生类完全初始化,我担心库的使用者在使用类时会发现自己受到限制.

我可以将构造函数中的逻辑拉出到受保护的Initialize()方法中,但是实现者可以直接调用Step1()Step3()不是调用Initialize().问题的关键在于如果Step2()被跳过则不会有明显的错误; 在某些情况下表现糟糕.

我觉得无论哪种方式都存在严重且不明显的"问题",未来的图书馆用户将不得不解决这些问题.我应该使用其他一些设计来实现这种初始化吗?

如有必要,我可以提供更多细节; 我只是想提供表达问题的最简单的例子.

LBu*_*kin 10

我会考虑创建一个抽象工厂,负责使用模板方法初始化实例化和初始化派生类的实例.

举个例子:

public abstract class Widget
{
    protected abstract void InitializeStep1();
    protected abstract void InitializeStep2();
    protected abstract void InitializeStep3();

    protected internal void Initialize()
    {
        InitializeStep1();
        InitializeStep2();
        InitializeStep3();
    }

    protected Widget() { }
}

public static class WidgetFactory
{
    public static CreateWidget<T>() where T : Widget, new()
    {
        T newWidget = new T();
        newWidget.Initialize();
        return newWidget;
    }
}

// consumer code...
var someWidget = WidgetFactory.CreateWidget<DerivedWidget>();
Run Code Online (Sandbox Code Playgroud)

这个工厂代码可以得到显着改进 - 特别是如果你愿意使用IoC容器来处理这个责任......

如果您无法控制派生类,则可能无法阻止它们提供可以调用的公共构造函数 - 但至少可以建立消费者可以遵循的使用模式.

并不总是可以阻止您的课程用户自己动手 - 但是,您可以提供基础设施来帮助消费者在熟悉设计时正确使用您的代码.

  • 我不同意.他正在寻找一种模式,派生类将执行结构化初始化 - 他担心这些衍生产品可能无法以适当的顺序执行所有必要的初始化步骤.执行构造和初始化的工厂可以帮助解决此问题.特别是因为在构造函数中调用虚方法的替代方案(通常)是不合需要的实践. (2认同)

Joh*_*ers 4

对于任何类的构造函数来说,这都太多了,更不用说基类了。我建议您将其分解为一个单独的Initialize方法。

  • 我喜欢您的解决方案,但要考虑的另一种可能性可能是将步骤 1 和 3 中的代码分解为传递给构造函数的一对接口。构造函数可以在适当的时候调用它们来完成自己的部分。简而言之,这很可能是组合战胜继承的地方之一。 (2认同)
  • 这并没有解决他对不按顺序调用步骤以及简化派生类创建的方法的担忧。 (2认同)
  • 首先,除非这个类层次结构中有一些东西使得 init 总是按这个顺序执行三个步骤,否则它不应该由基类强加。其次,如果它是固有的,那么基类的Initialize方法可以依次调用虚Step1、Step2、Step3初始化方法:模板函数模式。 (2认同)