2012-06-11 20 views
16

Ho cercato una risposta chiara a questa domanda di progettazione senza successo. Non sono riuscito a trovare aiuto neanche nello ".NET Framework design guidelines" né nello "C# programing guidelines". Io fondamentalmente devo esporre un modello come un'API così gli utenti possono definire e integrare i loro algoritmi nel mio quadro come questo:Delegati (espressioni Lambda) Vs Interfacce e classi astratte

1)

// This what I provide 
public abstract class AbstractDoSomething{ 
    public abstract SomeThing DoSomething(); 
} 

Gli utenti hanno bisogno di implementare questa classe astratta, si devono implementare il metodo DoSomething (che posso chiamare da dentro il mio quadro e utilizzarlo)

2)

ho scoperto che questo può anche acheived utilizzando delegati:

public sealed class DoSomething{ 
    public String Id; 
    Func<SomeThing> DoSomething; 
} 

In questo caso, un utente può utilizzare solo DoSomething classe in questo modo:

DoSomething do = new DoSomething() 
{ 
    Id="ThisIsMyID", 
    DoSomething = (() => new Something()) 
} 

Domanda

Quale di queste due opzioni è migliore per un facile da usare e più comprensibile da esporre come API?

EDIT

In caso di : La registrazione viene effettuata in questo modo (supponendo MyDoSomething estende AbstractDoSomething:

MyFramework.AddDoSomething("DoSomethingIdentifier", new MyDoSomething()); 

In caso di : La registrazione viene eseguita come questo :

MyFramework.AddDoSomething(new DoSomething()); 
+0

Come si registra AbstractDoSomethings con il framework? – dtb

+0

@dtb Vedi la mia modifica (modificato leggermente anche il codice) – GETah

risposta

15

Quale di queste due opzioni è la soluzione migliore per una facile, utilizzabile e, cosa più importante, da esporre come API?

Il primo è più "tradizionale" in termini di OOP e può essere più comprensibile per molti sviluppatori. Può anche avere vantaggi in termini di consentire all'utente di gestire la durata degli oggetti (ad esempio: è possibile consentire alla classe di implementare IDisposable e disporre di istanze all'arresto, ecc.), Oltre ad essere facile estenderlo nelle versioni future in un modo questo non rompe la retrocompatibilità, poiché l'aggiunta di membri virtuali alla classe base non infrange l'API. Infine, può essere più semplice da usare se si desidera utilizzare qualcosa come MEF per comporlo automaticamente, che può semplificare/rimuovere il processo di "registrazione" dal punto di vista dell'utente (in quanto possono semplicemente creare la sottoclasse e rilasciarlo in un cartella, e farlo scoprire/utilizzare automaticamente).

Il secondo è un approccio più funzionale ed è più semplice in molti modi. Ciò consente all'utente di implementare la tua API con molte meno modifiche al codice esistente, in quanto devono solo racchiudere le chiamate necessarie in un lambda con chiusure invece di creare un nuovo tipo.

Detto questo, se si sta andando a prendere l'approccio di utilizzare un delegato, non avrei nemmeno rendere l'utente creare una classe - basta usare un metodo come:

MyFramework.AddOperation("ThisIsMyID",() => DoFoo()); 

Questo rende un po 'più chiaro, a mio parere, che stai aggiungendo direttamente un'operazione al sistema. Inoltre, elimina completamente la necessità di un altro tipo nella tua API pubblica (DoSomething), che semplifica di nuovo l'API.

+0

+1. Grazie per la tua risposta. Il problema con la registrazione di un 'Func' direttamente con il mio framework è che non posso estendere tale funzionalità in futuro (non posso aggiungere un comportamento a' DoSomething') – GETah

+0

