Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
opi.doc
Скачиваний:
1
Добавлен:
01.07.2025
Размер:
1.81 Mб
Скачать

Бібліотека Ninject.

Щоб додати Ninject у проект, потрібен диспетчер бібліотечних пакетів Visual Studio. Клацніть правою кнопкою миші на проекті у вікні Solution Explorer (Провідник рішення) і вибурен в контекстному меню пункт Add Package Library Reference (Додати посилання на бібліотечний пакет), щоб відкрити діалогове вікно Add Library Package Reference (Додавання посилання на бібліотечний пакет). Клацніть на заголовку Online (Мережеві) в лівій частині діалогового вікна, а потім введіть Ninject у полі пошуку у верхньому правому куті. З'явиться ряд елементів, як показано на рисунку 3.

Рис. 3. Додавання Ninject в проект Visual Studio

З'явиться декілька пакетів, пов'язаних з Ninject, але основну бібліотеку Ninject можна визначити за іменем і описом – інші елементи будуть розширеннями Ninject, що забезпечують інтеграцію з різними платформами і інструментальними засобами розробки.

Клацніть на кнопці Install (Установити), розташованої праворуч від елемента, щоб додати бібліотеку до проекту. У вікні Solution Explorer відкриється папка References (Посилання), і збірка Ninject буде завантажена і додана в число посилань проекту.

Порада. У випадку виникнення проблем при компіляції проекту після установки пакета Ninject виберіть пункт Project Properties (Властивості проекту) з меню Project і змініть налаштування Target Framework (Цільова платформа) с. NET Framework 4 Client Profile (Клієнтський профіль. NET Framework 4) на. NET Framework 4. Клієнтський профіль – це спрощена установка, що пропускає бібліотеку, на яку покладається Ninject.

Порядок роботи з Ninject.

У лістингу 3 показаний інтерфейс, який виражає функціональність підсумовування вартості низки товарів, а також конкретну реалізацію цього інтерфейсу.

Лістинг 3. Клас, інтерфейс і його реалізація

public class Product {

public int ProductID { get; set; }

public string Name { get; set; }

public string Description { get; set; }

public decimal Price { get; set; }

public string Category { set; get; }

}

public interface IValueCalculator {

decimal ValueProducts(params Product[] products);

}

public class LinqValueCalculator : IValueCalculator {

public decimal ValueProducts(params Product[] products) {

return products.Sum(p => p.Price);

}

}

Клас Product є тим же самим, який використовувався раніше. Інтерфейс IValueCalculator визначає метод, який приймає один або більше об'єктів Product і повертає сукупне значення. Інтерфейс реалізовано у класі LinqValueCalculator, який застосовує метод розширення Sum LINQ, щоб генерувати сумарне значення властивостей Price об'єктів Product. Тепер потрібно створити клас, який буде використовувати IValueCalculator, і який призначений для DI. Цей клас представлений в лістингу 4.

Лістинг 4. Застосування інтерфейсу IValueCalculator

public class ShoppingCart {

private IValueCalculator calculator;

public ShoppingCart(IValueCalculator calcParam) {

calculator = calcParam;

}

public decimal CalculateStockValue() {

Product[] products = {

new Product() { Name = "Kayak", Price = 275M},

new Product() { Name = "Lifejacket", Price = 48.95M},

new Product() { Name = "Soccer ball", Price = 19.50M},

new Product() { Name = "Stadium", Price = 79500M}

};

decimal totalValue = calculator.ValueProducts(products);

return totalValue;

}

}

Цей приклад дуже простий. У ході підготовки для впровадження залежності (DI) конструктор класу ShoppingCart приймає реалізацію IValueCalculator як параметр. Метод CalculateStockValue створює масив об'єктів Product, а потім викликає ValueProducts в інтерфейсі IValueCalculator, щоб отримати суму, яка повертається в якості результату. Як видно на рисунку 4, який ілюструє відносини між нашими чотирма простими типами, ми успішно роз'єднали класи ShoppingCart і LinqValueCalculator.

Рис. 4 - Відношення між чотирма простими типами

