Введение в объектно ориентированное программирование

Для кого написана эта статья? Она написана для разработчиков, имеющих опыт написания кода для реальных задач. Дело в том, что многие из Вас уже используют ООП, почти не понимая его основных принципов. Особенно, если Вы веб-разработчик и пишете в основном на PHP или JavaScript, но у Вас возникают проблемы при чтении документации к популярным библиотекам, возникают проблемы структурирования кода при написании крупных проектов, этот материал будет очень Вам полезен.

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

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

Эти понятия принято называть "Парадигмами". Т.е. это некий набор правил и механизмов, каких-то фундаментальных принципов, которые мы используем для решения задач, в нашем случае, написания программ.

Процедурное программирование.

Парадигма процедурного программирования предполагает использование набора последовательности операторов. Для повторного использования кода и решения типовых задач последовательности операторов можно объединить в подпрограммы (функции, процедуры).

Процедурными языками можно назвать: SQL, PHP, C, C++, Javascript, Ruby, Bash, Perl, Pascal, Basic и другие. Но, забегая вперед, стоит отметить, что многие из этих языков являются комбинированными, то есть могут использовать и процедуры и объекты. Но вернемся на минутку в процедурной парадигме.

Главным отличием процедур от функций является то, что процедуры, выполняя последовательность действий не возвращают значения, их результатом скорее всего будет изменение каких-либо данных или вывод на экран. Функции, в свою очередь, всегда возвращают какое-нибудь значение.

Пример процедуры на языке JavaScript


// Результатом выполнения данной процедуры будет появление на экране сообщения,
// которое передали в аргументе message
// Процедура просто выводит сообщение, и не возвращает никаких значений
function saySomething(message) {
  alert( message );
}

// Вызов процедуры (подпрограммы)
saySomething("Hello World!!!");

Пример функции на языке JavaScript


// Данная функция вычисляет площадь прямоугольника со сторонами side_a и side_b
// и возвращает полученное значение
function getSquare(side_a, side_b) {
  return side_a * side_b;
}

// Последовательность операторов
var side_a = 10;
var side_b = 20;

var square = getSquare(side_a, side_b);
// Выведет "Площадь равна: 200 ед2."
alert("Площадь равна: " + square + " ед2.");

Иными словами можно сказать, что процедуры выполняют какие-либо действия, а функции вычисляют или порождают значения.

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

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

В парадигме ООП главными составляющими являются Классы и Объекты.

Классами называются комплексные типы данных, объединяющие в себе наборы полей и методов.

Объектами являются экземпляры классов. В ООП каждый объект является экземпляром какого-либо класса.

Представьте себе мобильный телефон. Это какое-то устройство с кнопками и дисплеем, по которому можно звонить. Вот это и есть Класс. У телефона могут быть свойства (поля) такие как название, модель, цвет, производитель, диапазон частот и т.д. И могут быть методы - действия, которые можно производить с телефоном: позвонить, прочитать смс, ответить на звонок и т.д. То есть класс это, я повторюсь, просто описание какого-либо существующего предмета или даже явления.

Объектом же или экземпляром класса является конкретный телефон, например черный iPhone 6 32gb. Мы уж точно знаем его модель, цвет объем памяти, и, кроме того, можем даже взять его в руку и позвонить кому-нибудь. То есть объект это уже какой-то конкретный осязаемый предмет.

Вот iPhone является экземпляром класса Мобильный Телефон. У 11-й нокии дела с экраном обстоят намного хуже, но она тоже является экземпляром класса Мобильный телефон, потому что по ней, как по iPhone можно звонить, чаще всего даже там, где с iPhone звонить уже не получается. Видите, характеристики объектов могут быть самыми разными, но объекты одного и того же класса всегда одинаковы по своему функциональному назначению. Бейте меня хоть всемером, я все равно скажу, что и Нокиа и iPhоne это два мобильных телефона, тут ничего не перепутать.

Ну что, немного кода? Давайте опишем наши трубки на PHP, чтобы понять, как пользоваться парадигмой.



// Описание класса
class MobilePhone {
 
  // Объявляем поля модель и цвет
  public $model;

  // Можно задавать значения по умолчанию 
  // 
  public $color = "black";

  // У класса может быть конструктор, который должен вызываться при создании объекта
  // В конструктор передаются аргументы. Например для задания свойств, без которых 
  // Объект не может существовать. Наш телефон не может существовать без модели, 
  // ее обязательно нужно задать
  public function __construct($model) {
    // В php для доступа к свойствам и методам внутри класса используется переменная $this
    $this->model = $model;
  }

  public function callTo($number) {
     print "Calling to {$number}";
  }

  public function sendSms($number, $message) {
    print "Sending text \"{$message}\" to {$number}";
  }
}

