Что значит программировать на интерфейс? - PullRequest
18 голосов
/ 12 сентября 2009

Я продолжаю слышать утверждение на большинстве сайтов, связанных с программированием:

Программа для интерфейса, а не для Реализация

Но я не понимаю, что это значит?
Примеры помогут.

РЕДАКТИРОВАТЬ: Я получил много хороших ответов, даже если бы вы могли дополнить его фрагментами кода для лучшего понимания предмета. Спасибо!

Ответы [ 17 ]

34 голосов
/ 12 сентября 2009

Вы, вероятно, ищете что-то вроде этого:

public static void main(String... args) {
  // do this - declare the variable to be of type Set, which is an interface
  Set buddies = new HashSet();

  // don't do this - you declare the variable to have a fixed type
  HashSet buddies2 = new HashSet();
}

Почему считается хорошим сделать это первым способом? Допустим, позже вы решите, что вам нужно использовать другую структуру данных, скажем, LinkedHashSet, чтобы воспользоваться преимуществами функциональности LinkedHashSet. Код должен быть изменен так:

public static void main(String... args) {
  // do this - declare the variable to be of type Set, which is an interface
  Set buddies = new LinkedHashSet();  // <- change the constructor call

  // don't do this - you declare the variable to have a fixed type
  // this you have to change both the variable type and the constructor call
  // HashSet buddies2 = new HashSet();  // old version
  LinkedHashSet buddies2 = new LinkedHashSet();
 }

Это не так уж плохо, верно? Но что, если вы написали геттеры так же?

public HashSet getBuddies() {
  return buddies;
}

Это тоже нужно изменить!

public LinkedHashSet getBuddies() {
  return buddies;
}

Надеюсь, вы видите, что даже с такой маленькой программой у вас есть далеко идущие последствия для того, что вы объявляете типом переменной. Поскольку объекты перемещаются туда-сюда так много, это определенно помогает программе легче кодировать и поддерживать, если вы просто полагаетесь на переменную, объявленную как интерфейс, а не как конкретную реализацию этого интерфейса (в этом случае объявите ее Установите, а не LinkedHashSet или что-то еще). Это может быть просто так:

public Set getBuddies() {
  return buddies;
}

Есть и другое преимущество: в этом (по крайней мере, для меня) разница помогает мне лучше проектировать программу. Но, надеюсь, мои примеры дают вам некоторое представление ... надеюсь, это поможет.

16 голосов
/ 12 сентября 2009

Однажды, его начальник поручили младшему программисту написать приложение для анализа бизнес-данных и объединить все это в красивые отчеты с метриками, графиками и всем этим. Босс дал ему файл XML с пометкой «вот несколько примеров бизнес-данных».

Программист начал кодировать. Несколько недель спустя он почувствовал, что метрик, графиков и прочего было достаточно, чтобы удовлетворить босса, и он представил свою работу. «Это замечательно, - сказал начальник, - но может ли он также отображать бизнес-данные из этой базы данных SQL, которую мы имеем?».

Программист вернулся к кодированию. Был код для чтения бизнес-данных из XML, разбросанный по всему его приложению. Он переписал все эти фрагменты, обернув их условием «если»:

if (dataType == "XML")
{
   ... read a piece of XML data ...
}
else
{
   .. query something from the SQL database ...
}

Когда его представили с новой версией программного обеспечения, начальник ответил: «Это здорово, но может ли он также сообщать о бизнес-данных из этого веб-сервиса?» Вспоминая все эти утомительные заявления, которые он должен был бы переписать СНОВА, программист пришел в ярость. «Сначала XML, затем SQL, а теперь веб-сервисы. Что такое НАСТОЯЩИЙ источник бизнес-данных?»

Босс ответил: «Все, что может обеспечить это»

В тот момент программист был просветленным .

7 голосов
/ 12 сентября 2009

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

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

Пример: предположим, что есть класс Screen, в котором есть методы Draw(image) и Clear(). В документации говорится что-то вроде «метод draw рисует указанное изображение на экране» и «метод clear очищает экран». Если вы хотите отображать изображения последовательно, правильным способом будет многократный вызов Clear() с последующим Draw(). Это было бы кодирование интерфейса. Если вы кодируете реализацию, вы можете сделать что-то вроде только вызова метода Draw(), потому что, взглянув на реализацию Draw(), вы знаете, что она вызывает Clear() внутри, прежде чем выполнять рисование. Это плохо, потому что вы теперь зависите от деталей реализации, которые вы не можете узнать, глядя на открытый интерфейс.

