Буратино (о полиморфизме):
— Тебя как зовут?
— Мальвина!
— Мы не в сауне! В жизни тебя как зовут?!
— Марина...
Думаю, все мы так или иначе слышали об объектно-ориентированном программировании. Эта заметка для тех, кто за 15 минут хочет понять, что это такое. На простом примере я продемонстрирую понятия класс, объект, конструктор, деструктор, инкапсуляция, и даже скажу пару слов про такие сложные вещи, как наследование и полиморфизм.
Для экспериментов мы будем использовать интерпретируемый язык программирования Python, который можно скачать отсюда (я использовал версию 2.5.4).
После установки Питона создайте пустой файл test.py, нажмите на него правой кнопкой мыши (будем предполагать, что вы используете Windows), и выберите пункт меню «Edit with IDLE». Откроются два окна. Окно «Python Shell» можете закрыть, а в оставшееся окно вставьте следующий текст, и сохраните файл:
Листинг 1. Реализация класса girl на языке программирования Python
# -*- coding: cp1251 -*-
class girl(object):
methods = {'и':'у','й':'ю','ти':'щу','ей':'ью','ри':'рю',
'ери':'еру','ети':'ечу','ись':'усь','йся':'юсь',
'оди':'ожу','рай':'раю','вись':'вюсь','тись':'чусь'}
def __init__(self, name = 'Наташа'):
print 'Привет, меня зовут', name
self.name = name
def __del__(self):
print 'Прощай'
def __getattr__(self, m):
for l in xrange( len(m) ):
try:
print m[:l] + girl.methods[ m[l:] ]
return
except KeyError: pass
if m[0] != '_': print 'Я не умею это делать'
Эта программа — описание класса girl (девушка). Класс — это некоторая абстрактная сущность, которая описывает всё то общее, что будут иметь объекты этого класса. Например, существуют девушки Маша, Наташа, Света, и другие, но при определённых обстоятельствах вам совершенно не важно знать имя девушки и другие её свойства: достаточно того, что этот объект — девушка, и она может делать всё то, что могут делать девушки. Это и есть основная идея объектно-ориентированного программирования.
Прежде чем мы подробно разберём описание класса, давайте посмотрим, как он работает. Нажмите F5 и в появившемся окне интерпретатора введите следующую команду после символов >>>:
>>> g = girl()
Привет, меня зовут Наташа
Мы только что создали объект класса girl, который теперь хранится в переменной g (точнее, переменная g с ним связана). Этот объект ещё называют экземпляром класса. Объект имеет свойства и методы. Свойства — эта та информация, которая хранится в объекте. Методы — это те действия, которые может выполнять объект (при выполнении методов обычно используются и модифицируются свойства объекта). Обычно все объекты одного класса обладают одними и теми же наборами методов и свойств, но различными значениями этих свойств.
Конструктор — тот метод, который автоматически выполняется в момент создания объекта. В данном случае девушка в момент создания поприветствовала нас и назвала своё имя. В Python конструктор имеет имя init.
В объектно-ориентированных языках программирования названия свойств и методов обычно записываются через точку после того объекта, к которому они относятся. Посмотрим, какие методы имеет наша девушка:
>>> g.подходи
подхожу
>>> g.раздевайся
раздеваюсь
>>> g.соси
сосу
>>> g.ложись
ложусь
>>> g.раздвигай
раздвигаю
>>> g.переворачивайся
переворачиваюсь
>>> g.одевайся
одеваюсь
Неплохой набор методов для класса girl! Кажется, что наша девушка может делать всё, что угодно, но на самом деле это не так:
>>> g.упячка
Я не умею это делать
После того, как девушка нам больше не нужна, мы можем удалить её:
>>> del g
Прощай
То, что мы видим, называется деструктором — это тот метод, который автоматически выполняется при удалении объекта. В Python деструктор имеет имя del.
Здесь есть небольшая тонкость: объект на самом деле удаляется не по команде del, а чуть позже: во время сборки мусора. Командой del мы кагбэ говорим, что объект g нам больше не нужен. А сборщик мусора удалит объект тогда, когда он больше не будет нужен нигде.
Проверим это. Допустим, у вас с другом одна девушка на двоих, однако вы общаетесь с ней через разные переменные (p и j):
>>> p = girl()
Привет, меня зовут Наташа
>>> j = p
>>> j.тяни
тяну
>>> p.толкай
толкаю
>>> del j
>>> del p
Прощай
Обратите внимание, что сборщик мусора удалил девушку только тогда, когда она не стала больше нужна ни как p, ни как j.
Иногда бывает так, что сборщик мусора не может удалить ненужных девушек. Рассмотрим ситуацию, когда у вас есть две девушки, и они вдруг полюбили друг друга:
>>> g1 = girl('Маша')
Привет, меня зовут Маша
>>> g2 = girl('Марина')
Привет, меня зовут Марина
>>> g1.танцуй
танцую
>>> g2.раздевайся
раздеваюсь
>>> g1.love = g2
>>> g2.love = g1
>>> del g1
>>> del g2
>>>
Девушки не удалились, хотя ни одна из них вам больше не нужна (вы выполнили для обеих команду del). Проблема здесь в том, что девушки нужны друг другу, поэтому сборщик мусора не удалил ни одну из них (это называется кольцевой зависимостью). После выполнения команд del у вас больше нет переменных g1 и g2, поэтому вы более не имеете никакой возможности связаться с девушками. Однако, они всё ещё занимают оперативную память. Если девушки вам нужны часто, то из-за подобных ситуаций па́ры (или даже тройки) девушек рано или поздно займут всё свободное пространство, и программа аварийно завершится. По этой же причине современные сложные программы (например, операционные системы) приходится иногда перезагружать, чтобы восстановить их работоспособность.
Теперь поговорим об инкапсуляции. Этот термин означает, что вы общаетесь с девушкой как с целостной сущностью, и вам не важно, как она устроена внутри. Её данные скрыты от вас. Создадим двух девушек:
>>> g1 = girl('Маша')
Привет, меня зовут Маша
>>> g2 = girl('Наташа')
Привет, меня зовут Наташа
>>> g1.пей
пью
>>> g2.пей
пью
Вроде бы обе девушки совершенно одинаковы: можно вызывать для обеих одни и те же методы. И всё же это различные девушки. У них есть свойства (в нашем случае — имена). К сожалению, мы не можем узнать эти имена без нарушения инкапсуляции, ибо девушки не содержат методов для того, чтобы сообщить своё имя. Поэтому обратимся к именам напрямую, без ведома девушек:
>>> print g1.name
Маша
>>> print g2.name
Наташа
>>>
Когда вы храните девушек или передаёте их для обработки в какую-нибудь функцию, вам не надо беспокоиться, и хранить вместе с девушками их имена или передавать имена вместе с ними: имена хранятся внутри девушек, в целости и сохранности.
Наследование применяется для того, чтобы создать более конкретный класс из более общего. Рассмотрим пример. Допустим, нам нужна кухарка. Кухарка — это тоже девушка, только помимо всего остального она умеет готовить. Так и напишем (добавьте эти строки в ваш файл test.py и сохраните):
Листинг 2. Реализация класса cook на языке программирования Python
class cook(girl):
methods = girl.methods
methods['готовь'] = 'Кушайте, пожалуйста!'
Теперь нажмите F5 и испытайте кухарку:
>>> c = cook()
Привет, меня зовут Наташа
>>> c.готовь
Кушайте, пожалуйста!
Обратите внимание, что, как и любая девушка, кухарка поприветствовала нас при создании. Наследование хорошо тем, что вы всегда можете использовать кухарку, как девушку:
>>> c.готовь
Кушайте, пожалуйста!
>>> c.раздевайся
раздеваюсь
Смысл этого страшного слова поясню на примере. Допустим, у вас есть некоторый класс gril, и вы не знаете, откуда он взялся. Вы создаёте объект этого класса и видите, что он всем похож на привычный нам класс girl, поэтому вы его используете соответственно:
>>> g = gril()
Привет, меня зовут Эльвира
>>> g.подходи
подхожу
>>> g.соси
сосу
Но, если вы нарушаете инкапсуляцию, происходит нечто странное:
>>> print g.name
Вася
Впрочем, какая разница, если объект делает всё то, что вам от него нужно...