2. Модификация и создание пользовательских классов
2. Модификация и создание пользовательских классов
Ruby является объектно-ориентированным языком программирования. Давайте рассмотрим особенности представления классов.
Объекты и классы
В реальной жизни все объекты обладают индивидуальными свойствами. Но, введя некоторые обобщения, можно поделить объекты реальной жизни на группы. Каждая из таких групп будет обладать одинаковым набором свойств и, как следствие, к ней будут применимы одинаковые наборы действий.
classString
def reverse
"?????"
end
def msiu
"МГИУ"
end
end
a="12345"
putsa.reverse
putsa.msiu
В языке программирования Ruby для описания набора свойств групп объектов и применимых к этим объектам действий используется такое понятие, как классы.
Класс – это формальное описание основных свойств объекта (его атрибутов) и методов, применимых к нему Задав описание класса, в дальнейшем можно создавать столько его экземпляров, сколько потребуется.
Классы в Ruby – открыты; если нам не хватает функциональности класса, то мы можем дополнить класс новым методом или переопределить старый.
Иерархия классов и полиморфизм
Зачастую из класса можно выделить группу объектов, обладающую каким-либо специальным свойством. Такую группу называют подклассом, или дочерним классом. Дочерний класс наследует все свойства родительского класса, но обладает отдельной функциональностью. С помощью символа < указывается родительский класс.
Пример 1.
Птицы всех видов несут яйца, но не все птицы умеют летать. Хотя в классе Penguin и не описан метод lay_egg, при необходимости он ищется (и находится!) в родительском классе (принцип наследования, иерархическая организация классов). Метод fly переопределен для пингвинов, и реакция на вызов этого метода определяется принадлежностью к тому или
Модификация и создание пользовательских классов иному классу (полиморфизм, различные реалиции на одинаковую команду).
class Bird def lay_egg
puts "Я из класса #{self.class}. Яйцо снесено!" end
def fly
puts "Все представители класса #{self.class} умеют летать!"
end
end
class Penguin < Bird def fly
puts "Представители класса #{self.class} не летают…" end end
b = Bird.new; b.lay_egg; b.fly p = Penguin.new; p.lay_egg; p.fly
Создание класса
Дальнейшее описание проиллюстрируем обыкновенными дробями. В процессе вычислений нередко возникает необходимость работы с дробными числами. Учитывая ограничения по точности представления чисел с плавающей точкой в компьютере, иногда бывает необходимо производить вычисления в терминах обыкновенных дробей, представляющих собой отношение целого числителя и целого знаменателя. В Ruby имеется класс Rational, описывающий такие дроби. Представим на время, что его нет и создадим нечто подобное.
classFrac
definitialize(a,b)
raise’Divisionbyzero’ifb.to_i == 0
@numerator,@denominator = a.to_i, b.to_i
end
end
fr=Frac.new(1,2)
Метод initialize называется конструктором класса. Он вызывается каждый раз, когда мы создаем экземпляр класса при помощи метода new.
Конструкция raise вызывает исключительную ситуацию (об этом будет рассказано позже) и выдает соответствующее сообщение об ошибке.
Переменные экземпляра
Переменные enumerator, @denominator – это переменные атрибуты экземпляра класса. В именах переменных экземпляра необходимо использовать префикс @. Каждый объект, принадлежащий данному классу, имеет свои собственные значения этих атрибутов (свойства), но пока их можно использовать только внутри методов самого класса. При попытке обратиться к таким переменным извне класса будет выдано сообщение об ошибке. Что же делать, если хочется иметь доступ к переменным экземпляра вне класса?
Один способ состоит в создании методов, возвращающих значение соответствующего атрибута. Но в случае большого числа атрибутов такой подход не удобен. Язык Ruby предлагает более удобную возможность.
Для контроля доступа к переменным экземпляра можно использовать макросы attr_reader (для чтения), attr_writer (для изменения значения) и attr_accessor (для выдачи разрешения на оба действия).
Метод to_s, присутствующий в классе, определяет, как объект должен отображаться при печати.
Переопределение операторов
Оперирование дробями предполагает возможность производить математические действия, например, сложение. Символ + является оператором языка Ruby. Но некоторые операторы для удобства пользователя могут быть переопределены. Но не все! Например, + может быть переопределен, = нет.
Реализуем операцию сложения дробей самостоятельно. Чтобы дроби не получались избыточными, нам надо будет уметь их сокращать. Это позволяет сделать алгоритм Евклида.
Модификация и создание пользовательских классов
class Frac
def initialize(a, b)
raise ’Division by zero’ if b.to_i == 0
@numerator,@denominator = a.to_i, b.to_i
simplify()
end
end
fr=Frac.new(1,2)
def +(b)
if b.class != Frac
raise "Undefined method + for class Frac and #{b.class}!"
end
return Frac.new(self.numerator * b.denominator + b.numerator * self.denominator,
self.denominator * b.denominator)
end
private
def simplify()
x, y = @numerator.abs, @denominator.abs x, y = y, x % y while x * y > 0
m = x + y
@numerator, @denominator = @numerator / m, @denominator / m
end
end
В операторе сложения мы проверяем, что второй аргумент операции + тоже является объектом типа Frac.
Для каждого объекта в Ruby можно применить метод class, чтобы выяснить, к какому классу он относится. Более правильно проверять, что объект относится к тому или иному классу, при помощи метода kind_of?.
Ключевое слово self указывает на объект, вызвавший метод. Запись self.numerator эквивалентна enumerator.
Метод simplify реализует алгоритм Евклида, упрощающий дроби. Чтобы у нас всегда были упрощенные дроби, он вызывается в конструкторе класса. Ключевое слово private определяет право доступа к следующим за ним методам только внутри класса. Таким образом, метод simplify не может быть использован вне класса Frac.
Аналогично + мы можем также переопределять и логические операторы. Например, оператор ==, позволяющий сравнивать два объекта на равество.
class Frac def ==(b)
if !b.kind_of?(Frac)
raise "Undefined method == for class Frac and #{b.class}!" end
return (self.numerator == b.numerator and self.denominator == b.denominator)
end
end
Если нам хочется, чтобы объекты нашего класса можно было бы сравнивать между собой (и, как следствие, сортирововать массив таких объектов методом sort), то необходимо переопределить метод <=>. Метод <=> должен возвращать 0 если объекты равны между собой; отрицательное число, если первый аргумент меньше второго; и положительное число в остальных случаях.
class Frac def <=>(b)
if !b.kind_of?(Frac)
raise "Undefined method <=> for class Frac and #{b.class}!" end
return self.numerator * b.denominator -
b. numerator * self.denominator <=> 0
end
end
Переменные класса
В случае необходимости иметь какую-то одинаковую для всех объектов класса характеристику используют переменные класса, которые синтаксически выделяются указанием префикса @@ в их имени.
Предположим, что нам потребовалось знать общее количество дробей, созданных при работе с классом Frac. Добавив в конструктор класса команду инкремента такой переменной, мы сможем узнать количество созданных дробей.
Методы класса
С понятием применения метода связано понятие ответственности за его выполнение. Иногда за то или иное действие отвечает не объект, а сам класс. В этом случае применяется метод класса. При его задании перед именем метода указывается имя класса.
Пример 2. Рассмотрим класс R@Point, описывающий точки на плоскости. Пусть нам требуется найти расстояние между двумя точками. В первом случае мы перекладываем ответственность за выполнение метода на одну из точек. Требование выглядит так: «точка, вычисли расстояние до другой точки». В этом случае непонятно, почему одна из точек берет на себя ответственность за выполнение этой обязанности, ведь они обе участвуют в данной операции на равных правах. Второй подход состоит в команде, отдаваемой самому классу: «класс, определи расстояние между двумя объектами».