我正在尝试构建一个功能包解析器。我有一个基类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的一部分,尽可能少修改)
答案 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);
}
}