2013-06-28 15 views
50

Pomagałem kilku znajomym w projekcie i istnieje klasa, która używa Ninject. Jestem dość nowy w C# i nie mam pojęcia, co robi ta klasa, dlatego muszę zrozumieć Ninject. Czy ktoś może wyjaśnić, czym jest Ninject i kiedy z niego korzysta (np. Jeśli to możliwe)? Lub jeśli możesz wskazać kilka linków, które też będą świetne.Co to jest Ninject i kiedy go używasz?

Próbowałem to pytanie: Ninject tutorials/documentations?, ale tak naprawdę nie pomogło początkującym, jak ja.

+5

ninject jest strukturą wtrysku zależności. zacznij tutaj: https: // github.com/ninject/ninject/wiki/Dependency-Injection-by-Hand dla dobrego wprowadzenia do koncepcji i używania ninlu – drch

+0

Dokumentacja na wiki wyjaśnia wszystko od podstaw. http://www.ninject.org/wiki.html – spender

Odpowiedz

35

Ninject jest kontenerem Inversion of Control.

Co robi?

Załóżmy, że masz klasę Car, która zależy od klasy Driver.

public class Car 
{ 
    public Car(IDriver driver) 
    { 
     /// 
    } 
} 

Aby użyć klasy Car go zbudować tak:

IDriver driver = new Driver(); 
var car = new Car(driver); 

IoC containter Centralizuje wiedzę o tym, jak zbudować klas. Jest to centralne repozytorium, które zna kilka rzeczy. Na przykład wie, że konkretną klasą, której potrzebujesz do zbudowania samochodu, jest Driver, a nie żadna inna IDriver.

Na przykład, jeśli tworzysz aplikację MVC, możesz powiedzieć Ninject, jak budować kontrolery. Dokonujesz tego rejestrując, które konkretne klasy spełniają określone interfejsy. W czasie wykonywania Ninject określi, które klasy są potrzebne do zbudowania wymaganego kontrolera, a wszystko za kulisami.

// Syntax for binding 
Bind<IDriver>().To<Driver>(); 

Jest to korzystne, ponieważ pozwala budować systemy, które są łatwiejsze do testowania jednostkowego. Załóżmy, że Driver zawiera cały dostęp do bazy danych dla Car. W badanej jednostki na samochód można to zrobić:

IDriver driver = new TestDriver(); // a fake driver that does not go to the db 
var car = new Car(driver); 

Istnieją całe ramy, które dbają o automatyczne tworzenie klas testowych dla Ciebie i są one nazywane wyśmianie ram.

Dalsze informacje:

+0

Dlaczego miałbyś chcieć zajęcia z testu? Jeśli masz środowisko programistyczne, dlaczego nie chcesz iść do bazy danych? Pracowałem nad projektami przy użyciu ninject i nie widzę korzyści. Nigdy nie zmieniamy klasy betonu, zawsze jest tak samo. – Kelly

45

Ninject jest zależność wtryskiwacz NET, praktyczna realizacja wzór Dependency iniekcji (forma Inwersja wzoru kontrolnego n).

Załóżmy, że masz dwie klasy DbRepository i Controller:

class Controller { 
    private DbRepository _repository; 

    // ... some methods that uses _repository 
} 

class DbRepository { 
    // ... some bussiness logic here ... 
} 