Я с нетерпением жду возможности узнать, разделяет ли кто-либо еще эту интерпретацию фразы в вопросе ОП, или я совершенно не согласен ...

5 голосов
/ 12 сентября 2009

Интерфейс определяет методы , на которые объект отвечает.

Когда вы кодируете интерфейс , вы можете изменить базовый объект, и ваш код будет все еще работать (поскольку ваш код не зависит от ВОЗ, выполняющей работу или КАК ее выполнять выполняется) Таким образом, вы получаете гибкость.

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

Итак, приведу ясный пример:

Если вам нужно держать несколько объектов, вы, возможно, решили использовать Vector .

Если вам нужен доступ к первому объекту вектора, вы можете написать:

 Vector items = new Vector(); 
 // fill it 
 Object first = items.firstElement();

Пока все хорошо.

Позже вы решили, что по какой-то причине вам нужно изменить реализацию (скажем, вектор создает узкое место из-за чрезмерной синхронизации)

Вы понимаете, что вам нужно использовать ArrayList instad.

Ну, твой код сломается ...

ArrayList items = new ArrayList();
// fill it  
Object first = items.firstElement(); // compile time error. 

Ты не можешь. Эта строка и все те строки, которые используют метод firstElement () , прервутся.

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

 List items = new Vector();
 // fill it 
 Object first = items.get( 0 ); //

В этой форме вы кодируете не метод get вектора , а метод get списка .

Неважно, как базовый объект выполняет метод, если он отвечает на контракт «получить 0-й элемент коллекции»

Таким образом, позже вы можете изменить его на любую другую реализацию:

 List items = new ArrayList(); // Or LinkedList or any other who implements List
 // fill it 
 Object first = items.get( 0 ); // Doesn't break

Этот пример может показаться наивным, но является основой, на которой основана технология ОО (даже для тех языков, которые не имеют статической типизации, таких как Python, Ruby, Smalltalk, Objective-C и т. Д.)

Более сложным примером является способ JDBC . Вы можете сменить драйвер, но большая часть вашего звонка будет работать так же. Например, вы можете использовать стандартный драйвер для баз данных Oracle или использовать более сложный драйвер, такой как Weblogic или Webpshere. Конечно, это не волшебство, вы все еще должны тестировать свой продукт раньше, но по крайней мере у вас нет таких вещей, как:

 statement.executeOracle9iSomething();

против

statement.executeOracle11gSomething();

Нечто подобное происходит с Java Swing.

Дополнительное чтение:

Принципы проектирования из шаблонов проектирования

Эффективный элемент Java: ссылки на объекты по их интерфейсам

(Покупка этой книги - одна из лучших вещей, которую вы могли бы сделать в жизни - и прочитайте, если, конечно, -)

5 голосов
/ 12 сентября 2009

Это способ разделения обязанностей / зависимостей между модулями. Определяя конкретный интерфейс (API), вы гарантируете, что модули на любой стороне интерфейса не будут "беспокоить" друг друга.

Например, скажем, модуль 1 позаботится об отображении информации о банковском счете для конкретного пользователя, а модуль 2 будет извлекать информацию о банковском счете из «любой» используемой серверной части.

Определяя несколько типов и функций вместе со связанными параметрами, например структуру, определяющую банковскую транзакцию, и несколько методов (функций), таких как GetLastTransactions (AccountNumber, NbTransactionsWanted, ArrayToReturnTheseRec) и GetBalance (AccountNumer), Module1 сможет получить необходимую информацию, и не беспокоиться о том, как эта информация хранится или рассчитывается или что-то еще. И наоборот, Module2 будет просто отвечать на вызов методов, предоставляя информацию в соответствии с заданным интерфейсом, но не будет беспокоиться о том, где эта информация должна отображаться, печататься или что-то еще ...

Когда модуль изменяется, реализация интерфейса может отличаться, но пока интерфейс остается тем же, модули, использующие API, в худшем случае могут нуждаться в перекомпиляции / перестройке, но им не нужно иметь свои логика изменена в любом случае.

Это идея API.

4 голосов
/ 12 сентября 2009

