355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Хэл Фултон » Программирование на языке Ruby » Текст книги (страница 5)
Программирование на языке Ruby
  • Текст добавлен: 24 сентября 2016, 06:40

Текст книги "Программирование на языке Ruby"


Автор книги: Хэл Фултон



сообщить о нарушении

Текущая страница: 5 (всего у книги 56 страниц) [доступный отрывок для чтения: 20 страниц]

1.2.7. Исключения

Как и многие другие современные языки, Ruby поддерживает исключения.

Исключения – это механизм обработки ошибок, имеющий существенные преимущества по сравнения с прежними подходами. Нам удается избежать возврата кодов ошибок и запутанной логики их анализа, а код, который обнаруживает ошибку, можно отделить от кода, который ее обрабатывает (чаще всего они так или иначе разделены).

Предложение raise возбуждает исключение. Отметим, что raise – не зарезервированное слово, а метод модуля Kernel. (У него есть синоним fail.)

raise                                               # Пример 1

raise "Произошла ошибка"                            # Пример 2

raise ArgumentError                                 # Пример 3

raise ArgumentError, "Неверные данные"              # Пример 4

raise ArgumentError.new("Неверные данные ")         # Пример 5

raise ArgumentError, " Неверные данные ", caller[0] # Пример 6

В первом примере повторно возбуждается последнее встретившееся исключение. В примере 2 создается исключение RuntimeError (подразумеваемый тип), которому передается сообщение "Произошла ошибка".

В примере 3 возбуждается исключение типа ArgumentError, а в примере 4 такое же исключение, но с сообщением "Неверные данные". Пример 5 – просто другая запись примера 4. Наконец, в примере 6 еще добавляется трассировочная информация вида "filename:line" или "filename:line:in 'method'" (хранящаяся в массиве caller).

А как обрабатываются исключения в Ruby? Для этой цели служит блок begin-end. В простейшей форме внутри него нет ничего, кроме кода:

begin

 #Ничего полезного.

 #...

end

Просто перехватывать ошибки не очень осмысленно. Но у блока может быть один или несколько обработчиков rescue. Если произойдет ошибка в любой точке программы между begin и rescue, то управление сразу будет передано в подходящий обработчик rescue.

begin

 x = Math.sqrt(y/z)

 # ...

rescue ArgumentError

 puts "Ошибка при извлечении квадратного корня."

rescue ZeroDivisionError

 puts "Попытка деления на нуль."

end

Того же эффекта можно достичь следующим образом:

begin

 x = Math.sqrt(y/z)

 # ...

rescue => err

 puts err

end

Здесь в переменной err хранится объект-исключение; при выводе ее на печать объект будет преобразован в осмысленную символьную строку. Отметим, что коль скоро тип ошибки не указан, то этот обработчик rescue будет перехватывать все исключения, производные от класса StandardError. В конструкции rescue => variable можно перед символом => дополнительно указать тип ошибки.

Если типы ошибок указаны, то может случиться так, что тип реально возникшего исключения не совпадает ни с одним из них. На этот случай после всех обработчиков rescue разрешается поместить ветвь else.

begin

 # Код, в котором может возникнуть ошибка...

rescue Type1

 # ...

rescue Type2

 # ...

else

 #Прочие исключения...

end

Часто мы хотим каким-то образом восстановиться после ошибки. В этом поможет ключевое слово retry (внутри тела обработчика rescue). Оно позволяет повторно войти в блок begin и попытаться еще раз выполнить операцию:

begin

 # Код, в котором может возникнуть ошибка...

rescue

 # Пытаемся восстановиться...

 retry # Попробуем еще раз.

end

Наконец, иногда необходим код, который «подчищает» что-то после выполнения блока begin-end. В этом случае можно добавить часть ensure:

begin

 # Код, в котором может возникнуть ошибка...

rescue

 # Обработка исключений.

ensure

 # Этот код выполняется в любом случае.

end

Код, помещенный внутрь части ensure, выполняется при любом способе выхода из блока begin-end – вне зависимости от того, произошло исключение или нет.

Исключения можно перехватывать еще двумя способами. Во-первых, существует форма rescue в виде модификатора:

x = a/b rescue puts("Деление на нуль!")

Кроме того, тело определения метода представляет собой неявный блок begin-end; слово begin опущено, а все тело метода подготовлено к обработке исключения и завершается словом end:

