工厂模式、开闭原则、接口和泛型
Factory Pattern, open closed principle, interfaces and generics
我曾尝试根据开放-封闭原则重构一些代码,但在应用设计模式时,我似乎无法正确理解以下 classes。
(对于下面列出的许多 class 内容,我深表歉意 - 我已尽可能减少它们,但剩下的部分需要向您展示我的设计)。
设置包含以下 classes:
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
.
在这段代码中,我认为有代码味道的迹象。在 PtpPortFactory
中的 CreatePort
中,漂亮的事情是它接受 PtpPortDetails
(继承自 PortDetails
)而不是强制转换。但是,如果我这样做,我无法创建一个 PonPortFactory
,它也实现了 IPortFactory
,因为这些端口需要 PonPortDetails
。或者 CatvPortFactory
就此而言。
使用端口工厂时出现另一个code smell:
PortType portType = command.PortType;
IPortFactory portFactory = portType.GetPortFactory();
var portsToSelectFrom = ports.Select(port => (IInternetPort) portFactory.CreatePort(port.Id, port.PortDetails)).ToList();
我真的很想不必从 IPort
到 IInternetPort
进行向下转换,而只需要 CreatePort
return IInternetPort
。
理解上面的最后一点信息大概是下面的class(基于Jimmy BogardsEnumeration
class):
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# 不支持 return 类型协变的障碍)。
此外,如果有任何其他技巧可以帮助我编写更好的代码,我们将不胜感激。
更新
由于评论中的要求,我在下面添加了更多代码。
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
到 select 端口。但是,当算法 select 编辑了正确的端口时,我们只对 Id
感兴趣,因此 return 类型只是 IPort
.
I think there's sign of code smell. In CreatePort in PtpPortFactory, the pretty thing would be for it to accept PtpPortDetails (which inherits from PortDetails) instead of having to cast.
不,这很好,因为依赖应该是抽象的而不是实现。所以在这种情况下传递 PortDetails 是可以的。
我看到的味道在这里:
public interface IPort
{
int Id { get; }
}
public interface IInternetPort : IPort
{
bool OfCapability(FiberCapability capability);
}
接口基本上用于定义行为。而且你在我看来可疑的接口中使用属性。
- 继承描述了一种 is-a 关系。
- 实现接口
描述了一种可以做到的关系。
这里你处理的是 AbstractFactory 模式。比方说,你可以有抽象工厂 BasePortFactory
可以做 IPortFactory
声明的事情。
所以你应该 return BasePortFactory
来自工厂方法。但这又是设计解决方案时的一个选择。
类似地,CreatePort
方法应该公开 return 类型,如果您想在 can-do
上使用 is-a
基础 class 或接口。
Update
此示例不是最适合您的场景,但这是为了描述我分享的想法:
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) { }
}
这里是需要将方法签名更改为 return 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();
P.S。 - 这些类型的问题应该在 code review or programmer.
上提出
以下是我对您要解决的问题(业务问题,而非设计问题)的理解:
你有一个端口列表,你想在列表上执行一些算法,从列表中选择一个端口(基于特定于算法的一些标准)。
在这里,我将建议一种对此进行建模的方法。我将假设您有以下输入 class:
public class PortInput
{
public int Id { get; set; }
public PortDetails PortDetails { get; set; }
}
这对应于您问题中的端口 class 的一部分。
这里是算法的界面:
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");
}
}
如您所见,除了算法 class 外,没有 class 知道端口 ID。 class确定功能的 es 不知道端口 ID,因为没有它们也可以完成工作。
另一件需要注意的事情是,您不需要在每次 运行 算法时都实例化一个新的功能服务。这与您的问题中描述的 IInternetPort 实现形成对比,在该实现中,您每次要执行算法时都会创建一个新实例。我猜你这样做是因为每个实例都绑定到不同的 ID。
这些 classes 使用依赖注入。您应该编写它们以便能够使用它们。您应该在 Composition Root.
中执行此操作
以下是如何使用 Pure DI 进行此类组合:
IPortSelectionAlgorithm algorithm =
new PortSelectionAlgorithm(
new PortCapabilityService(
new PtpPortCapabilityService(),
new CatvPortCapabilityService(),
new PonPortCapabilityService()));
我曾尝试根据开放-封闭原则重构一些代码,但在应用设计模式时,我似乎无法正确理解以下 classes。 (对于下面列出的许多 class 内容,我深表歉意 - 我已尽可能减少它们,但剩下的部分需要向您展示我的设计)。
设置包含以下 classes:
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
.
在这段代码中,我认为有代码味道的迹象。在 PtpPortFactory
中的 CreatePort
中,漂亮的事情是它接受 PtpPortDetails
(继承自 PortDetails
)而不是强制转换。但是,如果我这样做,我无法创建一个 PonPortFactory
,它也实现了 IPortFactory
,因为这些端口需要 PonPortDetails
。或者 CatvPortFactory
就此而言。
使用端口工厂时出现另一个code smell:
PortType portType = command.PortType;
IPortFactory portFactory = portType.GetPortFactory();
var portsToSelectFrom = ports.Select(port => (IInternetPort) portFactory.CreatePort(port.Id, port.PortDetails)).ToList();
我真的很想不必从 IPort
到 IInternetPort
进行向下转换,而只需要 CreatePort
return IInternetPort
。
理解上面的最后一点信息大概是下面的class(基于Jimmy BogardsEnumeration
class):
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# 不支持 return 类型协变的障碍)。
此外,如果有任何其他技巧可以帮助我编写更好的代码,我们将不胜感激。
更新
由于评论中的要求,我在下面添加了更多代码。
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
到 select 端口。但是,当算法 select 编辑了正确的端口时,我们只对 Id
感兴趣,因此 return 类型只是 IPort
.
I think there's sign of code smell. In CreatePort in PtpPortFactory, the pretty thing would be for it to accept PtpPortDetails (which inherits from PortDetails) instead of having to cast.
不,这很好,因为依赖应该是抽象的而不是实现。所以在这种情况下传递 PortDetails 是可以的。
我看到的味道在这里:
public interface IPort
{
int Id { get; }
}
public interface IInternetPort : IPort
{
bool OfCapability(FiberCapability capability);
}
接口基本上用于定义行为。而且你在我看来可疑的接口中使用属性。
- 继承描述了一种 is-a 关系。
- 实现接口 描述了一种可以做到的关系。
这里你处理的是 AbstractFactory 模式。比方说,你可以有抽象工厂 BasePortFactory
可以做 IPortFactory
声明的事情。
所以你应该 return BasePortFactory
来自工厂方法。但这又是设计解决方案时的一个选择。
类似地,CreatePort
方法应该公开 return 类型,如果您想在 can-do
上使用 is-a
基础 class 或接口。
Update
此示例不是最适合您的场景,但这是为了描述我分享的想法:
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) { }
}
这里是需要将方法签名更改为 return 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();
P.S。 - 这些类型的问题应该在 code review or programmer.
上提出以下是我对您要解决的问题(业务问题,而非设计问题)的理解:
你有一个端口列表,你想在列表上执行一些算法,从列表中选择一个端口(基于特定于算法的一些标准)。
在这里,我将建议一种对此进行建模的方法。我将假设您有以下输入 class:
public class PortInput
{
public int Id { get; set; }
public PortDetails PortDetails { get; set; }
}
这对应于您问题中的端口 class 的一部分。
这里是算法的界面:
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");
}
}
如您所见,除了算法 class 外,没有 class 知道端口 ID。 class确定功能的 es 不知道端口 ID,因为没有它们也可以完成工作。
另一件需要注意的事情是,您不需要在每次 运行 算法时都实例化一个新的功能服务。这与您的问题中描述的 IInternetPort 实现形成对比,在该实现中,您每次要执行算法时都会创建一个新实例。我猜你这样做是因为每个实例都绑定到不同的 ID。
这些 classes 使用依赖注入。您应该编写它们以便能够使用它们。您应该在 Composition Root.
中执行此操作以下是如何使用 Pure DI 进行此类组合:
IPortSelectionAlgorithm algorithm =
new PortSelectionAlgorithm(
new PortCapabilityService(
new PtpPortCapabilityService(),
new CatvPortCapabilityService(),
new PonPortCapabilityService()));