Интерфейсы стоит создавать когда есть общая задача и несколько вариантов ее решения, применяемых в зависимости от ситуации. Например задача сохранения данных, а в качестве решения сохранение данных:
- в базу данных;
- в файл;
- в сессию и др.
В данном случае есть смысл создать один общий интерфейс с обязательными методами вроде insert() - непосредственное сохранение данных. В зависимости от места хранения данных, каждый класс будет по своему реализовывать сохранение данных, но называться данный метод всегда будет insert(), если мы пропишем его в интерфейсе.
После создания интерфейса нужно создать несколько классов-сервисов его реализующих (имплементирующих).
Для начала, давайте рассмотрим пример без использования интерфейса.
При создании приложения согласно ООП, нужно отделять логику от функционала, разбивая задачи на отдельные классы.
Допустим есть 3 класса:
- Date (общий класс, получающий данные и использующий методы вспомогательного класса(сервиса) для их сохранения);
- saveDb (класс сохраняющий данные в базу данных);
- saveFile (класс сохраняющий данные в файл)
class Date{ //Db – класс сохраняющий данные в БД, $base – экземпляр этого класса public function date(saveDb $base){ $message = array(); //получение данных из формы $date = $this->validate($message); //валидация $base->insertInto(); //вызов методов класса Db для сохранения } public function validate($date){ //проверка данных return $date; } }в данном примере, для сохранения данных, методу date() класса Date, в качестве аргумента, нужно передать экземпляр конкретного класса - saveDb или saveFile.
Недостаток данного подхода в том, что класс проверяющий данные и класс сохраняющий данные сильно связаны между собой. В примере, мы указываем, что аргумент метода date() - это экземпляр класса работающего с базой данных. Далее используются нужные методы данного класса (saveDb) вроде insertInto().
А что, если понадобится сохранить данные не в БД, а в файл?!! В этом случае нужно будет менять весь метод date(), ведь у другого класса могут быть совсем другие методы, а не метод insertInto() и весь принцип построения другого класса может сильно отличаться. Может быть, там даже будет предусмотрена своя валидация и метод validate() класса Date окажется лишним.
Вот в таких случаях и используются интерфейсы. Они помогают в создании шаблонов для классов, которые будут использоваться в приложении и иметь общие методы делающие одни и те же задачи, но своим способом. Интерфейсы позволяют быстрее разобраться в коде и легче его поддерживать. Так же, они помогают создавать слабую связанность между классами, что можно продемонстрировать на примере:
interface Save { public function insert($date); } class saveDb implements Save { protected function connectDb(){} public function insert($date){ echo $date.' сохранено в БД <br>'; } } class saveFile implements Save { protected function openFile(){} public function insert($date){ echo $date.' сохранено в файл <br>'; } protected function closeFile(){} } class saveSession implements Save { public function insert($date){ echo $date.' сохранено в сессию <br>'; } } //////////////////////////////////// class Date { public $date; public function __construct($date) { $this->date = $date; } // Save – интерфейс для сохранения данных, $obj – экземпляр одного из классов реализующих данный интерфейс. public function save(Save $obj){ $date = $this->validate($this->date); //валидация $obj->insert($date); //вызов методов класса Db для сохранения } public function validate($date){ // тут проверка данных return $date; } } $date = new Date('Контент'); $db = new saveDb(); $date->save($db); //Контент сохранено в БД $file = new saveFile(); $date->save($file); //Контент сохранено в файл $ses = new saveSession(); $date->save($ses); //Контент сохранено в сессию
В моем упрощенном примере видно, что интерфейс обязует реализующие его классы иметь метод insert(), который (допустим) непосредственно занимаются вставкой данных. А кроме него, каждый класс может иметь другие необходимые для его работы методы. Для класса saveDb, это, например, метод connectDb(), создающий соединение с базой данных. Для тестирования, я сделал вывод сообщений при срабатывании метода insert().
Таким образом, при работе с данными, в конструктор нужного класса или другой метод, мы можем передавать класс-сервис, указывая, что он относится к определенному интерфейсу и быть уверенными, что для всех классов данной группы используются одинаковые методы. Информацию про данные методы (документацию), кстати, можно указать прямо в коде интерфейса возле соответствующих методов, тем более, что там будет намного меньше строк чем в реализующих его классах.
В данном примере, вместо зависимости от конкретного класса (saveDb) передается зависимость от интерфейса Save:
public function save(Save $obj)
Код класса Date не зависит от конкретного реализатора, а только от интерфейса. Стоит отметить, что данный пример построен на базе паттерна "Dependency injection", что переводится как "внедрение зависимости".
Интерфейс обеспечивает наличие указанных в нем методов во всех классах которые его реализуют. Поэтому можно быть уверенным, что вызов метода insert():
$obj->insert($date);приведет к сохранению данных. При этом место хранения зависит от реализации данного метода в каждом из классов. Так, метод insert() класса saveDb сохраняет в базу данных, метод insert() класса saveFile сохраняет в файл и тд. Выполните код данного примера и увидите, что класс Date отработал со всеми классами интерфейса Save и при этом, код самого класса Date никак не пришлось менять.