Программирование, с которым мы уже достаточно продолжительное время знакомимся, сегодня перейдет для нас на новый уровень.
Мы осваивали типы данных, как скалярные, так и структурные, составляли условия, программировали циклы, писали функции и пробовали передавать функции в функции как параметры. Все это было для нас просто программированием, хотя иногда и звучали заумные слова "структурное программирование", "функциональщина", "мультипарадигменность". Сегодня мы попробуем подвести под это все теоретическую основу, и затем освоить новые, соседние высоты.
Существует понятие парадигмы программирования. Парадигма - совокупность подходов, идей, понятий, даже принципов, которые определяют, как писать код. Удобный синоним - стиль, хотя понятие парадигмы несколько шире.
Зачастую парадигма или парадигмы, которые использует программист, определяются языком, проектом и существующим кодом на этом проекте, парадигмой, которой придерживается компания или конкретная команда проекта. Многие языки являются мультипарадигменными, т.е. поддерживают не одну, а несколько популярных парадигм, т.е. позволяют писать в разных парадигмах-стилях.
В различных источниках можно встретить множество названий парадигм, различные, и иногда даже противоречащие друг другу системы классификаций этих парадигм. Здесь мы перечислим самые распространенные из них и попытаемся сформулировать особенности каждой.
Существуют следующие парадигмы программирования:
- Императивная
- Структурная
- Модульная
- Объектно ориентированная
- Декларативная
- Функциональная
- Реактивная и многие многие другие менее известные.
Попробуем в этом многообразии разобраться.
Две основные парадигмы, на которые опираются многие другие парадигмы - это императивная и декларативная парадигмы.
Императивная парадигма программирования - это стиль написания кода, подразумевающий подробное описание алгоритма решения задачи и получения искомых данных. Императивный стиль использует много переменных в процессе своей работы и часто сохраняет в них промежуточные результаты вычислений. Императивный стиль является основным и самым распространенным стилем программирования.
В императивном стиле:
- описывают алгоритм решения поставленной задачи
- сохраняют состояния в переменных
- зачастую снабжают код комментариями, поскольку по самому коду бывает сложно понять его итоговую цель
Многие перечисленные парадигмы являются видом императивного подхода, попробуем проследить исторически, как они появлялись и какие вопросы решали.
В соответствии с существовавшей в 1940-х годах архитектурой ЭВМ (архитектура фон Неймана) в программировании использовался процедурных подход, процедурная парадигма программирвания.
Процедурная парадигма подразумевает написание алгоритмов программ пошагово и частое использование подпрограмм, называемых процедурами. Процедуры - практически синонимы функций, которые нам достаточно известны. Единственная разница их в том, что процедуры просто выполняют какие-то действия, ничего не возвращая, а функции могут и должны возвращать значения.
Процедурная парадигма допускает использование оператора goto, достоинства которого уже к шестидесятым годам представлялись спорными. В 1968 году Эдсгер Дейкстра пишет известную статью "О вреде оператора goto", дебаты усиливаются, и к 1970-м годам формулируют новую парадигму - структурную.
Структурная парадигма программирования запрещает использование оператора goto, настоятельно рекомендуя использовать другие подходы:
- последовательность (выполнение программы сверзу вниз),
- ветвление (при помощи условий);
- цикличность (при помощи операторов циклов).
В то же самое время развивалась модульная парадигма. По модульной парадигме, необходимо размещать самодостаточный код в отдельные модули (чаще всего, отдельные файлы), которые могут взаимодействовать между собой.

Декларативная парадигма программирования представляет собой стиль описания того. что вы хотите увидеть в итоге. Не написания алгоритма, как именно вы планируете получить желаемый результат, а подробное описание самого результата. Классические примеры языков с декларативным стилем - HTML, SQL. В декларативном стиле:
- не пишут, как решить задачу, но пишут, что требуется получить
- не сохраняют промежуточные состояния
- пишут так, чтобы по виду кода можно было понять его цель
К декларативным парадигмам часто относят функциональную, логическую и объектно-ориентированную парадигмы.
Основной отличительной чертой функционального программирования являются привелигированные права функций. Это означает, что с функциями можно работать так же, как с любыми другими данными, имеющимися в программе. Функции могут передаваться в качестве аргументов другим функциям, возвращаться в качестве результата или присваиваться переменным. Эта возможность рассматривать функции в качестве данных позволяет перейти на более высокий уровень абстракции и, следовательно, дает больше перспектив в плане многократного использования.(с) Алехандро Серано Мена
Второй важной особенностью функционального
программирования является активное использование понятия чистоты выражения. Чистым выражением называется выражение, не зависящее от внешних условий и не меняющее ничего вовне, реагирующее только на те данные, которые ему передают в работу, и меняющее соответственно только результат своей работы.