def some_method

 # Код...

rescue

 # Восстановление после ошибки...

end

На этом мы завершаем как обсуждение обработки исключений, так и рассмотрение основ синтаксиса и семантики в целом.

У Ruby есть многочисленные аспекты, которых мы не коснулись. Оставшаяся часть главы посвящена более развитым возможностям языка, в том числе рассмотрению ряда практических приемов, которые помогут программисту среднего уровня научиться «думать на Ruby».

1.3. ООП в Ruby

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

Похожие идеи, только под иными именами, встречаются и в других объектно-ориентированных языках, но одно и то же название может скрывать тонкие различия. В этом разделе мы уточним, что в Ruby понимается под каждым из элементов ООП.

1.3.1. Объекты

В Ruby все числа, строки, массивы, регулярные выражения и многие другие сущности фактически являются объектами. Работа программы состоит в вызове методов разных объектов:

3.succ                # 4

"abc".upcase          # "ABC"

[2,1,5,3,4].sort      # [1,2,3,4,5]

someObject.someMethod # какой-то результат

В Ruby каждый объект представляет собой экземпляр какого-то класса. Класс содержит реализацию методов:

"abc".class       # String

"abc".class.class # Class

Помимо инкапсуляции собственных атрибутов и операций объект в Ruby имеет уникальный идентификатор:

"abc".object_id # 53744407

Этот идентификатор объекта обычно не представляет интереса для программиста.

1.3.2. Встроенные классы

Свыше 30 классов уже встроено в Ruby. Как и во многих других объектно-ориентированных языках, в нем не допускается множественное наследование, но это еще не означает, что язык стал менее выразительным. Современные языки часто построены согласно модели одиночного наследования. Ruby поддерживает модули и классы-примеси, которые мы обсудим в следующей главе. Также реализованы идентификаторы объектов, что позволяет строить устойчивые, распределенные и перемещаемые объекты.

Для создания объекта существующего класса обычно используется метод new:

myFile = File.new("textfile.txt","w")

myString = String.new("Это строковый объект")

Однако не всегда его обязательно вызывать явно. В частности, при создании объекта String можно и не упоминать этот метод:

yourString = "Это тоже строковый объект"

aNumber =5 # и здесь метод new не нужен

Ссылки на объекты хранятся в переменных. Выше уже отмечалось, что сами переменные не имеют типа и не являются объектами – они лишь ссылаются на объекты.

x = "abc"

Из этого правила есть исключение: небольшие неизменяемые объекты некоторых встроенных классов, например Fixnum, непосредственно копируются в переменные, которые на них ссылаются. (Размер этих объектов не превышает размера указателя, поэтому хранить их таким образом более эффективно.) В таком случае во время присваивания делается копия объекта, а куча не используется.

При присваивании переменных ссылки на объекты обобществляются.

y = "abc"

x = y

x # "abc"

После выполнения присваивания x = y и x, и y ссылаются на один и тот же объект:

x.object_id # 53732208

y.object_id # 53732208

Если объект изменяемый, то модификация, примененная к одной переменной, отражается и на другой:

x.gsub!(/а/, "x")

y # "хbс"

Однако новое присваивание любой из этих переменных не влияет на другую:

# Продолжение предыдущего примера

x = "abc"

y # по-прежнему равно "хbс"

Изменяемый объект можно сделать неизменяемым, вызвав метод freeze:

x.freeze

x.gsub!(/b/,"y") # Ошибка!

Символ в Ruby ссылается на переменную по имени, а не по ссылке. Во многих случаях он может вообще не ссылаться на идентификатор, а вести себя как некая разновидность неизменяемой строки. Символ можно преобразовать в строку с помощью метода to_s.

Hearts = :Hearts # Это один из способов присвоить

Clubs = :Clubs # уникальное значение константе,

Diamonds = :Diamonds # некий аналог перечисления

Spades = :Spades # в языках Pascal или С.

puts Hearts.to_s # Печатается "Hearts"

Продемонстрированный выше фокус с «перечислением» был более осмыслен на ранних этапах развития Ruby, когда еще не было класса Symbol, а наличие двоеточия перед идентификатором превращало его в целое число. Если вы пользуетесь таким трюком, не предполагайте, что фактическое значение символа будет неизменным или предсказуемым – просто используйте его как константу, значение которой неважно.

