2010-08-02 14 views
11

Aggiornamento: vedere la fine della domanda su come ho implementato la soluzione.Come dovrei modellare il mio codice per massimizzare il riutilizzo del codice in questa situazione specifica?

Ci scusiamo per la domanda mal formulata, ma non ero sicuro del modo migliore per chiederlo. Non sono sicuro di come progettare una soluzione che può essere riutilizzata dove la maggior parte del codice è la stessa identica ogni volta che viene implementata, ma parte dell'implementazione cambierà ogni volta, ma seguirà modelli simili. Sto cercando di evitare di copiare e incollare il codice.

Disponiamo di un sistema di messaggistica interno per l'aggiornamento di tabelle su database su macchine diverse. Stiamo espandendo il nostro servizio di messaggistica per inviare dati a fornitori esterni e voglio codificare una soluzione semplice che può essere riutilizzata nel caso decidessimo di inviare dati a più di un fornitore. Il codice verrà compilato in un file EXE e verrà eseguito regolarmente per inviare messaggi al servizio dati del fornitore.

Ecco un abbozzo di quello che fa il codice:

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

voglio scrivere questo codice in modo tale che posso riutilizzare le parti che non cambiano, senza dover ricorrere alla copia e incollare e compilare il codice per SendMessagesToVendor(). Voglio che uno sviluppatore sia in grado di utilizzare uno OutboxManager e che abbia già scritto tutto il codice del database scritto, ma che sia costretto a fornire la propria implementazione dei dati di invio al fornitore.

Sono sicuro che ci sono buoni principi orientati agli oggetti che possono aiutarmi a risolvere questo problema, ma non sono sicuro di quale (i) sarebbe meglio usare.


Questa è la soluzione che ho finito per andare con, ispirato da Victor's answer e Reed's answer (and comments) di utilizzare un modello di interfaccia. Esistono tutti gli stessi metodi, ma ora sono nascosti in interfacce che il consumatore può aggiornare se necessario.

Non mi sono reso conto della potenza dell'implementazione dell'interfaccia fino a quando non mi sono reso conto che consentivo al consumatore della classe di collegare le proprie classi per l'accesso ai dati (IOutboxMgrDataProvider) e la registrazione degli errori (IErrorLogger). Mentre continuo a fornire implementazioni predefinite poiché non mi aspetto che questo codice cambi, è comunque possibile che il consumatore li sostituisca con il proprio codice. Tranne che per la scrittura di più costruttori (che è I may change to named and optional parameters), non ci è voluto molto tempo per cambiare la mia implementazione.

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

risposta

1

Direi usare Dependecy Injection . Fondamentalmente, si passa un'astrazione del metodo di invio.

Qualcosa di simile:

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 
    } 

    ... 
} 

Un altro approccio, come già accennato, l'ereditarietà.

In entrambi i casi: provare a rimuovere il codice di recupero DB da questa classe. Usa un'altra astrazione (es .: passando un'interfaccia IDataProvider o qualcosa di simile al costruttore). Renderà il tuo codice più testabile.

+0

@Victor Grazie per il suggerimento. Qual è il vantaggio di Dipendenza Iniezione sul modello astratto? –

+0

@ Ben: Sono davvero solo due opzioni per lavorare. DI ha il vantaggio di consentire ai tuoi clienti di lavorare sempre con una singola classe e potenzialmente di essere più facili a scopo di test. L'utilizzo dell'ereditarietà è potenzialmente più chiaro, dal momento che l'utente lavora con una singola classe, e il Principio di Sottoscrizione di Liskov dice che possono essere usati in modo intercambiabile dal lato dell'utente - quindi una classe contro due per l'utente. –

+0

@ Ben: La mia risposta era fondamentalmente solo questa: l'opzione 1 è "modello astratto"/ereditarietà, l'opzione 2 è DI. –

9

