Около месяца назад я писал об одном из столпов объектно-ориентированного программирования (в частности, об абстракции). В посте я определил абстракцию следующим образом:
Вместо этого мы собираемся абстрагировать идеи в их классы. И здесь есть ключевая идея: класс должен представлять существительное.
И хотя это все еще верно, идея абстрактных классов отличается от объектно-ориентированного программирования.
Звучит запутанно, правда? То есть:
- на одном уровне у нас есть абстракция, определяемая как идея, что мы берем идею и представляем ее в классе,
- на другом уровне у нас есть абстрактные классы, которые помогают определять функции, которые должны реализовывать подклассы.
И если это не достаточно запутанно, мы смешиваем это с интерфейсами, которые обеспечивают выполнение классов, которым должны следовать контракты, а затем мы смешиваем это с абстрактными классами, которые определяют методы, которые также должны быть реализованы, но также могут реализовывать собственные методы.
Еще не запутались? Без проблем. Весь смысл следующих трех постов в том, чтобы сделать следующее:
- Определите, что такое абстрактные классы,
- Описывать различные абстрактные классы и интерфейсы,
- Помогите решить, когда вы хотите использовать один над другим.
С учетом сказанного, вот вся идея абстрактных классов.
Абстрагирующее поведение
Прежде всего, есть разница в абстракции и абстрактных классах. Первый относится к идее представления чего-либо в программировании; последний относится к реальному способу написания кода.
И один из лучших способов, которые я нашел для того, чтобы думать об абстрактных классах в объектно-ориентированном программировании, состоит в том, чтобы думать о них так:
Абстрактные классы заменяют реализацию.
Возможно, их можно рассматривать как заполнители. В конечном счете, они обеспечивают поведение, которое должны реализовать подклассы.
Чем это отличается от интерфейса? Помните, что интерфейс определяет сигнатуру для функции (имя функции, ее аргументы и модификаторы видимости), которую должен реализовать класс.
Абстракция, с другой стороны, обеспечивает замену реализации, которую должен реализовать подкласс. Но, возможно, это лучше всего демонстрируется с помощью кода.
Абстракция на практике
Допустим, вы работаете над проектом и обнаружили, что у вас есть функциональность, существующая более чем в одном месте. Помимо нарушения всей идеи DRY, он также может стать местом, где вы можете абстрагировать функциональность в базовый класс и использовать его снова.
Давайте рассмотрим это в контексте издательской системы. Это не обязательно то, как это реализует WordPress, но он использует идею, с которой мы знакомы: таксономии.
Вспомним, что в WordPress у нас есть Теги и Категории. Между ними есть тонкие различия (например, иерархический он или нет), но они также имеют схожие атрибуты, такие как имя и ярлык.
Абстракция таксономии
Итак, мы можем начать с написания абстрактного класса таксономии, который абстрагирует общую функциональность в свой собственный класс.
<?php
abstract class Taxonomy
{
private $taxonomyName;
private $taxonomySlug;
public function __construct($name) {
$this->taxonomyName = $name;
$this->taxonomySlug = strtolower(str_ireplace(' ', '-', $this->taxonomyName));
}
public function getName() {
return $this->taxonomyName;
}
public function getSlug() {
return $this->taxonomySlug;
}
abstract protected function isHierarchical();
abstract protected function isCategory();
abstract protected function isTag();
}
В приведенном выше коде вы увидите, что я сделал следующее:
- объявил абстрактный класс
- определены несколько атрибутов, которые будут установлены в конструкторе
- обеспечил реализацию нескольких государственных функций,
- добавлено несколько защищенных методов.
Ключевым моментом при рассмотрении этого класса является то, что любой класс, реализующий этот абстрактный класс, автоматически будет иметь функциональные возможности, определенные в конструкторе, функции getName и функции getSlug.
Однако у них не будет реализации абстрактных функций. Это то, что осталось реализовать с помощью подклассов (которыми я сейчас поделюсь).
Конкретная таксономия: тег
Теперь, когда мы определили абстрактный класс, можно реализовать абстракцию. Например :
<?php
class Tag extends Taxonomy
{
protected function isHierarchical() {
return false;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
}
Обратите внимание, что в приведенном выше коде все, что делает класс, — это обеспечивает реализацию абстрактных функций, определенных в абстрактном классе (который указан функцией extends в определении класса).
Позже в этой статье я расскажу, как протестировать этот код, но обратите внимание, что вышеприведенное предлагает не только функциональность, которую вы видите, но и функциональность класса Taxonomy.
Конкретная таксономия: категория
Прежде чем взглянуть на это в действии, я также хочу определить категорию. Это будет включать код, который реализует функции из абстрактного класса, а также собственные функции.
<?php
class Category extends Taxonomy
{
private $parentId = -1;
protected function isHierarchical() {
return true;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
public function setParentId($parentId) {
$this->parentId = $parentId;
}
public function getParentId() {
return $this->parentId;
}
}
Здесь у нас есть все, что поставляется с классом Taxonomy, но мы также определили наше свойство для его родительского идентификатора и методов получения и установки. Хотя в данном случае это тривиально, он показывает, как могут функционировать иерархические категории.
Кроме того, если у категории нет родителя, то для идентификатора устанавливается значение -1, что упрощает запись для автоматического тестирования или даже проверки наличия родителя.
Видеть это в действии
Чтобы продемонстрировать весь этот код, у меня есть суть, которая включает весь код в один файл. Как лучшая практика, я не рекомендую это. Вместо этого каждый класс должен храниться в своем собственном файле, и каждый класс должен принадлежать пространству имен.
Но поскольку это чисто для демонстрационных целей, этого достаточно.
<?php
abstract class Taxonomy
{
private $taxonomyName;
private $taxonomySlug;
public function __construct($name) {
$this->taxonomyName = $name;
$this->taxonomySlug = strtolower(str_ireplace(' ', '-', $this->taxonomyName));
}
public function getName() {
return $this->taxonomyName;
}
public function getSlug() {
return $this->taxonomySlug;
}
abstract protected function isHierarchical();
abstract protected function isCategory();
abstract protected function isTag();
}
/*--*/
class Tag extends Taxonomy
{
protected function isHierarchical() {
return false;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
}
/*--*/
class Category extends Taxonomy
{
private $parentId = -1;
protected function isHierarchical() {
return true;
}
protected function isCategory() {
return $this->isHierarchical;
}
protected function isTag() {
return !$this->isHierarchical;
}
public function setParentId($parentId) {
$this->parentId = $parentId;
}
public function getParentId() {
return $this->parentId;
}
}
/*- Tag Demo ----------------------------*/
$tag = new Tag('Acme Tag');
echo $tag->getName();
echo $tag->getSlug();
/*- Category Demo -----------------------*/
$category = new Category('Acme Category');
echo $category->getName();
echo $category->getSlug();
echo $category->getParentId();
$category->setParentId(100);
echo $category->getparentId();
Когда вы запустите это в консоли, вы должны увидеть что-то вроде следующего вывода:
Возможно, вам придется добавить несколько операторов эха, чтобы убедиться, что он создает новые строки, но это зависит от вас.
Что насчет интерфейсов?
Итак, на данный момент:
- у нас есть рабочее определение того, что такое абстрактные классы,
- у нас есть пример того, как выглядят абстрактные классы,
- и у нас есть рабочая демонстрация того, как они могут работать.
Далее я углублюсь в обсуждение различий между абстрактными классами и интерфейсами, когда вы можете захотеть использовать один вместо другого или когда вы можете захотеть использовать их в сочетании друг с другом.