Функциональное и объектно-ориентированное программирование для новичков

В качестве примера используем язык PHP в связи с низким порогом вхождения, но основные принцыпы в других языках такие же. В данной статье как всегда описан лишь мой личный опыт.

Функциональное программирование

В функциональном программировании как понятно из названия код пишется с помощью функций.

<?php
function printText($text){
	echo $text;
}

printText('hello world');
?>

Переменные определенные внутри функций доступны только внутри функции и не могут быть использованы в других функциях или за ее пределами.

<?php
function printText(){
	$text = "text1";
	echo $text;
}

function printText2(){
	$text = "text2";
	echo $text;
}

printText();
printText2();
?>

Такой код не приведет к конфликту имен переменных или к переопределению их значений. Отсюда возникает вопрос как тогда передавать значения переменных из одной функции в другую. В этом случае можно определить переменную за пределами функции и в каждой функции объявить ее как глобальную.

<?php
$text = "test text";

function printText(){
	global $text;
	echo $text;
}

printText();
?>

Также можно передавать значения переменных в виде аргументов.

<?php
$text = "test";
$text2 = "text";

function printText($word1, $word2){
	echo $word1." ".$word2;
}

printText($text, $text2);
?>

В этом примере в качестве аргументов функции printText передали переменные $text и $text2. Далее в определении функции мы указали, что ожидаем переменные $word1 и $word2, а внутри функции как раз их и использовали для вывода текста. При выводе текста между значениями $word1 и $word2 вставили пробел путем конкатенации. Получается, что имена переменных были переопределены, но значения сохранились.

Зачем нужны функции, если в них пишется все тоже самое, что можно записать в файле? Ответ кроется в многократности использования кода т.е. чтобы не писать однотипный код в каждом файле можно создать файл, который будет содержать функции, затем где надо инклудить этот файл и вызывать необходимую функцию передавая ей параметры (аргументы).

Например, создадим файл конфигурации, 2 тестовых файла и файл с функцией.

<?php
//config.php

define("HOSTED_DOMAIN", "example.com");
?>
<?php
//test.php

require_once('config.php');
require_once('func.php');

$email = "test@example.com";
if(verifyDomain($email, HOSTED_DOMAIN)){
	echo "Domain verified.";
}else{
	echo "Domain not verified!";
}
?>
<?php
//test2.php

require_once('config.php');
require_once('func.php');

$email = "test@mydomain.com";
if(verifyDomain($email, HOSTED_DOMAIN)){
	echo "Domain verified.";
}else{
	echo "Domain not verified!";
}
?>
<?php
//func.php

function verifyDomain($email, $domain){
	$emailDomain = explode('@',$email);
	if($domain == $emailDomain[1]){
		return true;
	}
	return false;
}
?>

В этом примере функция verifyDomain возвращает true, если домен определенный в константе HOSTED_DOMAIN совпадает с доменом электронной почты. В любом другом случае возвращает false. Это как раз таки пример многократного использования одного и того же кода в разных скриптах.

При вызове скрипта интерпретатор сперва считывает код и если все хорошо, то уже переводит скрипт в байт-код и передает виртуальной машине, который уже переводит байт-код в машинный код и скармливает процессору. Отсюда следует, что если в файле есть переменные с одинаковыми именами за пределами функций, то их значения будут переопределены (достоверным будет значение последней переменной), а если есть функции с одинаковыми именами, то это приведет к конфликту имен.

Объектно-ориентированное программирование

ООП предлагает использовать объекты вместо функций и инкапсуляцию т.е. прятать код внутри классов, которые затем можно разнести по разным файлам, а к их методам (тут функции называются методами) ограничивать доступ, если необходимо. Public разрашает обращаться к методам или переменным отовсюду. Protected позволяет обращаться внутри самого класса либо путем наследования. Private позволяет обращаться только внутри самого класса. Static позволяет обращаться не создавая объект (экземпляр класса). Если доступ не указан, то считается Public по-умолчанию.

По сути объект это переменная инициализирующая класс (создающая экземпляр класса), но при этом, если класс самодостаточный, то он может быть инициализирован без объекта.
Создадим класс и вызовем его метод через объект:

