Что значит программировать на интерфейс?

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

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

Однако я не понимаю последствий?
Примеры помогут.

EDIT: я получил много хороших ответов, даже если бы вы пополнили его некоторыми фрагментами кода для лучшего понимания темы. Спасибо!

Ответ 1

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

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;
}

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

public Set getBuddies() {
  return buddies;
}

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

Ответ 2

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

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

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

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

При представлении новой итерации программного обеспечения босс ответил: "Это здорово, но может ли он также сообщать о бизнес-данных из этой веб-службы?" Вспоминая все эти утомительные заявления, которые он должен был бы переписать с нетерпением, программист пришел в ярость. "Сначала xml, затем SQL, теперь веб-службы! Каков реальный источник бизнес-данных?"

Босс ответил: "Все, что может его обеспечить"

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

Ответ 3

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

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

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

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

Ответ 4

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

Например, скажем, модуль 1 позаботится о отображении информации о банковском счете для конкретного пользователя, а module2 будет получать информацию о банковском счете из "любого" используемого back-end.

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

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

Это идея API.

Ответ 5

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

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

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

Итак, чтобы ясный пример:

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

Если вам нужен доступ к первому объекту 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 other имеет метод firstElement()), тогда использование интерфейса, а не реализация, даст вам гибкость для изменения.

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

В этом виде вы не кодируете метод получения Vector, а получить метод списка.

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

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

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

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

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

 statement.executeOracle9iSomething();

vs

statement.executeOracle11gSomething();

Что-то подобное происходит с Java Swing.

Дополнительная информация:

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

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

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

Ответ 6

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

Ответ 7

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

Ответ 8

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

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

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

Ответ 9

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

У каждого автомобиля есть двери.

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

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();
    }
}

Ремонтник может обрабатывать 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 and 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);

Ответ 10

В дополнение к другим ответам я добавляю больше:

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

Кроме того, программирование на интерфейс позволяет вам определить, каково поведение вашего кода, прежде чем писать его. Вы ожидаете, что класс что-то сделает. Вы можете проверить это, прежде чем писать фактический код, который это делает. Когда ваш интерфейс чист и выполнен, и вам нравится взаимодействовать с ним, вы можете написать фактический код, который делает что-то.

Ответ 11

Allen Holub написал отличную статью для JavaWorld в 2003 году по этой теме под названием Почему расширение является злым. Его утверждение о "программе для интерфейса", как вы можете понять из его названия, заключается в том, что вы должны с радостью реализовать интерфейсы, но очень редко используете ключевое слово extends для подкласса. Он указывает, среди прочего, то, что известно как проблема хрупкого базового класса. Материал из Википедии:

фундаментальная архитектурная проблема объектно-ориентированных систем программирования, где базовые классы (суперклассы) считаются "хрупкими", потому что, казалось бы, безопасные модификации базового класса, унаследованные производными классами, могут привести к сбою производных классов. Программист не может определить, безопасно ли изменение базового класса, просто исследуя в изоляции методы базового класса.

Ответ 12

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

Например, мы пишем класс 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() является лишь примером. Главное - разоблачить интерфейс, а не реализацию.

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

Ответ 13

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

Например, многие библиотеки БД действуют как интерфейсы, поскольку они могут работать со многими различными фактическими БД (MSSQL, MySQL, PostgreSQL, SQLite и т.д.) без кода, который вообще использует библиотеку БД.

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

Ответ 14

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

Ответ 15

Это означает, что ваши переменные, свойства, параметры и типы возвращаемых данных должны иметь тип интерфейса вместо конкретной реализации.

Это означает, что вы используете IEnumerable<T> Foo(IList mylist) вместо ArrayList Foo(ArrayList myList), например.

Используйте реализацию только при конструировании объекта:

IList list = new ArrayList();

Если вы это сделали, вы можете позже изменить тип объекта, возможно, позже вы захотите использовать LinkedList вместо ArrayList, это не проблема, потому что повсюду вы ссылаетесь на него как на "IList"

Ответ 16

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

Обратите внимание, что в качестве интерфейса может использоваться абстрактный класс.

enter image description here

Программирование на основе реализации:

Motorcycle motorcycle = new Motorcycle();
motorcycle.driveMoto();

Программирование на основе интерфейса:

Vehicle vehicle;
vehicle = new Motorcycle(); // Note that DI -framework can do it for you
vehicle.drive();

Ответ 17

В основном вы создаете метод/интерфейс следующим образом: create( 'apple' ), где метод create(param) исходит из абстрактного класса/интерфейса fruit, который позже реализуется конкретными классами. Это отличается от подкласса. Вы создаете контракт, который должны выполнять классы. Это также уменьшает сцепление и делает вещи более гибкими, когда каждый конкретный класс реализует ее по-разному.

Клиентский код не знает о конкретных типах используемых объектов и не знает о классах, реализующих эти объекты. Клиентский код знает об интерфейсе create(param) и использует его для создания фруктовых объектов. Ему нравится говорить: "Мне все равно, как вы его получите или сделаете, я просто хочу, чтобы вы отдали его мне".

Аналогом этого является набор кнопок включения и выключения. Это интерфейс on() и off(). Вы можете использовать эти кнопки на нескольких устройствах, телевизоре, радио, свет. Они все обрабатывают их по-другому, но нас это не волнует, все, о чем мы заботимся, это включить или выключить.