@GETah Come accennato nel mio post, se prevedi di voler aggiungere un comportamento, una classe è probabilmente migliore. (Vedi la frase sull'aggiunta di membri virtuali ...) Ecco, IMO, il vantaggio principale nell'usare le classi invece dei delegati. –

+0

Oh capisco. Grazie per i tuoi commenti – GETah

6

vorrei andare con la classe/interfaccia astratta se:

  • DoSomething è richiesto
  • DoSomething normalmente ottenere veramente grande (in modo implementazione del DoSomething può essere splited in diversi metodi privati ​​/ protetti)

vorrei andare con i delegati se:

  • DoSomethi ng può essere trattata come un evento (OnDoingSomething)
  • DoSomething è opzionale (in modo da default a un delegato no-op)
1

Come è tipico per la maggior parte di questi tipi di domande, vorrei dire " dipende". :)

Ma penso che la ragione per usare la classe astratta contro il lambda si riduca davvero al comportamento. Di solito, penso che il lambda sia usato come funzionalità di tipo callback - dove ti piacerebbe che accadesse qualcosa di personalizzato quando succede qualcos'altro. Lo faccio molto nel mio codice lato client: - effettuare una chiamata di servizio - ottenere alcuni dati indietro - ora invocano il mio callback per gestire i dati di conseguenza

Si può fare lo stesso con le lambda - hanno sono specifici e sono mirati a situazioni molto specifiche.

L'uso della classe astratta (o dell'interfaccia) si riduce in realtà a dove il comportamento della classe è guidato dall'ambiente circostante. Cosa sta succedendo, con quale cliente mi occupo, ecc.? Queste domande più grandi potrebbero suggerire che dovresti definire un insieme di comportamenti e quindi consentire ai tuoi sviluppatori (o ai consumatori della tua API) di creare i propri set di comportamento in base alle loro esigenze. Certo, potresti fare lo stesso con lambda, ma penso che sarebbe più complesso da sviluppare e anche più complesso da comunicare chiaramente ai tuoi utenti.

Quindi, suppongo che la mia regola empirica sia: - utilizzare lambda per specifici comportamenti personalizzati di callback o effetti collaterali; - usa classi o interfacce astratte per fornire un meccanismo per la personalizzazione del comportamento degli oggetti (o almeno la maggior parte del comportamento primario dell'oggetto).

Mi dispiace non posso darvi una definizione chiara, ma spero che questo aiuti. In bocca al lupo!

1

Un paio di cose da considerare:

Quante diverse funzioni/delegati dovrebbe essere sovrascritta? Se può funzionare, l'ereditarietà raggrupperà "insiemi" di sostituzioni in un modo più facile da capire. Se si dispone di una singola funzione di "registrazione", ma molte sotto-porzioni possono essere delegate all'esterno dell'implementatore, si tratta di un caso classico del modello "Modello", che ha più senso essere ereditato.

Quante implementazioni diverse della stessa funzione saranno necessarie? Se solo uno, l'ereditarietà è buona, ma se si hanno molte implementazioni un delegato potrebbe risparmiare un sovraccarico.

Se esistono più implementazioni, il programma deve passare da una di queste all'altra? O utilizzerà solo una singola implementazione. Se è necessario il passaggio, i delegati potrebbero essere più semplici, ma farei attenzione, soprattutto in base alla risposta al numero 1. Vedi il modello di strategia.

Se l'override richiede l'accesso a qualsiasi membro protetto, quindi l'ereditarietà. Se può fare affidamento solo sui pubblici, quindi delegare.

Altre scelte potrebbero essere eventi e metodi di estensione.

2

Anche se personalmente, se nella mia mano, andrei sempre in Delegate Model. Adoro la semplicità e l'eleganza delle funzioni di ordine superiore. Ma mentre implementa il modello, fai attenzione alle perdite di memoria. Gli eventi sottoscritti sono uno dei motivi più comuni delle perdite di memoria in .Net. Ciò significa, supponiamo che se si dispone di un oggetto con alcuni eventi esposti, l'oggetto originale non verrà mai eliminato finché tutti gli eventi non saranno stati annullati poiché l'evento crea un riferimento forte.

+1

Grazie per la risposta. Sfortunatamente il problema quando si creano API è ciò che si ama fare, ma ciò che gli altri amano fare: p – GETah

Problemi correlati