2. Модификация и создание пользовательских классов

We use cookies. Read the Privacy and Cookie Policy

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