<?php
class Test {
	function method1($text){
		echo $text;
	}	
}
$obj = new Test();
$obj->method1('print this text');
?>

Теперь попробуем создать самодостаточный класс. Для этого будем использовать конструктор и деструктор класса. По сути это методы, которые отрабатывют при создании и уничтожении класса:

<?php
class Test {
	function __construct(){
		self::method1('this is constructor');
	}

	function method1($text){
		echo $text;
	}

	function __destruct(){
		self::method1('this is destructor');
	}
}
new Test();
?>

Предопределенная переменная $this используется, чтобы получить доступ к методам, которые находятся внутри самого класса. Если класс инициализируется без объекта, то можно тоже самое делать используя self т.е. первый пример можно было бы записать так:

<?php
class Test {
	function __construct(){
		$this->method1('print this text');
	}

	function method1($text){
		echo $text;
	}	
}
$obj = new Test();
?>

Как оказалось официальный источник немного соврал и в реальности $this можно использовать, если даже класс инициализируется без объекта.

Внутри классов можно создавать что-то вроде глобальных переменных, которые будут видны во всех методах класса, а также назначать им доступ как и методам:

<?
class Test {
	private $test;

	function __construct(){
		echo $this->method1();
		echo $this->method2();
	}

	protected function method1(){
		$this->test = "method1";
		return $this->test;
	}

	private function method2(){
		$this->test = "method2";
		return $this->test;
	}
}

class Test2 extends Test {
	function __construct(){
		echo $this->method1();
		echo $this->method2();
	}
}
new Test();
new Test2();
?>

Если задаете private или protected, то через объект уже нельзя к ним обращаться снаружи класса (к примеру "$obj = new Test(); $obj->method1();" приведет к ошибке).
Рассмотрим пример статического метода:

<?php
class Func {
	static function printText($text){
		echo $text;
	}
}

class Test {
	function method1(){
		Func::printText('test text');
	}
}

$obj = new Test();
$obj->method1();
?>

Как видно метод класса Func был вызван в методе класса Test без инициализации класса.

Константы можно создавать внутри класса, тогда область их видимости будет ограничена самим классом.

<?php
define("TEST1", "test1");

class Test {
	const TEST2 = "test2";

	function method1(){
		echo TEST1;
	}

	function method2(){
		echo self::TEST2;
	}
}
$obj = new Test();
$obj->method1();
$obj->method2();
?>

Если попытаетесь вызвать константу TEST2 в другом классе, то получится ошибка, когда как TEST1 можно вызывать во всех классах.

Теперь посмотрим что будет, если у разных классов будут методы с одинаковым названием:

<?php
class Test {
	function blabla(){
		echo "blabla";
	}
}

class Test2 {
	function blabla(){
		echo "blabla";
	}
}

$obj = new Test();
$obj->blabla();
$obj2 = new Test2();
$obj2->blabla();
?>

А ничего не будет поскольку они в разных классах. В функциональном программировании это бы вызвало конфликт. Естественно задавать одинаковые названия классам не стоит.

UPDATE

Все что расписано в этом посте использовал при написании плагина для WordPress. Если интересно посмотреть на код, то на странице плагина можно посмотреть на бесплатную версию. Там можно увидеть UseCase статических методов и всякое такое. Код не шибко качественный, но вполне работоспособный.

Подобных плагинов достаточно много, но свой начал писать потому, что нужно было для корпоративного блога. В бесплатных плагинах проявлялись какие-то непонятные баги, а платные были сильно дорогими для молодой развивающейся компании. Это и сподвигло начать писать свой велосипед, куда я мог бы еще добавить те фичи, которые не встречал в подобных плагинах и пригодились бы в работе. Пока, что плагин дает заработок только в виде надбавки к зарплате.

Вывод

Функциональное (оно же процедурное) программирование простой и эффективный способ писать код, но до тех пор пока проект не разрастется до определенного размера. Когда проект раздувается, то при таком подходе шансы на возникновение ошибок очень велики. ООП тут привносит более расширенные возможности, но вместе с этим наоборот усложняет код, что приводит к новым ошибкам связанным с усложнением кода. Новичку данной дозы информации думаю хватит. В будущем возможно напишу еще о интерфейсах, абстракциях и состояниях, но это если будет настроение.