Класи ShoppingCart і LinqValueCalculator обидва залежать від IValueCalculator, але ShoppingCart не має безпосереднього взаємозв'язку з LinqValueCalculator; фактично, він навіть не знає про існування LinqValueCalculator. Можна змінити реалізацію LinqValueCalculator або навіть повністю замінити реалізацію IValueCalculator абсолютно новою, і клас ShoppingCart цього не відчує.

На замітку! Клас Product НЕ має ніякого безпосереднього взаємозв'язку з трьома іншими типами. Для нас це НЕ має значення. Product є еквівалентом типу моделі предметної області, і ми очікуємо, що подібні класи тісно пов'язані з іншою частиною програми. Якщо б мова йшла НЕ про побудові додатків MVC, ми могли б інакше дивитися на це і відокремити також і клас Product.

Наша мета полягає в отриманні можливості створення примірників ShoppingCart і впровадження реалізації класу IValueCalculator як параметра конструктора. Саме цю роль зіграє Ninject.

Щоб підготувати Ninject до використання, потрібно створити екземпляр ядра Ninject. є об'єктом, який ми будемо використовувати для обміну даними з Ninject. Ми виконаємо це в класі Program, створеному Visual Studio в якості частини шаблону проекту Console Application. Цей клас містить метод Main. Створення ядра демонструється в лістингу 5.

Лістинг 5. Підготовка ядра Ninject

using Ninject;

class Program

{

static void Main (string [] args)

{

IKernel ninjectKernel = new StandardKernel ();

}

}

Після того як ядро створено, робота з Ninject здійснюється у два етапи.

Перший етап прив'язка типів, які потрібно асоціювати, до створених інтерфейсів.

У даному випадку нам потрібно вказати Ninject, що при отриманні запиту на реалізацію IValueCalculator потрібно створити і повернути екземпляр класу LinqValueCalculator. Для цього ми застосовуємо методи Bind і Тo, що визначені в інтерфейсі IKernel, як показано в лістингу 6.

Лістинг 6. Прив'язка типу до Ninject

IKernel ninjectKernel = new StandardKernel ();

ninjectKernel.Bind<IValueCalculator>().To(typeof(LinqValueCalculator));

Оператор, виділений напівжирним, зв'язує інтерфейс IValueCalculator з класом реалізації LinqValueCalculator. Ми вказуємо інтерфейс, який потрібно зареєструвати, використовуючи його в якості параметра узагальненого типу методу Bind, і передаємо тип необхідної конкретної реалізації в якості параметра узагальненого типу методу Тo.

Другий етап - використання Ninject-методу Get для створення об'єкта, який реалізує інтерфейс і передає його конструктору класу (див. лістинг 7).

Лістинг 7. Створення примірника реалізації інтерфейсу допомогою Ninject

ninjectKernel.Bind<IValueCalculator>().To(typeof(LinqValueCalculator));

// Отримання реалізації інтерфейсу

IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>();

// Створення примірника ShoppingCart і впровадження залежності

ShoppingCart cart = new ShoppingCart(new LinqValueCalculator());

// Виконання обчислення і запис результату

Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());

Інтерфейс, для якого потрібна реалізація, ми вказуємо як параметр узагальненого типу методу Get. Ninject переглядає визначені нами прив'язки, бачить, що IValueCalculator прив'язаний до LinqValueCalculator, і створює новий екземпляр. Потім ми впроваджуємо реалізацію в конструктор класу ShoppingCart і викликаємо метод CalculateStockValue, який, у свою чергу, викликає метод, визначений в інтерфейсі. Цей код призводить до наступного результату:

Total: $ 79,843.45

На перший погляд здається дивним, навіщо турбуватися про встановлення та про застосуванні Ninject, якщо можна було б просто самостійно створити екземпляр LinqValueCalculator, як показано в наступному прикладі:

ShoppingCart cart = new ShoppingCart (new LinqValueCalculator ());

Для простого прикладу, подібного до наведеного, використання Ninject виглядає значно більш трудомістким, але при збільшенні складності програми застосування Ninject швидко стає менш трудомістким підходом. Далі ми збільшимо складність прикладу і продемонструємо ряд інших функціональних можливостей Ninject.

Створення ланцюжків залежностей

