Metadati & Reflection
Annotazioni per assembly, tipi, membri e parametri
- In Java @Blabla
- Un pò tipo i modificatori public, private,..
- Ma definite dall'utente
. sono classi che estendono System.Attribute
. per esempio, Serializable (System.SerializableAttribute) su una classe indica che i suoi oggetti sono serializzabili
public class TapAttribute : Attribute { } [Tap] //classe annotata con Tap public class Program { [Tap] public static void Main(string[] args) { } }
Dependency Injection
Un esempio di classe per spedire email:
public class EmailSender { public bool SendEmail(string to, string body) { if (to==null) throw new ArgumentNullException(nameof(to)); if (body==null) throw new ArgumentNullException(nameof(body)); //...send mail and return true return false; } }
Questa classe e i suoi tipi ausiliari sono un esempio di componente (una volta "impacchettate" in una DLL)
BulkEmailSender
public class BulkEmailSender { private readonly EmailSender _emailSender; private readonly string _footer; public BulkEmailSender(string footer) { this._emailSender = new EmailSender(); this._footer = footer; } public void SendEmail(List<string> addresses, string body) { if (addresses == null) throw new ArgumentNullException(nameof(addresses)); if (body == null) throw new ArgumentNullException(nameof(body)); foreach (var a in addresses) { if (!this._emailSender.SendEmail(a, body + this._footer)) throw new Exception("Cannot send email"); } } }
Esistono vari tipi di DI, consideriamo constructor injection (è quella da usare di default) che inietta tramite i costruttori
- Altre sono la method, property, field,..
L'idea è estremamente semplice: i costruttori richiedono gli oggetti che servono direttamente, invece di crearli
in codice (senza factory)
public BulkEmailSender(string footer) { this._emailSender = new EmailSender(); this._footer = footer; } public BulkEmailSender(IEmailSender emailSender, string footer) { this._emailSender = emailSender; this._footer = footer; }
in codice (con factory)
public BulkEmailSender(string footer) { this._emailSender = new EmailSender(); this._footer = footer; } public BulkEmailSender(IEmailSenderFactory factory, string footer) { this._emailSender = factory.CreateNew(); this._footer = footer; }
Dove:
interface IEmailSender { bool SendEmail(string to, string body); } interface IEmailSenderFactory { IEmailSender CreateNew(); } // e, per esempio, una factory è: class EmailSenderFactory : IEmailSenderFactory { public IEmailSender CreateNew() { return new EmailSender(); } }
Altro esempio di (non static) Factory
public class C { private readonly IPointFactory _pointFactory; private readonly ILineFactory _lineFactory; public C(IPointFactory pointFactory, ILineFactory lineFactory) { this._pointFactory = pointFactory; this._lineFactory = lineFactory; } public void DoSomething(/* ... */) { // ... ILine newLine = CreateLine(/* ... */); // ... } private ILine CreateLine(int x0, int y0, int x1, int y1) { IPoint p0 = this._pointFactory.Create(x0, y0); IPoint p1 = this._pointFactory.Create(x1, y1); return this._lineFactory.Create(p0, p1); } }
Diventa facile sostituire un'implementazione con un'altra
Di conseguenza, diventa più facile il testing ed estendere le funzionalità, per esempio, Decorator
Esempio di Decorator
class LoggingEmailSender : IEmailSender { private readonly IEmailSender _originalSender; public LoggingEmailSender(IEmailSender originalSender) { if (originalSender == null) throw new ArgumentNullException(nameof(originalSender); this._originalSender = originalSender; } public bool SendEmail(string to, string body) { Console.WriteLine("Sending mail to {0}", to); return this._originalSender.SendEmail(to, body); } }
Principio di minima conoscenza
public void Purchase(Customer c) { Money m=c.GetWallet().GetMoney(); this.RecordSale(..., m); } // Nel testing: Money m = new Money(5); Wallet w = new Wallet(m); Customer c = new Customer(w); Goods g = g.Purchase(c); //assert
Versione tradizionale con singleton
DB.Init("..."); //inizializza il singleton Logger.Init(); //idem, l'ordine è importante! var bulk=new BulkEmailSender(); //apparentemente indipendente da sopra //ma se il Logger non è stato inizializzato //EmailSender (istanziato da BulkEmailSender) //si shchianta
con la DI
vard db=new DB(...); var logger=new Logger(db); var emailSender=new EmailSender(logger); var bulk=new BulkEmailSender(emailSender, "");
sbagliando l'ordine la compilazione fallisce!
DI container
Siccome le dipendenze sono esplicite, la composizione degli oggetti spesso si può automatizzare, è quello che fanno i container DI
Si programmano associando implementazioni a interfacce:
- via codice, max flessibilità e controllo statico sui tipi, ma la riconfigurazione richiede ricompilazione
- via file di configurazione (tipicamente XML)
E poi loro gestiscono la creazione e lo scope (singleton, factory o container)
Non è necessario usare un DI container per usare la DI
Come per esempio non è necessario usare una libreria di logging per "loggare"
Le implementazioni non mancano: Ninject, Autofac, Guice, NanoContainer, Spring.NET, Unity,..
Inversion of Control e Dependency Injection sono la stessa cosa?
IoC è un concetto più generale
- Ogni framework usa IoC (quando associate un'azione a un pulsante in una GUI lo state usando)
DI è il nome più corretto
Ninject
Un framework per le dependency injection open source per .NET
E una maniera di disaccoppiare le classi e rendere più facili il riuso del codice, cambiando le implementazioni dell'interfaccia, rendere il codice più testabile e mantenere il codice
DI Container
Una libreria che automatizza molti dei task per creare e comporre degli oggetti.
Ninject è un framework open source, i vantaggi sono tanti, è ancora in sviluppo, è veloce e non pesa molto, potente fornisce tutte le funzionalità ed è facile da utilizzare
Official site: http://www.ninject.org
Disponibile anche tramite NuGet
Vediamo l'esempio citato sopra
public interface IEmailSender { bool SendEmail(string to, string body); } public class AnEmailSender : IEmailSender { /∗ ... ∗/ } public class BulkEmailSender { private readonly IEmailSender emailSender; private readonly string footer; public BulkEmailSender(IEmailSender emailSender /∗, string footer∗/) { this. emailSender = emailSender; this. footer = String.Empty; } public void SendEmail(List<string> addresses, string body) { /∗ ... ∗/ } }
Come faccio a usare Ninject?
Costruisco il kernel, un oggetto centrale
Ikernel kernel=new StandardKernel();
dico al nucleo quali sono le classi che mi interessano
kernel.Bind<IEmailSender>().To<AnEmailSender>();
richiedo un istanza del servizio
var bulkES=kernel.Get<BulkEmailSender>();
usiamo il servizio
bulkES.SEndEmail(new List<string> {"[email protected]", "[email protected]"}, "hi");
La capacità di associare il tipo del servizio (interfacce o classe astratte) al tipo di implementazione del mio servizio
Tipato fortemente o debolmente
kernel.Bind<IEmailSender>().To<AnEmailSender>(); // ok kernel.Bind<IEmailSender>().To<string>(); // compilation error kernel.Bind(typeof(IEmailSender)).To(typeof(AnEmailSender)); // ok kernel.Bind(typeof(IEmailSender)).To(typeof(string)); // ”ok”
Le classi sono per default legate a se stesse
kernel.Bind<BulkEmailSender>().To<BulkEmailSender>(); kernel.Bind<BulkEmailSender>().ToSelf();
Injection Patterns
Ninject supporta due forme i injection:
Constructor Injection
Inizialization methods
Costruire l'oggetto, qual è l'ambito in cui l'oggetto deve essere ancora vivo, e si usano i metodi che finoscono con scope.
Transient -> .InTransientScope() -> una nuova istanza ogni volta (equivalente di new)
Singleton -> .InSingletonScope() -> all'interno esiste una sola istanza (creato l'oggetto e viene richiesto sempre e solo questo)
Thread -> .InThreadScope() -> una istanza per thread
Request -> .InRequestScope() -> una istanza per la durata del web request
Custom -> .InScope() -> definire una funzione e quale oggetto venga restituito a seconda del contesto in esecuzione
kernel.Bind<IEmailSender>().To<AnEmailSender>().InTransientScope(); kernel.Bind<BulkEmailSender>().ToSelf().InSingleTonScope(); var b1=kernel.Get<BulkEmailSender(); var b2=kernel.Get<BulkEmailSender>(); Debug.Assert(ReferenceEquals(b1,b2)); var e1=kernel.Get<IEmailSender>(); var e2=kernel.Get<IEmailSender>(); Debug.Assert(!RefernceEquals(e1.e2));
Torniamo al nostro esempio:
public interface IEmailSender { bool SendEmail(string to, string body); } public class BulkEmailSender { private readonly IEmailSender _emailSender; private readonly string _footer; public BulkEmailSender (IEmailSender emailSender, string footer) { this._emailSender=emailSender; this._footer=footer; } public void SendEmail(List<string> addresses, string body) { /*...*/ } } public class AnEmailSender : IEmailSender { /*...*/ }
Ora, kernel.Get<BulkEmailSender>() non funziona più
- Passare argomenti alla Get<>()
kernel.Get<BulkEmailSender>(new ConstructorArgument("footer","Bye");
- aggiustare l'argomento
//usando WithConstructorArgument kernel.Bind<BulkEmailSender>().ToSelf().WithConstructorArgument("footer","my Footer"); //delegate crendo nuova istanza kernel.Bind<BulkEmailSender>().ToMethod(context => new BulkEmaailSender(context.kernel.Get<IEmailSender>(),"my footer"));
Refactory esempio:
public interface IEmailSenderFactory : IEmailSenderFactory public IEmailSender Creat() { return new AnEmailSender(); } } public class IBulkEmailSenderFactory { IBulkEmailSender Create(string footer); }
implementazioni:
public class EmailSenderFactory : IEmailSenderFactory { public IEmailSender Create() { return new AnEmailSender(); } } public class BulkEmailSenderFactory : IBulkEmailSenderFactory { private readonly IEmailSenderFactory emailSenderFactory; public BulkEmailSenderFactory(IEmailSenderFactory emailSenderFactory) { this. emailSenderFactory = emailSenderFactory; } public IBulkEmailSender Create(string footer) { return new BulkEmailSender(this. emailSenderFactory.Create(), footer); } }
collego il kernel
IKernel kernel = new StandardKernel(); kernel.Bind<IEmailSender>().To<AnEmailSender>(); kernel.Bind<IEmailSenderFactory>().To<EmailSenderFactory>().InSingletonScope(); kernel.Bind<IBulkEmailSenderFactory>().To<BulkEmailSenderFactory>().InSingletonScope(); // ... var factory = kernel.Get<IBulkEmailSenderFactory>(); var bulkEmailSender = factory.Create("Bye");
Multi Injection
public interface IWeapon { string Hit(string target); } public class Sword : IWeapon { public string Hit(string target) { return "Slice " + target + " in half"; } } public class Dagger : IWeapon { public string Hit(string target) { return "Stab " + target + " to death"; } } //... static void Main(string[] args) { IKernel kernel = new StandardKernel(); kernel.Bind<IWeapon>().To<Sword>(); kernel.Bind<IWeapon>().To<Dagger>(); var weapon = kernel.Get<IWeapon>(); // error: which one? IEnumerable<IWeapon> weapons = kernel.GetAll<IWeapon>(); foreach (var w in weapons) Console.WriteLine(w.Hit(”bad guy”));
Se la classe Samurai riceve un IWeapon[], List<IWeapon> o IEnumerable<IWeapon>
public class Samurai { private readonly IEnumerable<IWeapon> allWeapons; public Samurai(IWeapon[] allWeapons) { this. allWeapons = allWeapons; } public void Attack(string target) { foreach (var weapon in this. allWeapons) Console.WriteLine(weapon.Hit(target)); }
dopo dobbiamo fare:
kernel.Get<Samurai>().Attack("your enemy");
Recommended Comments
There are no comments to display.