Как работает эта штука «Программирование на интерфейсы»?

Мне нравится идея «программировать интерфейсы» и избегать использования ключевого слова «new».

Однако что мне делать, если у меня есть два класса с одинаковым интерфейсом, но принципиально разные в настройке. Не вдаваясь в подробности моего конкретного кода, у меня есть интерфейс с методом «DoStuff». Этот интерфейс реализуют два класса. Один из них очень прост и не требует никакой инициализации. Другой имеет пять различных переменных, которые необходимо настроить. В сочетании они позволяют классу работать буквально миллионами способов при вызове DoStuff.

Так когда же мне «новые» эти классы? Я думал об использовании фабрик, но я не думаю, что они подходят в этом случае из-за огромной разницы в настройке. (Кстати: на самом деле существует около десяти различных классов, использующих интерфейс, каждый из которых позволяет формировать часть сложного конвейера и каждый с различными требованиями к конфигурации).


person Community    schedule 10.05.2009    source источник
comment
DoStuff? Надеюсь, ты несерьезно. Будем молиться, чтобы это был только пример.   -  person duffymo    schedule 11.05.2009
comment
Я уверен, что это безопасное предположение.   -  person Marc W    schedule 11.05.2009
comment
Донован, какой-либо из этих ответов дал вам нужную информацию? Я вижу, вы не приняли ни одного ответа.   -  person Marc W    schedule 12.05.2009


Ответы (5)


Я думаю, вы неправильно понимаете концепцию программирования интерфейсов. Вы всегда должны использовать ключевое слово new в объектно-ориентированных языках для создания новых экземпляров объектов. То, что вы программируете для интерфейсов, не отменяет этого требования.

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

В вашем случае просто реализуйте DoStuff в ваших конкретных классах, поскольку каждый класс нуждается в его реализации (независимо от того, выполняется ли это просто или с 10 другими инициализированными объектами и настройками). Например, если у вас есть интерфейс IInterface и класс SomeClass, реализующий IInterface. Вы можете объявить экземпляр SomeClass как таковой:

IInterface myInstance = new SomeClass();

Это позволяет передавать этот экземпляр другим функциям, не беспокоя эти функции о деталях реализации класса этого экземпляра.

person Marc W    schedule 10.05.2009
comment
Вам не всегда нужно использовать ключевое слово new - например, в C ++ такого требования нет. - person ; 11.05.2009
comment
Причина моего вопроса в том, что я изучаю принципы SOLID и пытаюсь их применять. Я несколько раз читал, что нового ключевого слова следует избегать, насколько это возможно, используя фабрики и т. Д. Очевидно, есть случаи, когда это требуется, но я неправильно понял принципы? - person ; 11.05.2009
comment
Вам действительно нужно использовать фабрики, только если они действительно необходимы. Я обнаружил, что чаще всего они слишком тяжелы. Я никогда не слышал, чтобы избегать нового ключевого слова. Мне это кажется глупым. Я бы посоветовал взять книгу о шаблонах проектирования «Банда четырех», чтобы узнать больше о фабриках. amazon.com/ - person Marc W; 11.05.2009
comment
Проблема с использованием «нового» заключается в том, что в этот момент оно связывает ссылку на конкретный класс реализации. Хотя иногда это нормально, часто вам нужно больше гибкости. Вот почему вы используете шаблон Factory (хотя, возможно, это просто устраняет проблему, поскольку фабрика должна быть где-то создана ...) Контейнеры внедрения зависимостей - это фабрики на стероидах, которые концептуально помещают конкретизацию всех интерфейсов в одно место; у вас все еще есть привязки, но теперь они не разбросаны по всем чертовым классам в программе. - person Donal Fellows; 17.10.2010

Что ж, у вас действительно есть 3 варианта. Используйте новый, используйте фабрику или используйте контейнер DI. С контейнером DI ваши пять переменных, скорее всего, должны быть в каком-либо файле конфигурации.

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

person Community    schedule 10.05.2009

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

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

Возьмите упомянутый вами класс с этими полями.

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

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

Возможно, вам стоит взглянуть на контейнер IoC и внедрение зависимостей?

person Lasse V. Karlsen    schedule 10.05.2009

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

Избегание ключевого слова new может быть полезным, потому что оно создает зависимость от реализующего класса. Лучшим решением было бы использовать Dependancy Injection.

Например

public interface IDoStuff
{
    void DoStuff();
}

public class DoStuffService
{
    private IDoStuff doer;

    public DoStuffService()
    {
        //Class is now dependant on DoLotsOfStuff
        doer = new DoLotsOfStuff(1,true, "config string");
    }
}

public class DoStuffBetterService
{
    private IDoStuff doer;

    //inject dependancy - no longer dependant on DoLotsOfStuff
    public DoStuffBetterService(IDoStuff doer)
    {
        this.doer = doer;
    }
}

Очевидно, вам все равно нужно где-то создать передаваемый объект IDoStuff. Контейнер Inversion of Control (IoC) - хороший инструмент, помогающий реализовать это. Вот хорошее руководство по Castle Windsor Container, если вам интересно узнать больше. (Есть много других контейнеров IoC, я просто использую этот.)

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

person ebrown    schedule 11.05.2009

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

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

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

Если что-то непонятно - спрашивайте.

person Yaroslav Yakovlev    schedule 20.08.2009