Что такое полиморфизм. Реализация полиморфизма

Объектно-ориентированное программирование (ООП) – подход к созданию программ, основанный на использовании классов и объектов, взаимодействующих между собой.

Класс (java class) описывает устройство и поведение объектов. Устройство описывается через набор характеристик (свойств), а поведение – через набор доступных для объектов операций (методов). Классы можно создавать на основе уже имеющихся, добавляя или переопределяя свойства и методы.

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

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

Наследование является неотъемлемой частью Java. При использовании наследования принимается во внимание, что новый класс, наследующий свойства базового (родительского) класса имеет все те свойства, которым обладает родитель. В коде используется операнд extends , после которого указывается имя базового класса. Тем самым открывается доступ ко всем полям и методам базового класса.

Используя наследование, можно создать общий "java class", который определяет характеристики, общие для набора связанных элементов. Затем можно наследоваться от него и создать дополнительные классы, для которых определить дополнительные уникальные для них характеристики.

Главный наследуемый класс в Java называют суперклассом super . Наследующий класс называют подклассом . Таким образом подкласс - это специализированная версия суперкласса, которая наследует все свойства суперкласса и добавляет свои собственные уникальные элементы.

Рассмотрим пример описания java class"a студента Student, который имеет имя, фамилию, возраст, и номер группы. Класс студента будем создавать на основе super класса пользователя User, у которого уже определены имя, фамилия и возраст:

