2010-08-02 14 views
11

Actualizado: Consulte el final de la pregunta sobre cómo implementé la solución.¿Cómo debo modelar mi código para maximizar la reutilización del código en esta situación específica?

Disculpe la pobremente formulada pregunta, pero no estaba seguro de cómo pedirla. No estoy seguro de cómo diseñar una solución que pueda reutilizarse donde la mayoría del código sea exactamente igual cada vez que se implemente, pero parte de la implementación cambiará cada vez, pero seguirá patrones similares. Estoy tratando de evitar copiar y pegar el código.

Tenemos un sistema de mensajería de datos interno para actualizar tablas en bases de datos en diferentes máquinas. Estamos ampliando nuestro servicio de mensajería para enviar datos a proveedores externos y deseo codificar una solución simple que pueda reutilizarse si decidimos enviar datos a más de un proveedor. El código se compilará en un EXE y se ejecutará regularmente para enviar mensajes al servicio de datos del proveedor.

He aquí un esbozo de lo que hace el código:

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

Quiero escribir el código de tal manera que pueda volver a utilizar las piezas que no cambian sin tener que recurrir a la copia y pegar y completar el código para SendMessagesToVendor(). Quiero que un desarrollador pueda usar un OutboxManager y tenga todo el código de la base de datos escrito ya escrito, pero se verá obligado a proporcionar su propia implementación de envío de datos al proveedor.

Estoy seguro de que hay buenos principios orientados a objetos que pueden ayudarme a resolver ese problema, pero no estoy seguro de cuál (es) sería mejor usar.


Esta es la solución que acabé yendo con, inspirado por Victor's answer y Reed's answer (and comments) utilizar un modelo de interfaz. Todos los mismos métodos existen, pero ahora están escondidos en interfaces que el consumidor puede actualizar si es necesario.

No me di cuenta del poder de la implementación de la interfaz hasta que me di cuenta de que permitía al consumidor de la clase conectar sus propias clases para el acceso a datos (IOutboxMgrDataProvider) y registro de errores (IErrorLogger). Si bien aún proporciono implementaciones predeterminadas ya que no espero que este código cambie, aún es posible que el consumidor las anule con su propio código. Excepto por escribir varios constructores (I may change to named and optional parameters), realmente no me llevó mucho tiempo cambiar mi implementación.

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

Respuesta

1

Yo diría que use Dependecy Injection . Básicamente, pasas una abstracción del método de envío.

Algo así como:

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 
    } 

    ... 
} 

Otro enfoque, como ya se ha mencionado, la herencia.

En cualquier caso: intente eliminar el código de recuperación de base de datos de esta clase. Utilice otra abstracción para eso (es decir, pasar una interfaz IDataProvider o algo así al constructor). Hará que tu código sea más comprobable.

+0

@Victor Gracias por la sugerencia. ¿Cuál es la ventaja de Dependency Injection sobre el modelo Abstract? –

+0

@Ben: en realidad son solo dos opciones para trabajar. DI tiene la ventaja de permitir que sus clientes siempre trabajen con una sola clase y, potencialmente, ser más fácil para fines de prueba. El uso de la herencia es potencialmente más claro, ya que el usuario trabaja con una sola clase, y el Principio de Substición Liskov dice que pueden usarse de forma intercambiable por el lado del usuario, por lo que una clase vs. dos para que el usuario vea. –

+0

@Ben: Mi respuesta fue básicamente esto: la opción 1 es "modelo abstracto"/herencia, la opción 2 es DI. –

9

Hay dos enfoques muy sencillo:

  1. gane OutboxManager una clase abstracta, y proporcionar una subclase por vendedor. El SendMessagesToVendor se puede marcar como abstracto, lo que obliga a que cada proveedor vuelva a implementarlo. Este enfoque es simple, se ajusta bien a los principios OO, y también tiene la ventaja de permitirle suministrar la implementación para los otros métodos, pero aún permite la anulación de una versión específica del proveedor si desea permitir eso más adelante.

  2. Tenga OutboxManager encapsule alguna otra clase o interfaz que proporcione la información específica del proveedor requerida en SendMessagesToVendor. Esto podría ser fácilmente una pequeña interfaz que se implementa por proveedor, y SendMessagesToVendor podría usar esta implementación de interfaz para enviar sus mensajes. Esto tiene la ventaja de permitirle escribir parte del código aquí, reduciendo potencialmente la duplicación entre los proveedores.También potencialmente permite que su método SendMessagesToVendor sea más consistente y más fácil de probar, ya que solo debe confiar en la funcionalidad específica del proveedor requerida aquí. Esto también podría, potencialmente, implementarse como un delegado aprobado como un enfoque relacionado (pero ligeramente diferente) (personalmente prefiero que una interfaz se implemente sobre un delegado, sin embargo).

+0

+1 por sugerir dos buenas alternativas de manera breve y clara. –

+0

¿Podría responder http://stackoverflow.com/questions/9511137/is-the-solution-design-optimal-c-sharp-3-tier? – Lijo

1

Una forma de hacerlo es mediante el uso de interfaces.

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

Luego, en su constructor tome una instancia como parámetro.

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

Me parece que está todo el camino hasta allí.

Algunos pasos básicos:

1 de averiguar qué partes del código son los mismos sin importar el proveedor.
2 Escriba ésos en un módulo reutilizable (probablemente un .dll)
3 Determine qué cambios por proveedor.
4 Determine qué (de los anteriores) es código - escriba módulos específicos para eso.
5 Determine qué (de lo anterior) es la configuración: cree un esquema de configuración para ellos.

Su .exe llamará al objeto apropiado OutboxManager para el proveedor correcto.

2

Si hace esto una clase base abstracta por lo que tiene que heredarse, puede forzar este método para implementarse en el objeto concreto.

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

Cree una clase base abstracta y haga que el método que se debe cambiar sea abstracto protegido, p.

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

Cada aplicación hereda de esta clase abstracta pero sólo proporciona la implementación de SendMessagesToVendor() de la aplicación compartida se define en la clase base abstracta.

+0

Debe eliminar la implementación del método abstracto ... –

+0

Sí, tiene razón, demasiado rápido para copiar y pegar. Lo he actualizado ahora. –

0

Lo mismo que Mr Copsey. La solución de manifiesto n. ° 1 es de hecho subclasificación. Tienes, ya sea por suerte o habilidad, ya estructurado tu código para que esto sea fácil de implementar.

Dependiendo de la naturaleza de las diferencias entre los proveedores, si hay mucha funcionalidad común, otra alternativa podría ser tener una base de datos con un registro para cada proveedor, y tener un par de indicadores que controlen el procesamiento. Si puede dividirlo en "si flag1 es verdadero, haga lo A, haga lo B; siempre haga C; si flag2 es verdadero, haga lo demás, ya terminamos", entonces, en lugar de repetir un montón de códigos entre los proveedores, puede permitir que los datos controlen el procesamiento.

Ah, y podría agregar lo que tal vez sea obvio: si la única diferencia son los valores de los datos, entonces por supuesto solo almacene los valores de los datos en alguna parte. Me gusta tomar un ejemplo trivial, si la única diferencia entre los proveedores es el nombre de dominio al que se conecta, simplemente cree una tabla con vendorid y nombre de dominio, lea el valor y conéctelo.

Cuestiones relacionadas