1.3.3. Модули и классы-примеси

Многие встроенные методы наследуются от классов-предков. Особо стоит отметить методы модуля Kernel, подмешиваемые к суперклассу Object. Поскольку класс Object повсеместно доступен, то и добавленные в него из Kernel методы также доступны в любой точке программы. Эти методы играют важную роль в Ruby.

Термины «модуль» и «примесь» – почти синонимы. Модуль представляет собой набор методов и констант, внешних по отношению к программе на Ruby. Его можно использовать просто для управления пространством имен, но основное применение модулей связано с «подмешиванием» его возможностей в класс (с помощью директивы include). В таком случае он используется как класс-примесь.

Этот термин очевидно заимствован из языка Python. Стоит отметить, что в некоторых вариантах LISP такой механизм существует уже больше двадцати лет.

Не путайте описанное выше употребление термина «модуль» с другим значением, которое часто придается ему в информатике. Модуль в Ruby – это не внешний исходный текст и не двоичный файл (хотя может храниться и в том, и в другом виде). Это объектно-ориентированная абстракция, в чем-то похожая на класс.

Примером использования модуля для управления пространством имен служит модуль Math. Так, чтобы получить определение числа π, необязательно включать модуль Math с помощью предложения include; достаточно просто написать Math::PI.

Примесь дает способ получить преимущества множественного наследования, не отягощенные характерными для него проблемами. Можно считать, что это ограниченная форма множественного наследования, но создатель языка Мац называет его одиночным наследованием с разделением реализации.

Отметим, что предложение include включает имена из указанного пространства имен (модуля) в текущее. Метод extend добавляет объекту функции из модуля. В случае применения include методы модуля становятся доступны как методы экземпляра, а в случае extend – как методы класса.

Необходимо оговориться, что операции load и require не имеют ничего общего с модулями: они относятся к исходным и двоичным файлам (загружаемым динамически или статически). Операция load читает файл и вставляет его в текущую точку исходного текста, так что начиная с этой точки становятся видимы все определения, находящиеся во внешнем файле. Операция require аналогична load, но не загружает файл, если он уже был загружен ранее.

Программисты, только начинающие осваивать Ruby, особенно имеющие опыт работы с языком С, могут поначалу путать операции require и include, которые никак не связаны между собой. Вы еще поймаете себя на том, что сначала вызываете require, а потом include для того, чтобы воспользоваться каким-то внешним модулем.

1.3.4. Создание классов

В Ruby есть множество встроенных классов, и вы сами можете определять новые. Для определения нового класса применяется такая конструкция:

class ClassName

# ...

end

Само имя класса – это глобальная константа, поэтому оно должно начинаться с прописной буквы. Определение класса может содержать константы, переменные класса, методы класса, переменные экземпляра и методы экземпляра. Данные уровня класса доступны всем объектам этого класса, тогда как данные уровня экземпляра доступны только одному объекту

Попутное замечание: строго говоря, классы в Ruby не имеют имен. «Имя» класса – это всего лишь константа, ссылающаяся на объект типа Class (поскольку в Ruby Class – это класс). Ясно, что на один и тот же класс могут ссылаться несколько констант, и их можно присваивать переменным точно так же, как мы поступаем с любыми другими объектами (поскольку в Ruby Class – это объект). Если вы немного запутались, не расстраивайтесь. Удобства ради новичок может считать, что в Ruby имя класса – то же самое, что в C++.

Вот как определяется простой класс:

class Friend

 @@myname = "Эндрю"           # переменная класса

 def initialize(name, sex, phone)

  @name, @sex, @phone = name, sex, phone

  # Это переменные экземпляра

 end

 def hello # метод экземпляра

  puts "Привет, я #{@name}."

 end

 def Friend.our_common_friend # метод класса

  puts "Все мы друзья #{@@myname}."

 end

end

f1 = Friend.new("Сюзанна","F","555-0123")

f2 = Friend.new("Том","M","555-4567")

f1.hello                      # Привет, я Сюзанна.

f2.hello                      # Привет, я Том.

Friend.our_common_friend      # Все мы друзья Эндрю.

Поскольку данные уровня класса доступны во всем классе, их можно инициализировать в момент определения класса. Если определен метод с именем initialize, то гарантируется, что он будет вызван сразу после выделения памяти для объекта. Этот метод похож на традиционный конструктор, но не выполняет выделения памяти. Память выделяется методом new, а освобождается неявно сборщиком мусора.