По сути, это утверждение действительно о зависимостях. Если я кодирую свой класс Foo для реализации (Bar вместо IBar), то Foo теперь зависит от Bar. Но если я кодирую свой класс Foo для интерфейса (IBar вместо Bar), то реализация может варьироваться, и Foo больше не зависит от конкретной реализации. Такой подход дает гибкую, слабосвязанную кодовую базу, которую легче повторно использовать, подвергать рефакторингу и модульному тестированию.

3 голосов
/ 12 сентября 2009

Возьмите красный блок 2x4 Lego и прикрепите его к синему блоку 2x4 Lego, чтобы один располагался поверх другого. Теперь удалите синий блок и замените его желтым блоком 2x4 Lego. Обратите внимание, что красный блок не должен был изменяться, даже если «реализация» присоединенного блока различалась.

Теперь возьмите другой блок, который не разделяет «интерфейс» Lego. Попробуйте прикрепить его к красному 2x4 лего. Чтобы это произошло, вам нужно будет поменять либо лего, либо другой блок, возможно, срезав немного пластика или добавив новый пластик или клей. Обратите внимание, что изменяя «реализацию», вы вынуждены изменить ее или клиента.

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

3 голосов
/ 12 сентября 2009

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

3 голосов
/ 12 сентября 2009

Слушай, я не осознавал, что это было для Java, и мой код основан на C #, но я верю, что это дает смысл.

У каждой машины есть двери.

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

interface IDoor
{
    void Open();
    void Close();
}

class BackwardDoor : IDoor
{
    public void Open()
    {
       // code to make the door open the "wrong way".
    }

    public void Close()
    {
       // code to make the door close properly.
    }
}

class RegularDoor : IDoor
{
    public void Open()
    {
        // code to make the door open the "proper way"
    }

    public void Close()
    {
        // code to make the door close properly.
    }
}

class RedUkTaxiDoor : BackwardDoor
{
    public Color Color
    {
        get
        {
            return Color.Red;
        }
    }
}

Если вы ремонтируете автомобильную дверь, вам все равно, как выглядит дверь, открывается ли она так или иначе. Ваше единственное требование - чтобы дверь действовала как дверь, например, IDoor.

class DoorRepairer
{
    public void Repair(IDoor door)
    {
        door.Open();
        // Do stuff inside the car.
        door.Close();
    }
}

Repairer может обрабатывать RedUkTaxiDoor, RegularDoor и BackwardDoor. И любой другой тип дверей, например, двери грузовика, двери лимузина.

DoorRepairer repairer = new DoorRepairer();

repairer.Repair( new RegularDoor() );
repairer.Repair( new BackwardDoor() );
repairer.Repair( new RedUkTaxiDoor() );

Примените это для списков, у вас есть LinkedList, Stack, Queue, обычный список, и, если вы хотите, свой собственный, MyList. Все они реализуют интерфейс IList, который требует от них реализации Add и Remove. Так что, если ваш класс добавляет или удаляет элементы в любом заданном списке ...

class ListAdder
{
    public void PopulateWithSomething(IList list)
    {
         list.Add("one");
         list.Add("two");
    }
}

Stack stack = new Stack();
Queue queue = new Queue();

ListAdder la = new ListAdder()
la.PopulateWithSomething(stack);
la.PopulateWithSomething(queue);
2 голосов
/ 28 ноября 2012

«Программа для интерфейса» может быть более гибкой.

Например, мы пишем класс Printer, который предоставляет сервис печати. в настоящее время есть 2 класса (Cat и Dog), которые должны быть напечатаны. Поэтому мы пишем код, как показано ниже

class Printer
{
    public void PrintCat(Cat cat)
    {
        ...
    }

    public void PrintDog(Dog dog)
    {
        ...
    }
    ...
}

А если новый класс Bird также нуждается в этой услуге печати? Мы должны изменить класс Printer, чтобы добавить новый метод PrintBird (). В действительности, когда мы разрабатываем класс Printer, мы можем не знать, кто будет его использовать. Так как же написать Printer? Программа для интерфейса может помочь, см. Ниже код

class Printer
{
    public void Print(Printable p)
    {
        Bitmap bitmap = p.GetBitmap();

        // print bitmap ...
    }
}

С этим новым принтером можно распечатать все, если он поддерживает интерфейс Printable. Здесь метод GetBitmap () является лишь примером. Ключевым моментом является предоставление интерфейса, а не реализации.

Надеюсь, это полезно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...