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

时间:2010-08-02 16:12:41

标签: c# .net design-patterns oop

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

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

我们有一个内部数据消息系统,用于在不同机器上跨数据库更新表。我们正在扩展我们的消息服务以向外部供应商发送数据,我想编写一个简单的解决方案,如果我们决定向多个供应商发送数据,可以重复使用。代码将被编译为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 answerReed's answer (and comments)的启发,可以使用界面模型。所有相同的方法都存在,但现在它们隐藏在消费者可以根据需要更新的接口中。

我没有意识到接口实现的强大功能,直到我意识到我允许类的使用者插入他们自己的类来进行数据访问(IOutboxMgrDataProvider)和错误记录({{1} })。虽然我仍然提供默认实现,因为我不希望这些代码发生变化,但消费者仍然可以使用自己的代码覆盖它们。除了写出多个构造函数(I may change to named and optional parameters)之外,实际上并没有花太多时间来改变我的实现。

IErrorLogger

7 个答案:

答案 0 :(得分:9)

有两种非常简单的方法:

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

  2. 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和域名的表,读取值并将其插入。