Путаница с интерфейсами, фабриками и инверсией управления - PullRequest
3 голосов
/ 01 марта 2010

Использование интерфейсов - очень простой способ удаления зависимостей, но что происходит, когда одному из ваших классов нужен метод, не определенный интерфейсом? Если вы используете инжектор конструктора или фабрику, как получить доступ к этому дополнительному методу без приведения? Возможно ли это?

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

interface IFoo {
    int X { get; set; }
    int Y { get; set; }
}

public class A : IFoo {
    int X { get; set; }
    int Y { get; set; }
}

public class B : IFoo {
    int X { get; set; }
    int Y { get; set; }
    int Z { get; set; }
}

public static class FooFactory {
    public static IFoo GetFoo(string AorB) {
        IFoo result = null;
        switch (AorB) {
            case "A":
                result = new A();
                break;
            case "B":
                result = new B();
                break;
        }
        return result;
    }
}

public class FooContainer {
    private IFoo foo;

    public FooContainer(IFoo foo) {
        this.foo = foo;
    }

    /* What methods would you define here. I'm new to IoC. */
}

public class Main(...) {
    int x,y,z;
    IFoo fooA = FooFactory.GetFoo("A");
    x = foo.X;
    y = foo.Y;

    IFoo fooB = FooFactory.GetFoo("B");
    x = foo.X;
    y = foo.Y;
    z = foo.Z; /* Does not compile */
    z = ((B)foo).Z; /* Compiles, but adds unwanted dependency */
}

Ответы [ 3 ]

6 голосов
/ 01 марта 2010

Тебе действительно нужно разыграть. Это нормально, а иногда и необходимо. Обычно это признак того, что что-то не так.

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

Допустим, у вас есть интерфейс IContact , и некоторые из ваших сущностей, которые реализуют это, являются вашими классами Клиент , Покупатель и Подрядчик . Если у вас есть метод SendChristmasCard , который принимает IContact, он должен заботиться только о членах IContact . Если у вас есть логика внутри этого метода, который выполняет выбор Case на obj.GetType().ToString, чтобы выяснить, является ли он Клиентом или нет, тогда:

  1. Эта функциональность, вероятно, должна быть отключена в Customer -центрической стороне вашей кодовой базы, принимая объект Customer в качестве параметра. (В вашем примере была бы отдельная логика для воздействия на класс A и класс B.)
  2. IContact должен определять общие члены, которые будет вызывать ваш метод SendChristmasCard , и полностью игнорировать логику, которая происходит внутри конкретного объекта. Каждый класс, который реализует IContact , будет реализовывать эти члены по-своему. (В вашем примере класс A также реализует свойство B, но ничего с ним не сделает.)

В случае, когда метод возвращает интерфейс, и вы используете объект, вышеупомянутое все еще применимо, но по моему опыту это может , время от времени Лучше мириться с кастингом. Усложнение, которое вы добавляете, «исправляя» ситуацию, может усложнить ситуацию. Я бы сказал, что чем дальше и нестандартнее логика, тем проще. SendChristmasCard явно не является основной функциональностью; и если фабричный метод IContact - это только удобный метод, который дает вам все контакты, то, возможно, просто используйте его, передайте его SendChristmassCard (IContact Contact) , и внутри проверьте тип, чтобы сказать «это было отличная покупка у вас в этом году "или" это была отличная продажа для вас в этом году "и т. д. Но если это основная логика в вашей системе, вам действительно нужно искать лучший путь .

Ознакомьтесь с Pattern Decorator , что может помочь в таких ситуациях.

1 голос
/ 02 марта 2010

Когда возникает необходимость понизить значение объекта, обычно это признак того, что API мог бы быть лучше.

Понижение абстрактного типа является нарушением принципа подстановки Лискова . Обычно это можно решить, изменив стиль рассматриваемого интерфейса. Вместо того, чтобы выставлять множество свойств и запросов (в терминологии CQS ), измените фокус на более командный -ориентированный подход. Это Голливудский принцип .

Вместо того, чтобы IFoo выставлять свойства X и Y, вы можете переопределить его поведение для набора команд:

public interface IFoo
{
    void DoStuff();

    void DoSomethingElse(string bar);

    void DoIt(DateTime now);
}

Конкретные реализации могут затем инкапсулировать любые данные, которые они хотели бы (например, свойства X, Y или Z), без необходимости знать о них потребителю.

Когда интерфейс становится слишком большим, пришло время применить Принцип разделения интерфейса или Принцип единой ответственности .

1 голос
/ 01 марта 2010

Если вы пытаетесь получить доступ к методу, который недоступен для интерфейса, не используйте Factory. Вы, очевидно, жестко программируете зависимость ... так что просто продолжайте (, но только если это действительно необходимо ).

Не нужно слишком усложнять вещи.

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

...