KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программирование » Хэл Фултон - Программирование на языке Ruby

Хэл Фултон - Программирование на языке Ruby

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Хэл Фултон, "Программирование на языке Ruby" бесплатно, без регистрации.
Перейти на страницу:

class Bank

 def open_safe

  # ...

 end


 def close_safe

  # ...

 end


 private :open_safe, :close_safe


 def make_withdrawal(amount)

  if access_allowed

   open_safe

   get_cash(amount)

   close_safe

  end

 end


 # Остальные методы закрытые.


private


 def get_cash

  # ...

 end


 def access_allowed

  # ...

 end

end

Поскольку методы из семейства attr просто определяют методы, метод private определяет и видимость атрибутов.

Реализация метода private может показаться странной, но на самом деле она весьма хитроумна. К закрытым методам нельзя обратиться, указав вызывающий объект; они вызываются только от имени неявно подразумеваемого объекта self. То есть вызвать закрытый метод из другого объекта не удастся: просто не существует способа указать объект, от имени которого данный метод вызывается. Заодно это означает, что закрытые методы доступны подклассам того класса, в котором определены, но опять же в рамках одного объекта.

Модификатор доступа protected налагает меньше ограничений. Защищенные методы доступны только экземплярам того класса, в котором определены, и его подклассов. Для защищенного метода разрешается указывать вызывающий объект, так что к ним можно обращаться из других объектов (при условии, что вызывающий и вызываемый объекты принадлежат одному классу). Обычно защищенные методы применяются для определения методов доступа, чтобы два объекта одного типа могли взаимодействовать. В следующем примере объекты класс Person можно сравнивать по возрасту, но сам возраст недоступен вне класса Person:

class Person

 def initialize(name, age)

  @name, @age = name, age

 end


 def <=>(other)

  age <=> other.age

 end


 attr_reader :name, :age

 protected :age

end


p1 = Person.new("fred", 31)

p2 = Person.new("agnes", 43)

compare = (p1 <=> p2) # -1

x = p1.age            # Ошибка!

Чтобы завершить картину, модификатор public делает метод открытым. Неудивительно!..

И последнее: методы, определенные вне любого класса и модуля (то есть на верхнем уровне программы), по умолчанию закрыты. Поскольку они определены в классе Object, то видимы глобально, но обращаться к ним с указанием вызывающего объекта нельзя.

11.1.9. Копирование объектов

Встроенные методы Object#clone и #dup порождают копию вызывающего объекта. Различаются они объемом копируемого контекста. Метод #dup копирует только само содержимое объекта, тогда как clone сохраняет и такие вещи, как синглетные классы, ассоциированные с объектом.

s1 = "cat"


def s1.upcase

 "CaT"

end


s1_dup = s1.dup

s1_clone = s1.clone

s1              #=> "cat"

s1_dup.upcase   #=> "CAT" (синглетный метод не копируется)

s1_clone.upcase #=> "СаТ" (используется синглетный метод)

И dup, и clone выполняют поверхностное копирование, то есть копируют лишь содержимое самого вызывающего объекта. Если вызывающий объект содержит ссылки на другие объекты, то последние не копируются — копия будет ссылаться на те же самые объекты. Проиллюстрируем это на примере. Объект arr2 — копия arr1, поэтому изменение элемента целиком, например arr2[2], не оказывает влияния на arr1. Но исходный массив и его копия содержат ссылку на один и тот же объект String, поэтому изменение строки через arr2 приведет к такому же изменению значения, на которое ссылается arr1.

arr1 = [ 1, "flipper", 3 ]

arr2 = arr1.dup


arr2[2] = 99

arr2[1][2] = 'a'


arr1 # [1, "flapper", 3]

arr2 # [1, "flapper", 99]

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

