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

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

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

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

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

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

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

Самым наглядным языком программирования для демонстрации описания интерфейсов я считаю Java (хотя можно было бы выбрать и C#, PHP или практически любой другой по вкусу). В теории все просто:

  • Ключевое слово interface обозначает описание интерфейса;
  • За ним следует название конкретного интерфейса, которое впоследствии можно будет использовать в коде при его упоминании (некоторые программисты на правах традиции начинают названия интерфейсов с заглавной буквы I, мне в свое время даже пытались объяснить зачем так надо делать, но аргументы не показались мне достаточно весомыми);
  • Далее идет тело интерфейса, в котором перечисляются все заголовки методов, которые должны быть в классе, реализующем данный интерфейс (никакой реализации!);
  • Впоследствии приписав к заголовку любого класса ключевое слово implements с последующим указанием названия интерфейса, можно обязать этот класс реализовать указанные в описания интерфейса методы. Существует небольшое исключение для абстрактных классов (то есть классов,для которых не может быть создан объект, обозначаются ключевым словом abstract), они могут и не реализовать все методы интерфейса, но тогда эта обязанность будет переложена на их наследников.

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

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

На практике же это выглядит это примерно следующим образом:

// описание интерфейса
interface Renderable
{
    // обязуем реализовать метод draw
    public void draw();
}

// конкретная реализация интерфейса
class SomeText implements Renderable
{
   string text;
   public SomeText(string str)
   {
      this.text=str;
   }
   public void draw()
   {
       // вынуждены подчиниться и реализовать
       System.out.println(this.text);
   }
}

// класс-клиент
class Render
{
  public Render(Renderable obj)
  {
     // можно быть уверенным, что
     // метод draw реализован
     obj.draw();
     /*
         в качестве альтернативы можно было бы написать как-то так:
         if(obj instanceof Renderable)obj.draw();
         то есть проверить реализован ли интерфейс
         вместо использования его названия в роли типа данных
     */
  }

В данном примере ситуация тривиальна: класс-клиент Render умеет лишь визуализировать классы, которые он получает в конструктор, вызывая у них метод draw. Для обеспечения такой возможности описан интерфейс Renderable, который реализуется в классе SomeText. Хоть класс Render ничего и не знает о том, какой именно класс ему подсунут, благодаря интерфейсу он сможет вывести на экран любой объект, корректно реализующий наш интерфейс, в том числе и SomeText.

Как я уже упоминал: альтернативой такому подходу является использование полиморфизма и наследования. Такой подход более распространен в других языках программирования, например C++, но пример я приведу все равно на Java, основываясь на предыдущем примере, чтобы читателям было проще сравнивать.

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

// теперь используем абстрактный класс
abstract class Renderable
{
    // реализуем метод draw
    public void draw()
    {
       System.out.println("Вывод на экран недоступен!");
    }
}

// реализация интерфейса (на этот раз неформального)
class SomeText extends Renderable
{
   // на этот раз используем extends (наследование)
   // вместо implements
   string text;
   public SomeText(string str)
   {
      this.text=str;
   }
   public void draw()
   {
       // переопределяем метод draw
       // но могли этого и не делать, тогда
       // использовался бы метод из Renderable
       System.out.println(this.text);
   }
}

// класс-клиент
class Render
{
  public Render(Renderable obj)
  {
     // можно быть уверенным, что
     // метод draw реализован
     obj.draw();
     /*
        на этот раз так как в крайнем случае
        в крайнем случае вызовется хотябы
        метод из класса Renderable
     */
  }

Минимальные изменения - суть та же. Сразу хочу отметить, что этот процесс так прост только в Java, в других языках программирования понадобилось бы использование дополнительных модификаторов для метода draw (например в C#: virtual или abstract в классе-потомке и override в классе-наследнике, это необходимо для обеспечения возможности их переопределения).

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