Więc teraz masz dwa problemy:

  1. Musisz inialize _repository go używać.Masz następujące sposoby:

    1. W konstruktorze ręcznie. Ale co, jeśli zmienia się konstruktor DbRepository? Musisz przepisać swoją klasę Controller, ponieważ zmieniono część kodu. Nie jest trudno, jeśli masz tylko jeden Controller, ale jeśli masz kilka klas, które są zależne od Twojego Repository masz prawdziwy problem.
    2. Możesz użyć lokalizatora usług lub fabryki lub czegoś podobnego. Teraz masz zależność od lokalizatora usług. Masz globalny lokalizator usług i cały kod musi z niego korzystać. Jak zmienisz zachowanie lokalizatora usług, gdy potrzebujesz użyć w jednej części kodu jednej logiki aktywacji i czegoś innego w innej części kodu? Jest tylko jeden sposób - przekazywanie lokalizatora usług przez konstruktorów. Ale z coraz większą liczbą klas będziesz musiał go przekazywać coraz więcej. W każdym razie, to fajny pomysł, ale to zły pomysł.

      class Controller { 
          private DbRepository _repository; 
      
          public Controller() { 
          _repository = GlobalServiceLocator.Get<DbRepository>() 
          } 
      
          // ... some methods that uses _repository 
      } 
      
    3. Można użyć wstrzyknięcia zależności. Spójrz na kod:

      class Controller { 
          private IRepository _repository; 
      
          public Controller(IRepository repository) { 
           _repository = repository; 
          } 
      } 
      

    Teraz, kiedy trzeba kontroler piszesz: ninjectDevKernel.Get<Controller>(); lub ninjectTestKernel.Get<Controller>();. Możesz przełączać opcje rozwiązywania zależności między chorymi tak szybko, jak chcesz. Widzieć? To proste, nie musisz dużo pisać.

  2. Nie można wykonywać testów jednostkowych. Twój Controller ma zależność od DbRepository i jeśli chcesz przetestować jakąś metodę, która używa repozytorium, twój kod trafi do bazy danych i poprosi o dane. To jest powolne, bardzo powolne. Jeśli zmieni się twój kod w DbRepository, twój test jednostki na Controller spadnie. Tylko test integracyjny musi w tym przypadku zawierać "problemy". Czego potrzebujesz w testach jednostkowych - do odizolowania twoich klas i przetestowania tylko jednej klasy w jednym teście (w idealnej - tylko jednej metodzie). Jeśli Twój kod DbRepository nie powiedzie się, możesz pomyśleć, że kod Controller zawiódł - i to jest złe (nawet jeśli masz testy dla DbRepository i Controller - obaj się nie uda i możesz zacząć z niewłaściwego miejsca). Ustalenie, gdzie naprawdę jest błąd, zajmuje dużo czasu. Musisz wiedzieć, że niektóre klasy są w porządku, a inna klasa nie.

  3. Jeśli chcesz zastąpić DbRepository czymś innym we wszystkich klasach, musisz wykonać dużo pracy.

  4. Nie można łatwo kontrolować czasu życia DbRepository. Obiekt tej klasy tworzy podczas inicjowania Controller i usuwa po usunięciu Controller. Nie ma współdzielenia między różnymi instancjami klasy Controller i nie ma współdzielenia między innymi innymi klasami. Z Ninject można po prostu napisać:

    kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope(); 
    

I Szczególną cechą wstrzykiwania zależności - rozwój zwinny! Opisujesz, że twój kontroler używa repozytorium z interfejsem IRepository. Nie musisz pisać DbRepository, możesz napisać prostą klasę MemoryRepository i rozwinąć Controller, podczas gdy inni faceci rozwijają się DbRepository. Kiedy DbRepository zostanie zakończony, po prostu ponownie się powiąże w twoim narzędziu do rozwiązywania zależności, domyślnie IRepository jest teraz DbRepository. Napisałeś dużo kontrolerów? Wszystkie z nich będą teraz używać DbRepository. To super.

Więcej:

  1. Inversion of control (wiki)
  2. Dependency injection (wiki)
  3. Inversion of Control Containers and the Dependency Injection pattern (Martin Fowler)
+2

Nadal nie rozumiem, jak ninject ułatwiłoby to tylko ręczne wstrzykiwanie. Jeśli mam strategię przesyłania plików dla każdej firmy i strategia musi zostać załadowana w czasie wykonywania w oparciu o wybraną firmę. Wygląda na to, że ninject nie zmienia zbyt wiele kodu, czy robię to ręcznie, czy też używam ninject. Jedyne korzyści, jakie widzę z programu Ninject, to to, że jeśli chcę pisać do bazy danych lub logować się do pliku tekstowego podczas testów, mogę łatwo zmienić strategię podczas uruchamiania. – MIKE

+1

Nawet do tego, możesz użyć abstrakcyjnej fabryki, aby osiągnąć to samo. Gdy flister może wywołać metodę taką jak LoadAssemblyAndUnwrap lub GetType, aby utworzyć instancję odpowiedniego typu. Zestaw lub typ do użycia może być przechowywany w pliku konfiguracyjnym. Tak więc nie ma potrzeby polegania na innej bibliotece, Ninject, Unity lub podobnych rzeczach. :) – gWay

+0

