2013-06-19 4 views
1

Я создаю простое приложение с использованием MEF, чтобы лучше понять его, и у меня возникла проблема. Приложение представляет собой простой калькулятор, который позволяет создавать новые операции. Каждая операция - это класс, который экспортирует IOperation. Вот класс операции ADD:Контейнер для инъекций MEF

[Export(typeof(IOperation))] 
internal class Add : IOperation 
{ 
    CompositionContainer _container; 

    public string Name 
    { 
     get { return "Add"; } 
    } 

    public string Symbol 
    { 
     get { return "+"; } 
    } 

    public IOperand Compute(params IOperand[] operands) 
    { 
     IOperand result = _container.GetExportedValue<IOperand>(); 
     result.Value = operands.Sum(e => e.Value); 
     return result; 
    } 
} 

(. IOperand представляет собой интерфейс, который только выставляет двойной Причина этого в том, что в версии 2 вы можете иметь выражение типа «(2 + 2) * 4»)

Моя проблема, как вы можете видеть, заключается в том, что _container имеет значение null, когда я ударил Compute. Этот конкретный класс создается, когда контейнер составляет [ImportMany(typeof(IOperation))]. Поэтому мой вопрос: есть ли способ сообщить контейнеру, который инвертирует контроль, чтобы передать ссылку на него на этот объект?

PS: Я не хотел бы делать _container общественным достоянием.

edit1: Вот только реализация IOperand до сих пор:

[Export(typeof(IOperand))] 
public class SimpleResult : IOperand 
{ 
    private double _value; 
    public double Value 
    { 
     get 
     { 
      return _value; 
     } 
     set 
     { 
      _value = value; 
     } 
    } 
} 

Это "Main", где композиция происходит:

public class Calculator 
{ 
    [ImportMany(typeof(IOperation))] 
    private List<IOperation> _knownOperations; 
    private List<ICalculatorButton> _buttons; 
    private CompositionContainer _container; 

    public Calculator() 
    { 
     _container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly())); 
     _container.SatisfyImportsOnce(this); 
     _buttons = new List<ICalculatorButton>(); 

     ICalculatorButton button; 
     for (int i = 0; i < 10; i++) 
     { 
      button = _container.GetExportedValue<IDigitButton>(); 
      button.Symbol = i.ToString(); 
      ((IDigitButton)button).Value = i; 
      _buttons.Add(button); 
     } 
     foreach (IOperation op in _knownOperations) 
     { 
      button = _container.GetExportedValue<IOperationButton>(); 
      button.Symbol = op.Symbol; 
      ((IOperationButton)button).Operation = op; 
      _buttons.Add(button); 
     } 
    } 

    public IReadOnlyList<IOperation> KnownOperations 
    { 
     get { return _knownOperations.AsReadOnly(); } 
    } 

    public IReadOnlyList<ICalculatorButton> Buttons 
    { 
     get { return _buttons.AsReadOnly(); } 
    } 

    public IOperand Calculate(IOperation operation, params IOperand[] operands) 
    { 
     IOperand result = operation.Compute(operands); 
     return result; 
    } 

    public IOperand Calculate(IOperation operation, params double[] operands) 
    { 
     List<IOperand> res = new List<IOperand>(); 
     foreach (double item in operands) 
     { 
      IOperand aux = _container.GetExportedValue<IOperand>(); 
      aux.Value = item; 
      res.Add(aux); 
     } 
     return Calculate(operation, res.ToArray()); 
    } 
} 
+1

Где экспортирован 'реализация IOperand'? Вы должны просто импортировать его с помощью атрибута '[Import]' и вообще не иметь ссылки на контейнер, предполагая, что композиция происходит в другом месте. – Matt

+0

@Matt Я отредактировал вопрос и вставил реализацию IOperand. Дело в том, что я не думаю, что хочу «Импортировать» его ... Я хочу каждый раз предоставлять новый экземпляр Calculate называется – Leonardo

ответ

5

Когда вы получаете новый молоток, все это гвоздь.

При использовании DI и MEF, в частности, вы должны всегда спрашивать себя, где его использовать и где вы на самом деле ничего не получаете. DI не полностью заменяет стандартную инстанцию. В вашем примере: вы хотите построить модульный калькулятор, в котором вы можете расширить набор операторов. В этом есть смысл. Но вам действительно нужно плагин для ввода для кнопок? Я бы просто создал их стандартным образом, по одному для каждой операции. То же самое для операндов, действительно ли имеет смысл использовать DI здесь? Я бы сомневался в этом, так как я предполагаю, что в вашей окончательной программе будет только один тип операндов? Таким образом, вы можете просто сделать класс доступным и создать его там, где вам это нужно.

При использовании инъекции зависимостей ваши «модули» не должны ссылаться на контейнер. То, что вы хотите, - это хорошее и чистое разделение проблем, и все это называется Inversion of Control, потому что вы передаете контроль над экземпляром объекта из модуля в контейнер. Если теперь модуль связан с запросом контейнера на создание объекта для себя, вы на самом деле ничего не получили.

Вариант 1:

Вы добавляете ссылку на контейнер в любом случае, и внедрить его:

public Calculator() 
{ 
    _container = new CompositionContainer(new AssemblyCatalog(Assembly.GetExecutingAssembly())); 
    // This registers the instance _container as CompositionContainer 
    _container.ComposeExportedValue<CompositionContainer>(_container); 
    _container.SatisfyImportsOnce(this); 
    _buttons = new List<ICalculatorButton>(); 
... 


[ImportingConstructor] 
public Add(CompositionContainer container) 
{...} 

Вариант 2:

Вы можете определить метод Compute с ref параметра:

public IOperand Compute(params IOperand[] operands, ref IOperand result) 
{   
    result.Value = operands.Sum(e => e.Value); 
    return result; 
} 

Это выражение метод вычисления не несет ответственности за создание результата. Мне действительно нравится эта версия, потому что она имеет смысл семантически, очень приятно обрабатывать модульное тестирование, и вам не нужно ссылаться на контейнер.

Вариант 3:

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

одно замечание:

Вы строите и использовать контейнер в Calculator классе. То же самое: ваши модули не должны знать контейнер. Вы должны построить контейнер на самом высоком уровне и позволить ему собрать все. Ваш калькулятор должен иметь только те свойства, которые он логически нуждается, и даже не должен знать, вводится ли они контейнером, установленным в единичном тесте или только из кода (за исключением, конечно, атрибутов импорта/экспорта).

И наконец:

Mike Taulty on MEF (Videos)

Это действительно хорошо, я как бы узнал MEF с этим видео, и он строит калькулятор тоже :)

+0

Я смотрю видео ... выглядит очень красиво ... im on video 3 до сих пор ... Дело в том, что, в конце концов, я думаю, что пропустил мое приложение ... – Leonardo

Смежные вопросы