Public class User { int age; String firstName; String lastName; // Конструктор public User(int age, String firstName, String lastName) { this.age = age; this.firstName = firstName; this.lastName = lastName; } }

Теперь создаем отдельный класс Student, наследующего свойства super класса. При наследовании класса необходимо также переопределить и конструкторы родительского класса:

Public class Student extends User { int group; // Конструктор public Student(int age, String firstName, String lastName) { super(age, firstName, lastName); } boolean isMyGroup(int g) { return g == group; } }

Ключевое слово extends показывает, что мы наследуемся от класса User.

Ключевое слово super

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

Рассмотрим как происходит наследование с точки зрения создания объекта:

Student student = new Student(18, "Киса", "Воробьянинов", 221);

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

У суперкласса могут быть несколько перегруженных версий конструкторов, поэтому можно вызывать метод super() с разными параметрами. Программа выполнит тот конструктор, который соответствует указанным аргументам.

Вторая форма ключевого слова super действует подобно ключевому слову this , только при этом мы всегда ссылаемся на суперкласс подкласса, в котором она использована. Общая форма имеет следующий вид:

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

Class A { int i; } // наследуемся от класса A class B extends A { int i; // имя переменной совпадает и скрывает переменную i в классе A B(int a, int b) { super.i = a; // обращаемся к переменной i из класса A i = b; // обращаемся к переменной i из класса B } void show() { System.out.println("i из суперкласса равно " + super.i); System.out.println("i в подклассе равно " + i); } } class MainActivity { B subClass = new B(1, 2); subClass.show(); }

В результате в консоли мы должны увидеть:

I из суперкласса равно 1 i в подклассе равно 2

Переопределение методов, Override

Если в иерархии классов имя и сигнатура типа метода подкласса совпадает с атрибутами метода суперкласса, то метод подкласса переопределяет метод суперкласса. Когда переопределённый метод вызывается из своего подкласса, он всегда будет ссылаться на версию этого метода, определённую подклассом. А версия метода из суперкласса будет скрыта.

Если нужно получить доступ к версии переопределённого метода, определённого в суперклассе, то необходимо использовать ключевое слово super .

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

В Java SE5 появилась анотация @Override; . Если необходимо переопределить метод, то используйте @Override, и компилятор выдаст сообщение об ошибке, если вместо переопределения будет случайно выполнена перегрузка.

В Java можно наследоваться только от одного класса.

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

В информатике инкапсуляцией (лат. en capsula) называется упаковка данных и/или функций в единый объект.

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

Модификаторы доступа

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

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

Желательно использовать доступ к свойствам класса только через его методы (принцип bean классов, "POJO"), который позволяет валидировать значения полей, так как прямое обращение к свойствам отслеживать крайне сложно, а значит им могут присваиваться некорректные значения на этапе выполнения программы. Такой принцип относится к управлению инкапсулированными данными и позволяет быстро изменить способ хранения данных. Если данные станут храниться не в памяти, а в файлах или базе данных, то потребуется изменить лишь ряд методов одного класса, а не вводить эту функциональность во все части системы.

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

Пример простого описания робота

Public class Robot { private double x = 0; // Текущая координата X private double y = 0; // Текущая координата Y private double course = 0; // Текущий курс (в градусах) public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } public double getCourse() { return course; } // Определение курса public void setCourse(double course) { this.course = course; } // Передвижение на дистанцию distance public void forward(int distance) { // Обращение к полю объекта X x = x + distance * Math.cos(course / 180 * Math.PI); // Обращение к полю объекта Y y = y + distance * Math.sin(course / 180 * Math.PI); } // Печать координат робота public void printCoordinates() { System.out.println(x + "," + y); } }

В представленном примере робота используются наборы методов, начинающие с set и get . Эту пару методов часто называют сеттер/геттер. Данные методы используются для доступа к полям объекта. Наименования метода заканчиваются наименованием поля, начинающееся с ПРОПИСНОЙ буквы.

В методах set мы передаем значение через формальный параметр во внутрь процедуры. В коде процедуры мы присваиваем значение переменной объекта/класса с использованием ключевого слова this .

This.course = course ...

Использование ключевого слова this необходимо, т.к. наименование формального параметра совпадает с наименованием переменной объекта. Если бы наименования отличались бы, то можно было бы this не использавать.

Полиморфизм, polymorphism

Полиморфизм является одним из фундаментальных понятий в объектно-ориентированном программировании наряду с наследованием и инкапсуляцией. Слово полиморфизм греческого происхождения и означает "имеющий много форм". Чтобы понять, что означает полиморфизм применительно к объектно-ориентированному программированию, рассмотрим пример создания векторного графического редактора, в котором необходимо использовать ряд классов в виде набора графических примитивов - Square , Line , Circle , Triangle , и т.д. У каждого из этих классов необходимо определить метод draw для отображения соответствующего примитива на экране.

Очевидно, придется написать некоторый код, который для изображения рисунка будет последовательно перебирать все примитивы, которые необходимо вывести на экран, и вызывать метод draw у каждого из них.

Человек, незнакомый с полиморфизмом, вероятнее всего создаст несколько массивов: отдельный массив для каждого типа примитивов и напишет код, который последовательно переберет элементы из каждого массива и вызовет у каждого элемента метод draw. В результате получится примерно следующий код:

// Определение массивов графических примитивов Square s = new Square ; Line l = new Line ; Circle c = new Circle ; Triangle t = new Triangle; // Заполнение всех массивов соответствующими объектами. . . // Цикл с перебором всех ячеек массива. for (int i = 0; i < s.length; i++){ // вызов метода draw() в случае, если ячейка не пустая. if (s[i] != null) s.draw(); } for(int i = 0; i < l.length; i++){ if (l[i] != null) l.draw(); } for(int i = 0; i < c.length; i++){ if (c[i] != null) c.draw(); } for(int i = 0; i < t.length; i++){ if (t[i] != null) t.draw(); }

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

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

Public class Shape { public void draw() { System.out.println("Заглушка"); } }

После этого мы создаем различные классы-наследники: Square (Квадрат), Line (Линия), Сircle (круг) и Triangle (Треугольник):

Public class Point extends Shape { public void draw() { System.out.println("Квадрат"); } } public class Line extends Shape { public void draw() { System.out.println("Линия"); } } public class Сircle extends Shape { public void draw() { System.out.println("Круг"); } } public class Triangle extends Shape { public void draw() { System.out.println("Треугольник"); } }

В наследниках у нас переопределен метод draw. В результате получили иерархию классов, которая изображена на рисунке.

Теперь проверим удивительную возможность полиморфизма:

// Определение и инициализация массива Shape a = new Shape {new Shape(), new Triangle(), new Square(), new Сircle()}; // Перебор в цикле элементов массива for(int i = 0; i < a.length; i++) { a[i].draw(); }

В консоль будут выведены следующие строки:

Заглушка Треугольник Квадрат Круг

Таким образом каждый класс-наследник вызвал именно свой метод draw, вместо того, чтобы вызвать метод draw из родительского класса Shape.

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

Перегрузка метода, overload

В процедурном программировании тоже существует понятие полиморфизма, которое отличается от рассмотренного механизма в ООП. Процедурный полиморфизм предполагает возможность создания нескольких процедур или функций с одинаковым именем, но разными количеством или типами передаваемых параметров. Такие одноименные функции называются перегруженными, а само явление - перегрузкой (overload). Перегрузка функций существует и в ООП и называется перегрузкой методов. Примером использования перегрузки методов в языке Java может служить класс PrintWriter , который используется в частности для вывода сообщений на консоль. Этот класс имеет множество методов println, которые различаются типами и/или количеством входных параметров. Вот лишь несколько из них:

Void println() // переход на новую строку void println(boolean x) // выводит значение булевской переменной (true или false) void println(String x) // выводит строку - значение текстового параметра

s_a_p 20 августа 2008 в 19:09

Полиморфизм для начинающих

  • PHP

Полиморфизм — одна из трех основных парадигм ООП. Если говорить кратко, полиморфизм — это способность обьекта использовать методы производного класса, который не существует на момент создания базового. Для тех, кто не особо сведущ в ООП, это, наверно, звучит сложно. Поэтому рассмотрим применение полиморфизма на примере.

Постановка задачи

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

Самые простые варианты, которые приходят в голову — написать три отдельных класса и работать с ними. Или написать один класс, в которым будут все свойства, присущие всем трем типам публикаций, а задействоваться будут только нужные. Но ведь для разных типов аналогичные по логике методы должны работать по-разному. Делать несколько однотипных методов для разных типов (get_news, get_announcements, get_articles) — это уже совсем неграмотно. Тут нам и поможет полиморфизм.

Абстрактный класс

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

abstract class Publication
{
// таблица, в которой хранятся данные по элементу
protected $table ;

// свойства элемента нам неизвестны
protected $properties = array();

// конструктор

{
// обратите внимание, мы не знаем, из какой таблицы нам нужно получить данные
$result = mysql_query ("SELECT * FROM `" . $this -> table . "` WHERE `id`="" . $id . "" LIMIT 1" );
// какие мы получили данные, мы тоже не знаем
$this -> properties = mysql_fetch_assoc ($result );
}

// метод, одинаковый для любого типа публикаций, возвращает значение свойства
public function get_property ($name )
{
if (isset($this -> properties [ $name ]))
return $this -> properties [ $name ];

Return false ;
}

// метод, одинаковый для любого типа публикаций, устанавливает значение свойства
public function set_property ($name , $value )
{
if (!isset($this -> properties [ $name ]))
return false ;

$this -> properties [ $name ] = $value ;

Return $value ;
}

// а этот метод должен напечатать публикацию, но мы не знаем, как именно это сделать, и потому объявляем его абстрактным
abstract public function do_print ();
}

Производные классы

Теперь можно перейти к созданию производных классов, которые и реализуют недостающую функциональность.

class News extends Publication
{
// конструктор класса новостей, производного от класса публикаций
public function __construct ($id )
{
// устанавливаем значение таблицы, в которой хранятся данные по новостям
$this -> table = "news_table" ;
parent :: __construct ($id );
}

Public function do_print ()
{
echo $this -> properties [ "title" ];
echo "

" ;
echo $this -> properties [ "text" ];
echo "
Источник: " . $this -> properties [ "source" ];
}
}

Class Announcement extends Publication
{
// конструктор класса объявлений, производного от класса публикаций
public function __construct ($id )
{
// устанавливаем значение таблицы, в которой хранятся данные по объявлениям
$this -> table = "announcements_table" ;
// вызываем конструктор родительского класса
parent :: __construct ($id );
}

// переопределяем абстрактный метод печати
public function do_print ()
{
echo $this -> properties [ "title" ];
echo "
Внимание! Объявление действительно до "
. $this -> properties [ "end_date" ];
echo "

" . $this -> properties [ "text" ];
}
}

Class Article extends Publication
{
// конструктор класса статей, производного от класса публикаций
public function __construct ($id )
{
// устанавливаем значение таблицы, в которой хранятся данные по статьям
$this -> table = "articles_table" ;
// вызываем конструктор родительского класса
parent :: __construct ($id );
}

// переопределяем абстрактный метод печати
public function do_print ()
{
echo $this -> properties [ "title" ];
echo "

" ;
echo $this -> properties [ "text" ];
echo "
" . $this -> properties [ "author" ];
}
}

Теперь об использовании

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

// наполняем массив публикаций объектами, производными от Publication
$publications = new News ($news_id );
$publications = new Announcement ($announcement_id );
$publications = new Article ($article_id );

Foreach ($publications as $publication ) {
// если мы работаем с наследниками Publication
if ($publication instanceof Publication ) {
// то печатаем данные
$publication -> do_print ();
} else {
// исключение или обработка ошибки
}
}

Вот и все. Легким движением руки брюки превращаются в элегантные шорты:-).

Основная выгода полиморфизма — легкость, с которой можно создавать новые классы, «ведущие себя» аналогично родственным, что, в свою очередь, позволяет достигнуть расширяемости и модифицируемости. В статье показан всего лишь примитивный пример, но даже в нем видно, насколько использование абстракций может облегчить разработку. Мы можем работать с новостями точно так, как с объявлениями или статьями, при этом нам даже не обязательно знать, с чем именно мы работаем! В реальных, намного более сложных приложениях, эта выгода еще ощутимей.

Немного теории

  • Методы, которые требуют переопределения, называются абстрактными. Логично, что если класс содержит хотя бы один абстрактный метод, то он тоже является абстрактным.
  • Очевидно, что обьект абстрактного класса невозможно создать, иначе он не был бы абстрактным.
  • Производный класс имеет свойства и методы, принадлежащие базовому классу, и, кроме того, может иметь собственные методы и свойства.
  • Метод, переопределяемый в производном классе, называется виртуальным. В базовом абстрактном классе об этом методе нет никакой информации.
  • Суть абстрагирования в том, чтобы определять метод в том месте, где есть наиболее полная информация о том, как он должен работать.
UPD: по поводу sql-inj и нарушения MVC — господа, это просто пример, причем пример по полиморфизму, в котором я не считаю нужным уделять значения этим вещам. Это тема для совсем других статей.

Лекция в виде презентации в формате pdf с примерами - 27 слайдов.
ВолгГТУ, кафедра ПОАС, - 2010 год

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

Фрагменты из лекции

Понятие полиморфизма

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

Преимущества использования полиморфизма

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

Параметризованный полиморфизм

  • Обеспечивается за счет так называемых обобщенных функций, которые в языке Си++ называются шаблонами
  • Аргументом обобщенной функции является тип, который используется при ее параметризации
  • С помощью механизма шаблонов можно создать функцию, которая бы работала с разнотипными аргументами
  • Примером таких функций являются обобщенные алгоритмы из STL

Чистый полиморфизм

  • Чистый полиморфизм имеет место, когда одна и та же функция применяется к аргументам различных типов
  • В случае чистого полиморфизма имеется одна функция (тело кода) и несколько ее интерпретаций
  • Реализация чистого полиморфизма возможна только при наличии полиморфных переменных, а точнее полиморфных аргументов
  • Чистый полиморфизм позволяет реализовывать обобщенные алгоритмы
Перегрузка или полиморфизм ad hoc
  • Перегрузка возникает, когда имеется два или более кода, связанных с одним именем
  • Главное назначение перегрузки − сужение концептуального пространства
Перегрузка методов в несвязанных классах
  • Все ОО-языки разрешают использовать методы с одинаковыми именами в несвязанных между собою классах − это перегрузка методов
  • В этом случае привязка перегруженного имени производится за счет информации о классе, к которому относится получатель сообщения

Параметрическая перегрузка

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

Class MyEllipse
{
public:
float area() const

};


{
public:
float area() const
{//использ. более эффективный алгоритм расчета
}
};

MyEllipse ellipse;
MyCircle circle;

// Будет вызван метод MyEllipse::area()
ellipse.print();


// ВНИМАНИЕ!!! Будет вызван метод MyEllipse::area()
circle.print();


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

Class MyEllipse
{
public:
virtual float area() const
{ /* численный метод расчета */ }
void print() { printf("area = %f\n", area()); }
};
class MyCircle: public MyEllipse
{
public:
float area() const
{ //использ. более эффективн. алгоритм расчета
return 3.14*Radius1*Radius2;
}
};

MyEllipse ellipse;
MyCircle circle;

// Будет вызван метод MyEllipse::area()
printf("Ellipse area= %f\n", ellipse.area());

// Будет вызван метод MyEllipse::area()
ellipse.print();

// Будет вызван метод MyCircle::area()
printf("Circle area= %f\n", circle.area());

// ВНИМАНИЕ!!! Будет вызван метод MyCircle::area()
circle.print();

Назначение механизма переопределения методов
  • Наличие механизма переопределения методов позволяет реализовать в базовом классе общую часть поведения, подразумевая, что отдельные действия будут доопределены (переопределены) в производных классах
  • Таким образом, главное назначение механизма переопределения методов - сокращение объема программы

Отложенные методы

  • Отложенный метод − это частный случай переопределения, когда метод базового класса не имеет реализации, а любая полезная деятельность задается в методе дочернего класса
Отложенные методы в языке Си++
  • В языке Си++ отложенный метод должен быть описан в явном виде с ключевым словом virtual
  • Тело отложенного метода не определяется, вместо этого функции «приписывается» значение 0

PHP, ООП, Инкапсуляция, Наследование, Полиморфизм

В этом уроке я расскажу Вам о трех основных понятиях Объектно-Ориентированного Программирования: об Инкапсуляции, Наследовании, Полиморфизме; И научу Вас применять их в разработке.

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

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

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

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

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

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

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

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

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

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

Это значит, что в классах Квадрат и Треугольник нам не придется повторно задавать поля цвета и площади. Достаточно указать, что упомянутые классы наследуются от класса Фигура.

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

А теперь давайте представим, что в нашем будущем блоге статьи могут иметь различные типы. Для начала остановимся на двух типах: обычная статья-заметка и новостная статья. Для новостной статьи важна дата ее публикации, ведь она несет в себе некоторую конкретную новость.

Чтобы реализовать эту ситуацию в PHP, нам потребуется определить два класса: класс для обычной статьи-заметки и класс для новостной статьи. При этом пусть новостная статья расширяет возможности обычной статьи, т. е. наследуется от нее. Для наследования классов в PHP используется ключевое слово extends .

Class { ... // содержимое класса } class { ... // содержимое класса }

Приведенный код определяет класс NewsArticle как наследника Article . Класс Article в свою очередь является родительским для класса NewsArticle . Это значит, что поля и методы класса Article будут также присутствовать в классе NewsArticle , и заново их определять не нужно.

С помощью наследования можно выстраивать целую иерархию классов, наследуя один от другого. В то же время у любого класса может быть только один родитель:


Иногда у нас может появиться необходимость переопределить один из методов родительского класса. Давайте еще раз приведем реализацию класса Article :

Class Article { ... // поля класса // Функция для вывода статьи function view() { echo "

$this->title

$this->content

"; } }

Предположим, что вывод новостной статьи должен отличаться от представления обычной статьи, и мы должны дополнительно выводить время публикации новости. При этом в классе Article уже существует метод view() , отвечающий за вывод статьи. Можно поступить двумя способами. В первом случае можно придумать новый метод в классе NewsArticle , например, с именем viewNews() специально для вывода новости. Однако правильнее использовать одинаковые методы для выполнения схожих действий в наследуемых классах. Поэтому будет лучше, если метод для вывода новости в классе NewsArticle будет называться так же, как и в родительском классе - view() . Для реализации такой логики в PHP существует возможность переопределять родительские методы, т. е. задавать в дочерних классах методыс названиями, совпадающими в родительских классах. Реализация этих методов в родительских классах в таком случае становится неактуальной для класса-потомка. Давайте приведем пример класса NewsArticle с переопределенным методом view() :

Class NewsArticle extends Article { $datetime; // дата публикации новости // Функция для вывода статьи function view() { echo "

$this->title

". strftime("%d.%m.%y", $this->datetime). " Новость

$this->content

"; } }

В приведенном коде используется функция strftime() , которая позволяет выводить даты в удобном виде. Для лучшего понимания кода ознакомьтесь со спецификацией этой функции в справочнике. Для нас же сейчас важно, чтобы вы обратили внимание на то, что класс NewsArticle , как и Article , определяет метод view() . Соответственно, все объекты этого класса будут использовать метод view() , объявленный в классе NewsArticle , а не в Article .

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

Полиморфизм

Полиморфизм - взаимозаменяемость объектов с одинаковым интерфейсом.

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

Давайте рассмотрим следующий пример, который дает представление о сути полиморфизма :

Class A { function Test() { echo "Это класс A
"; } function Call() { $this->Test(); } } class B extends A { function Test() { echo "Это класс B
"; } } $a = new A(); $b = new B(); $a->Call(); // выводит: "Это класс A" $b->Test(); // выводит: "Это класс B" $b->

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

$a->Call(); // выводит: "Это класс A"

В этой строке происходит вызов метода Call() у объекта класса А . Как и определено в функции Call() класса A , происходит вызов метода Test() . Отрабатывает метод Test() у объекта класса A , и на экран выводится текст "Это класс А ".

$b->Test(); // выводит: "Это класс B"

В данной строке происходит вызов метода Test() у объекта класса B . Метод Test() класса B выводит на экран текст "Это класс В ".

$b->Call(); // выводит: "Это класс B"

Наконец, в последней строке происходит вызов класса Call() у объекта класса В . Но в реализации класса B мы не увидим такого метода, а это значит, что он наследуется от класса A , т. к. класс B - это потомок класса A . Что же мы видим в реализации метода Call() класса A ? Следующий код:

$this->Test();

Метод Call() вызывает метод Test() того объекта, в котором находится. Это значит, что отработает метод Test() объекта класса B . Именно этим объясняется результат, выведенный на экране.

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

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

Полиморфизм (программирование)

Кратко смысл полиморфизма можно выразить фразой: «Один интерфейс , множество реализаций».

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

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

  • внешняя общность проявляется как одинаковый набор методов с одинаковыми именами и сигнатурами (именами методов, типами аргументов и их количеством);
  • внутренняя общность - одинаковая функциональность методов. Её можно описать интуитивно или выразить в виде строгих законов, правил, которым должны подчиняться методы. Возможность приписывать разную функциональность одному методу (функции, операции) называется перегрузкой метода (перегрузкой функций , перегрузкой операций ).

Примеры

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

В функциональных языках

В Haskell существует два вида полиморфизма - параметрический (чистый) и специальный, (на основе классов ). Специальный называют еще ad hoc (от лат. ad hoc - специально). Их можно отличить следующим образом:

Параметрический полиморфизм

Специальный полиморфизм

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

Программистам, переходящим от ООП к ФП , следует знать важное отличие их системы классов. Если в ООП класс «привязан» к объекту, т. е. к данным, то в ФП - к функции. В ФП сведения о принадлежности к классу передаются при вызове функции, а не хранятся в полях объекта. Такой подход, в частности, позволяет решить проблему метода нескольких объектов (в ООП метод вызывается у одного объекта). Пример: метод сложения (чисел, строк) требует двух аргументов, причем одного типа.

Неявная типизация

В некоторых языках программирования (например, в Python и Ruby) применяется так называемая утиная типизация (другие названия: латентная, неявная), которая представляет собой разновидность сигнатурного полиморфизма. Таким образом, например, в языке Python полиморфизм не обязательно связан с наследованием.

Формы полиморфизма

Статический и динамический полиморфизм

(упоминается в классической книге Саттера и Александреску, которая является источником).

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

В одном случае конкретный смысл фрагмента зависит от того, в каком окружении код был построен. Это т.н. статический полиморфизм. Перегрузка функций, шаблоны в Си++ реализуют именно статический полиморфизм. Если в коде шаблонного класса вызвана, например, std::sort, то реальный смысл вызова зависит от того, для каких именно типовых параметров будет развернут данный шаблон - вызовется одна из std::sort.

В другом случае конкретный смысл фрагмента определяется только на этапе исполнения и зависит от того, как именно и где именно был построен данный объект. Это обычный, динамический полиморфизм, реализуется через виртуальные методы.

Полиморфизм включения

Этот полиморфизм называют чистым полиморфизмом. Применяя такую форму полиморфизма, родственные объекты можно использовать обобщенно. С помощью замещения и полиморфизма включения можно написать один метод для работы со всеми типами объектов TPerson. Используя полиморфизм включения и замещения можно работать с любым объектом, который проходит тест «is-A». Полиморфизм включения упрощает работу по добавлению к программе новых подтипов, так как не нужно добавлять конкретный метод для каждого нового типа, можно использовать уже существующий, только изменив в нем поведение системы. С помощью полиморфизма можно повторно использовать базовый класс; использовать любого потомка или методы, которые использует базовый класс.

Параметрический полиморфизм

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

Параметрические методы

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

Параметрические типы

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

Полиморфизм переопределения

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

Полиморфизм-перегрузка

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