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

◇◆丶佛笑我妖孽 提交于 2019-11-28 12:04:29

I would say use Dependecy Injection. Basically, you pass an abstraction of the send method.

Something like:

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
    }

    ...
}

Another approach, as already mentioned, inheritance.

In either case: try to remove DB retrieval code from this class. Use another abstraction for that (ie: passing an IDataProvider interface or something like that to the constructor). It will make your code more testable.

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

If you make this an abstract base class so it has to be inherited you can force this method to be implemented in the concrete object.

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();
    }
}

One way you could do it is through the use of interfaces.

public interface IVendorSender
{
    IEnumerable<OutboxMsg> GetMessages();
}

Then in your constructor take an instance as a parameter.

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

It looks to me like you're most of the way there.

Some basic steps:

1 Figure out what parts of your code are the same no matter what the vendor.
2 Write those into a re-usable module (probably a .dll)
3 Determine what changes per vendor.
4 Determine what (of the above) is code - write specific modules for that.
5 Determine what (of the above) is configuration - create a config scheme for those.

Your .exe will then acually call the appropriate OutboxManager object for the correct vendor.

Create an abstract base class and have the method that needs to be changed as abstract protected e.g.

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

Each implementation inherits from this abstract class but only provides the implementation for SendMessagesToVendor() the shared implementation is defined in the abstract base class.

Ditto to Mr Copsey. Manifest solution #1 is indeed sub-classing. You have, whether by luck or skill, already structured your code to make this easy to implement.

Depending on the nature of the differences between vendors, if there is a lot of common functionality, another alternative might be to have a database with a record for each vendor, and have a couple of flags that control processing. If you can break it down to "if flag1 is true do thing A else do thing B; always do thing C; if flag2 is true do thing D else we're done", then rather than repeating a bunch of code across vendors you may be able to let data control the processing.

Oh, and I might add the perhaps obvious: If the only difference is data values, then of course just store the data values somewhere. Like to take a trivial example, if the only difference between vendors is the domain name that you connect to, then just create a table with vendorid and domain name, read the value and plug it in.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!