Как мне смоделировать мой код, чтобы максимизировать повторное использование кода в этой конкретной ситуации?

Обновлено: см. конец вопроса о том, как я реализовал решение.

Извините за плохо сформулированный вопрос, но я не был уверен, как лучше спросить об этом. Я не уверен, как разработать решение, которое может быть повторно использовано, когда большая часть кода является то же самое каждый раз, когда она будет реализована, но часть реализации будет меняться каждый раз, но следовать аналогичным шаблонам. Я пытаюсь избежать копирования и вставки кода.

У нас есть внутренняя система обмена сообщениями для обновления таблиц по базам данных на разных машинах. Мы расширяем нашу службу обмена сообщениями для отправки данных внешним поставщикам, и я хочу закодировать простое решение, которое можно повторно использовать, если мы решим отправить данные нескольким вендорам. Код будет скомпилирован в 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 и иметь все написанные ранее коды базы данных, но должен быть вынужден предоставить собственную реализацию отправки данных поставщику.

Я уверен, что есть хорошие объектно-ориентированные принципы, которые могут помочь мне решить эту проблему, но я не уверен, какой из них лучше всего использовать.


Это решение, с которым я столкнулся, вдохновленный ответом Виктора и Рид-ответ (и комментарии) использовать модель интерфейса. Все те же методы существуют, но теперь они спрятаны в интерфейсы, которые потребитель может обновить при необходимости.

Я не понял возможности реализации интерфейса до тех пор, пока не понял, что я разрешаю потребителю класса подключать собственные классы для доступа к данным (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()

Ответ 1

Я бы сказал, используйте Dependecy Injection. В принципе, вы передаете абстракцию метода отправки.

Что-то вроде:

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
    }

    ...
}

Другой подход, как уже упоминалось, наследование.

В любом случае: попытайтесь удалить код извлечения DB из этого класса. Используйте для этого другую абстракцию (т.е. Передать интерфейс IDataProvider или что-то подобное конструктору). Это сделает ваш код более надежным.

Ответ 2

Существует два очень простых подхода:

  • Сделайте OutboxManager абстрактным классом и предоставите подкласс для каждого поставщика. SendMessagesToVendor может быть помечен как абстрактный, заставляя его переопределять каждый поставщик. Этот подход прост, хорошо подходит для принципов OO, а также имеет преимущество, позволяя вам реализовать реализацию для других методов, но все же позволяя переопределять версию для конкретного поставщика, если вы хотите разрешить это позже.

  • Имейте OutboxManager инкапсулировать какой-либо другой класс или интерфейс, который предоставляет специфическую для поставщика информацию, требуемую в SendMessagesToVendor. Это может быть небольшим интерфейсом, который реализуется для каждого поставщика, а SendMessagesToVendor может использовать эту реализацию интерфейса для отправки своих сообщений. Это имеет то преимущество, что позволяет вам написать код здесь - потенциально уменьшая дублирование между поставщиками. Это также потенциально позволяет вашему методу SendMessagesToVendor быть более последовательным и более легко проверяемым, поскольку вам нужно только полагаться на конкретные функции поставщика, необходимые здесь. Это также может быть реализовано как делегат, переданный как связанный (но немного другой) подход (я лично предпочитаю интерфейс, который будет реализован над делегатом, однако).

Ответ 3

Если вы делаете это абстрактным базовым классом, поэтому его нужно унаследовать, вы можете принудительно реализовать этот метод в конкретном объекте.

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

Ответ 4

Один из способов сделать это - использование интерфейсов.

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

Ответ 5

Мне кажется, что ты больше всего хочешь.

Некоторые основные шаги:

1 Посмотрите, какие части вашего кода одинаковы независимо от того, какой поставщик.

2 Запишите их в повторно используемый модуль (возможно, DLL)
3 Определите, какие изменения для каждого поставщика.

4 Определите, что (из вышеперечисленного) - это код для записи конкретных модулей для этого.

5 Определите, что (из вышеперечисленного) является конфигурацией - создайте для них конфигурационную схему.

Затем ваш .exe вызовет соответствующий объект OutboxManager для правильного поставщика.

Ответ 6

Создайте абстрактный базовый класс и получите метод, который необходимо изменить как защищенный от абстракции, например.

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(), общая реализация определяется в абстрактном базовом классе.

Ответ 7

То же самое касается мистера Копси. Манифест-решение №1 действительно является подклассом. У вас есть ли удача или умение уже структурированный код для упрощения его реализации.

В зависимости от характера различий между поставщиками, если существует много общих функций, другой альтернативой может быть наличие базы данных с записью для каждого поставщика и наличие нескольких флагов, которые контролируют обработку. Если вы можете разбить его на "если флаг1 является истинным, делайте что-то другое, делайте вещь B, всегда делайте вещь C, если флаг2 истинно, делайте D, иначе мы закончили", а вместо того, чтобы повторять кучу кода между поставщиками, вы может позволить управлять данными обработки.

О, и я мог бы добавить, возможно, очевидное: если единственное различие - это значения данных, то, конечно, просто где-то сохраняйте значения данных. Как принять тривиальный пример, если единственная разница между поставщиками - это имя домена, к которому вы подключаетесь, а затем просто создайте таблицу с именем vendorid и доменом, прочитайте значение и подключите его.