Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
138 views
in Technique[技术] by (71.8m points)

c# - How should I model my code to maximize code re-use in this specific situation?

Updated: See end of question for how I implemented the solution.

Sorry for the poorly-worded question, but I wasn't sure how best to ask it. I'm not sure how to design a solution that can be re-used where most of the code is the exact same each time it is implemented, but part of the implementation will change every time, but follow similar patterns. I'm trying to avoid copying and pasting code.

We have an internal data messaging system for updating tables across databases on different machines. We're expanding our messaging service to send data to external vendors and I want to code a simple solution that can be re-used should we decide to send data to more than one vendor. The code will be compiled into an EXE and run on a regular basis to send messages to the vendor's data service.

Here's a rough outline of what the code does:

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.
    }
}

I want to write this code in such a way that I can re-use the parts that don't change without having to resort to copying and pasting and filling in the code for SendMessagesToVendor(). I want a developer to be able to use an OutboxManager and have all of the database code written already written, but be forced to supply their own implementation of sending data to the vendor.

I'm sure there are good object-oriented principles that can help me solve that problem, but I'm not sure which one(s) would be best to use.


This is the solution I ended up going with, inspired by Victor's answer and Reed's answer (and comments) to use an interface model. All of the same methods are there, but now they are tucked away into interfaces that the consumer can update if necessary.

I didn't realize the power of the interface implementation until I realized that I allow the consumer of the class to plug in their own classes for the data access (IOutboxMgrDataProvider) and error logging (IErrorLogger). While I still provide default implementations since I don't expect this code to change, it's still possible for the consumer to override them with their own code. Except for writing out multiple constructors (which I may change to named and optional parameters), it really didn't take a lot of time to change my implementation.

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()
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There are two very simple approaches:

  1. Make OutboxManager an abstract class, and provide a subclass per vendor. The SendMessagesToVendor can be marked abstract, forcing it to be reimplemented by each vendor. This approach is simple, fits OO principles well, and also has the advantage of allowing you to supply the implementation for the other methods, but still allowing overriding for a vendor specific version if you want to allow that later.

  2. Have OutboxManager encapsulate some other class or interface which provides the vendor-specific information required in SendMessagesToVendor. This could easily be a small interface that is implemented per-vendor, and SendMessagesToVendor could use this interface implementation to send its messages. This has the advantage of allowing you to write some of the code here - potentially reducing duplication across vendors. It also potentially allows your SendMessagesToVendor method to be more consistent, and more easily testable, since you only have to rely on the specific vendor functionality required here. This could also, potentially, be implemented as a delegate passed in as a related (but slightly different) approach (I personally prefer an interface to be implemented over a delegate, however).


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...