Как мне смоделировать мой код, чтобы максимизировать повторное использование кода в этой конкретной ситуации? - PullRequest
11 голосов
/ 02 августа 2010

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

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

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

Ответы [ 7 ]

9 голосов
/ 02 августа 2010

Есть два очень простых подхода:

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

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

2 голосов
/ 02 августа 2010

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

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();
    }
}
1 голос
/ 02 августа 2010

Я бы сказал, используйте 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 или что-то подобное в конструктор). Это сделает ваш код более тестируемым.

1 голос
/ 02 августа 2010

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

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...
    }    
}
0 голосов
/ 02 августа 2010

Так же мистеру Копси.Манифест № 1 действительно является подклассом.Вы, по стечению обстоятельств или навыкам, уже структурировали свой код, чтобы упростить его реализацию.

В зависимости от характера различий между поставщиками, если есть много общих функций, другой альтернативой может бытьиметь базу данных с записями для каждого поставщика и пару флагов, управляющих обработкой.Если вы можете разбить его на следующее: «если flag1 - истина, то делать что-то A, а что-то делать B; всегда делать что-то C; если flag2 - true, делать что-то D, иначе мы сделали», вместо того, чтобы повторять связку кода между поставщикамиможет позволить данным контролировать процесс обработки.

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

0 голосов
/ 02 августа 2010

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

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

0 голосов
/ 02 августа 2010

Мне кажется, ты там почти весь.

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

1 Выясните, какие части вашего кода одинаковы, независимо от поставщика.
2 Запишите их в модуль многократного использования (возможно, в .dll)
3 Определите, какие изменения для каждого поставщика.
4 Определите, что (из вышеперечисленного) является кодом - напишите специальные модули для этого.
5 Определите, что (из вышеперечисленного) является конфигурацией - создайте схему конфигурации для них.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...