Программирование на языке Ruby, часть 3
Автор: Hiran Ramankutty
Перевод: Андрей Киселев


Методы

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

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)

Из этого примера все должно быть понятно.

Единичные (Singleton) методы

Поведение экземпляра определяется его принадлежностью к тому или иному классу, однако порой возникает необходимость в наделении объекта некоторой, свойственной только ему, индивидуальностью. В большинстве языков программирования вам придется создавать для этого отдельный класс. 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 очень похожи на классы, но используются для группировки родственных классов в одном месте. Вот три основных отличия модулей от классов:

  1. Модули не имеют экземпляров модулей.
  2. Модули не могут иметь дочерние модули.
  3. Модули определяются конструкцией module ... end.

Грубо говоря, существуют две основные причины использования модулей. Первая -- собрать методы и константы в одном месте. Например:

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-программисты обнаружат сходство между процедурными объектами и указателями на функции.

Заключение

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

Успехов в труде...

Hiran Ramankutty

Я -- студент последнего курса Правительственного Колледжа Компьютерных Наук в городе Трикур (Trichur), Индия. Кроме Linux, я с большим удовольствием занимаюсь изучением физики.


Copyright (C) 2003, Hiran Ramankutty.

Hosted by uCoz