// Создадим Nokia
$nokia = new MobilePhone("Nokia 1100");
// Позвоним по Nokia
$nokia->callTo("+79180000000");

// Создадим iPhone
$iPhone = new MobilePhone("iPhone 6");
// Зададим iPhone цвет
$iPhone->color = "White";
// Отправим СМС
$iPhone->sendSms("+79180000000","Привет, как дела?");

Комбинированные языки все равно используют процедурную парадигму при структуризации программы. Например в JavaScript, Ruby или PHP, так или иначе, программа начинается с каких-то операторов, создания объектов и т.д. Вот пример на PHP, у меня есть Класс Application, который содержит весь функциональный код, но мне все равно нужно выполнить несколько операторов в процедурном стиле.


<?php

// Вручную создается экземпляр класса Applcation 
$application = new Application();

// Вызывается метод, который выполняет всю необходимую работу
$application->doSomething();

// Я могу еще просто вывести что-нибудь на экран с помощью процедуры print
print("Application started");


Конечно большинство приложений на PHP вообще полностью процедурные, а объекты здесь используются для доступа к определенным модуля, как и в javascript. Но, если Вы сталкивались с Zend Framework или Symfony, могли заметить, что там как раз используется преимущественно ООП.

А в полностью объектно-ориентированных языках, таких как Java или C#, сначала создается главный класс приложения, у которого есть главный метод, который вызывается самым первым. Здесь вне классов никакой код существовать не может.


package team.dinamika.oops;

// Объявляется класс
public class Application {

  // Объявляется главный метод
  public static void main(String[] args) {
     // Здесь следует набор операторов
     // И у нас нет никакой процедуры, чтобы что-то вывести на экран
     // Но есть объект System со свойством out, это поток вывода, 
     // мы можем туда отправить наш текст
     System.out.print("Application started");
  }
}

Видите? Я не могу писать никакой функциональный код вне класса. При выполнении программы будет вызван главный метод (main) моего класса Application.

Вот таким образом с точки зрения парадигмы комбинированные языки и отличаются от Объектно-ориентированных.

Три принципа объектно-ориентированного программирования.

Из мультика "Три Богатыря"

Три кита, на которых свет стоит. Три богатыря. Это важнейшие принципы, которые раскрывают всю сущность парадигмы ООП. Это Инкапсуляция, Наследование и Полиморфизм.

Инкапсуляция

Это способность объекта содержать внутри себя (инкапсулировать) поля и методы.

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

Мы можем дать товарищу наш объект - мобильный телефон, и он просто наберет номер и позвонит. Либо дать ему трубку, провода и рассказать, как связаться с телефонисткой. "Алло, Барышня! Соедините с усадьбой Галицыных!". Прошлый век.

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

Наследование

Наследованием называется способность классов порождать потомков, которым передаются все поля и методы (которые не скрыты) родительского объекта.

Иными словами, на базе телефона Nokia 1100 можно сделать более функциональный телефон, воспользовавшись его модулем связи.

То же самое происходит с объектами, на базе одних классов можно создавать другие. Создадим телефон с mp3-плейером на базе 11-й Nokia.


// Вот класс Nokia1100, он уже умеет звонить и писать смс
class Nokia1100 {
 
  public $color = "black";

  // Конструктор объявлен без параметров, можно его даже не объявлять
  public function __construct() {
  }

  public function callTo($number) {
     print "Calling to {$number}";
  }

  public function sendSms($number, $message) {
    print "Sending text \"{$message}\" to {$number}";
  }
}

// А вот NokiaMp3, который умеет играть музыку
// С помощью инструкции extends мы наследуемся от класса Nokia1100 и теперь 
// у класса NokiaMp3 есть все те же методы и поля что и у Nokia1100 и свой новый метод playMp3
class NokiaMp3 extends Nokia1100 {

   public function playMp3($song) {
     // Код с проигрыванием Mp3
   }   
}

Вот так работает наследование.

Полиморфизм

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

Ну представьте себе браузеры Opera и Chrome. Оба браузера отображают веб-страницы, за исключением того, что Opera использует свои прокси-сервера, через которые в сжатом виде в наш браузер попадает контент, именно таким образом Opera добивается экономии трафика.

И Chrome и Opera являются браузерами. Они даже визуально для пользователя почти не отличаются. Оба они отображают веб-страницы, но внутри они делают это по-разному.

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

Еще пример. Печать документов в операционных системах осуществляется через службу печати. В распоряжении системы есть несколько объектов, делающих одно и то же, выполняющих печать. Цветной принтер, лазерный принтер и печать в файл PDF. Для операционной системы нет никакой разницы, кому из них отдать документ на печать, разница есть для пользователя, он поручит нужному объекту распечатать документ. Вот что такое полиморфизм. И вот что такое родственные объекты.

Vasiliy Vanin - Systems Architect
Василий Ванин
Системный архитектор