Теперь взгляните на следующий фрагмент, обращая особое внимание на методы getmyvar, setmyvar и myvar=:

class MyClass

 NAME = "Class Name"  # константа класса

 @@count = 0          # инициализировать переменную класса

 def initialize       # вызывается после выделения памяти для объекта

  @@count += 1

  @myvar = 10

 end

 def MyClass.getcount # метод класса

  @@count             # переменная класса

 end

 def getcount         # экземпляр возвращает переменную класса!

  @@count             # переменная класса

 end

 def getmyvar         # метод экземпляра

  @myvar              # переменная экземпляра

 end

 def setmyvar(val)    # метод экземпляра устанавливает @myvar

  @myvar = val

 end

 def myvar=(val)      # другой способ установить @myvar

  @myvar = val

 end

end

foo = MyClass.new # @myvar равно 10

foo.setmyvar 20 # @myvar равно 20

foo.myvar =30 # @myvar равно 30

Здесь мы видим, что getmyvar возвращает значение переменной @myvar, а setmyvar устанавливает его. (Многие программисты говорят о методах чтения и установки). Все это работает, но не является характерным способом действий в Ruby. Метод myvar= похож на перегруженный оператор присваивания (хотя, строго говоря, таковым не является); это более удачная альтернатива setmyvar, но есть способ еще лучше.

Класс Module содержит методы attr, attr_accessor, attr_reader и attr_writer. Ими можно пользоваться (передавая символы в качестве параметров) для автоматизации управления доступом к данным экземпляра. Например, все три метода getmyvar, setmyvar и myvar= можно заменить одной строкой в определении класса:

attr_accessor :myvar

При этом создается метод myvar, который возвращает значение @myvar, и метод myvar=, который позволяет изменить значение той же переменной. Методы attr_reader и attr_writer создают соответственно версии методов доступа к атрибуту для чтения и для изменения.

Внутри методов экземпляра, определенных в классе, можно при необходимости пользоваться переменной self. Это просто ссылка на объект, от имени которого вызван метод экземпляра.

Для управления видимостью методов класса можно пользоваться модификаторами private, protected и public. (Переменные экземпляра всегда закрыты, обращаться к ним извне класса можно только с помощью методов доступа.) Каждый модификатор принимает в качестве параметра символ, например :foo, а если он опущен, то действие модификатора распространяется на все последующие определения в классе. Пример:

class MyClass

 def method1

  # ...

 end

 def method2

  # ...

 end

 def method3

  # ...

 end

 private :method1

 public

 :method2

 protected :method3

 private

 def my_method

  # ...

 end

 def another_method

  # ...

 end

end

В этом классе метод method1 закрытый, method2 открытый, a method3 защищенный. Поскольку далее вызывается метод private без параметров, то методы my_method и another_method будут закрытыми.

Уровень доступа public не нуждается в объяснениях, он не налагает никаких ограничений ни на доступ к методу, ни на его видимость. Уровень private означает, что метод доступен исключительно внутри класса или его подклассов и может вызываться только в «функциональной форме» от имени self, причем вызывающий объект может указываться явно или подразумеваться неявно. Уровень protected означает, что метод вызывается только внутри класса, но, в отличие от закрытого метода, не обязательно от имени self.

По умолчанию все определенные в классе методы открыты. Исключение составляет лишь initialize. Методы, определенные на верхнем уровне программы, тоже по умолчанию открыты. Если они объявлены закрытыми, то могут вызываться только в функциональной форме (как, например, методы, определенные в классе Object).

Классы в Ruby сами являются объектами – экземплярами метакласса Class. Классы в этом языке всегда конкретны, абстрактных классов не существует. Однако теоретически можно реализовать и абстрактные классы, если вам это для чего-то понадобится.

Класс Object является корнем иерархии. Он предоставляет все методы, определенные во встроенном модуле Kernel.

Чтобы создать класс, наследующий другому классу, нужно поступить следующим образом:

class MyClass < OtherClass

 # ...

end

Помимо использования встроенных методов, вполне естественно определить и собственные либо переопределить унаследованные. Если определяемый метод имеет то же имя, что и существующий, то старый метод замещается. Если новый метод должен обратиться к замещенному им «родительскому» методу (так бывает часто), можно воспользоваться ключевым словом super.