Ci sono due approcci molto semplice:

  1. guadagna OutboxManager una classe astratta, e di fornire una sottoclasse per ogni fornitore. SendMessagesToVendor può essere contrassegnato come astratto, forzandolo a essere reimplementato da ciascun fornitore. Questo approccio è semplice, si adatta bene ai principi OO e ha anche il vantaggio di consentire l'implementazione per gli altri metodi, ma consente comunque di eseguire l'override per una versione specifica del fornitore, se si desidera consentire in seguito.

  2. Avere OutboxManager incapsulare qualche altra classe o interfaccia che fornisce le informazioni specifiche del fornitore richieste in SendMessagesToVendor. Questa potrebbe facilmente essere una piccola interfaccia implementata per fornitore, e SendMessagesToVendor potrebbe utilizzare questa implementazione dell'interfaccia per inviare i suoi messaggi. Questo ha il vantaggio di consentire di scrivere qui parte del codice, riducendo potenzialmente la duplicazione tra i vari fornitori.Inoltre, consente potenzialmente al tuo metodo SendMessagesToVendor di essere più coerente e più facilmente verificabile, dal momento che devi fare affidamento solo sulla specifica funzionalità del venditore richiesta qui. Questo potrebbe anche, potenzialmente, essere implementato come delegato passato come approccio correlato (ma leggermente diverso) (personalmente preferisco un'interfaccia da implementare su un delegato, comunque).

+0

+1 per suggerire due buone alternative in modo breve e chiaro. –

+0

Potresti rispondere http://stackoverflow.com/questions/9511137/is-the-solution-design-optimal-c-sharp-3-tier? – Lijo

1

Un modo per farlo è attraverso l'uso di interfacce.

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

Quindi nel costruttore prendere un'istanza come parametro.

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

Mi sembra che tu sia quasi tutto lì.

alcuni passaggi fondamentali:

1 Capire quali parti del codice sono gli stessi a prescindere dal fornitore.
2 Scrivere quelli in un modulo riutilizzabile (probabilmente a .dll)
3 Determinare quali modifiche per fornitore.
4 Determinare quale (di sopra) è codice - scrivere moduli specifici per quello.
5 Determinare quale (di sopra) è la configurazione - creare uno schema di configurazione per quelli.

Il file .exe chiamerà quindi l'oggetto appropriato OutboxManager per il fornitore corretto.

2

Se si rende questa una classe base astratta, quindi deve essere ereditata, è possibile forzare questo metodo a essere implementato nell'oggetto 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

Creare una classe di base astratta e avere il metodo che deve essere modificato come protetto astratto, ad es.

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

Ogni implementazione eredita da questa classe astratta ma fornisce solo l'implementazione per SendMessagesToVendor() attuazione condivisa è definito nella classe base astratta.

+0

È necessario rimuovere l'implementazione dal metodo astratto ... –

+0

Sì, hai ragione, troppo veloce per copiare e incollare. L'ho aggiornato ora. –

0

Ditto a Mr Copsey. La soluzione n. 1 manifest è in effetti una sottoclasse. Hai, per fortuna o per competenza, già strutturato il tuo codice per renderlo facile da implementare.

A seconda della natura delle differenze tra i fornitori, se esiste molta funzionalità comune, un'altra alternativa potrebbe essere quella di avere un database con un record per ogni fornitore e avere un paio di flag che controllano l'elaborazione. Se riesci a scomporlo su "se flag1 è vero fai cosa A else do cosa B; fai sempre cosa C; se flag2 è vero fai cosa D altro che abbiamo finito", quindi piuttosto che ripetere un mucchio di codice tra i vari fornitori potrebbe essere in grado di consentire ai dati di controllare l'elaborazione.

Oh, e potrei aggiungere il forse ovvio: se l'unica differenza sono i valori dei dati, ovviamente è sufficiente memorizzare i valori dei dati da qualche parte. Come fare un esempio banale, se l'unica differenza tra i fornitori è il nome di dominio a cui ci si connette, basta creare una tabella con il fornitore e il nome di dominio, leggere il valore e collegarlo.

Problemi correlati