2015-02-14 13 views
1

Удивление, если кто-то может бросить некоторые указания на мой взгляд. Моя стандартная настройка приложений всегда была приложением nTier (презентация, бизнес, данные и обычно общий). Я избегал установки и контейнера IoC (использовал их в приложениях других людей целую вечность, просто не настраивал их) до тех пор, пока я могу, но, наконец, должен сделать решительный шаг.StructureMap и nTier Application

Мое понимание IoC допускает инъекцию зависимостей, что, в свою очередь, делает модульное тестирование возможным (что намного проще), поэтому в моей голове я хотел бы хотя бы выполнить модульные тесты на бизнес-уровне .... но каждый пример настройка IoC, например StructureMap, делает IoC на уровне презентации. Итак ... что я спрашиваю, это то, что является «лучшей практикой» для приложения nTier с IoC.

Спасибо.

ответ

2

Основным преимуществом DI является не модульное тестирование (хотя это, безусловно, преимущество). Основным преимуществом является ослабление сцепления. Приложение, которое является «проверяемым», необязательно слабо связано.

Однако развязка приносит гораздо больше к столу, чем просто проверяемость.

слабосвязанность Преимущество

  1. Late Binding (услуги могут быть заменены с другими службами, часто без изменения существующего кода)
  2. расширяемости (код может быть продлен, часто без изменения существующего кода)
  3. Параллельная разработка (абстрактные контракты определены, что могут придерживаться несколько разработчиков)
  4. Поддержание работоспособности (классы с четко определенными обязанностями проще в обслуживании)
  5. Испытание (классы легче тестировать).

IMHO, при объединении DI с программными шаблонами, расширяемость, безусловно, является основным преимуществом. Рассмотрим следующие типы:

public interface IWriter 
{ 
    void WriteSomething(); 
} 

public interface ISomeService 
{ 
    void Write(); 
} 

Вы можете продлить услугу с помощью Decorator Pattern:

public class NullWriter : IWriter 
{ 
    public void WriteSomething() 
    { 
     // Do nothing - this is a "null object pattern". 
    } 
} 

public class HelloWriter : IWriter 
{ 
    public readonly IWriter innerWriter; 

    public HelloWriter(IWriter innerWriter) 
    { 
     if (innerWriter == null) 
      throw new ArgumentNullException("innerWriter"); 
     this.innerWriter = innerWriter; 
    } 

    public void WriteSomething() 
    { 
     this.innerWriter.WriteSomething(); 

     Console.WriteLine("Hello."); 
    } 
} 

public class GoodbyeWriter : IWriter 
{ 
    public readonly IWriter innerWriter; 

    public GoodbyeWriter(IWriter innerWriter) 
    { 
     if (innerWriter == null) 
      throw new ArgumentNullException("innerWriter"); 
     this.innerWriter = innerWriter; 
    } 

    public void WriteSomething() 
    { 
     this.innerWriter.WriteSomething(); 

     Console.WriteLine("Goodbye."); 
    } 
} 

public class SomeService : ISomeService 
{ 
    private readonly IWriter writer; 

    public SomeService(IWriter writer) 
    { 
     if (writer == null) 
      throw new ArgumentNullException("writer"); 
    } 

    public void Write() 
    { 
     this.writer.WriteSomething(); 
    } 
} 

И выше будет подключен вверх, как:

// Composition Root 
var nullWriter = new NullWriter(); 
var goodbyeWriter = new GoodbyeWriter(nullWriter); 
var helloWriter = new HelloWriter(goodbyeWriter); 
var service = new SomeService(helloWriter); 
// End Composition Root 

// Execute 
service.Write(); 

//Writes: 

//Hello. 
//Goodbye. 

Теперь, что сценарий , вы можете расширить то, что SomeService делает , не изменяя ни один из существующих типов. Единственной частью приложения, которое необходимо изменить, является composition root.

public class HowAreYouWriter : IWriter 
{ 
    public readonly IWriter innerWriter; 

    public HowAreYouWriter(IWriter innerWriter) 
    { 
     if (innerWriter == null) 
      throw new ArgumentNullException("innerWriter"); 
     this.innerWriter = innerWriter; 
    } 

    public void WriteSomething() 
    { 
     this.innerWriter.WriteSomething(); 

     Console.WriteLine("How are you?"); 
    } 
} 

// Composition Root 
var nullWriter = new NullWriter(); 
var goodbyeWriter = new GoodbyeWriter(nullWriter); 
var howAreYouWriter = new HowAreYouWriter(goodbyeWriter); 
var helloWriter = new HelloWriter(howAreYouWriter); 
var service = new SomeService(helloWriter); 
// End Composition Root 

// Execute 
service.Write(); 

//Writes: 

//Hello. 
//How are you? 
//Goodbye. 

Конвенция по конфигурации

Один дополнительный (часто забывают) преимущество DI является Convention over Configuration. При объединении инжекции конструктора с контейнером DI многие из них предоставляют возможность автоматически отображать ISomeService в SomeService. Некоторые контейнеры (например, StructureMap) также имеют возможность создавать собственные соглашения.

Пособие не очевидно, потому что оно действительно не начинает окупаться, пока вы не регистрируете десятки типов, используя соглашение. Однако вы можете значительно уменьшить количество кода, необходимого для составления вашего приложения, если вы его используете.

N-уровня приложения

Для одного применения, обычно имеется один composition root как можно ближе к точке входа приложения, как это возможно. В MVC это будет в методе HttpApplication.Start.

Однако, это может различаться в зависимости от того, считаете ли вы, что слои дизайна приложения должны быть DI Friendly Libraries или DI Friendly Frameworks, и считаете ли вы, что часть приложения является «подключаемой», которую вы добавляете после того, как она построена (в основном, создание корня композиции, который может загружать динамические зависимости).

Есть по существу три подхода, которые обычно следуют, чтобы решить эту проблему:

  1. сделать все виды государственных и компоновать их в том же проекте, который содержит ваш уровень представления.
  2. Поместите корень композиции в каждый слой и сделайте общедоступный API каждого уровня внутренним. Затем создайте публичный API слоя с помощью основного проекта. В общем, вам также необходимо создать общедоступный метод Compose на каждом уровне, который вызывается из основного метода Compose.
  3. Создайте отдельный проект корня композиции, чтобы собрать все части вместе.

Я даже видел, как некоторые люди рекомендуют помещать все ваши «слои» в один проект, и если вы не намерены использовать эти штуки индивидуально, это не так сильно. Галерея NuGet - один из таких проектов, который построен таким образом.

IMHO, делая все общедоступным и помещая корень композиции в основное приложение, как правило, является лучшим вариантом для одного многоуровневого приложения, чьи части не предназначены для использования с другим приложением.

Заключительное слово

Если вы серьезно относитесь к изучению DI, забрать книгу Dependency Injection in .NET Марк Seemann. Возможно, вы не думаете о том, что DI является достаточно большой областью развития для самостоятельной учебы, но эта книга действительно дает много преимуществ, которые выходят за рамки только DI, такие как принципы SOLID.

+0

... но каково расположение приложения nTier? На каком слое вы положите IoC? Разве IoC не нуждается в области видимости интерфейсов и конкретного класса? Если это так, не означает ли это, что вам нужно поместить IoC в отдельный проект, чтобы он был полностью развязан ... но тогда у вас нет круговой ссылки при попытке вызвать Bootstrapper из App.Start в (если вы пытаетесь использовать DI на контроллерах). Вот почему я прошу совета. – user2761804