【问题标题】:How do I write an interface or abstract class that specifies creation logic?如何编写指定创建逻辑的接口或抽象类?
【发布时间】:2015-06-04 11:22:12
【问题描述】:

我有一个通用类,它处理可以从字符串反序列化的小部件。泛型类的实例将这些小部件之一的类型作为模板参数,然后从字符串创建这些小部件。我希望使用 C# 泛型的协方差属性来编写像WidgetUser<IWidget> 这样的代码来处理可能是WidgetUser<RedWidget> 或WidgetUser<BlueWidget> 的对象。问题是,要从WidgetUser<T> 中的字符串创建小部件,我不得不添加new() 作为保护。这使得WidgetUser<IWidget> 成为无效类型。目前,我有这样的代码:

interface IWidget
{
    // Makes this widget into a copy of the serializedWidget
    void Deserialize(string serializedWidget);
}
class WidgetUser<T> where T : IWidget, new()
{
    public void MakeAndUse(string serializedWidget)
    {
        var widget = new T();
        widget.Deserialize(serializedWidget);
        Use(widget);
    }
}

使用这段代码,我可以使WidgetUser&lt;BlueWidget&gt; 很好,因为BigWidget 满足new()。我不能写WidgetUser&lt;IWidget&gt;,因为IWidget(或等效抽象类)的实例不能保证与new()一起工作。解决方法可能是这样的:

abstract class WidgetUser
{
    public abstract void MakeAndUse();
}

class WidgetUser<T> : WidgetUser
    where T : IWidget, new() 
{ 
    /* same as before but with an 'override' on MakeAndUse */ 
}

使用这段代码,我可以创建一个WidgetUser&lt;BlueWidget&gt;,然后编写只处理WidgetUser 的代码。我可以使用抽象类BaseWidget 而不是IWidget 来完成类似的代码,它完成几乎相同的事情。这是功能性的,但我怀疑有一种更直接的方法不会强迫我定义一个虚拟类。如何在不创建虚拟类或额外工厂的情况下将我的意图传达给类型系统。我只想要一个界面,上面写着“您可以从字符串中制作其中一个”。

TL;DR: 有没有办法编写一个接口或抽象类,让我可以从字符串创建一个实例,但不需要我将new() 作为WidgetUser&lt;T&gt; 的保护?

【问题讨论】:

  • because instances of IWidget are not guaranteed to work with new() 为什么?是因为new() 必须有一个公共的无参数构造函数吗?如果是这样,请使用class 代替您的Constraints on Type Parameter。
  • 您要么需要以某种方式提供工厂对象,要么需要使用new() 约束。为什么你不能简单地扭转你的问题并说小部件类必须有一个无参数的构造函数来与你的小部件用户一起工作?
  • 如何为小部件指定接口或抽象基类以保证无参数构造函数?
  • 目前我不明白你的摘要MakeAndUse有什么问题,所以我不知道。
  • because instances of IWidget are not guaranteed to work with new() 为什么?

标签: c# oop generics interface abstract-class


【解决方案1】:

我会实现WidgetFactory 并调用WidgetFactory.Create&lt;T&gt;(serializedWidget) 以避免使用new T()

【讨论】:

    【解决方案2】:

    这里的问题是你的Deserialize() 方法应该是一个静态方法。因此它不应该是IWidget 本身的成员 - 它应该是工厂接口的成员,或者它应该是从具体工厂方法调用的具体 Widget 类的静态成员。我在下面展示后一种方法。

    (或者,您可以使用Func&lt;IWidget&gt; 委托来指定它,但更常见的是提供完整的工厂接口。)

    所以我建议你创建工厂接口:

    interface IWidgetFactory
    {
        IWidget Create(string serialisedWidget);
    }
    

    然后从IWidget中删除Deserialize():

    interface IWidget
    {
        // .. Whatever
    }
    

    然后在IWidget的每个具体实现中添加一个静态Deserialize()方法:

    class MyWidget: IWidget
    {
        public static MyWidget Deserialize(string serializedWidget)
        {
            // .. Whatever you need to deserialise into myDeserializedObject
            return myDeserializedObject;
        }
    
        // ... Any needed IWidget-implementing methods and properties.
    }
    

    然后使用具体小部件类中的静态Deserialize() 方法为您的具体小部件类实现工厂:

    sealed class MyWidgetFactory : IWidgetFactory
    {
        public IWidget Create(string serialisedWidget)
        {
            return MyWidget.Deserialize(serialisedWidget);
        }
    }
    

    然后将构造函数添加到您的WidgetUser 类,该类接受IWidgetFactory 并在MakeAndUse() 中使用它:

    class WidgetUser
    {
        public WidgetUser(IWidgetFactory widgetFactory)
        {
            this.widgetFactory = widgetFactory;
        }
    
        public void MakeAndUse(string serializedWidget)
        {
            var widget = widgetFactory.Create(serializedWidget);
            Use(widget);
        }
    
        private readonly IWidgetFactory widgetFactory;
    }
    

    请注意,在这种情况下,您不再需要 WidgetUser 的类型参数,因此我已将其删除。

    那么当你创建WidgetUser 时你必须提供一个工厂:

    var widgetUser = new WidgetUser(new MyWidgetFactory());
    
    ...
    
    widgetUser.MakeAndUse("MySerializedWidget1");
    widgetUser.MakeAndUse("MySerializedWidget2");
    

    传入工厂允许更多的灵活性。

    例如,假设您的序列化方案包括一种从序列化字符串中判断它是哪种小部件的方法。为简单起见,假设它以"[MyWidget]" 开头,如果它是MyWidget,则以["MyOtherWidget"] 开头,如果它是MyOtherWidget。

    然后你可以实现一个工厂,作为一个“虚拟构造函数”,它可以在给定序列化字符串的情况下创建任何类型的 Widget,如下所示:

    sealed class GeneralWidgetFactory: IWidgetFactory
    {
        public IWidget Create(string serialisedWidget)
        {
            if (serialisedWidget.StartsWith("[MyWidget]"))
                return myWidgetFactory.Create(serialisedWidget);
            else if (serialisedWidget.StartsWith("[MyOtherWidget]"))
                return myOtherWidgetFactory.Create(serialisedWidget);
            else
                throw new InvalidOperationException("Don't know how to deserialize a widget from: " + serialisedWidget);
    
        }
    
        readonly MyWidgetFactory      myWidgetFactory      = new MyWidgetFactory();
        readonly MyOtherWidgetFactory myOtherWidgetFactory = new MyOtherWidgetFactory();
    }
    

    请注意,这通常不是最好的处理方式 - 您最好使用 Autofac 等依赖容器来管理此类事情。

    【讨论】:

      猜你喜欢
      • 2014-08-21
      • 2011-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-11-06
      • 1970-01-01
      相关资源
      最近更新 更多