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

3
我正在尝试构建一个功能性的包解析器。我有一个基类Datagram,现在我天真地想象它会被定义为以下内容:

(注意:这个类不是抽象类!)

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

然后针对特定的数据报文,以Ethernet和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 的一部分添加这些扩展的数据包解释库,尽量少地修改它)


转换一个数据报到另一个数据报是否有任何限制?因此,您可以将任何数据报转换为任何其他数据报吗? - ckruczek
我不这么认为,它们都是由字节数组构建的(或者应该是这样),并通过解释该数组来定义自己 - 因此要将一个转换为另一个,您只需要使用另一个对象的字节创建一个新对象。不过,如果没有构造函数问题,不确定如何进行“转换”而不仅仅是构造具有相同基础数据的新对象? - Joe
3个回答

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);
    }
}

这并不强制要求构造函数,public EthernetDatagram() : base(new Datagram())是允许的。或者更可能的是像public EthernetDatagram(Datagram datagram, string name) : base(datagram) { _name=name;或类似的东西。反射将在两者上失败。只要构造函数引用了“base”,它就可以编译,但它可以有任何参数。 - Joe
有一些没有有效载荷的数据报,我没有包含它们。起初我考虑过使用IPayloadDatagram接口,但我确定当时有某些原因让我不这样做(可能是为了让所有这些工作正常),我会尝试回到接口上。相比继承,我更喜欢接口。 - Joe

3
I would suggest a different solution. I would apply the 依赖反转原则使用新解决方案进行更新:
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”以精确达到您想要的结果。 现在唯一需要提供的是接口实现、默认构造函数就行了。


让我们在聊天中继续这个讨论 - ckruczek

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);
    }
}

我曾经尝试过类似的东西,但是我从未能够让它正常工作。我的问题在于 (T)Datagram.CreateFromDatagram(datagram.Payload) 中的 datagram.Payload 不是类型 T(它只是一个“通用”的 Datagram - 我刚意识到这个问题并不是我在问题中标记的抽象!),所以 GetConverter 总是普通 Datagram 的版本。对于造成的困惑,我感到很抱歉。如果 Payload 是正确的类型 T,我就不需要使用 As<T> 扩展了,可以直接使用 Payload 属性。 - Joe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接