Объектно-ориентированное программирование (часть2)
Основные понятия
В объектно-ориентированном программировании выделяют три основных элемента: инкапсуляция, наследование, полиморфизм. Статья не ставит своей целью всестороннее рассмотрение всех аспектов ООП. Здесь лишь кратко будет рассмотрена их суть.
Инкапсуляция. Инкапсуляция – это скрытие реализации. Для пользователей класса неважно как реализован класс, важено лишь какие методы доступны, т.е. какой интерфейс представляет класс. Мы уже дважды встречали инкапсуляцию. В первом случае мы объявили поля класса как закрытые (private), т.е. скрыли их от посторонних глаз. Методы также можно делать закрытыми (private), они не будут доступны для внешнего пользователя, однако их можно будет вызывать внути открытых (public) методов этого же класса. Закрытые методы необходимы для того, чтобы упростить открытый методы метод, разбив сложную задачу на несколько более простых. В первую очередь это необходимо для реорганизации кода, для более удобного его чтения и понимания. Во втором случае это также закрытое поле $db. Мы присвоили внутренней переменной соответствующий тип данных, скрыв не только реализацию, но и процесс создание объекта.
Замечание
Объект можно создать и внутри класса, например, в конструкторе.
Управление инкапсуляцие осуществляется при помощи модификаторов доступа – их три:
Public – поля и методы видны везде – в самом классе, в классе потомке, просто во внешнем участке кода, использующем класс.
Private – данные видны только в том классе, в котором они определены.
Protected – данные видны как в том классе, в котором они определены, так и в классе, который наследует первоначальный класс, однако для внешнего кода данные не доступны.
Наследование. В косвенной форме мы уже сталкивались со связью классов друг с другом, когда объявляли переменную типа DataBase. Мы неявно произвели связывание с помощью включения. Но что же такое наследование в более обычном его понимании? Под наследованием понимается расширение старого класса до нового путём расширения функциональности, т.е. новый класс будет содержать те же методы (которые, кстати, могут и изменить свою реализацию) и свойства прежнего класса.
Новый класс мы называем классом-потомком, а прежний, от которого происходит наследование, классом-предком или базовым классом. Наследование используется, в первую очередь, для построения иерархических систем, в котором классы-потомки развивают функциональность базовых классов. Наследование также может использоваться для изменения логики первоначальной реализации - т.е. для модификации уже существующего приложения.
Рассмотрим пример. Пусть необходимо расширить перечень параметров, которые может ввести пользователь в гостевой книге. Пусть помимо имени, email и сообщения пользователи получают возможность вводить адрес Web-сайта и номер ICQ. Для этого не требуется создавать новый класс, мы просто наследуем уже готовую реализацию от класса GuestBook.
<?
class SharedGuestBook extends GuestBook
{
private $url;
private $icq;
public function __construct($name, $email, $msg, $url, $icq)
{
parent :: __construct($name, $email, $msg);
$this->url = $url;
$this->icq = $icq;
}
public function getUrl()
{
return $this->url;
}
public function getIcq()
{
return $this->icq;
}
}
?>
Как видно из листинга, старые методы не нужно реализовывать - именно так достигается одна из важнейших задач объектно-ориентированного подхода - повторное использование кода.
Обратите внимание на реализацию конструктора - parent :: __construct($name, $email, $msg); - обратимся к официальной документации (http://www.php.net/manual/ru/language.oop5.paamayim-nekudotayim.php): Когда дочерний класс перегружает методы, объявленные в классе-родителе, PHP не будет осуществлять автоматический вызов методов, принадлежащих классу-родителю. Этот функционал возлагается на метод, перегружаемый в дочернем классе. Данное правило распространяется на [url =
http://www.php.net/manual/ru/language.oop5.decon.php]конструкторы и деструкторы[/url], перегруженные и "магические" методы.
С помощью ключевого слова extends как раз и осуществляется наследование. Теперь настало время изменить класс GuestBookDb для работы с новым типом данных.
<?
class SharedGuestBookDb extends GuestBookDb
{
public function Select()
{
$sql = "SELECT name, email, msg, url, icq FROM new_guestbook";
$dbArray = $this->db->Db2Array();
foreach($dbArray as $rows)
{
$outPut[] = new SharedGuestBook($rows['name'], $rows['email'], $rows['msg'], $rows['url'], $rows['icq']);
}
return $outPut;
}
public function Insert($obj)
{
$name = $obj->getName();
$email = $obj->getEmail();
$msg = $obj->getMsg();
$url = $obj->getUrl();
$icq = $obj->getIcq();
$sql = "INSERT INTO new_guestbook (name, email, msg, url, icq) VALUES('$name', '$email', '$msg', '$url', '$icq')";
if($this->db->Insert($sql) === TRUE)
return TRUE;
return FALSE;
}
}
?>
В данном случае нет необходимости перегружать конструктор, т.к. он остался неизменным. Мы просто переопределили необходимые методы – изменили их функционал, оставив их предыдущие названия, а это уже полиморфизмом пахнет
При наследовании мы можем переопределить любой метод, но иногда нам надо запретить переопределение методов, оставив первоначальную реализацию. Для подобного случая необходимо использовать ключевое слово final. Рассмотрим пример.
<?
class SomeClass
{
private $var;
final public function PrintVar()
{
echo $var;
}
public function SetVar()
{
$this->var = "From SomeClass";
}
}
class SomeClassNew extends SomeClass
{
public function SetVar()
{
$this->var = "From SomeClassNew";
}
}
?>
Если мы бы начали переопределять метод PrintVar(), то возникла бы ошибка.
Также мы можем объявлять и классы с ключевым словом final, если будет необходимость запретить наследование от данного класса.
Полиморфизм. Полиморфизмом называется возможность применения методов с одинаковыми именами в группе классов, связанных наследованием.
Конструкторы и деструкторы
Конструктор. Мы уже немного знакомы с конструкторами. Давайте познакомимся с ними поближе. И так, конструкторы, в сущности являющиеся специальными методами, вызываются каждый раз при создании объекта класса, в котором определен конструктор. В классе может и не быть конструктора, а также может быть только один конструктор. Обычно конструктор используют для инициализации какого-либо поля класса, т.е. для задания первоначального состояния объекта, например, с помощью вызова некоторых методов, как правило, приватных. Если конструктор содержит параметры, то и объект должен быть вызван с соответствующими параметрами. Естественно, если мы объявим метод с параметрами, то и вызвать его должны с тем же количеством параметров, а, как уже говорилось выше, конструктор – специальный метод, который вызывается при создании объекта. Поэтому и объект мы должны создавать с параметрами. Давайте создадим объект для нашего класса GuestBook
<?
$guest_book = new GuestBook($name, $email, $msg);
?>
Если бы не было конструктора или конструктор не содержал бы параметров, то мы создавали бы объект так:
<?
$var = new SomeClass();
?>
Нам также известно из предыдущих примеров как вызывать конструкторы, объявленные в родительских классах – parent :: __construct();
Деструктор. Деструктор вызывается при уничтожении объекта. Деструктор не может содержать параметры. Как и конструктор, деструктор тоже специальный метод со специальным именем __destruct(). Обычно деструктор используется для очищения памяти от различного мусора: закрытие соединения с базой данных, закрытие файла, удаление ненужных переменных и т.д. Для вызова деструктора класса-предка, по аналогии с конструктором, - parent :: __destruct();
Моделирование на более высоком уровне абстракции. Абстрактные классы и интерфейсы.
Архитектор начинает свой проект с макета здания, с чертежа. Программист же начинает проект с абстрактной модели. Мы уже моделировали гостевую книгу, но на наименьшем уровне абстракции. Вспомним, что класс является абстрактным типом данных, поэтому абстракция имеет достаточно важное значение. Мы проектируем сущность, но мы еще не знаем или пока не хотим затрагивать реализацию, поэтому мы только делаем макеты методов, которые будут работать с данными. На первоначальном этапе нам надо только описать функциональность класса, оставляя детали реализации. Нам просто надо понять то, что мы хотим сделать над теми данными, которые мы включили в наш тип. Также мы заботимся о тех программистах, которые будут или разрабатывать логику методов, или использовать наши будущие методы, или как-то иначе использовать наш тип данных. Для организации вышесказанного на арену выходят абстрактные классы, их абстрактные методы, а также интерфейсы.
Если в классе объявлены абстрактные методы, то и класс тоже должен быть объявлен как абстрактный. Абстрактный класс может содержать и обычные методы и поля. Нельзя создавать объект абстрактного класса, но его нужно переопределять классами потомками. Т.е. абстрактные классы служат прототипами классов, которые наследуют их основу. Мы безоговорочно должны принять правила, диктуемые нам абстрактными классами. Если мы объявили абстрактный метод с двумя параметрами, то в последующих классах мы должны реализовать одноименный метод именно с тем же числом параметров.
Создадим абстрактный класс для класса GuestBookDb, а класс SharedGuestBookDb мы можем унаследовать как от абстрактного класса, так и от класса GuestBookDb.
<?
abstract class GbDb
{
private $db;
public function __construct($db)
{
$this->db = $db;
}
abstract public function Select();
abstract public function Insert($obj);
}
class GuestBookDb extends GbDb
{
public function Select()
{
// реализация
}
public function Insert($obj)
{
// реализация
}
}
class SgbDb extends GuestBookDb
{
public function Select()
{
// реализация
}
public function Insert($obj)
{
// реализация
}
}
?>
Абстрактные классы выгодны тогда, когда в них содержится менее абстрактная реализация, например, как в нашем примере. А если нам необходима только абстракция? И нужно унаследовать класс от нескольких абстрактных сущностей, мы же помним, что наследовать мы можем только от одного класса? И на сцену выходят интерфейсы. В интерфейсе можно объявлять без спецификаторов, в т.ч. и abstract. В интерфейсе можно определять только абстракцию действий, т.е. методы, которые могут быть только публичными. Действительно, мы же абстрагируем видимую реализацию. Нам надо показать не только себе, но и другим разработчикам работу нашего класса. Давайте рассмотрим интерфейс на практике. Немного изменим наш предыдущий код.
<?
interface GbDb
{
function Select();
function Insert($obj);
}
class GuestBookDb implements GbDb
{
public function Select()
{
// реализация
}
public function Insert($obj)
{
// реализация
}
}
class SgbDb extends GuestBookDb
{
public function Select()
{
// реализация
}
public function Insert($obj)
{
// реализация
}
}
?>
Мы можем наследовать сразу несколько интерфейсов. Усовершенствуем нашу гостевую, добавим методы обновления базы данных и выборки определенной записи по конкретному идентификационному номеру.
<?
interface GbDb
{
function Select();
function Insert($obj);
}
interface GbDbNew
{
function Update($id, $obj);
function SelectWithId($id);
}
class GuestBookDb implements GbDb, GbDbNew
{
public function Select()
{
// реализация
}
public function Insert($obj)
{
// реализация
}
public function Update($id, $obj)
{
// реализация
}
public function SelectWithId($id)
{
// реализация
}
}
class SgbDb extends GuestBookDb
{
public function Select()
{
// реализация
}
public function Insert($obj)
{
// реализация
}
public function Update($id, $obj)
{
// реализация
}
public function SelectWithId($id)
{
// реализация
}
}
?>
Мы можем наследовать одновременно как класс, так и интерфейс. При этом класс наследуется первым. Один интерфейс может наследовать другой интерфейс применяя ключевое слово extends.
Вот мы и рассмотрели некоторые особенности объектно-ориентированного подхода написания сценариев на языке РНР. Мы не затронули некоторые темы, например, статические элементы класса, обработку исключений. Когда вы научитесь проектировать классы и писать ОО код, то и эти темы будут вам более понятны.
URL:
https://visavi.net/articles/58