При отриманні вказівки про створення типу Ninject аналізує зв'язку між цим ти пом та іншими типами. За наявності додаткових залежностей Ninject дозволяє їх і створює екземпляри усіх необхідних класів. Для демонстрації цієї функціональної можливості ми створили новий інтерфейс і реалізує його клас, як показано в лістингу 8.

Лістинг 8. Визначення нового інтерфейсу та його реалізації

public interface IDiscountHelper {

decimal ApplyDiscount(decimal totalParam);

}

public class DefaultDiscountHelper : IDiscountHelper {

private decimal discountRate;

public DefaultDiscountHelper(decimal discountParam) {

discountRate = discountParam;

}

public decimal ApplyDiscount(decimal totalParam) {

return (totalParam - (discountRate/ 100m * totalParam));

}

}

Інтерфейс IDiscounHelper визначає метод ApplyDiscount, який застосовує знижку до значенням типу decimal. Клас DefaultDiscounterHelper реалізує інтерфейс і застосовує фіксовану 10-відсоткову знижку. Потім інтерфейс IDiscountHelper можна додати в LinqValueCalculator в якості залежності (див. лістинг 9).

Лістинг 9. Додавання залежності в клас LinqValueCalculator

using System.Linq;

public interface IValueCalculator {

decimal ValueProducts(params Product[] products);

}

public class LinqValueCalculator : IValueCalculator {

private IDiscountHelper discounter;

public LinqValueCalculator(IDiscountHelper discountParam) {

discounter = discountParam;

}

public decimal ValueProducts(params Product[] products) {

return discounter.ApplyDiscount(products.Sum(p => p.Price));

}

}

Доданий конструктор класу приймає реалізацію інтерфейсу IDiscountHelper, який потім використовується в методі ValueProducts для застосування знижки до сумарного значення об'єктів Product, що оброблюються. Ми прив'язуємо інтерфейс IDiscountHelper до класу реалізації допомогою ядра Ninject, як це було зроблено для IValueCalculator (див. лістингу 10).

Лістинг 10. Прив'язка іншого інтерфейсу до його реалізації

IKernel ninjectKernel = new StandardKernel();

ninjectKernel.Bind<IValueCalculator>().To(typeof(LinqValueCalculator));

ninjectKernel.Bind<IDiscountHelper>().To(typeof(DefaultDiscountHelper))

// get the interface implementation

IValueCalculator calcImpl = ninjectKernel.Get<IValueCalculator>();

ShoppingCart cart = new ShoppingCart(calcImpl);

Console.WriteLine("Total: {0:c}", cart.CalculateStockValue());

Крім того, код в лістингу 6.10 використовує створені нами класи та інтерфейси, прив'язані за допомогою Ninject. Код, який створює реалізацію IValueCalculator, не вимагає ніяких змін.

Ninject відомо, що в разі запиту IValueCalculator потрібно створити екземпляр класу LinqValueCalculator. Ninject досліджує цей клас і виявляє, що той залежить від інтерфейсу, який можна дозволити. Ninject створює екземпляр DefaultDiscountHelper, впроваджує його в конструктор класу LinqValueCalculator і повертає результат у вигляді IValueCalculator. Подібним чином Ninject перевіряє кожен клас, екземпляр якого створює, незалежно від довжини або складності ланцюжка залежностей.

Визначення значень властивостей і параметрів

Створювані Ninject класи можна конфігурувати, надаючи відомості про властивості під час прив'язки інтерфейсу до його реалізації. Ми змінили клас StandardDiscountHelper, включивши в нього зручне властивість для вказівки розміру знижки (див. лістинг 11).

Лістинг 11. Додавання властивості в клас реалізації

public class DefaultDiscountHelper : IDiscountHelper {

public private decimal DiscountSize { get; set; }

public decimal ApplyDiscount(decimal totalParam) {

return (totalParam - (DiscountSize / 100m * totalParam));

}

}

Під час прив'язки конкретного класу до типу за допомогою Ninject можна використовувати метод WithPropertyValue для установки значення DiscountSize в класі DefaultDiscountHelper, як показано в лістингу 12.

Лістинг 12. Використання Ninject спільно з методом WithPropertyValue

IKernel ninjectKernel = new StandardKernel();

ninjectKernel.Bind<IValueCalculator>().To(typeof(LinqValueCalculator));

ninjectKernel.Bind<IDiscountHelper>()

