Como devo modelar meu código para maximizar a reutilização de código nessa situação específica?

Atualizado: consulte o final da pergunta para saber como implementei a solução.

Sinto muito pela pergunta mal formulada, mas não tinha certeza da melhor maneira de fazer. Não tenho certeza de como projetar uma solução que possa ser reutilizada, onde a maior parte do código é exatamente o mesmo toda vez que é implementada, mas parte da implementação muda sempre, mas segue padrões semelhantes. Estou tentando evitar copiar e colar código.

Temos um sistema interno de mensagens de dados para atualizar tabelas entre bancos de dados em máquinas diferentes. Estamos expandindo nosso serviço de mensagens para enviar dados a fornecedores externos e desejo codificar uma solução simples que possa ser reutilizada caso decida enviar dados para mais de um fornecedor. O código será compilado em um EXE e executado regularmente para enviar mensagens ao serviço de dados do fornecedor.

Aqui está um esboço do que o código faz:

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

Quero escrever esse código de forma a poder reutilizar as partes que não mudam sem ter que recorrer a copiar, colar e preencher o código paraSendMessagesToVendor(). Eu quero que um desenvolvedor possa usar umOutboxManager e tenha todo o código do banco de dados gravado já gravado, mas seja forçado a fornecer sua própria implementação de envio de dados ao fornecedor.

Tenho certeza de que existem bons princípios orientados a objetos que podem me ajudar a resolver esse problema, mas não tenho certeza de qual deles seria melhor usar.

Esta é a solução que acabei usando, inspirada emA resposta de Victor eResposta de Reed (e comentários) para usar um modelo de interface. Todos os mesmos métodos estão lá, mas agora eles estão escondidos em interfaces que o consumidor pode atualizar, se necessário.

Eu não percebi o poder da implementação da interface até perceber que permitia ao consumidor da classe conectar suas próprias classes para o acesso a dados (IOutboxMgrDataProvider) e registro de erros (IErrorLogger) Embora eu ainda forneça implementações padrão, já que não espero que esse código seja alterado, ainda é possível que o consumidor as substitua por seu próprio código. Exceto por escrever vários construtores (queEu posso mudar para parâmetros nomeados e opcionais), realmente não demorou muito tempo para alterar minha implementação.

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

questionAnswers(7)

yourAnswerToTheQuestion