使用静态工厂的替代方法(因为不能使用抽象静态方法)

时间:2017-11-14 09:57:15

标签: c# inheritance

我正在尝试构建一个功能包解析器。我有一个基类Datagram,现在我天真想象我有这样的定义:

(注意编辑,这个类不是抽象的!)

public class Datagram
{
    public abstract static Datagram CreateFromDatagram(Datagram datagram);
}

然后对于特定的数据报,比如以太网和Tcp作为例子:

public class EthernetDatagram : Datagram, IPayloadDatagram
{
    public override static Datagram CreateFromDatagram(Datagram datagram)
    {
        return new EthernetDatagram();
    }

    public Datagram Payload { get; }
}

public class TcpDatagram : Datagram, IPayloadDatagram
{
    public overrides static Datagram CreateFromDatagram(Datagram datagram)
    {
        return new TcpDatagram();
    }

    public Datagram Payload { get; }
}

这个(不可能的)抽象静态方法的原因是我想要一个扩展方法,它允许我将所有这些数据包“链接”在一起:

public static class DatagramExtensions
{
    public static T As<T>(this IPayloadDatagram datagram) where T : Datagram
    {
        return (T)T.CreateFromDatagram(datagram.Payload);
    }
}

所以,我需要做的就是拥有一个全新的数据报类型ANewDatagram,让它定义它的工厂方法CreateFromDatagram,然后我就可以快乐地使用我的功能扩展了:

SomeDatagram.As<EthernetDatagram>().As<TcpDatagram>().As<ANewDatagram>()... 

并且它都是可扩展的。

鉴于这不会起作用,因为我无法继承抽象类,那么实例化这样的泛型类会有什么好处呢?

我可以使用反射,但那会将其隐藏在用户之外。当我尝试创建ANewDatagram时,我必须记住我稍后会反映CreateFromDatagram方法。

我目前正在使用反射来获取构造函数 - 但是我没有办法强制执行有一个特定的构造函数来获取有效负载。如果有人创建了一个新的Datagram,那么他们无法保证他们会添加正确的构造函数,我必须在评论中告知他们,这很可能会被遗漏,而且失败点最迟是在运行时可能的一点。

是否有更好的替代方案,架构方面或某种形式的接口/继承可以解决这个问题?

(如果有人想查看我正在使用的完整源代码,我正在尝试将这些扩展添加到数据包解释库中作为https://github.com/PcapDotNet/Pcap.Net的一部分,尽可能少修改)

3 个答案:

答案 0 :(得分:3)

  

......但是我无法强制执行具体的操作   获取有效负载的构造函数。

您可以通过在基础抽象类中声明适当的构造函数来强制执行它。

我还建议对您的代码进行一些修改。由于所有派生类都应该具有相同的Payload属性,因此请在基类Datagram类中声明它。还要考虑将Datagram类声明为实现IPayloadDatagram接口。没有必要将每个派生类标记为实现此接口。

以下是希望满足您需求的示例代码:

public interface IPayloadDatagram
{
    Datagram Payload { get; }
}

public abstract class Datagram : IPayloadDatagram
{
    public Datagram Payload { get; }

    protected Datagram(Datagram datagram)
    {
        Payload = datagram;
    }
}

public class EthernetDatagram : Datagram
{
    public EthernetDatagram(Datagram datagram) : base(datagram)
    {
    }
}

public static class DatagramExtensions
{
    public static T As<T>(this IPayloadDatagram datagram) where T : Datagram
    {
        return (T)Activator.CreateInstance(typeof(T), datagram.Payload);
    }
}

答案 1 :(得分:3)

我建议采用不同的解决方案。我会应用Depdency Inversion Principle

使用新解决方案进行更新:

public class Datagram
{
    public byte[] Data { get; set; }
}


public interface IPayload
{
    Datagram Payload { get; }

}    

public interface IConvertible
{
    IPayload Convert(IPayload load);
}

public class EthernetDatagram : IPayload , IConvertible
{
    public Datagram Payload
    {
        get
        {
            return null;
        }
    }


    public IPayload Convert(IPayload load)
    {
        return new EthernetDatagram();
    }
}

public class TcpDatagram : IConvertible, IPayload
{
    public Datagram Payload
    {
        get
        {
            return null;
        }
    }

    public IPayload Convert(IPayload load)
    {
        return null;
    }
}

public static  class Extension
{
    public static IPayload As<T>(this IPayload load) where T : class, IConvertible, new()
    {
        IConvertible conv = new T();
        return conv.Convert(load);
    }
}

class Program
{
    static void Main(string[] args)
    {
        IPayload load = new TcpDatagram();

        var result = load.As<EthernetDatagram>();
    }
}

使用此解决方案,您可以采取相反的方式。您将离开硬反射的路径并将其移动到不同的抽象层,方法是将要转换的具体类型传递给并完全控制。您需要为新数据报做的就是实现这两个接口,只要您希望它们之间进行转换即可。 这更适合你的问题吗?

更新评论:

新解决方案将克服前一种解决方案的缺点。 通用参数没有反映和指定'转换'类型。您可以将“As”连接到一起,以达到您想要的效果。 你现在唯一需要提供的是接口实现,默认的ctor就是这样。

答案 2 :(得分:0)

如果您有一个静态方法,但该方法引用了实现定义的操作,该怎么办?

这会添加编译时检查,因此用户至少应该知道转换。

public interface IPayloadDatagram
{
    Datagram Payload { get; }
}

public abstract class Datagram
{
    public static Datagram CreateFromDatagram(Datagram datagram)
    {
        var action = datagram.GetConverter();
        return action(datagram);
    }

    protected abstract Func<Datagram, Datagram> GetConverter();
}

public class EthernetDatagram : Datagram, IPayloadDatagram
{
    protected override Func<Datagram, Datagram> GetConverter()
    {
        return x => new EthernetDatagram();
    }

    public Datagram Payload { get; set; }
}

public class TcpDatagram : Datagram, IPayloadDatagram
{
    protected override Func<Datagram, Datagram> GetConverter()
    {
        return x => new TcpDatagram();
    }

    public Datagram Payload { get; set; }
}

public static class DatagramExtensions
{
    public static T As<T>(this IPayloadDatagram datagram) where T : Datagram
    {
        return (T)Datagram.CreateFromDatagram(datagram.Payload);
    }
}