我应该如何建模我的代码以在这种特定情况下最大化代码重用?

Ben*_*ack 11 .net c# oop design-patterns

更新:请参阅问题的结尾,了解我如何实施解决方案.

抱歉这个措辞不好的问题,但我不确定如何最好地问它.我不确定如何设计一个可以重复使用的解决方案,其中大多数代码在每次实现时完全相同,但实现的一部分将每次都改变,但遵循类似的模式.我试图避免复制和粘贴代码.

我们有一个内部数据消息系统,用于在不同机器上跨数据库更新表.我们正在扩展我们的消息服务以向外部供应商发送数据,我想编写一个简单的解决方案,如果我们决定向多个供应商发送数据,可以重复使用.代码将被编译为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.
    }
}
Run Code Online (Sandbox Code Playgroud)

我想以这样一种方式编写这段代码,即我可以重新使用不会改变的部分而不必诉诸于复制和粘贴并填写代码SendMessagesToVendor().我希望开发人员能够使用OutboxManager并编写已经编写的所有数据库代码,但是被迫提供他们自己的向供应商发送数据的实现.

我确信有很好的面向对象原则可以帮助我解决这个问题,但我不确定哪一个最好用.


这是我最终选择的解决方案,受到Victor的回答Reed的答案(和评论)的启发,使用界面模型.所有相同的方法都存在,但现在它们隐藏在消费者可以根据需要更新的界面中.

直到我意识到我允许类的使用者为数据访问(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()
Run Code Online (Sandbox Code Playgroud)

Ree*_*sey 9

有两种非常简单的方法:

  1. 创建OutboxManager一个抽象类,并为每个供应商提供一个子类.该SendMessagesToVendor可标识为抽象的,迫使它由每个供应商重新实现.这种方法很简单,很好地符合OO原则,并且还具有允许您为其他方法提供实现的优点,但如果您希望稍后允许,则仍允许覆盖供应商特定版本.

  2. OutboxManager封装了一些其它类或接口,其提供在需要的供应商特定信息SendMessagesToVendor.这可以很容易地成为每个供应商实现的小型接口,并且SendMessagesToVendor可以使用此接口实现来发送其消息.这样做的好处是允许您在此处编写一些代码 - 可能会减少供应商之间的重复.它还可能使您的SendMessagesToVendor方法更加一致,并且更容易测试,因为您只需依赖此处所需的特定供应商功能.这也可以作为一个委托作为相关(但略有不同)的方法传入(我个人更喜欢通过委托实现的接口).


Vic*_*aci 1

我会说使用依赖注入。基本上,您传递了 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
    }

    ...
}
Run Code Online (Sandbox Code Playgroud)

正如已经提到的,另一种方法是继承。

无论哪种情况:尝试从此类中删除数据库检索代码。为此使用另一个抽象(即:将 IDataProvider 接口或类似的东西传递给构造函数)。它将使您的代码更易于测试。