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

Всегда существует очевидное: Определите свою собственную тестовую функцию... Назовите его от gdb. Например:

#define SHOW(X) cout << # X " = " << (X) << endl

void testPrint( map<int,int> & m, int i )
{
  SHOW( m[i] );
  SHOW( m.find(i)->first );
}

int
main()
{
    std::map<int,int> m;
    m[1] = 2;
    m[2] = 4;
    return 0;  // Line 15.
}

И:

....
Breakpoint 1 at 0x400e08: file foo.C, line 15.
(gdb) run
Starting program: /tmp/z/qD 

Breakpoint 1, main () at qD.C:15
(gdb) call testPrint( m, 2)
m[i] = 4
(*m.find(i)).first = 2
(gdb) 
19
задан Bakuriu 24 April 2014 в 08:15
поделиться

14 ответов

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

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

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

35
ответ дан 30 November 2019 в 01:49
поделиться

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

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

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

IList list = new ArrayList();

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

1
ответ дан 30 November 2019 в 01:49
поделиться

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

1
ответ дан 30 November 2019 в 01:49
поделиться

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

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

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

1
ответ дан 30 November 2019 в 01:49
поделиться

In addition to the other answers, I add more:

You program to an interface because it's easier to handle. The interface encapsulates the behavior of the underlying class. This way, the class is a blackbox. Your whole real life is programming to an interface. When you use a tv, a car, a stereo, you are acting on its interface, not on its implementation details, and you assume that if implementation changes (e.g. diesel engine or gas) the interface remains the same. Programming to an interface allows you to preserve your behavior when non-disruptive details are changed, optimized, or fixed. This simplifies also the task of documenting, learning, and using.

Also, programming to an interface allows you to delineate what is the behavior of your code before even writing it. You expect a class to do something. You can test this something even before you write the actual code that does it. When your interface is clean and done, and you like interacting with it, you can write the actual code that does things.

2
ответ дан 30 November 2019 в 01:49
поделиться

Аллен Холуб написал отличную статью для JavaWorld в 2003 году на эту тему под названием Почему расширение - это зло . Его взгляд на оператор «программа к интерфейсу», как вы можете понять из его названия, заключается в том, что вы должны с удовольствием реализовывать интерфейсы, но очень редко использовать ключевое слово extends для подкласса. Он указывает, среди прочего, на то, что известно как хрупкая проблема базового класса . Из Википедии:

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

2
ответ дан 30 November 2019 в 01:49
поделиться

Take a red 2x4 Lego block and attach it to a blue 2x4 Lego block so one sits atop the other. Now remove the blue block and replace it with a yellow 2x4 Lego block. Notice that the red block did not have to change even though the "implementation" of the attached block varied.

Now go get some other kind of block that does not share the Lego "interface". Try to attach it to the red 2x4 Lego. To make this happen, you will need to change either the Lego or the other block, perhaps by cutting away some plastic or adding new plastic or glue. Notice that by varying the "implementation" you are forced to change it or the client.

Being able to let implementations vary without changing the client or the server - that is what it means to program to interfaces.

3
ответ дан 30 November 2019 в 01:49
поделиться

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

3
ответ дан 30 November 2019 в 01:49
поделиться

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

4
ответ дан 30 November 2019 в 01:49
поделиться

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

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

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

5
ответ дан 30 November 2019 в 01:49
поделиться

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

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

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

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

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

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

 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 объекта Vector , а метод get объекта List .

Не имеет значения, как базовый объект выполняет метод, если он отвечает на контракт «получить 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();

vs

statement.executeOracle11gSomething();

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

Дополнительная литература:

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

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

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

5
ответ дан 30 November 2019 в 01:49
поделиться

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

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

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

7
ответ дан 30 November 2019 в 01:49
поделиться

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

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

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

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

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

Начальник ответил: «Все, что может предоставить это»

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

18
ответ дан 30 November 2019 в 01:49
поделиться

Послушайте, я не понимал, что это было для 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();
    }
}

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

DoorRepairer repairer = new DoorRepairer();

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

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

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);
3
ответ дан 30 November 2019 в 01:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: