OP, non riesco ancora a comprendere appieno il tuo progetto, ma ritengo che i tuoi obiettivi siano:
- Spiana la strada per aggiornamenti occasionali alla tua logica aziendale
- Riduci al minimo l'impatto su applicazioni di terze parti che già fanno riferimento a una delle tue DLL,
Library.Core.dll
.
Il tuo piano attuale sembra essere quello di distribuire un'altra DLL fianco a fianco con Library.Core.dll e lasciare invariato la DLL originale. Questo non funzionerà. A meno che una DLL non sia specificatamente codificata per permetterlo, un'altra DLL non può sovrascrivere la sua funzionalità - questo sarebbe un grosso onere per gli sviluppatori e causerebbe un rischio per la sicurezza, dato che chiunque potrebbe mettere una DLL là fuori che rompe un'applicazione, potenzialmente in un modo malevolo.
Quindi dovrai rilasciare almeno una nuova versione di Library.Core.dll
. Se si utilizzano lo stesso numero di versione e la chiave di firma, i client non dovranno ricompilare. Oppure, se desideri aumentare il numero di versione (generalmente consigliato), puoi dire ai tuoi clienti di utilizzare un redirect binding se non si desidera ricompilare.
Considerato quanto sopra, il nuovo piano è
- Scrivi una nuova versione di
Library.Core.dll
che è identica all'interfaccia dell'originale. Questa DLL deve supportare funzionalità future senza dover essere modificata.
- Scrivi una nuova rete
Library.Functionalities.dll
che conterrà l'implementazione. Potresti finire per rilasciare nuove versioni di questo in futuro.
Ecco come farei al riguardo:
Innanzitutto, scrivi Library.Functionalities.dll
usando le moderne tecniche IoC, introducendo nuovi costruttori secondo necessità ma mantenendo il modello di oggetto originale.
In secondo luogo, scrivi un nuovo Library.Core.dll
che sarà una facciata a Library.Functionalities.dll
. Ecco un esempio:
[assembly:InternalsVisibleTo("Library.UnitTests")]
namespace Library.Core
{
class User
{
private readonly IUser _innerUser;
public User() : base (Factory.Resolve<IUser>()); //For public consumption
internal User(IUser innerUser) //For your unit testing projects
{
_innerUser = innerUser;
}
public void LogOff()
{
_innerUser.LogOff(); //Wrap the inner user object
}
}
internal class Factory
{
private static MyIoCContainer _container = new MyIoCContainer(); //AutoFac, Unity, Ninject, whatever
static Factory() //Composition root here
{
ContainerBuilder builder = new ContainerBuilder();
Library.Functionalities.Application.RegisterDependencies(builder);
_container = builder.Build();
}
static public T Resolve<T>()
{
return _container.Resolve<T>();
}
}
}
E in Library.Functionalities.dll:
namespace Library.Functionalities
{
public class Application
{
static void RegisterDependencies(ContainerBuilder builder)
{
builder.RegisterType<IUser, User>();
}
}
}
Nell'esempio precedente, Library.Core.User
è solo un involucro sottile per Library.Functionalities.User
. Se il chiamante lo costruisce usando il vecchio metodo (senza iniezione), sostituirà un'iniezione predefinita da una fabbrica.
Puoi scrivere la fabbrica come preferisci. Nel mio esempio utilizzo un semplice approccio al contenitore IoC.
Quando hai finito, la risoluzione effettiva della dipendenza su User
è nascosta al client, che può continuare a fare riferimento a Library.Core.dll
, completamente inconsapevole che sta parlando solo con una facciata.
Quando hai nuove funzionalità da distribuire, puoi semplicemente aggiornare Library.Functionalities.dll
e, se hai codificato correttamente la fabbrica, la nuova versione dovrebbe essere prelevata automaticamente. E sarai comunque in grado di utilizzare tutte le moderne tecniche IoC, iniettare shim per il test delle unità, ecc.