工厂模式,开闭原则,接口和泛型
本文关键字:接口 原则 泛型 模式 工厂 | 更新日期: 2023-09-27 18:12:38
我尝试过基于开闭原则重构一些代码,但是当涉及到应用设计模式时,我似乎无法正确地获得以下类。(我为下面列出的许多类道歉-我已经尽可能地减少了它们,但其余的需要向您展示我的设计)。
设置由以下类组成:
public interface IPortFactory
{
IPort CreatePort(int id, PortDetails details);
}
public class PtpPortFactory : IPortFactory
{
public IPort CreatePort(int id, PortDetails details)
{
var ptpPortDetails = details as PtpPortDetails;
if (ptpPortDetails == null)
{
throw new ArgumentException("Port details does not match ptp ports", "details");
}
return new PtpPort(id, FiberCapability.FromValue(ptpPortDetails.Capability));
}
}
public interface IPort
{
int Id { get; }
}
public interface IInternetPort : IPort
{
bool OfCapability(FiberCapability capability);
}
public class PtpPort : IInternetPort
{
private readonly FiberCapability _capability;
public PtpPort(int id, FiberCapability capability)
{
_capability = capability;
Id = id;
}
public int Id { get; private set; }
public bool OfCapability(FiberCapability capability)
{
return capability.Equals(_capability);
}
}
除了PtpPort
,我有PonPort
,它也实现了IInternetPort
,而CatvPort
只实现了IPort
。
在这段代码中,我认为有代码气味的迹象。在CreatePort
和PtpPortFactory
中,它可以接受PtpPortDetails
(继承自PortDetails
),而不必强制转换。然而,如果我这样做,我不能创建一个PonPortFactory
,它也实现了IPortFactory
,因为这些端口需要PonPortDetails
。或者CatvPortFactory
。
PortType portType = command.PortType;
IPortFactory portFactory = portType.GetPortFactory();
var portsToSelectFrom = ports.Select(port => (IInternetPort) portFactory.CreatePort(port.Id, port.PortDetails)).ToList();
我真的不希望从IPort
到IInternetPort
进行向下转换,而只是让CreatePort
返回IInternetPort
。
理解上述内容所需的最后一点信息可能是以下类(基于Jimmy bogard的Enumeration
类):
public abstract class PortType : Enumeration<PortType, int>
{
public static readonly PortType Ptp = new PtpPortType();
public static readonly PortType Pon = new PonPortType();
public static readonly PortType Catv = new CatvPortType();
protected PortType(int value, string description)
: base(value, description) { }
public abstract IPortFactory GetPortFactory();
private class CatvPortType : PortType
{
public CatvPortType() : base(2, "catv") { }
public override IPortFactory GetPortFactory()
{
return new CatvPortFactory();
}
}
private class PonPortType : PortType
{
public PonPortType() : base(1, "pon") { }
public override IPortFactory GetPortFactory()
{
throw new NotImplementedException("Pon ports are not supported");
}
}
private class PtpPortType : PortType
{
public PtpPortType() : base(0, "ptp") { }
public override IPortFactory GetPortFactory()
{
return new PtpPortFactory();
}
}
}
我真的希望有人能一路帮助我(我已经尝试过引入泛型,但似乎总是遇到c#不支持返回类型协方差的障碍)。
此外,任何其他提示和技巧,帮助我在我的道路上写更好的代码将是非常感激的。
更新由于评论中的要求,我在下面添加了更多的代码。
public Port Handle(TakeInternetPortCommand command)
{
var portLocatorService = new PortLocatorService();
IList<Port> availablePorts = portLocatorService.FindAvailablePorts(command.Pop, command.PortType);
PortType portType = command.PortType;
IPortFactory portFactory = portType.GetPortFactory();
var portsToSelectFrom = ports.Select(port => (IInternetPort) portFactory.CreatePort(port.Id, port.PortDetails)).ToList();
IPort port = _algorithm.RunOn(portsToSelectFrom);
Port chosenPort = availablePorts.First(p => p.Id == port.Id);
chosenPort.Take(command.Spir);
_portRepository.Add(chosenPort);
return chosenPort;
}
不要被突然出现一个Port
类型的事实搞糊涂了。这是另一个有界上下文中的聚合(在DDD意义上)。该算法需要将IInternetPort
列表作为输入,因为它在内部使用OfCapability
方法来选择端口。然而,当算法选择了正确的端口时,我们只对Id
感兴趣,因此返回类型只是IPort
。
这是我如何理解你试图解决的问题(业务问题,而不是设计问题):
你有一个端口列表,你想在列表上执行一些算法,从列表中选择一个端口(基于特定于算法的一些标准)。
这里我将提出一种建模方法。我将假设您有以下输入类:
public class PortInput
{
public int Id { get; set; }
public PortDetails PortDetails { get; set; }
}
这对应于你问题中Port类的一部分。
这里是算法的接口:
public interface IPortSelectionAlgorithm
{
int SelectPort(PortInput[] port_inputs);
}
请注意这个接口模拟了我们想要解决的问题。例如,给定一个端口列表->,我们需要选择一个
下面是这种算法接口的实现:
public class PortSelectionAlgorithm : IPortSelectionAlgorithm
{
private readonly ICapabilityService<PortDetails> m_CapabilityService;
public PortSelectionAlgorithm(ICapabilityService<PortDetails> capability_service)
{
m_CapabilityService = capability_service;
}
public int SelectPort(PortInput[] port_inputs)
{
//Here you can use m_CapabilityService to know if a specific port has specific capability
...
}
}
这个实现声明的是它需要一些服务,该服务知道如何根据端口详细信息获取端口的功能。以下是服务契约的定义:
public interface ICapabilityService<TDetails> where TDetails : PortDetails
{
bool OfCapability(TDetails port_details, FiberCapability capability);
}
对于每个端口类型,我们可以创建这样的服务的实现,如下所示:
public class PtpPortCapabilityService: ICapabilityService<PtpPortDetails>
{
public bool OfCapability(PtpPortDetails port_details, FiberCapability capability)
{
...
}
}
public class CatvPortCapabilityService : ICapabilityService<CatvPortDetails>
{
public bool OfCapability(CatvPortDetails port_details, FiberCapability capability)
{
...
}
}
public class PonPortCapabilityService : ICapabilityService<PonPortDetails>
{
public bool OfCapability(PonPortDetails port_details, FiberCapability capability)
{
//If such kind of port does not have any capability, simply return false
...
}
}
然后,我们可以创建另一个实现,它使用这些单独的服务来告诉任何端口的功能:
public class PortCapabilityService : ICapabilityService<PortDetails>
{
private readonly ICapabilityService<PtpPortDetails> m_PtpPortCapabilityService;
private readonly ICapabilityService<CatvPortDetails> m_CatvPortCapabilityService;
private readonly ICapabilityService<PonPortDetails> m_PonPortCapabilityService;
public PortCapabilityService(ICapabilityService<PtpPortDetails> ptp_port_capability_service, ICapabilityService<CatvPortDetails> catv_port_capability_service, ICapabilityService<PonPortDetails> pon_port_capability_service)
{
m_PtpPortCapabilityService = ptp_port_capability_service;
m_CatvPortCapabilityService = catv_port_capability_service;
m_PonPortCapabilityService = pon_port_capability_service;
}
public bool OfCapability(PortDetails port_details, FiberCapability capability)
{
PtpPortDetails ptp_port_details = port_details as PtpPortDetails;
if (ptp_port_details != null)
return m_PtpPortCapabilityService.OfCapability(ptp_port_details, capability);
CatvPortDetails catv_port_details = port_details as CatvPortDetails;
if (catv_port_details != null)
return m_CatvPortCapabilityService.OfCapability(catv_port_details, capability);
PonPortDetails pon_port_details = port_details as PonPortDetails;
if (pon_port_details != null)
return m_PonPortCapabilityService.OfCapability(pon_port_details, capability);
throw new Exception("Unknown port type");
}
}
可以看到,除了算法类之外,没有其他类知道端口号。决定功能的类不知道端口id,因为它们可以在没有端口id的情况下完成工作。
另一件需要注意的事情是,您不需要在每次运行算法时实例化一个新的功能服务。这与你的问题中描述的IInternetPort实现形成对比,在IInternetPort中,每次要执行算法时都要创建一个新实例。我猜你这样做是因为每个实例都被绑定到一个不同的ID。
这些类使用依赖注入。您应该组合它们以便能够使用它们。你应该在Composition根目录下做。 下面是如何使用纯DI来完成这样的组合:IPortSelectionAlgorithm algorithm =
new PortSelectionAlgorithm(
new PortCapabilityService(
new PtpPortCapabilityService(),
new CatvPortCapabilityService(),
new PonPortCapabilityService()));
我想这里有代码气味的迹象。在PtpPortFactory中的CreatePort中,它可以接受PtpPortDetails(继承自PortDetails),而不必强制转换。
不,这很好,因为依赖应该在抽取上,而不是在实现上。所以在这种情况下,传递PortDetails是可以的。
我看到的气味在这里:
public interface IPort
{
int Id { get; }
}
public interface IInternetPort : IPort
{
bool OfCapability(FiberCapability capability);
}
接口主要用于定义行为。你在一个界面中使用属性,我觉得很可疑。
- 继承描述了一个is-a关系。
- 实现接口描述了can-do关系。
这里处理的是AbstractFactory模式。比方说,你可以有一个抽象工厂BasePortFactory
,它可以做IPortFactory
声明的事情。所以你应该从工厂方法返回BasePortFactory
。但在设计解决方案时,这又是一个选择问题。
类似地,如果你想使用is-a
而不是can-do
的东西,CreatePort
方法应该暴露基类或接口的返回类型。
这个示例可能不是最适合您的场景,但它描述了我分享的想法:
public interface IInternetPort
{
bool OfCapability(FiberCapability capability);
}
/// <summary>
/// This class can be a replacement of (IPort) interface. Each port is enabled for query via IInternetPort.
/// As a default behavior every port is not Internet enabled so OfCapability would return false.
/// Note: If you want you can still keep the IPort interface as Marker interface.
/// /// </summary>
public abstract class Port : IInternetPort
{
public int Id { get; private set; }
public Port(int Id)
{
this.Id = Id;
}
public virtual bool OfCapability(FiberCapability capability)
{
// Default port is not internet capable
return false;
}
}
/// <summary>
/// This class is-a <see cref="Port"/> and can provide capability checker.
/// Overiding the behavior of base for "OfCapability" would enable this port for internet.
/// </summary>
public class PtpPort : Port
{
private readonly FiberCapability _capability;
public PtpPort(int id, FiberCapability capability) : base(id)
{
_capability = capability;
}
public override bool OfCapability(FiberCapability capability)
{
return capability.Equals(_capability);
}
}
/// <summary>
/// this test class doesn't need to implement or override OfCapability method
/// still it will be act like any other port.
/// | TestPort port = new TestPort(22);
/// | port.OfCapability(capability);
/// </summary>
public class TestPort : Port
{
public TestPort(int id): base(id) { }
}
这里是需要将方法的签名更改为返回Port而不是IPort的工厂。
public interface IPortFactory
{
Port CreatePort(int id, PortDetails details);
}
public class PtpPortFactory : IPortFactory
{
public Port CreatePort(int id, PortDetails details)
{
var ptpPortDetails = details as PtpPortDetails;
if (ptpPortDetails == null)
{
throw new ArgumentException("Port details does not match ptp ports", "details");
}
return new PtpPort(id, FiberCapability.FromValue(ptpPortDetails.Capability));
}
}
现在这行不需要任何外部强制转换。
var portsToSelectFrom = ports.Select(port => portFactory.CreatePort(port.Id, port.PortDetails)).ToList();
注:-这类问题应该在代码审查或程序员的时候问。