Самый «чистый» способ — потребовать, чтобы классы реализовывали метод deep_copy. Он мог бы рекурсивно обходить все объекты, на которые ссылается исходный объект, и вызывать для них метод deep_copy. Необходимо было бы еще добавить метод deep_copy во все встроенные классы Ruby, которыми вы пользуетесь.

Но есть и более быстрый способ с использованием модуля Marshal. Если вы сериализуете исходный объект, представив его в виде строки, а затем загрузите в новый объект, то этот новый объект будет копией исходного.

arr1 = [ 1, "flipper", 3 ]

arr2 = Marshal.load(Marshal.dump(arr1))


arr2[2] = 99

arr2[1][2] = 'a'


arr1 # [1, "flipper", 3]

arr2 # [1, "flapper", 99]

Обратите внимание, что изменение строки через arr2 не отразилось на строке, на которую ссылается arr1.

11.1.10. Метод initialize_copy

При копировании объекта методом dup или clone конструктор не вызывается. Копируется вся информация о состоянии.

Но что делать, если вам такое поведение не нужно? Рассмотрим пример:

class Document

 attr_accessor :title, :text

 attr_reader :timestamp


 def initialize(title, text)

  @title, @text = title, text

  @timestamp = Time.now

 end

end


doc1 = Document.new("Random Stuff",File.read("somefile"))

sleep 300 # Немного подождем...

doc2 = doc1.clone


doc1.timestamp == doc2.timestamp # true

# Оп... временные штампы одинаковы!

При создании объекта Document с ним ассоциируется временной штамп. При копировании объекта копируется и его временной штамп. А как быть, если мы хотим запомнить время, когда было выполнено копирование?

Для этого нужно определить метод initialize_copy. Он вызывается как раз при копировании объекта. Этот метод аналогичен initialize и позволяет полностью контролировать состояние объекта.

class Document # Определяем новый метод в классе.

 def initialize_copy(other)

  @timestamp = Time.now

 end

end


doc3 = Document.new("More Stuff", File.read("otherfile"))

sleep 300                        # Немного подождем...

doc4 = doc3.clone


doc3.timestamp == doc4.timestamp # false

# Теперь временные штампы правильны.

Отметим, что метод initialize_copy вызывается после того, как вся информация скопирована. Поэтому мы и опустили строку:

@title, @text = other.title, other.text

Кстати, если метод initialize_copy пуст, то поведение будет такое же, как если бы он не был определен вовсе.

11.1.11. Метод allocate

Редко, но бывает, что нужно создать объект, не вызывая его конструктор (в обход метода initialize). Например, может статься, что состояние объекта полностью определяется методами доступа к нему; тогда не нужно вызывать метод new (который вызовет initialize), разве что вы сами захотите это сделать. Представьте, что для инициализации состояния объекта вы собираете данные по частям: начать следует с «пустого» объекта, а не получить все данные заранее, а потом вызвать конструктор.

Метод allocate появился в версии Ruby 1.8, чтобы упростить решение этой задачи. Он возвращает «чистый», еще не инициализированный объект класса.

class Person

 attr_accessor :name, :age, :phone


 def initialize(n,a,p)

  @name, @age, @phone = n, a, p

 end

end


p1 = Person.new("John Smith",29,"555-1234")


p2 = Person.allocate


p p1.age # 29

p p2.age # nil

11.1.12. Модули

Для использования модулей в Ruby есть две основных причины. Первая — облегчить управление пространством имен; если поместить константы и методы в модули, то будет меньше конфликтов имен. Хранящийся таким образом метод (метод модуля) вызывается с указанием имени модуля, то есть без вызывающего объекта. Точно так же вызывается и метод класса. Увидев вызовы вида File.ctime или FileTest.exist?, мы не можем определить по контексту, что File — это класс, а FileTest — модуль.

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

Мы уже говорили о методах модуля, а как насчет методов экземпляра? Модуль — это не класс, у него не может быть экземпляров, а к методу экземпляра нельзя обратиться, не указав вызывающий объект.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*