Понимание использования интерфейса и абстрактного класса в PHP
Доброго времени суток. В интернете нашел интересную статью о понимании использования интерфейсов и абстрактных классов в PHP. Разницу между ними. И решил сделать перевод (но немножко дополненный), т.к. статья наглядно показывает нам, как и когда использовать интерфейсы и абстрактные классы. И так, поехали…
Интерфейс
Давайте обратимся к официальной документации PHP и посмотрим определение интерфейса:
Интерфейсы объектов позволяют создавать код, который указывает, какие методы должен реализовать класс, без необходимости определять, как именно они должны быть реализованы.
Интерфейс в PHP определяется, как и класс за исключением вместо ключевого слова class мы указываем interface.
Все методы интерфейса являются абстрактными и открытыми – это природа интерфейса. Пример объявления интерфейса:
<?php
interface Logger
{
public function execute()
}
В интерфейсе тело метода не определено, мы определяем только имя и параметры. Теперь давайте рассмотрим, когда использовать интерфейс. Например, у нас есть следующий код:
<?php
class LogToDatabase
{
public function execute($message)
{
var_dump('log the message to a database :'.$message);
}
}
class LogToFile
{
public function execute($message)
{
var_dump('log the message to a file :'.$message);
}
}
class UsersController
{
protected $logger;
public function __construct(LogToFile $logger)
{
$this->logger = $logger;
}
public function show()
{
$user = 'nahid';
$this->logger->execute($user);
}
}
$controller = new UsersController(new LogToFile);
$controller->show();
В приведенном выше примере автор не использует интерфейс. Автор пишет в журнал, используя класс LogToFile. Но если мы захотим писать данные в журнал с использованием класса LogToDatabase. Необходимо изменить жестко закодированную ссылку на класс:
public function __construct(LogToFile $logger)
Заменить на:
public function __construct(LogToDatabase $logger)
В большом проекте, когда есть несколько классов, это нужно сделать для каждого класса в ручную. Но если, мы будем использовать интерфейс, то это проблема будет решена. И нам не нужно будет менять код в ручную. Теперь давайте рассмотрим следующий код, в котором уже используется интерфейс:
<?php
interface Logger
{
public function execute($message);
}
class LogToDatabase implements Logger
{
public function execute($message){
var_dump('log the message to a database :'.$message);
}
}
class LogToFile implements Logger
{
public function execute($message)
{
var_dump('log the message to a file :'.$message);
}
}
class UsersController
{
protected $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
public function show()
{
$user = 'nahid';
$this->logger->execute($user);
}
}
$controller = new UsersController(new LogToDatabase);
$controller->show();
Теперь, если мы записываем данные не LogToDatabase, а с помощью LogToFile нам не нужно менять метод конструктора в ручную. В методе конструктора мы вводим интерфейс. Поэтому, если у вас есть несколько классов и вы переключаетесь с одного класса на другой, вы получите тот же результат без изменения каких-либо ссылок на классы.
В приведенном выше примере автор пишет данные в журнал, используя LogToDatabase, и теперь, если захочет записать данные в журнал, используя LogToFile, то можно просто заменить:
$controller = new UsersController(new LogToFile);
$controller->show();
Таким образом мы получаем результат без изменения других классов.
Абстрактный класс
Давайте посмотрим официальную документацию PHP для того, чтобы понять что такое абстрактный класс.
PHP 5 поддерживает определение абстрактных классов и методов. Объекты классов, определенных как абстрактные, не могут быть созданы, и любой класс, который содержит по крайней мере один абстрактный метод, должен быть определен как абстрактный. Методы, объявленные абстрактными, несут, по существу, лишь описательный смысл и не могут включать реализацию.
Рассмотрим пример:
<?php
abstract class AbstractClass
{
// Force Extending class to define this method
abstract protected function getValue();
public function printOut()
{
print $this->getValue() . "\n";
}
}
Когда использовать абстрактный класс?
Например, давайте рассмотрим класс Tea
<?php
class Tea
{
public function addTea()
{
var_dump('Add proper amount of tea');
return $this;
}
protected function addHotWater()
{
var_dump('Pour Hot water into cup');
return $this;
}
protected function addSugar()
{
var_dump('Add proper amount of sugar');
return $this;
}
protected function addMilk()
{
var_dump('Add proper amount of Milk');
return $this;
}
public function make()
{
return $this
->addHotWater()
->addSugar()
->addTea()
->addMilk();
}
}
$tea = new Tea();
$tea->make();
Теперь рассмотрим класс Coffee:
class Coffee
{
public function addCoffee()
{
var_dump('Add proper amount of tea');
return $this;
}
protected function addHotWater()
{
var_dump('Pour Hot water into cup');
return $this;
}
protected function addSugar()
{
var_dump('Add proper amount of sugar');
return $this;
}
protected function addMilk()
{
var_dump('Add proper amount of Milk');
return $this;
}
public function make()
{
return $this
->addHotWater()
->addSugar()
->addCoffee()
->addMilk();
}
}
$tea = new Coffee();
$tea->make();
В приведенных выше классах три метода addHotWater(), addSugar(), addMilk() одинаковы. Поэтому мы должны удалить дублированный код:
<?php
abstract class Template
{
public function make()
{
return $this
->addHotWater()
->addSugar()
->addPrimaryToppings()
->addMilk();
}
protected function addHotWater()
{
var_dump('Pour Hot water into cup');
return $this;
}
protected function addSugar()
{
var_dump('Add proper amount of sugar');
return $this;
}
protected function addMilk()
{
var_dump('Add proper amount of Milk');
return $this;
}
protected abstract function addPrimaryToppings();
}
class Tea extends Template
{
public function addPrimaryToppings()
{
var_dump('Add proper amount of tea');
return $this;
}
}
$tea = new Tea();
$tea->make();
class Coffee extends Template
{
public function addPrimaryToppings()
{
var_dump('Add proper amount of Coffee');
return $this;
}
}
$coffee = new Coffee();
$coffee->make();
Автор создает абстрактный класс и называет его Template. В этом классе определяет три метода addHotWater (), addSugar () и addMilk () и создает абстрактный метод с именем addPrimaryToppings.
Теперь, если создавать класс Tea его необходимо делать расширенным для класса Template, тогда мы получаем три определенных метода и должны определить метод addPrimaryToppings ().
Точно так же и для класса Coffee.
Перевод статьи «Understanding use of Interface and Abstract class»
а почему нельзя вместо public function __construct(LogToFile $logger), указать просто public function __construct($logger) ???
Конечно можно, но так мы указываем, что в качестве аргумента $logger должен быть объект класса LogToFile.
а если не указывать что в качестве аргумента $logger должен быть объект класса LogToFile («просто public function __construct($logger)»), что мешает использовать и «$controller = new UsersController(new LogToFile);» и «$controller = new UsersController(new LogToDatabase);», как в примере с интерфейсом ?
просто не совсем понятна необходимость использования интерфейса и соответственно его предназначение (я только учусь ООП)
Акцент в примере не правильный. Суть не в том что в конструкторе переписывается название интерфейса или класса, а в том что интерфейс заставляет написать метод в твоих классах и назвать его правильно execute.