Функциональная парадигма программирования совмещает в себе оба подхода, поскольку может не сохранять состояния, довольно наглядна, но может включать в себя и алгоритм решения задачи, а не только описание результата.
И наконец парадигма программирования, "...о необходимости которой все время говорили большевики...", Объектно Ориентированная Парадигма Программрования.
Все подходы, упоминавшиеся до этого, работают так: есть переменные, в которых хранятся данные, и функции, которые с этими данными работают. С усложнением программ и проектов такие функции становятся либо более сложными, если программисты идут по пути их универсализации, либо, если программисты идут по пути упрощения, такие функции упрощаются, но их становится чрезмерно много.
Объектно-ориентированный подход позволяет объединить данные, с которыми работает программист, с методами обработки этих данных. Благодаря ряду принципов, которые на самом деле не принципы ООП, а принципы правильного построения ООП, данные комбинируются в новые типы данных, функции для их обработки называются удобно, а зачастую их названия совпадают, принципы их применения зачастую идентичны и взаимосвязанны. Но не будем забегать вперед, все по порядку.
Начнем с определения сущности.
Сущность - описательное понятие, обобщающее несколько конкретных реализаций этого понятия. Человек - это сущность, Сигизмунд Аристархович Нетудыхата - конкретный человек. Сущность описывает какие-либо свойства человека, но не конкретизирует их. К примеру, у человека есть цвет глаз, рост, вес, пол, возраст, имя, фамилия, но нигде не указано конкретно, какого роста или какого пола человек, потому что сущность описывает общее понятие.
Класс - программная реализация сущности, описание сущности на языке программирования. Класс также называют конструкцией для создания нового типа данных, поскольку на основе существующих данных классы позволяют создавать новые. Так же класс называют чертежом, описывающим конкретную реализацию, но не реализующим.
Объект - реализация описанного в классе, конкретный экземпляр описанного в классе.
Строка - это сущность, у строки есть длина, есть символы, из которых она состоит, можно ее померять, поменять, дописать что-то в конец или в начало, заменить элемент.
Класс в данном случае - тип данных строка, функции для работы со строкой, свойства строки, такие как длина, нулевой элемент, кодировка и так далее.
Объектом будет являться конкретная строка, например, "Добрый вечер". Эта строка - объект класса Строка, но у объекта уже можно получить конкретную длину, 12 символов, найти нулевой символ "Д".
Создадим новый тип данных для описания человека, Person. Для описания человека мы чаще всего используем его имя, возраст, пол. Обычно мы для обработки таких данных передаем их параметрами в функцию, в ООП же мы создадим тип данных Person, а описательные данные придадим классу в качестве свойств.
class Person {
public $name, $surname, $age, $gender;
}
...Далее следует создать обработчик для этих данных, обычная функция нам поможет:
class Person {
public $name, $surname, $age, $gender;
public function get_info(){
//...
}
}В коде выше мы переменные, которые ранее были просто разрозненными переменными, поместили в рамки класса Person, как и функцию-обработчик, которая ранее работала только с параметрами. Теперь эта функция является методом. У нее на данный момент нет параметров, это просто заготовка.
Person - это класс, программная реализация сущности, описание человека, с которым нам нужно работать, не конкретного человека, а вообще тех свойств человека, которые могут быть нам полезны, и методов для работы с этими свойствами.
Продолжим, создадим объект класса Person и выведем его полное имя, воспользовавшись написанной нами функцией get_info:
<?php
class Person {
public $age, $name, $surname, $gender;
function get_info($obj){
return $obj->name . ' ' . $obj->surname . "\n";
}
}
$sasha = new Person();
$sasha->age = 17;
$sasha->name = "Alexander";
$sasha->surname = "Svistoplyasov";
$sasha->gender = 1;
echo $sasha->get_info($sasha);В данном коде мы реализовали функцию, передав туда объект и вернув его конкатенированные свойства.
Переменные, которые используются в классе и описывают объект, называются свойствами объекта или просто свойствами. Можно также встретить в литературе термины свойств класса, или переменных - членов класса, но вернее называть их свойствамии объекта.
Функции, которые используются в классе, называются методами объекта или методами класса. И то и то отчасти верно. Выполняются эти функции по-отношению к свойствам объекта, обрабатывают объект, следовательно, это методы работы с объектами. С другой стороны, описываются они в классе.
Обратите внимание на запись и чтение свойств объекта и вызов функции его класса, все это выполняется через оператор "стрелочка".
На данный момент мы не воспользовались ни одним преимуществом ООП, просто перевели код в термины объектов и классов.
Мы передали в функцию класса тот объект, с которым этой функции необходимо работать, так, как мы всегда делали в структурном программировании. Но у нас ООП, и есть специальный инструмент для упрощения этой процедуры, $this.
Ключевым понятием ООП является понятие this.
This - это переменная, которая есть у каждого метода класса. В this попадает объект, для работы с которым вызывался метод. Когда мы у объекта $sasha вызываем метод get_fullname, в this метода get_fullname попадает объект $sasha.
Какой объект вызвал метод, к такому объекту и обращается программист в рамках этого метода, когда пишет this. Каждый раз при вызове метода объекта this заполняется этим объектом. Объект - ссылочный тип данных, то, что вы поменяете в свойствах this, поменяется и в самом объекте.
Изменим кода класс в соответствии с этим пониманием:
class Person {
public $age, $name, $surname, $gender;
function get_info(){
return $this->name . ' ' . $this->surname . "\n";
}
}Вызов метода у объекта, соответственно, не требует параметра:
echo $sasha->get_info();Для простоты понимания this рекомендуется считать его невидимым параметром каждого метода, который передается не аргументом метода, а слева от стрелочки.
ООП не имеет смысла, если просто оборачивать код в классы и объекты. Настоящее ООП подразумевает использование принципов ООП, которые правильнее понимать не как дополнения, а как важные правила, без комплексного соблюдения которых нет смысла в применении объектно-ориентированной парадигмы.
Наследование - принцип ООП, позволяющий одному классу приобрести все свойства и методы другого класса без их копирования. Реализуется посредством ключевого слова extends. На нашем примере у класса Person будет класс - наследник Student. Сущность Student является в то же время сущностью Person, но с дополнительными свойствами и методами, которые присущи только студентам и доступны только им, а просто объектам класса Person не присущи и не доступны.
<?php
class Person {
public $age, $name, $surname, $gender;
function get_info(){
return $this->name . ' ' . $this->surname . "\n";
}
}
class Student extends Person {
public $group;
public function set_group($group){
$this->group = $group;
}
public function get_group(){
return $this->group ? $this->group . "\n" : " unknown group" . "\n";
}
public function get_info(){
return parent::get_info(). " group: " . $this->get_group();
}
}
$sasha = new Person();
$sasha->age = 17;
$sasha->name = "Alexander";
$sasha->surname = "Svistoplyasov";
$sasha->gender = 1;
// echo $sasha->get_fullname();
$petya = new Student();
$petya->name = "Petro";
$petya->surname = "Peterkovich";
$petya->group = "PHP123";
echo $petya->get_info();Обратите внимание, в новом классе есть новые методы и новое свойство. В унаследованном классе можно как создавать новые методы так и менять существующие. Хорошим тоном считается улучшать методы, унаследованные от родителей, так, чтобы они могли выполнять то же, что и методы родителей, но иначе, иногда лучше, иногда просто по-другому.
Наследование в PHP возможно только одиночное, это значит что у любого класса может быть только один класс - родитель.
Терминология в данном случае проста. Родительский класс, класс-потомок или дочерний класс, дерево наследования.
Инкапсуляцией называется принцип ООП, позволяющий скрывать детали реализации некоторых действий (методов) и доступы к ряду свойств объектов или классов. В языке php самая классическая инкапсуляция.
Реализуется инкапсуляция за счет спецификаторов (другая версия - модификаторов) доступа.
Спецификаторы доступа регламентируют доступность к методам и свойствам классов и объектов.
Public означает, что метод можно запустить откуда угодно: из методов этого же класса, методов классов-потомков или извне дерева наследования.
Protected означает, что метод можно запустить только из дерева наследования: из методов этого же класса или методов классов-потомков.
Private означает, что метод можно запустить только из метода того же класса, а методы классов-наследников такой возможности не имеют.
Аналогично и со свойствами: публичное свойство доступно отовсюду, защищенное из методов дерева наследования, приватное - только из методов того же класса.
class Student extends Person {
protected $group;
public function set_group($group){
$this->group = $group;
}
public function get_group(){
return $this->group ? $this->group . "\n" : " unknown group" . "\n";
}
public function get_info(){
return parent::get_info(). " group: " . $this->get_group();
}
}
$petya = new Student();
$petya->name = "Petro";
$petya->surname = "Peterkovich";
$petya->group = "PHP123";
echo $petya->get_info();Приведенный выше код не работает, php выдает ошибку, т.к. записать извне дерева наследования данные в переменную group он не может.
Исправить это можно следующим образом: вместо
$petya->group = "PHP123";
нужно написать
$petya->set_group("PHP123");

