В первой части мы рассмотрели основные синтаксические конструкции языка программирования Ruby. Вторая часть была посвящена итераторам и основным принципам объектно-ориентированного программирования. В этой части мы поговорим об объектно-ориентированном программировании более детально.
Метод -- это некоторая функция, описывающая реакцию объекта на поступивший запрос. Взгляните на пример вызова метода, приведенный ниже:
print "asdfgh".length ^D 6
Из примера видно, что для строкового объекта вызывается метод с именем length'.
Немного усложним:
foo = "abc" print foo.length,"\n" foo = [1, 2] print foo.length,"\n" ^D 3 2
Из этого примера видно, что решение о том какой метод вызвать принимается во время исполнения и, что выбор этот зависит от содержимого переменной. (в первом случае переменная foo представляется как строковая переменная, во втором -- как массив прим. перев.).
У читателя может возникнуть вопрос: "Как же так? Ведь длина строки и длина массива считаются по разному?". Не стоит беспокоиться. К счастью, Ruby автоматически выбирает соответствующий ситуации метод. Эта особенность в объектно-ориентированном программировании называется полиморфизмом.
Нет никакой необходимости знать о том как работает тот или иной метод, но каждый должен знать о том какие методы имеются у объекта. При вызове неизвестного объекту метода, возникает ошибка. Например, попробуйте вызвать метод "length"для объекта "foo", присвоив ему предварительно значение "5".
Я уже упоминал о специальной псевдопеременной self. Она определяет объект, чей метод был вызван. Однако указание этой переменной очень часто опускается, например:
self.method_name(arguments...)
может быть сокращено до
method_name(arguments...)
Эти два вызова идентичны друг другу, просто второй способ является сокращенным вариантом первого.
Реальный мир состоит из объектов, которые могут быть классифицированы. Например, годовалый малыш, увидев собаку, думает о ней как "гав-гав". В терминах объектно-ориентированного программирования, "гав-гав" -- это класс, а объект, принадлежащий классу -- экземпляр класса.
В Ruby, как и в других объектно-ориентированных языках программирования, сперва определяется класс, чтобы описать поведение объекта, а затем создается экземпляр класса -- отдельный объект. А теперь давайте определим класс.
class Dog def bark print "Гав гав\n" end end ^D
Описание класса должно начинаться с ключевого слова class и заканчиваться ключевым словом end. Ключевое слово def в данном контексте начинает определение метода класса.
Итак, мы описали класс с именем Dog', а теперь создадим объект.
tommy = Dog.new tommy.bark ^D Гав гав
Этот код создает новый экземпляр класса Dog и связывает его с переменной tommy. Метод new' любого класса создает новый экземпляр этого класса. Теперь переменная tommy обладает свойствами класса Dog и может "прогавкать" (метод bark').
Вы когда нибудь задавались вопросом -- как разные люди классифицируют объекты? Например, как люди видят собаку? Математик может описать собаку как комбинацию чисел и геометрических фигур. Физик -- как результат работы естественных и искусственных сил. Моя сестра (биолог) может представить собаку как разновидность отряда псовых домашних (canine domesticus). Для нее собака -- это разновидность псовых, псовые -- разновидность млекопитающих, а млекопитающие -- всегда животные.
Отсюда видно, что классификация объектов имеет форму иерархии, хотя и не всегда. Посмотрим, как это можно реализовать в Ruby.
class Animal def breath print "Вдох и выдох\n" end end class Cat<Animal def bark print "мяу\n" end end tama = Cat.new tama.breath tama.bark ^D Вдох и выдох мяу
Из примера видно, что класс Cat не имеет определения метода breath (рус. - "дыхание", прим. перев.), но этот метод наследуется от родительского класса Animal. И имеет дополнительный специфичный метод bark' (рус. - "лаять", здесь следует читать как "звук, издаваемый животным", прим. перев.).
Известно, что свойства родительского класса не всегда наследуются потомками. Например, пингвины не могут летать, хотя и являются птицами. Однако пингвины несут в себе многие другие черты, присущие птицам, например, они откладывают яйца. Подобные случаи могут быть реализованы и в Ruby, однако оставляю читателям возможность разобраться с этим самим.
При создании нового класса, наследующего черты родительского класса, нам необходимо будет только определить методы, описывающие различия между потомком и родителем. Это одно из достоинств объектно-ориентированного программирования, которое иногда называют "дифференциальным программированием".
Мы уже наблюдали различия в поведении экземпляров дочерних классов после переопределения методов родительского класса. Посмотрите на пример ниже:
class Human def print_id print "Я - человек.\n" end def train_toll(age) print "Детский билет.\n" if age < 12 end end Human.new.print_id class Student1<Human def print_id print "Я - студент.\n" end end Student1.new.print_id class Student2<Human def print_id super print "И студент.\n" end end Student2.new.print_id ^D Я - человек. Я - студент. Я - человек. И студент.
В дочернем классе, переопределяющем метод родительского класса, можно вызвать оригинальный метод родителя с помощью ключевого слова super'. Попробуйте запустить этот код:
class Student3<Human def train_toll(age) super(11) # принудительно снижаем возраст end end Student3.new.train_toll(25) ^D Детский билет.
Надеюсь, этих простых примеров достаточно для того, чтобы понять принципы наследования и переопределения методов.
Возможность вызова метода может быть ограничена. Для функции (определяемой на верхнем уровне) смотрите ниже:
def sqr(x) x * x end print sqr(5) ^D 25Если определение функции находится за пределами описания класса, то она добавляется как метод класса Object. Класс Object является базовым для всех остальных классов -- в Ruby все классы являются потомками класса Object. Таким образом, метод sqr' может вызываться из других классов.
Теперь, когда любой класс может вызвать метод sqr', давайте попробуем вызвать его с помощью псевдопеременной self':
print self.sqr(5) ^D ERR: private method sqr' called for (Object)
Как показано выше, вызов функции с помощью self' приводит к выдаче сообщения об ошибке. Это сообщение недостаточно понятно (интуитивно), что оно означает?
Суть в том, что функции, определенные за пределами какого либо класса должны вызываться как простые функции, а не как методы. Посмотрите какое сообщение об ошибке получается при вызове не определенного метода.
Методы, определенные как простые функции, должны вызываться как простые функции, подобно функциям C++, даже в пределах класса.
Область видимости метода может быть ограничена ключевыми словами "public" и "private", где public -- определяет общедоступные методы класса, а private -- скрытые, которые могут быть вызваны только из других методов класса.
class Test def bar print "bar -< " foo end private def foo print "foo\n" end end temp = Test.new temp.bar temp.foo ^D bar -< foo ERR: private method foo' called for (Test)
Из этого примера все должно быть понятно.
Поведение экземпляра определяется его принадлежностью к тому или иному классу, однако порой возникает необходимость в наделении объекта некоторой, свойственной только ему, индивидуальностью. В большинстве языков программирования вам придется создавать для этого отдельный класс. Ruby же позволяет добавить специфичный метод к конкретному экземпляру (объекту) без лишних телодвижений.
class SingletonTest def size print "25\n" end end t1=SingletonTest.new t2=SingletonTest.new def t2.size print "10\n" end t1.size t2.size ^D 25 10
Где t1 и t2 -- принадлежат одному и тому же классу и тем не менее, для экземпляра t2 переопределяется метод size', обеспечивая его индивидуальность. Такой специфичный метод называется единичный метод (singleton method).
В качестве примера применения единичного метода можно привести кнопки в окне программы с графическим интерфейсом, где каждая кнопка должна выполнять свои действия по нажатии. Мы можем переопределить метод, обрабатывающий нажатие у каждой из имеющихся кнопок.
Модули в Ruby очень похожи на классы, но используются для группировки родственных классов в одном месте. Вот три основных отличия модулей от классов:
Грубо говоря, существуют две основные причины использования модулей. Первая -- собрать методы и константы в одном месте. Например:
print Math::PI,"\n" print Math.sqrt(2),"\n" ^D 3.141592654 1.414213562
Оператор ::' указывает на то, что константа определена в соответствующем модуле или классе. Чтобы обращаться к методам или константам напрямую, следует использовать директиву include'
include Math print sqrt(2),"\n" print PI,"\n" ^D 1.414213562 3.141592654
Другая причина -- "смешение" (mixin') модулей. Смысл этого термина достаточно сложен для понимания, поэтому остановимся на нем подробнее.
Некоторые объектно-ориентированные языки допускают наследование от нескольких родительских классов, такая возможность называется множественным наследованием. Ruby преднамеренно лишен этой возможности. Вместо этого он допускает смешение (mixin) с модулем.
Как сказано выше, модуль во многом похож на класс. Методы или константы в модуле не могут наследоваться, но могут быть добавлены в другие модули или классы с помощью директивы include. Таким образом, подключение модуля к определению, добавляет (подмешивает) свойства модуля к свойствам класса или модуля.
Смешение модулей можно наблюдать в стандартной библиотеке, где в результате "смешения" модулей с классом, имеющим метод each', возвращающий каждый элемент, последний получает в свое распоряжение методы sort', find' и т.п..
Между множественным наследованием и "смешением" существуют следующие отличия:
Эти различия запрещают сложные взаимоотношения между классами, простота ставится во главу угла. По этой причине Ruby не поддерживает множественного наследования. В языках, поддерживающих множественное наследование, вполне может возникнуть ситуация, когда классы имеют несколько предков, а взаимоотношения между экземплярами классов создают запутанную сеть... Ситуация слишком сложна для восприятия человеческим мозгом, по крайней мере -- моим...
С другой стороны, смешение представляет все это, просто как "коллекцию специфических свойств из всего, что было добавлено".
Даже в языках с множественным наследованием, считается, что лучше расширять классы с использованием смешения, нежели разрабатывать сложные отношения наследования. В Ruby эта идея была продвинута дальше, заменив собой идею множественного наследования.
Представьте себе, что вы пишите программу, которая обрабатывает некоторые сигналы. Знакомые с этим поймут всю прелесть передачи процедуры в виде аргумента методу (который занимается обработкой сигналов).
Процедурные объекты создаются с помощью встроенного метода proc. Исполняемый код записывается внутри фигурных скобок. Вызов на исполнение производится методом call процедурного объекта. Смотрите ниже:
obj = proc{print "Hello world\n"} obj.call ^D Hello world
C-программисты обнаружат сходство между процедурными объектами и указателями на функции.
Успехов в труде...
Я -- студент последнего курса Правительственного Колледжа Компьютерных
Наук в городе Трикур (Trichur), Индия. Кроме Linux, я с большим удовольствием
занимаюсь изучением физики.
Команда переводчиков:
Владимир Меренков, Александр Михайлов, Иван
Песин, Сергей Скороходов, Александр Саввин, Роман Шумихин, Александр Куприн,
Андрей Киселев
Со всеми предложениями, идеями и комментариями обращайтесь к Александру Куприну ([email protected]). Убедительная просьба: указывайте сразу, не возражаете ли Вы против публикации Ваших отзывов в рассылке.
Сайт рассылки: http://gazette.linux.ru.net
Эту статью
можно взять здесь: http://gazette.linux.ru.net/lg86/ramankutty.html
http://subscribe.ru/ E-mail: [email protected] |
Отписаться Убрать рекламу |