Как я должен смоделировать свой код для максимизации повторного использования кода в этой определенной ситуации?

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

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

У нас есть внутренняя система обмена сообщениями данных для того, чтобы обновить таблицы через базы данных по различным машинам. Мы разворачиваем нашу службу обмена сообщениями, чтобы отправить данные внешним поставщикам, и я хочу кодировать простое решение, которое может быть снова использовано, должен мы решать отправить данные больше чем одному поставщику. Код будет скомпилирован в EXE и выполнен регулярно для отправки сообщений в услугу передачи данных поставщика.

Вот грубая схема того, что делает код:

public class OutboxManager 
{
    private List _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 и уже имейте весь код базы данных, написанный записанный, но быть вынужденными предоставить их собственную реализацию передающих данных поставщику.

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


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

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

public class OutboxManager
{
    private IEnumerable _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()

11
задан Community 23 May 2017 в 12:27
поделиться

7 ответов

Я бы сказал использовать 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
ответ дан 3 December 2019 в 08:53
поделиться

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

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
ответ дан 3 December 2019 в 08:53
поделиться

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

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

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

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

0
ответ дан 3 December 2019 в 08:53
поделиться

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

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

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

0
ответ дан 3 December 2019 в 08:53
поделиться

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

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

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

9
ответ дан 3 December 2019 в 08:53
поделиться

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

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...
    }    
}
1
ответ дан 3 December 2019 в 08:53
поделиться

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

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
ответ дан 3 December 2019 в 08:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: