更新:请参阅问题的结尾,了解我如何实施解决方案。
对于措辞不好的问题,我很抱歉,但我不确定如何最好地提出这个问题。我不确定如何设计一个可以重复使用的解决方案,其中大多数代码在每次实现时完全相同,但实现的一部分将每次都改变,但遵循类似的模式。我试图避免复制和粘贴代码。
我们有一个内部数据消息系统,用于在不同机器上跨数据库更新表。我们正在扩展我们的消息服务以向外部供应商发送数据,我想编写一个简单的解决方案,如果我们决定向多个供应商发送数据,可以重复使用。代码将被编译为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
并且已经编写了所有编写的数据库代码,但是被迫提供他们自己的向供应商发送数据的实现。
我确信有很好的面向对象原则可以帮助我解决这个问题,但我不确定哪一个最适合使用。
这是我最终使用的解决方案,受到Victor's answer和Reed's answer (and comments)的启发,可以使用界面模型。所有相同的方法都存在,但现在它们隐藏在消费者可以根据需要更新的接口中。
我没有意识到接口实现的强大功能,直到我意识到我允许类的使用者插入他们自己的类来进行数据访问(IOutboxMgrDataProvider
)和错误记录({{1} })。虽然我仍然提供默认实现,因为我不希望这些代码发生变化,但消费者仍然可以使用自己的代码覆盖它们。除了写出多个构造函数(I may change to named and optional parameters)之外,实际上并没有花太多时间来改变我的实现。
IErrorLogger
答案 0 :(得分:9)
有两种非常简单的方法:
使OutboxManager
成为一个抽象类,并为每个供应商提供一个子类。 SendMessagesToVendor
可以标记为抽象,强制每个供应商重新实现它。这种方法很简单,很好地符合OO原则,并且还具有允许您为其他方法提供实现的优点,但如果您希望稍后允许,仍允许覆盖供应商特定版本。
让OutboxManager
封装一些其他类或接口,提供SendMessagesToVendor
中所需的供应商特定信息。这可以很容易地成为每个供应商实现的小型接口,SendMessagesToVendor
可以使用此接口实现来发送其消息。这样做的好处是可以在这里编写一些代码 - 可以减少供应商之间的重复。它还可能允许您的SendMessagesToVendor
方法更加一致,并且更容易测试,因为您只需要依赖此处所需的特定供应商功能。这也可以作为一个委托作为相关(但略有不同)的方法传入(我个人更喜欢通过委托实现的接口)。
答案 1 :(得分:2)
如果将其设为抽象基类,则必须继承它,可以强制在具体对象中实现此方法。
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();
}
}
答案 2 :(得分:1)
你可以做到的一种方法是使用接口。
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...
}
}
答案 3 :(得分:1)
我会说使用Dependecy Injection。基本上,你传递了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接口或类似的东西传递给构造函数)。它将使您的代码更易于测试。
答案 4 :(得分:0)
在我看来,你大部分时间都在那里。
一些基本步骤:
1无论供应商是什么,都要弄清楚代码的哪些部分是相同的。
2将它们写入可重复使用的模块(可能是.dll)
3确定每个供应商的更改。
4确定(上述)代码 - 为此编写特定模块。
5确定(上述)配置 - 为这些配置方案创建配置方案。
然后,您的.exe将为正确的供应商实际调用相应的OutboxManager
对象。
答案 5 :(得分:0)
创建一个抽象基类,并将需要更改的方法更改为抽象保护,例如
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()的实现,共享实现在抽象基类中定义。
答案 6 :(得分:0)
与Copsey先生同上。清单解决方案#1确实是子类。无论是运气还是技巧,您已经构建了代码以使其易于实现。
根据供应商之间差异的性质,如果存在许多常见功能,另一种替代方案可能是为每个供应商提供一个包含记录的数据库,并且有一些控制处理的标志。如果你可以把它分解为“如果flag1是真的那么做事A还做事B;总是做事C;如果flag2是真的做事D还有其他我们已经完成了”,然后而不是在供应商之间重复一堆代码也许可以让数据控制处理。
哦,我可能会添加或许显而易见:如果唯一的区别是数据值,那么当然只是将数据值存储在某处。想要一个简单的例子,如果供应商之间的唯一区别是你连接的域名,那么只需创建一个包含vendorid和域名的表,读取值并将其插入。