.To(typeof(DefaultDiscountHelper)).WithPropertyValue ("DiscountSize", 50M);

Зверніть увагу, що для встановлення необхідно вказати рядкове значення імені властивості. Щоб отримати примірник методу ShoppingCart , не потрібно змінювати ні якісь інші прив'язки, ні спосіб використання методу Get. Встановлення значення властивості здійснюється слідом за створенням класу DefaultDiscountHelper і веде до поділу сумарного значення елементів навпіл. Ця зміна приводить до наступного результату:

Total: $ 39,921.73

Якщо потрібно встановити значення більш ніж однієї властивості, для охоплення їх усіх виклики метод WithPropertyValue можна об'єднати в ланцюжок. Аналогічно можна чинити з параметрами конструктора. У лістингу 13 представлений змінений код класу DefaultDiscounter, в якому розмір знижки передається як параметр конструктора.

Лістинг13. Використання властивості конструктора в класі реалізації

public class DefaultDiscountHelper : IDiscountHelper {

private decimal discountRate;

public decimal ApplyDiscount(decimal totalParam) {

return (totalParam - (discountRate/ 100m * totalParam));

}

}

Щоб прив'язати цей клас з допомогою Ninject, ми вказуємо значення параметра конструктора з допомогою методу WithConstructorArgument (див. лістинг 14).

Лістинг 14. Прив'язка до класу, який вимагає параметр конструктора

IKernel ninjectKernel = new StandardKernel();

ninjectKernel.Bind<IValueCalculator>().To(typeof(LinqValueCalculator));

ninjectKernel.Bind<IDiscountHelper>()

.To(typeof(DefaultDiscountHelper))

.WithConstructorArgument("discountParam", 50M);

Ця методика дозволяє впровадити значення в конструктор. Повторимо ще раз: ці методи можна об'єднувати в ланцюжок для передачі декількох значень і їх змішування і узгодження з залежностями. Ninject з'ясує, що саме потрібно, і створить відповідні компоненти.

Використання самоприв’язки

Корисною функціональної можливістю повної інтеграції Ninject в код є самоприв’язка, яка дозволяє запитувати конкретний клас (і, отже, створювати його екземпляр) з ядра Ninject. Цей підхід може здаватися дивним, але він означає, що ми позбавлені від необхідності виконання початкового впровадження залежності вручну, тобто:

IValueCalculator calclmpl = ninjectKernel.Get <IValueCalculator> ();

ShoppingCart cart = new ShoppingCart (calclmpl);

Замість цього можна просто запросити примірник ShoppingCart і покласти на Ninject турботу про залежностях класу IValueCalculator. Використання самопрівязкі продемонстровано в лістингу 15.

Лістинг 15. Використання самоприв’язки Ninject

ShoppingCart cart = NinjectKernel. Get <ShoppingCart> ();

Для забезпечення самоприв’язки класу ніяка підготовка НЕ потрібна. Ninject робить припущення про те, що нам потрібно, під час запиту конкретного класу, що не має прив'язки.

Самоприв’язка допомагає обробити перший впровадження залежностей в дозастосування і включає всі необхідні елементи, у числі яких конкретні об'єкти, в область дії Ninject. Якщо витратити деякий час на реєстрацію типу самоприв’язки, то можна використовувати функціональні можливості, доступні для інтерфейсу, такі як вказівку значень параметрів конструктора і властивостей. Для реєстрації самоприв’язки застосовується метод ToSelf, як показано в лістингу 16.

Лістинг 16. Самоприв’язка конкретного типу

ninjectKernel.Bind<ShoppingCart>().ToSelf().WithParameter("<parameterName",

<paramvalue>);

Цей приклад прив'язує клас ShoppingCart до самого себе, а потім викликає метод WithParameter для передачі значення властивості (уявної). Самоприв’язку можна використовувати тільки з конкретними класами.

Прив'язка до довільного типу

Хоча основна наша увага звернена до інтерфейсів (оскільки саме вони найбільш важливі в додатках MVC), Ninject можна застосовувати також для прив'язки конкретних класів. У попередньому розділі було показано, як виконати прив'язку конкретного класу до самого себе, але конкретний клас можна прив'язати також до похідного класу. У лістингу 17 приведений код класу ShoppingCart, який був змінений для підтримки успадкування, і похідний клас LimitShoppingCart, що є вдосконаленою версією батьківського класу, яка виключає всі елементи, вартість яких перевищує зазначену граничну ціну.

