Категории
Самые читаемые
Лучшие книги » Компьютеры и Интернет » Программирование » Программирование на языке Ruby - Хэл Фултон

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

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

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 87 88 89 90 91 92 93 94 95 ... 156
Перейти на страницу:

class MyClass

 attr_reader :alpha, :beta

 def initialize(a,b,g)

  @alpha, @beta, @gamma = a, b, g

 end

end

x = MyClass.new(10,11,12)

x.instance_variable_set("@alpha",234)

p x.alpha # 234

x.instance_variable_set("@gamma",345) # 345

v = x.instance_variable_get("@gamma") # 345

Прежде всего, отметим, что имя переменной должно начинаться со знака @, иначе произойдет ошибка. Если это кажется вам неочевидным, вспомните, что метод attr_accessor (и ему подобные) принимает для формирования имени метода символ, поэтому-то знак @ и опускается.

Не нарушает ли существование таких методов принцип инкапсуляции? Нет. Конечно, эти методы потенциально опасны. Пользоваться ими следует с осторожностью, а не при всяком удобном случае. Но нельзя говорить, что инкапсуляция нарушена, не видя, как эти инструменты применяются в конкретном случае. Если это делается обдуманно, ради ясно осознанной цели, то все хорошо. Если же цель состоит в том, чтобы нарушить проект или обойти неудачное проектное решение, это печально. Ruby намеренно предоставляет доступ к внутренним деталям объектов тем, кому это действительно нужно; ответственный программист не станет пользоваться свободой во вред.

11.3.5. Метод define_method

Помимо ключевого слова def, единственный нормальный способ добавить метод в класс или объект — воспользоваться методом define_method, причем он позволяет сделать это во время выполнения.

Конечно, в Ruby практически все происходит во время выполнения. Если окружить определение метода обращениями к puts, как в примере ниже, вы это сами увидите.

class MyClass

 puts "до"

 def meth

  #...

 end

 puts "после"

end

Но внутри тела метода или в другом аналогичном месте нельзя заново открыть класс (если только это не синглетный класс). В таком случае в прежних версиях Ruby приходилось прибегать к помощи eval, теперь же у нас есть метод define_method. Он принимает символ (имя метода) и блок (тело метода).

Первая (ошибочная) попытка воспользоваться этим методом могла бы выглядеть так:

# Не работает, так как метод define_method закрытый.

if today =~ /Saturday | Sunday/

 define_method(:activity) { puts "Отдыхаем!" }

else

 define_method(:activity) { puts "Работаем!" }

end

activity

Поскольку define_method — закрытый метод, приходится поступать так:

# Работает (Object - это контекст верхнего уровня).

if today =~ /Saturday | Sunday/

 Object.class_eval { define_method(:activity) { puts "Отдыхаем!" } }

else

 Object.class_eval { define_method(:activity) { puts "Работаем!" } }

end

activity

Можно было бы поступить так же внутри определения класса (в применении к классу Object или любому другому). Такое редко бывает оправданно, но если вы можете сделать это внутри определения класса, вопрос о закрытости не встает.

class MyClass

 define_method(:mymeth) { puts "Это мой метод." }

end

Есть еще один трюк: включить в класс метод, который сам вызывает define_method, избавляя от этого программиста:

class MyClass

 def self.new_method(name, &block)

  define_method(name, &block)

 end

end

MyClass.new_method(:mymeth) { puts "Это мой метод." }

x = MyClass.new

x.mymeth # Печатается "Это мой метод."

То же самое можно сделать и на уровне экземпляра, а не класса:

class MyClass

 def new_method(name, &block)

  self.class.send(:define_method,name, &block)

 end

end

x = MyClass.new

x.new_method(:mymeth) { puts "Это мой метод." }

x.mymeth # Печатается "Это мой метод."

Здесь метод экземпляра тоже определен динамически. Изменился только способ реализации метода new_method. Обратите внимание на трюк с send, позволивший нам обойти закрытость метода define_method. Он работает, потому что в текущей версии Ruby метод send позволяет вызывать закрытые методы. (Некоторые сочтут это «дыркой»; как бы то ни было, пользоваться этим механизмом следует с осторожностью.)

По поводу метода define_method нужно сделать еще одно замечание. Он принимает блок, а в Ruby блок — замыкание. Это означает, что в отличие от обычного определения метода, мы запоминаем контекст, в котором метод был определен. Следующий пример практически бесполезен, но этот момент иллюстрирует:

class MyClass

 def self.new_method(name, &block)

  define_method(name, &block)

 end

end

a,b = 3,79

MyClass.new_method(:compute) { a*b }

x = MyClass.new

puts x.compute # 237

a,b = 23,24

puts x.compute # 552

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

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

class SomeClass

 @@var = 999

 define_method(:peek) { @@var }

end

x = SomeClass.new p

x.peek # 999

А теперь попробуем проделать с переменной экземпляра класса такой трюк:

class SomeClass

 @var = 999

 define_method(:peek) { @var }

end

x = SomeClass.new

p x.peek # Печатается nil

Мы ожидали, что будет напечатано 999, а получили nil. Почему? Объясню чуть позже.

С другой стороны, такой код работает правильно:

class SomeClass

 @var = 999

 x = @var

 define_method(:peek) { x }

end

x = SomeClass.new p

x.peek # 999

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

Поскольку имя @var в этом контексте относится к переменной экземпляра объекта, а не класса, то переменная экземпляра класса оказывается скрыта переменной экземпляра объекта, хотя последняя никогда не использовалась и технически не существует.

В предыдущих версиях Ruby мы часто определяли методы во время выполнения с помощью eval. В принципе во всех таких случаях может и должен использоваться метод define_method. Некоторые тонкости вроде рассмотренной выше не должны вас останавливать.

11.3.6. Метод const_missing

Метод const_missing аналогичен методу method_missing. При попытке обратиться к неизвестной константе вызывается этот метод — если он, конечно, определен. В качестве параметра ему передается символ, ссылающийся на константу.

Чтобы перехватывать обращения к отсутствующим константам глобально, определите следующий метод в самом классе Module (это родитель класса Class).

class Module

 def const_missing(x)

  "Из Module"

 end

end

class X

end

p X::BAR     # "Из Module"

p BAR        # "Из Module"

p Array::BAR # "Из Module"

Можно выполнить в нем любые действия: вернуть фиктивное значение константы, вычислить его и т.д. Помните класс Roman из главы 6? Воспользуемся им, чтобы трактовать любые последовательности римских цифр как числовые константы:

class Module

 def const_missing(name)

  Roman.decode(name)

 end

end

year1 = MCMLCCIV # 1974

year2 = MMVIII   # 2008

Если такая глобальность вам не нужна, определите этот метод на уровне конкретного класса. Тогда он будет вызываться из этого класса и его потомков.

class Alpha

 def self.const_missing(sym)

  "В Alpha нет #{sym}"

 end

end

class Beta

 def self.const_missing(sym)

  "В Beta нет #{sym}."

 end

end

class A < Alpha

end

class В < Beta

end

p Alpha::FOO # "В Alpha нет FOO"

p Beta::FOO  # "В Beta нет FOO"

p A::FOO     # "В Alpha нет FOO"

1 ... 87 88 89 90 91 92 93 94 95 ... 156
Перейти на страницу:
На этой странице вы можете бесплатно скачать Программирование на языке Ruby - Хэл Фултон торрент бесплатно.
Комментарии