如何编写指定创建逻辑的接口或抽象类?

时间:2015-03-31 07:43:32

标签: c# oop generics interface abstract-class

我有一个泛型类来处理可以从字符串反序列化的小部件。泛型类的实例将采用其中一个窗口小部件的类型作为模板参数,然后从字符串创建这些窗口小部件。我希望使用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<BlueWidget>正常,因为BigWidget满足new()。我无法编写WidgetUser<IWidget>因为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<BlueWidget>,然后编写仅处理WidgetUser的代码。我可以使用类似的代码与抽象类BaseWidget而不是IWidget来完成几乎相同的事情。这是有用的,但我怀疑有一种更直接的方法并没有强迫我定义一个虚拟类。如何在不创建虚拟类或额外工厂的情况下将我的意图传达给类型系统。我只想要一个界面说“#34;你可以用字符串制作其中一个&#34;。

TL; DR: 有没有办法编写一个接口或抽象类,让我从字符串创建一个实例,但是不要求我new()作为WidgetUser<T>的守护者?

2 个答案:

答案 0 :(得分:0)

我会实施WidgetFactory并致电WidgetFactory.Create<T>(serializedWidget)以避免使用new T()

答案 1 :(得分:0)

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

(或者,您可以使用Func<IWidget>委托来指定它,但提供完整的工厂界面更为常见。)

所以我建议你创建工厂界面:

interface IWidgetFactory
{
    IWidget Create(string serialisedWidget);
}

然后从Deserialize()

中删除IWidget
interface IWidget
{
    // .. Whatever
}

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

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.
}

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

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

然后你可以实现一个工厂作为&#34;虚拟构造函数&#34;可以创建任何类型的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等依赖容器来管理此类事情。