【问题标题】:How can I move this virtual method call out of the constructor?如何将此虚拟方法调用移出构造函数?
【发布时间】:2011-05-11 19:49:09
【问题描述】:

我有几个类构成了一个过于复杂的对象图。这是一个较小的场景。假设 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(); } 
}

这是令人讨厌的事件注册,但目的是将这种丑陋从想要了解属性的类中移出。现在我进入了场景 2,它监控不同的对象/属性,并使用我的具体类作为抽象类的指南:

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

    protected abstract void RegisterForEvents()

    // ...
}

哎呀。我在构造函数中有一个虚拟方法调用。理论上它对我的所有场景都是安全的,但是 R# 警告一直在挖我。我敢肯定,如果有一天我继续前进,它会导致一个需要一段时间才能解决的问题。该方法肯定需要使用派生类型的属性。

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

我缺少什么吗?如果论点有说服力,我什至会以“这是一个警告,而不是规则”作为答案。

【问题讨论】:

    标签: c# constructor


    【解决方案1】:

    您的场景似乎足够复杂,可以考虑使用完全不同的类实例化方法。用工厂来建造财产间谍怎么样?

    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 就会感觉很自然,不需要太多的重构。

    更新:另一种选择,以防 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();
    

    换句话说,工厂方法位于共同祖先中。这种方法的缺点是它不是 正交的(工厂没有与正在构建的类分离),因此可扩展性和灵活性较差。但是,在某些情况下可能是一个有效的选项。

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

    【讨论】:

    • 嗯...我确实需要将 FooBase 参数传递给它,但我认为我可以使用额外的受保护方法来设置该属性。我喜欢!
    【解决方案2】:

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

    【讨论】:

    • 对。我不是在问,“为什么这是一件坏事?”。我在问,“有没有办法在没有风险的情况下完成同样的事情。”
    猜你喜欢
    • 2010-11-12
    • 2012-09-02
    • 1970-01-01
    • 2019-01-30
    • 2010-09-09
    • 2018-02-09
    • 2011-07-16
    • 2016-04-09
    相关资源
    最近更新 更多