Перегрузка операторов, строго говоря, не является неотъемлемой особенностью ООП, но этот механизм знаком программистам на C++ и некоторых других языках. Поскольку большинство операторов в Ruby так или иначе являются методами, то не должен вызывать удивления тот факт, что их можно переопределять или определять в пользовательских классах. Переопределять семантику оператора в существующем классе редко имеет смысл, зато в новых классах определение операторов – обычное дело.

Можно создавать синонимы методов. Для этого внутри определения класса предоставляется такой синтаксис:

alias newname oldname

Число параметров будет таким же, как для старого имени, и вызываться метод-синоним будет точно так же. Обратите внимание на отсутствие запятой; alias – это не имя метода, а ключевое слово. Существует метод с именем alias_method, который ведет себя аналогично, но в случае его применения параметры должны разделяться запятыми, как и для любого другого метода.

1.3.5. Методы и атрибуты

Как мы уже видели, методы обычно используются в сочетании с простыми экземплярами классов и переменными, причем вызывающий объект отделяется от имени метода точкой (receiver.method). Если имя метода является знаком препинания, то точка опускается. У методов могут быть аргументы:

Time.mktime(2000, "Aug", 24, 16, 0)

Поскольку каждое выражение возвращает значение, то вызовы методов могут сцепляться:

3.succ.to_s

/(x.z).*?(x.z).*?/.match("x1z_1a3_x2z_1b3_").to_a[1..3]

3+2.succ

Отметим, что могут возникать проблемы, если выражение, являющееся результатом сцепления, имеет тип, который не поддерживает конкретный метод. Точнее, при определенных условиях некоторые методы возвращают nil, а вызов любого метода от имени такого объекта приведет к ошибке. (Конечно, nil – полноценный объект, но он не обладает теми же методами, что и, например, массив.)

Некоторым методам можно передавать блоки. Это верно для всех итераторов – как встроенных, так и определенных пользователем. Блок обычно заключается в операторные скобки do-end или в фигурные скобки. Но он не рассматривается так же, как предшествующие ему параметры, если таковые существуют. Вот пример вызова метода File.open:

my_array.each do |x|

 some_action

end

File.open(filename) { |f| some_action }

Именованные параметры будут поддерживаться в последующих версиях Ruby, но на момент работы над этой книгой еще не поддерживались. В языке Python они называются ключевыми аргументами, сама идея восходит еще к языку Ada.

Методы могут принимать переменное число аргументов:

receiver.method(arg1, *more_args)

В данном случае вызванный метод трактует more_args как массив и обращается с ним, как с любым другим массивом. На самом деле звездочка в списке формальных параметров (перед последним или единственным параметром) может «свернуть» последовательность фактических параметров в массив:

def mymethod(a, b, *с)

 print a, b

 с.each do |x| print x end

end

mymethod(1,2,3,4,5,6,7)

# a=1, b=2, c=[3,4,5,6,7]

В Ruby есть возможность определять методы на уровне объекта (а не класса). Такие методы называются синглетными; они принадлежат одному-единственному объекту и не оказывают влияния ни на класс, ни на его суперклассы. Такая возможность может быть полезна, например, при разработке графических интерфейсов пользователя: чтобы определить действие кнопки, вы задаете синглетный метод для данной и только данной кнопки.

Вот пример определения синглетного метода для строкового объекта:

str = "Hello, world!"

str2 = "Goodbye!"

def str.spell

 self.split(/./).join("-")

end

str.spell # "H-e-l-l-o-,– -w-o-r-l-d-!"

str2.spell # Ошибка!

Имейте в виду, что метод определяется для объекта, а не для переменной. Теоретически с помощью синглетных методов можно было бы создать систему объектов на базе прототипов. Это менее распространенная форма ООП без классов[5]5
  Типичный пример – язык JavaScript (Прим. перев.)


[Закрыть]
. Основной структурный механизм в ней состоит в конструировании нового объекта путем использования существующего в качестве образца; новый объект ведет себя как старый за исключением тех особенностей, которые были переопределены. Тем самым можно строить системы на основе прототипов, а не наследования. Хотя у нас нет опыта в этой области, мы полагаем, что создание такой системы позволило бы полнее раскрыть возможности Ruby.


    Ваша оценка произведения:

Популярные книги за неделю