更新:请参阅问题的结尾,了解我如何实施解决方案。
对于措辞不好的问题,我很抱歉,但我不确定如何最好地提出这个问题。我不确定如何设计一个可以重复使用的解决方案,其中大多数代码在每次实现时都完全相同,但实现的一部分每次都会改变,但遵循类似的模式。我试图避免复制和粘贴代码。
我们有一个内部数据消息系统,用于在不同机器上跨数据库更新表。我们正在扩展我们的消息服务以向外部供应商发送数据,我想编写一个简单的解决方案,如果我们决定向多个供应商发送数据,可以重复使用。代码将被编译为EXE并定期运行以将消息发送到供应商的数据服务。
以下是代码的大致概述:
public class OutboxManager
{
private List<OutboxMsg> _OutboxMsgs;
public void DistributeOutboxMessages()
{
try {
RetrieveMessages();
SendMessagesToVendor();
MarkMessagesAsProcessed();
}
catch Exception ex {
LogErrorMessageInDb(ex);
}
}
private void RetrieveMessages()
{
//retrieve messages from the database; poplate _OutboxMsgs.
//This code stays the same in each implementation.
}
private void SendMessagesToVendor() // <== THIS CODE CHANGES EACH IMPLEMENTATION
{
//vendor-specific code goes here.
//This code is specific to each implementation.
}
private void MarkMessagesAsProcessed()
{
//If SendMessageToVendor() worked, run this method to update this db.
//This code stays the same in each implementation.
}
private void LogErrorMessageInDb(Exception ex)
{
//This code writes an error message to the database
//This code stays the same in each implementation.
}
}
我想以这样一种方式编写这段代码,即我可以重新使用不会改变的部分,而不必诉诸于复制和粘贴并填写代码 SendMessagesToVendor()。我希望开发人员能够使用 OutboxManager 并且已经编写了所有编写的数据库代码,但是被迫提供他们自己的向供应商发送数据的实现。
我确信有很好的面向对象原则可以帮助我解决这个问题,但我不确定哪一个最好用。
这是我最终选择的解决方案,受到启发 维克多的答案 和 里德的回答(和评论) 使用接口模型。所有相同的方法都存在,但现在它们隐藏在消费者可以根据需要更新的界面中。
直到我意识到我允许类的使用者插入自己的类进行数据访问时,我才意识到接口实现的强大功能(IOutboxMgrDataProvider)和错误记录(IErrorLogger)。虽然我仍然提供默认实现,因为我不希望这些代码发生变化,但消费者仍然可以使用自己的代码覆盖它们。除了写出多个构造函数(其中 我可以更改为命名和可选参数),它确实没有花很多时间来改变我的实现。
public class OutboxManager
{
private IEnumerable<OutboxMsg> _OutboxMsgs;
private IOutboxMgrDataProvider _OutboxMgrDataProvider;
private IVendorMessenger _VendorMessenger;
private IErrorLogger _ErrorLogger;
//This is the default constructor, forcing the consumer to provide
//the implementation of IVendorMessenger.
public OutboxManager(IVendorMessenger messenger)
{
_VendorMessenger = messenger;
_OutboxMgrDataProvider = new DefaultOutboxMgrDataProvider();
_ErrorLogger = new DefaultErrorLogger();
}
//... Other constructors here that have parameters for DataProvider
// and ErrorLogger.
public void DistributeOutboxMessages()
{
try {
_OutboxMsgs = _OutboxMgrDataProvider.RetrieveMessages();
foreach om in _OutboxMsgs
{
if (_VendorMessenger.SendMessageToVendor(om))
_OutboxMgrDataProvider.MarkMessageAsProcessed(om)
}
}
catch Exception ex {
_ErrorLogger.LogErrorMessage(ex)
}
}
}
//...interface code: IVendorMessenger, IOutboxMgrDataProvider, IErrorLogger
//...default implementations: DefaultOutboxMgrDataProvider(),
// DefaultErrorLogger()
我会说使用 依赖注射。基本上,您传递了send方法的抽象。
就像是:
interface IVendorMessageSender
{
void SendMessage(Vendor v);
}
public class OutboxManager
{
IVendorMessageSender _sender;
public OutboxManager(IVendorMessageSender sender)
{
this._sender = sender; //Use it in other methods to call the concrete implementation
}
...
}
另一种方法,如前所述,继承。
在任何一种情况下:尝试从此类中删除数据库检索代码。使用另一个抽象(即:将IDataProvider接口或类似的东西传递给构造函数)。它将使您的代码更易于测试。
我会说使用 依赖注射。基本上,您传递了send方法的抽象。
就像是:
interface IVendorMessageSender
{
void SendMessage(Vendor v);
}
public class OutboxManager
{
IVendorMessageSender _sender;
public OutboxManager(IVendorMessageSender sender)
{
this._sender = sender; //Use it in other methods to call the concrete implementation
}
...
}
另一种方法,如前所述,继承。
在任何一种情况下:尝试从此类中删除数据库检索代码。使用另一个抽象(即:将IDataProvider接口或类似的东西传递给构造函数)。它将使您的代码更易于测试。
如果将其设为抽象基类,则必须继承它,可以强制在具体对象中实现此方法。
using System;
using System.Collections.Generic;
public abstract class OutboxManagerBase
{
private List<string> _OutboxMsgs;
public DistributeOutboxMessages()
{
try {
RetrieveMessages();
SendMessagesToVendor();
MarkMessagesAsProcessed();
}
catch Exception ex {
LogErrorMessageInDb(ex);
}
}
private void RetrieveMessages()
{
//retrieve messages from the database; poplate _OutboxMsgs.
//This code stays the same in each implementation.
}
protected abstract void SendMessagesToVendor();
private void MarkMessagesAsProcessed()
{
//If SendMessageToVendor() worked, run this method to update this db.
//This code stays the same in each implementation.
}
private void LogErrorMessageInDb(Exception ex)
{
//This code writes an error message to the database
//This code stays the same in each implementation.
}
}
public class OutBoxImp1 : OutboxManagerBase
{
protected override void SendMessagesToVendor()
{
throw new NotImplementedException();
}
}
你可以做到的一种方法是使用接口。
public interface IVendorSender
{
IEnumerable<OutboxMsg> GetMessages();
}
然后在构造函数中将实例作为参数。
public class OutboxManager
{
private readonly IVendorSender _vendorSender;
public OutboxManager(IVendorSender vendorSender)
{
_vendorSender = vendorSender ?? new DefaultSender();
}
private void SendMessagesToVendor() // <== THIS CODE CHANGES EACH IMPLEMENTATION
{
_vendorSender.GetMessages(); // Do stuff...
}
}
它看起来像你在那里的大部分路。
一些基本步骤:
1无论供应商是什么,都要弄清楚代码的哪些部分是相同的。
2将它们写入可重复使用的模块(可能是.dll)
3确定每个供应商的更改。
4确定(上述)代码 - 为此编写特定模块。
5确定(上述)配置 - 为这些配置方案创建配置方案。
然后,您的.exe将正确地调用相应的 OutboxManager 对象为正确的供应商。
创建一个抽象基类,并将需要更改的方法更改为抽象保护,例如
public abstract class OutboxManager
{
private List<OutboxMsg> _OutboxMsgs;
public void DistributeOutboxMessages()
{
try {
RetrieveMessages();
SendMessagesToVendor();
MarkMessagesAsProcessed();
}
catch (Exception ex) {
LogErrorMessageInDb(ex);
}
}
private void RetrieveMessages()
{
//retrieve messages from the database; poplate _OutboxMsgs.
//This code stays the same in each implementation.
}
protected abstract void SendMessagesToVendor(); // <== THIS CODE CHANGES EACH IMPLEMENTATION
private void MarkMessagesAsProcessed()
{
//If SendMessageToVendor() worked, run this method to update this db.
//This code stays the same in each implementation.
}
private void LogErrorMessageInDb(Exception ex)
{
//This code writes an error message to the database
//This code stays the same in each implementation.
}
}
每个实现都继承自此抽象类,但仅提供SendMessagesToVendor()的实现,共享实现在抽象基类中定义。
同样来自Copsey先生。清单解决方案#1确实是分类。无论是运气还是技巧,您已经构建了代码以使其易于实现。
根据供应商之间差异的性质,如果有很多共同的功能,另一种选择可能是拥有一个包含每个供应商记录的数据库,并且有几个控制处理的标志。如果你可以把它分解为“如果flag1是真的那么做其他事做B事情;总是做事情C;如果flag2是真的做事情D其他我们已经完成了”,然后而不是重复一堆代码跨供应商你或许可以让数据控制处理。
哦,我可能会添加或许显而易见:如果唯一的区别是数据值,那么当然只是将数据值存储在某处。想要一个简单的例子,如果供应商之间的唯一区别是您连接的域名,那么只需创建一个包含vendorid和域名的表,读取值并将其插入。