Xwab
Форумыnavigate_nextПрограммирование на PHP

Интерфейсы в php
Сообщения
mastes

Для чего нужны интерфейсы? Как ими пользоватся? Когда их стоит и спользовать?

16 Июл 2012, 4:20
Башка

Само понятие интерфейса исходит из модели "Контрактов".
Модель Контрактов - это такая модель, которая включает использование сущностей типа "Контракт". Любая сущность другого типа, подписавшаяся на контракт должна исполнить все указанные в нем требования. В переводе на язык объектной-ориентации контрактом является интерфейс, а сущностями других типов являются классы. Класс может подписаться на интерфейс (реализовать интерфейс), но для этого обязан исполнить указанные в нем требования (реализовать все методы).
Модель контрактов или Интерфейсы в ООП позволяют решить несколько задач:
1. Обеспечить частичную реализацию механизма множественного наследования, так как любой класс может реализовывать множество интерфейсов;
2. Обеспечить дополнительный полиморфизм, так как можно ссылаться на интерфейсы, а не на конкретные классы;
3. Разделить семантику и реализацию как C++ подходе.
Важным отличием интерфейса от класса или абстрактного класса является то, что интерфейс не содержит реализации своих методов, то есть определяет только семантику реализующих его классов.
Семантика метода - это описание его структуры без алгоритма.
В данном коде (написанном на Java для наглядности):

public static void method(int x, String y){
System.out.println("First arg = "+x+"; Second arg = "+y);
}

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

{
System.out.println("First arg = "+x+"; Second arg = "+y);
}

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

<?php
interface Compared{
/*
* Метод сравнивает вызываемый объект с аргументом.
* @params mixed $object Сравниваемый объект.
* @return boolean true - если объекты равные, иначе - false.
* @throws Exception Выбрасывается в случае, если объекты не являются сравниваемыми. На пример как сравнение строки и числа.
*/
public function equals($object);
}

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

<?php
class Example implement Compared{
protected x;

public function __construct($x){
$this->x = $x;
}

public function getX(){
return $this->x;
}

/*
* Метод сравнивает вызываемый объект с аргументом.
* @params mixed $object Сравниваемый объект.
* @return boolean true - если объекты равные, иначе - false.
* @throws Exception Выбрасывается в случае, если объекты не являются сравниваемыми. На пример как сравнение строки и числа.
*/
public function equals($object){
// Реализуя интерфейс, класс так же наследует всю его семантику, в том числе и описание метода
if(!($object instanceof self)){ // Здесь правильнее было бы использовать вместо self ключевое слово static для работы с наследованием
throw new Exception('Не сравниваемые данные');
}
// Класс реализует метод equals по своему
return $this->x === $object->getX();
}
}

Некоторая функция может ссылаться на интерфейс, а не на класс:

<?php
function eq(Compared $object){
...
}

Внутри функции eq мы можем быть уверены, что аргумент $object можно сравнивать с другими данными.

Польза интерфейсов так же в том, что можно подменить один класс другим при условии, что они оба реализуют один и тот же интерфейс, на пример:

<?php
// Все лекарства используют одну и ту же семантику
interface Medicine{
  // Метод определяет на сколько прибавляется жизнь при использовании лекарства
  public function use($life);
}

class Tablet implement Medicine{
  // Метод определяет на сколько прибавляется жизнь при использовании лекарства
  public function use($life){
    return $life+10;
  }
}

class Prick implement Medicine{
  // Метод определяет на сколько прибавляется жизнь при использовании лекарства
  public function use($life){
    return $life+50;
  }
}

// Класс персонажей некоторой онлайн игры
class Person{
  // Любой персонаж может носить у себя набор лекарств
  protected $medicine = [];
  // Любой персонаж имеет некоторое число жизней
  protected $life = 100;
 
  // Метод применяет лекарство на персонаже
  public function useMedicine($index){
    $this->life = $this->medicine[$index]->use($this->life);
  }

  // Метод добавляет лекарство в рюкзак персонажа
  /* Обратите внимание на тип аргумента, он может быть любым классом, реализующим интерфейс Medicine, то есть можно реализовать сколько угодно лекарств и использовать их не переписывая код метода addMedicine */
  public function addMedicine(Medicine $medicine){
    $this->medicine[] = $medicine;
  }
}

16 Июл 2012, 5:25
xmikex

А какой смысл делать именно интерфейсом medicine, а не абстнактным классом?

16 Июл 2012, 6:37
RiO

xmikex, а какой смысл делать абстрактный класс ?

16 Июл 2012, 6:55
Башка

Ну здесь нужно думать о связях. Существует несколько типов связей, на пример ассоциация (когда один объект ведает о другом объекте) или наследование (частный случай ассоциации, когда один класс является другим классом, но его более частным случаем). Так вот наследование от абстрактного класса это связь типа "является", а реализация интерфейса это связь типа "реализует". То есть в данном примере можно связать все через абстрактный класс Medicine, но в тех случаях, когда нет необходимости или возможности реализовать связь "является", как на пример приведенный выше интерфейс Compared, то следует использовать интерфейсы. Если бы мы реализовали механизм сравнивания объектов через абстрактный класс, то наследуемые от него классы больше не смогли бы наследовать другое поведение от других классов. Другими словами, интерфейсы добавляют семантику в класс, а наследование используется тогда, когда необходимо уточнить родительский класс.
добавлено спустя 1 минуту:
Вообще наследование (extends) применяется тогда и только тогда, когда один класс является другим классом, но в более частном случае, на пример Модератор является частным случаем Пользователя. Интерфейсы же используются когда нужна уверенность в том, что тот или иной класс реализует те или иные методы, тогда эти методы выносятся в интерфейс и реализуются классами. В этом случае класс не является интерфейсом, он лишь обязуется реализовать все его методы (перенос семантики).
добавлено спустя 2 минуты:
Можно выделить еще один способ выбора интерфейса или класса: обычно интерфейсы имеют имя в форме прилагательного, на пример Compared - сравниваемый, или Serializable - сериализуемый, а классы в форме существительных. То есть интерфейсы это прилагательное к классу, а классы это сущности. Но так бывает не всегда.
добавлено спустя 4 минуты:
Реализация Medicine через абстрактный класс выглядела бы так:

<?php

abstract class Medicine{
  public function use($life){
    return $life+$this->life();
  }

  public abstract function life();
}

class Tablet extends Medicine{
  public function life(){
    return 10;
  }
}

16 Июл 2012, 7:52
Ответить на тему