如何将这个虚方法调用移出构造函数?

时间:2011-05-11 19:49:09

标签: c# constructor

我有几个类形成了太复杂的对象图。这是一个较小的场景。假设INotifyPropertyChanged到位。

class A
{
    public InternalType InterestingProperty { get; set; }
}

class B
{
    public A Component { get; set; }
}

我的帮助程序类监视这些事件,并在对象的属性发生更改时更新其属性。它这样做是因为其他一些对于尽可能多的对象上的十几个属性感兴趣的类很容易访问。这些都包含在一个具有多个变体的框架中,因此继承正在发挥作用。

我已经完成了第一个场景,最终得到了一个类似的具体类:

class ScenarioOnePropertySpy
{
    protected ScenarioOne PropertySpy(Foo thingToMonitor)
    {
        _thingToMonitor = thingToMonitor;
        RegisterForEvents()
    }

    public B InterestingB { get; }

    protected RegisterForEvents()
    {
        // * Register for _thingToMonitor propertyChanged if first time.
        // * If B is different, unregister the old and register the new.
        // * If B hasn't been set yet register for PropertyChanged on it.
        // * If B.Component isn't the same as last time unregister the
        //      old and register the new.
    }

    protected Update()
    {
        // Some monitored object changed; refresh property values and
        // update events in case some monitored object was replaced.
        B = _thingToMonitor.B;
        RegisterForEvents()
    }

    private Handle_PropertyChanged(...) { Update(); } 
}

这是一个icky事件注册,但是将那个丑陋的想法变成了想要了解这些属性的类是目的。现在我转到监视不同对象/属性的方案2,并使用我的具体类作为抽象方法的指南:

abstract class PropertySpy
{
    protected PropertySpy(FooBase thingToMonitor)
    {
        _thingToMonitor = thingToMonitor;
        RegisterForEvents()
    }

    protected abstract void RegisterForEvents()

    // ...
}

糟糕。我在构造函数中有一个虚方法调用。从理论上讲,它对我的​​所有场景都是安全的,但是R#警告一直在挖掘我。我相信如果有一天我继续前进会导致一个问题需要一段时间才能弄明白。该方法肯定需要使用派生类型的属性。

我可以删除方法并强制派生类型自己进行事件管理。这会破坏基类的目的。有人会忘记遵守合同,而且会变成支持事件;我花了足够的时间来编写文档。我想到的另一个是使RegisterForEvents()publich并要求用户在构造之后调用它。那个“创建然后初始化”模式在.NET中很糟糕,人们总是忘记。目前我正在玩另一个类的概念,该类执行通过构造函数注入的事件注册。然后派生类可以提供该类以实现与没有危险的虚拟方法相同的效果。但是,进行注册的事情实际上需要与PropertySpy相同的属性接口;这看起来很乏味,但我觉得“丑陋而且有效”比我所做的要好。

我缺少什么?如果论证令人信服,我甚至会把“这是一个警告,而不是一个规则”作为答案。

2 个答案:

答案 0 :(得分:2)

您的场景似乎很复杂,需要考虑一种完全不同的类实例化方法。如何使用工厂来建造财产间谍?

public class PropertySpyFactory<T> where T : PropertySpy, new()
{
    public static T Create()
    {
        T result = new T();
        // … whatever initialization needs to be done goes here …
        result.RegisterForEvents();
        return result;
    }
}

ScenarioOnePropertySpy spy = PropertySpyFactory<ScenarioOnePropertySpy>.Create();

它在代码中是可以挽救的,实例初始化可以很容易地扩展,一旦你转向IoC,它会感觉非常自然,并且不需要太多的重构。

UPDATE:另一个选项,例如a)您的间谍层次结构足够扁平,b)您不需要使用共同的祖先,或者您可以用接口代替它:

public abstract class PropertySpy<T> where T : PropertySpy, new()
{
    public static T Create()
    {
        T result = new T();
        // … whatever initialization needs to be done goes here …
        result.RegisterForEvents();
        return result;
    }

    …
}

public class ScenarioOnePropertySpy : PropertySpy<ScenarioOnePropertySpy>
{
    …
}

ScenarioOnePropertySpy spy = ScenarioOnePropertySpy.Create();

换句话说,工厂方法位于共同的祖先之内。这种方法的缺点是 正交(工厂没有与正在构造的类分开),因此不易扩展和灵活。但是,在某些情况下可能是一个有效的选择。

最后但并非最不重要的是,您可以再次在每个类中创建工厂方法。优点是您可以保持构造函数受到保护,从而强制用户使用工厂方法而不是直接实例化。

答案 1 :(得分:0)

我认为关键问题是,在调用虚方法时,您的子类构造函数和初始化函数尚未执行。因此,在重写的方法中,您的子类可能没有预期初始化的所有内容。