Слабая связь с использованием только примитивных типов / делегатов - PullRequest
3 голосов
/ 24 июня 2011

У меня есть концептуальный / теоретический вопрос о слабой связи и интерфейсах.

Так что одним из способов использования интерфейса может быть инкапсуляция параметров, требуемых определенным конструктором:

class Foo
{
    public Foo(IFooInterface foo)
    {
       // do stuff that depends on the members of IFooInterface
    }
}

Поэтому, пока объект, переданный в, реализует договор, все будет работать.Насколько я понимаю, основное преимущество здесь заключается в том, что он обеспечивает полиморфизм, но я не уверен, имеет ли это какое-либо отношение к слабой связи.

Допустим, в качестве аргумента, IFooInterface выглядит следующим образом:

interface IFooInterface
{
   string FooString { get; set; }
   int FooInt { get; set; }

   void DoFoo(string _str); 
}

С точки зрения слабой связи, не лучше ли НЕ использовать IFooInterface в вышеприведенном конструкторе, а вместо этого настроить Foo следующим образом:

class Foo
{
    public Foo(string _fooString, int _fooInt, Action<string> _doFoo)
    {
       // do the same operations
    }
}

Потому что, скажем,Я хочу добавить функциональность Foo в другой проект.Это означает, что другой проект также должен ссылаться на IFooInterface, добавляя другую зависимость.Но таким образом я могу поместить Foo в другой проект, и он выражает именно то, что ему нужно для работы.Очевидно, я могу просто использовать перегруженные конструкторы, но, скажем так, ради аргумента я не хочу и / или не могу модифицировать конструкторы Foo.

Самый существенный недостаток (по крайней мере для меня) заключается в том, что если у вас есть метод с кучей примитивных параметров, он становится уродливым и трудным для чтения.Поэтому у меня возникла идея создать своего рода функцию обтекания, которая позволит вам передавать интерфейс, а не все примитивные типы:

    public static Func<T, R> Wrap<T, R>(Func<T, object[]> conversion)
    {
        return new Func<T, R>(t =>
        {
            object[] parameters = conversion(t);

            Type[] args = new Type[parameters.Length];

            for (int i = 0; i < parameters.Length; i++)
            {
                args[i] = parameters[i].GetType();
            }

            ConstructorInfo info = typeof(R).GetConstructor(args);

            return (R)info.Invoke(parameters);
        });
    }

Идея в том, что я могу вернуть функцию, которая принимаетэкземпляр некоторого интерфейса, который соответствует требованиям Foo, но Foo буквально ничего не знает об этом интерфейсе.Его можно использовать так:

public Foo MakeFoo(IFooInterface foo)
{
    return Wrap<IFooInterface, Foo>(f => 
       new object[] { f.FooString, f.FooInt, f.DoFoo })(foo);  
}

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

Интересно, что думают некоторые опытные программисты.

Ответы [ 2 ]

3 голосов
/ 24 июня 2011

В вашем первоначальном примере вы довольно близки к шаблону Parameter Object , хотя здесь чаще используется простой класс (часто с автоматическими свойствами) без дополнительной абстракции интерфейса.

Обычно, когда вы слышите о передаче интерфейса в конструктор, это не замена примитивов, а форма внедрения зависимости.Вместо прямой зависимости от MyFooRepository можно взять зависимость от IFooRepository, которая удалит связь с конкретной реализацией.

2 голосов
/ 24 июня 2011

Моя первая мысль - вы не указали Action<string> и Action<int> для сеттеров FooString и FooInt соответственно.Реализация IFooInterface может иметь правила, относящиеся к этим установщикам, и может потребовать доступа к другим деталям реализации, не представленным на интерфейсе.

В том же духе, вы должны принять Func<string> и Func<int> какхорошо: реализация IFooInterface может иметь правила о том, что FooString и FooInt с течением времени.Например, DoFoo может пересчитать эти значения;вы не можете предполагать, что они являются просто переходами к полям, которые никогда не меняются.

Если пойти еще дальше, если получателям, установщикам или DoFoo требуется доступ к общему состоянию, функции и действия будутнеобходимо закрыть один и тот же набор переменных при их создании.В этот момент вы будете заниматься умственной гимнастикой, чтобы понять переменные времена жизни и отношения между делегатами.

Это сочетание состояния и поведения - это именно то, что выражает класс, а сокрытие деталей реализации - это точночто обеспечивает интерфейс.Разбиение этих понятий на составляющие их элементы, безусловно, достижимо, но оно также нарушает согласованность, полученную при группировании членов по типу.

Другими словами, вы можете дать мне лапшу, соус, овощи и гамбургерНо это не спагетти, а фрикадельки: -)

...