Ninject i inne kontenery DI są przydatne w dwóch przypadkach: 1) Zbyt wiele zależności, aby związać wszystko razem. Deklaratywna konfiguracja jest bardziej czytelna i zapisywalna 2) Kiedy nie wiesz, jak rozwiązywać typ w czasie kompilacji (na przykład formanty Asp są tworzone w czasie wykonywania i naprawdę możesz mieć dużo niż –

2

Inne odpowiedzi są świetne, ale chciałbym również zwrócić uwagę na to Implementing Dependency Injection using Ninject artykuł.
Jest to jeden z najlepszych artykułów, jakie przeczytałem, co wyjaśnia Dependency Injection i Ninject z bardzo eleganckim przykładem.

Oto fragment z artykułu:

Poniżej interfejsu będą realizowane przez naszego (SMSService) i (MockSMSService), w zasadzie nowy interfejs (ISMSService) narazi te same zachowania zarówno usług jak kod poniżej:

public interface ISMSService 
{ 
void SendSMS(string phoneNumber, string body); 
} 

(SMSService) wdrażanie do realizacji (ISMSService) interfejs:

public class SMSService : ISMSService 
{ 
public void SendSMS(string mobileNumber, string body) 
{ 
SendSMSUsingGateway(mobileNumber, body); 
} 




private void SendSMSUsingGateway(string mobileNumber, string body) 
{ 
/*implementation for sending SMS using gateway*/ 
Console.WriteLine("Sending SMS using gateway to mobile: 
    {0}. SMS body: {1}", mobileNumber, body); 
} 
} 

(MockSMSService) z zupełnie innej realizacji, używając tego samego interfejsu:

public class MockSMSService :ISMSService 
{ 
public void SendSMS(string phoneNumber, string body) 
{ 
SaveSMSToFile(phoneNumber,body); 
} 

private void SaveSMSToFile(string mobileNumber, string body) 
{ 
/*implementation for saving SMS to a file*/ 
Console.WriteLine("Mocking SMS using file to mobile: 
    {0}. SMS body: {1}", mobileNumber, body); 
} 
} 

musimy wdrożyć zmiany do naszego (UIHandler) Konstruktor klasy zdać uzależnienia przez niego, w ten sposób, kod, który używa (UIHandler) może określić, jakie konkretne wdrażanie (ISMSService) używać:

public class UIHandler 
{ 
private readonly ISMSService _SMSService; 

public UIHandler(ISMSService SMSService) 
{ 
_SMSService = SMSService; 
} 
public void SendConfirmationMsg(string mobileNumber) { 

_SMSService.SendSMS(mobileNumber, "Your order has been shipped successfully!"); 
} 
} 

teraz musimy utworzyć oddzielną klasę (NinjectBindings), która dziedziczy (NinjectModule). Ta klasa będzie odpowiedzialna za rozwiązywanie zależności w czasie wykonywania, a następnie nadpisamy zdarzenie load, które jest używane do konfigurowania powiązania w nim. Zaletą programu Ninject jest to, że nie musimy zmieniać naszego kodu (ISMSService), (SMSService) i (MockSMSService).

public class NinjectBindings : Ninject.Modules.NinjectModule 
{ 
public override void Load() 
{ 
Bind<ISMSService>().To<MockSMSService>(); 
} 
} 

Teraz w UI kodu formularza użyjemy wiązania dla Ninject który zadecyduje których realizacja używać:

class Program 
{ 
static void Main(string[] args) 
{ 
IKernel _Kernal = new StandardKernel(); 
_Kernal.Load(Assembly.GetExecutingAssembly()); 
ISMSService _SMSService = _Kernal.Get<ISMSService>(); 

UIHandler _UIHandler = new UIHandler(_SMSService); 
_UIHandler.SendConfirmationMsg("96279544480"); 

Console.ReadLine(); 
} 
} 

Teraz kod jest przy użyciu Ninject Kernal rozwiązać wszystkie łańcuch zależności , jeśli chcemy używać prawdziwej usługi (SMSService) w trybie Release (w środowisku produkcyjnym) zamiast fałszywej, musimy zmienić na klasie wiązania Ninject (NinjectBindings) tylko, aby użyć właściwej implementacji lub używająC#if Dyrektywa DEBUG jak poniżej:

public class NinjectBindings : Ninject.Modules.NinjectModule 
{ 
public override void Load() 
{ 
#if DEBUG 
Bind<ISMSService>().To<MockSMSService>(); 
#else 
Bind<ISMSService>().To<SMSService>(); 
#endif 

} 
} 

Teraz nasza wiążąca klasa (NinjectBindings) utrzymuje się na szczycie całego kodu wykonawczego i możemy łatwo kontrolować konfigurację w jednym miejscu.


Zobacz także What is Inversion of Control? kilka bardzo prostych przykładów wspomniano, aby zrozumieć IoC.

Powiązane problemy