Лістинг 17. Створення похідного класу корзини для покупок

public class ShoppingCart {

protected IValueCalculator calculator;

protected Product[] products;

public ShoppingCart(IValueCalculator calcParam) {

calculator = calcParam;

products = new[] {

new Product() { Name = "Kayak", Price = 275M},

new Product() { Name = "Lifejacket", Price = 48.95M},

new Product() { Name = "Soccer ball", Price = 19.50M},

new Product() { Name = "Stadium", Price = 79500M}

};

}

public virtual decimal CalculateStockValue() {

decimal totalValue = calculator.ValueProducts(products);

return totalValue;

}

}

public class LimitShoppingCart : ShoppingCart {

public LimitShoppingCart(IValueCalculator calcParam)

: base(calcParam) {

// / Виконання деяких дій або дій НЕ потрібно

}

public override decimal CalculateStockValue() {

// Фільтрація елементів, вартість яких перевищує межу

var filteredProducts = products

.Where(e => e.Price < ItemLimit)

.Select(e => e);

// Виконання обчислення

return calculator.ValueProducts(filteredProducts.ToArray());

}

public decimal ItemLimit { get; set; }

}

Можна виконати прив'язку батьківського класу так, наче його примірник був запитаний з Ninject , і при цьому створюється екземпляр похідного класу, як показано в лістингу 18.

Лістинг 18. Прив'язка класу до похідної версії

ninjectKernel.Bind<ShoppingCart>()

.To<LimitShoppingCart>()

.WithPropertyValue("ItemLimit", 200M);

Ця методика особливо добре підходить для прив'язки абстрактних класів до їхніх конкретних реалізацій.

Використання умовної прив'язки

За допомогою Ninject можна здійснити прив'язку декількох реалізацій одного і того ж інтерфейсу або декількох похідних класів від одного і того ж класу і визначити інструкції щодо того, які реалізації повинні використовуватися в різних умовах.

Для демонстрації цієї функціональної можливості ми створили нову реалізацію інтерфейсу IValueCalculator по імені IterativeValueCalculator , яка показана в лістингу 19.

Лістинг 19. Нова реалізація інтерфейсу IValueCalculator

public class IterativeValueCalculator : IValueCalculator {

public decimal ValueProducts(params Product[] products) {

decimal totalValue = 0;

foreach (Product p in products) {

totalValue += p.Price;

}

return totalValue;

}

}

Тепер, коли у нас є декілька реалізацій для вибору, можна створити прив'язки Ninject , доступні для вибіркового використання. Приклад наведено в лістингу 20.

Лістинг 20. Умовна прив'язка Ninject

ninjectKernel.Bind<IValueCalculator>().To<LinqValueCalculator>();

ninjectKernel.Bind<IValueCalculator>()

.To<IterativeValueCalculator>()

.WhenInjectedInto<LimitShoppingCart>();

Нова прив'язка вказує, що для обслуговування запитів інтерфейсу IValueCalculator потрібно створити екземпляр класу IterativeValueCalculator , коли об'єкт, у який впроваджується залежність, є екземпляром класу LimitShoppingCart.

Ми залишили початкову прив'язку інтерфейсу IValueCalculator діючою. Ninject намагається знайти прив'язку, яка найбільш підходить, і якщо критерій умовної прив'язки не може бути задоволений, допомагає виконати певну за замовчуванням прив'язку для цього ж класу або інтерфейсу – тобто Ninject своєму розпорядженні значенням для обходу. Найбільш корисні методи умовної прив'язки перераховані в таблиці 1.

Таблиця 1. Методи умовної прив'язки Ninject

Метод

Дія

When ( predicate )

Прив'язка використовується , коли обчислене значення предиката (predicate) - лямбда-вирази - рівне true

WhenClassHas < T > ()

Прив'язка використовується , коли впроваджуваний клас позначений атрибутом , тип якого вказаний аргументом T

WhenInjectedInto < T > ()

Прив'язка використовується, коду типом впроваджуваного класу є Т

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]