Ruby/Подробнее о методах: различия между версиями
Содержимое удалено Содержимое добавлено
DannyS712 (обсуждение | вклад) м <source> -> <syntaxhighlight> (phab:T237267) |
|||
Строка 7:
Благодаря тому, что указание класса-носителя метода необязательно, на Ruby можно программировать в функциональном стиле, не заботясь о создании класса-«носителя» для каждой группы методов. Метод создаётся с помощью ключевых слов <code>def … end</code>.
<
return a + b
end
sum(10, 2) #=> 12</
Ruby по умолчанию возвратит из метода результат последнего выполненного выражения, поэтому в конце метода или в условных конструкциях слово <code>return</code> можно опускать. Поскольку методы могут быть переопределены в процессе выполнения программы, можно «на ходу» переписать метод так:
<
a + b
end
sum(10, 2) #=> 12</
<!-- Про case и if планируется рассказывать в самом конце, а программу is_odd можно переписать как !(number%2).zero?
Строка 25:
Условное выражение и выражение <code>case</code> также возвращают свой результат, поэтому если метод проверяет условия, удобно написать его примерно так:
<
def is_odd(number)
case number % 2
Строка 36:
is_odd(4) #=> false
is_odd(3) #=> true</
И <code>return</code>, и промежуточное присвоение можно опустить. Подобные свойства Ruby позволяют делать программы невероятно сжатыми без существенной потери читаемости.
Строка 45:
У методов могут быть необязательные аргументы. Для этого им нужно присвоить значение, которое следует применять «по умолчанию»:
<
a + b
end
sum(10, 2) #=> 12
sum(10) #=> 15</
==== Методы с восклицательным и вопросительным знаком ====
Строка 60:
Обычно программист, чтобы проверить, пуст ли массив, посмотрит его длину:
<
if arr.length == 0
puts "empty"
else
puts "not empty"
end</
У массива в Ruby есть метод-предикат <code>.empty?</code>, возвращающий <code>true</code> если массив пуст.
<
if arr.empty?
puts "empty"
else
puts "not empty"
end</
Если вы реализуете программу, которой будут пользоваться другие, считается хорошим тоном реализовывать методы-предикаты.
Строка 80:
Ещё одна их прелесть — сочетание с модификаторами выражения:
<
p "Array has something" if arr.any?</
Методы с восклицательным знаком на конце меняют объект, к которому привязаны.
<
string.strip! #=> "Some string with spaces" — возвращает результат операции…
string #=> "Some string with spaces" …и меняет состояние объекта-адресата</
==== Методы присваивания ====
Строка 95:
Знак равенства в конце названия метода означает, что этот метод присваивает свойству объекта значение:
<
def capacity
@capacity
Строка 106:
bottle = Bottle.new
bottle.capacity = 10 #=> 10, автоматически преобразуется в вызов метода capacity=</
второй метод
<
class Bottle
attr_accessor :capacity, :contents
Строка 122:
bottle.capacity #=> 0.5
bottle.contents #=> "milk"
</syntaxhighlight>
.......
Строка 129:
Операторы (умножение, деление, возведение в степень и так далее — вплоть до сравнения!) — тоже методы. Например:
<
def+(another)
12 + another
Строка 136:
whisk = Broom.new
whisk + 10 #=> 22</
Это применяется, например, во встроенном в Ruby объекте <code>Time</code>. При прибавлении к нему целого числа он возвращает новый объект <code>Time</code> с добавленным количеством секунд:
<
t + 60 #=> Sun Jun 11 20:30:51 — на минуту позже</
То же самое характерно для имеющегося в стандартной библиотеке класса <code>Date</code>, но, в отличие от <code>Time</code>, он считает дни вместо секунд.
<
d = Date.today #=> Sun Jun 11
d + 1 #=> Mon Jun 12 — на день позже</
==== «Поглощение» аргументов метода ====
Строка 153:
Можно «свернуть» аргументы с помощью звёздочки — тогда метод получит массив в качестве аргумента:
<
members[0] + members[1]
end
sum(10, 2) #=> 12</
Поскольку теперь наш метод принимает неограниченное количество элементов, мы можем пользоваться ими как массивом и в теле функции:
<
initial = 0
members.collect{ | item | initial += item }
Строка 168:
sum(10, 2) #=> 12
sum(10, 2, 12, 34) #=> 58</
Можно разделить аргументы на обязательные и необязательные, просто пометив последний аргумент «звёздочкой». Если методу будут переданы только обязательные аргументы, в переменной «со звёздочкой» в теле метода будет пустой массив.
Строка 174:
Звёздочкой полезно пользоваться и когда нужно передать методу аргументы, но не хочется указывать их по отдельности. Следуя тому же примеру:
<
sum(*array_to_sum) #=> 58</
=== Подробнее о замыканиях ===
Строка 198:
При передаче замыкания методу, замыкание следует за ''скобками аргументов''.
<
Поскольку при отсутствии аргументов скобки необязательны, простейшая запись такова:
<
Важно помнить, что замыкание использует методы и переменные, указанные при его создании, то есть замыкание ''захватывает контекст'', но переменные, определённые в замыкании, остаются для него локальными!
<
# переменную word, и она определена в нём
puts word # вызывает сообщение об ошибке —
# вне замыкания об этой переменной ничего не известно</
Необходимо заметить, что если переменная была определена ранее, то она может использоваться внутри замыкания:
<
puts (1..3).map{ word = 'Вау!'; word }
puts word # выведет Вау!
</syntaxhighlight>
или
<
i=0
(1..3).map do |x|
Строка 224:
end
puts i # выведет 3
</syntaxhighlight>
Как уже упоминалось, если замыкание многострочное, целесообразней пользоваться формой с <code>do … end</code>:
<
(1..3).map do
random_number = rand()
puts "Вау — случайный номер!\n" + random_number.to_s
end</
==== Замыкания принимают аргументы ====
Строка 238:
Другое замечательное свойство замыканий — они, как и функции, могут принимать аргументы. В таком случае метод, которому передано замыкание, сам «решает», что это замыкание получит в качестве аргумента. Например, уже продемонстрированный метод <code>.map</code> ещё и передаёт замыканию аргумент, который можно захватить следующим образом:
<
i
end</
{{Внимание|Данный листинг не будет работать на Ruby версии 1.9.2}}
Строка 251:
Ключевое слово <code>yield</code> в методе открывает раздвижные двери, впускающие аргумент[ы] в замыкание.
<
yield "и раз"
yield "и два"
Строка 257:
twice { |words| puts "!!! " + words } #=> !!! и раз
#=> !!! и два</
При этом строка будет передаваться замыканию в переменную <code>words</code> при каждом выполнении.
Строка 263:
Если замыкание обязательно, следует пометить его как последний аргумент метода и в начале аргумента добавить [[w:Aмперсанд|амперсанд]]:
<
yield "и раз"
yield "и два"
end
twice #=> Ошибка LocalJumpError - отсутствует замыкание</
Последнее утверждение не совсем верно. Даже совсем не верно. Указания переменной замыкания недостаточно для контроля наличия входного замыкания. Дело в том, что в случае, если замыкание не вызывается, то и ошибки не будет:
<
return a if a
yield "и раз"
Строка 279:
func true #=> true
func false #=> LocalJumpError: no block given</
Более того, вызов функции <code>twice</code> без указания замыкания также приведёт к ошибке. Таким образом, гораздо лучше вместо введения обязательного параметра задавать замыкание по-умолчанию:
<
return a if a
closure ||= lambda{ |words| puts "!!! " + words }
Строка 295:
func(false){ |words| puts "??? " + words } #=> ??? и раз
#=> ??? и два</
Здесь <code>lambda</code> — пустая функция, а <code>closure.call</code> — явный способ вызова замыкания на выполнение.
Строка 301:
Замыкание можно также передать другому методу, просто указав его как последний аргумент с амперсандом:
<
File.open(file, 'w', &closure)
end</
Наконец, на десерт, напишем свой inject.
<
class Array
def inject2 ( buf )
Строка 318:
p [1,2,3].inject2(10){|b,e| b + e} #=> 16
p [1,2,3].inject(10){|b,e| b + e} #=> 16
</syntaxhighlight>
==== Некоторые применения замыканий ====
Строка 326:
Типичное применение замыкания — когда после выполнений некой операции нужно «вынести мусор»: закрыть открытый ресурс или отсоединиться от сети. Предположим, что мы пишем метод для интернет-системы. При этом мы хотим выполнить несколько операций. Но чтобы их выполнить, нужно подключить пользователя к Сети. После того, как операции завершились, надо его так же незаметно отключить.
<
В данном случае мы пишем только замыкание с <code>download_email</code>, все заботы по открытию (а главное — закрытию) соединения возьмёт на себя метод <code>connected</code>:
<
connect_to_internet
result = yield
disconnect
result
end</
В данном случае мы сохраняем то, что вернуло замыкание, в метод, закрываем соединение и возвращаем результат замыкания как свой собственный.
Строка 343:
Если воспользоваться встроенной проверкой исключений, то метод принимает такой вид:
<
connect_to_internet
begin
Строка 351:
end
result
end</
Тогда, даже если метод вызовет ошибку, соединение всё равно будет закрыто.
Строка 363:
Студент МЭТТ Ширяев Денис на одном из зачётов предложил использовать метод <code>.size</code> в качестве итератора. Он использовал его для подсчёта количества элементов массива, удовлетворяющих условию. По сути, он предложил укоротить связку <code>.find_all{ … }.size</code>. Вот как будет выглядеть программа подсчёта количества чётных элементов массива:
<
array.size{ |i| (i % 2).zero? } #=> 3</
Чтобы заставить работать данную программу, необходимо перед использованием метода <code>.size</code> переопределить его, написав следующий код, который будет реализовывать эту функциональность:
<
def size(&closure)
closure ? inject(0){ |count, elem| (yield elem) ? count + 1 : count } : length
end
end</
Метод реализован только для массивов, но возможно его добавление к хешам или строкам.
Строка 380:
Студенты часто возмущаются: почему, чтобы получить случайное число от 3 до 6 нужно писать нечто невнятное вида:
<
Откуда чего берётся? Почему нельзя написать проще? Например вот так:
<
Действительно, почему? Давайте добавим такую функциональность к классу Range:
<
def rand
first + Kernel.rand(last - first + (exclude_end? ? 0 : 1))
end
end</
Для проверки можно выполнить следующий код:
<
Что и требовалось реализовать. Кстати, данная реализация имеет один изъян: для строковых диапазонов метод <code>Range#rand</code> будет выдавать ошибку. Решается проблема достаточно просто. Надо реализовать <code>Array#rand</code> (получение случайного элемента массива), а внутри <code>Range#rand</code> вызывать связку <code>.to_a.rand</code>. Теперь тоже самое, но на Ruby:
<
def rand
self[Kernel.rand(size)]
Строка 410:
to_a.rand
end
end</
Или еще проще (без изменения класса Array):
<
def rand
to_a.sample
end
end</
Для проверки выполним следующий код:
<
Странно, но, видимо, всё работает!
Строка 432:
Важно помнить, что в Ruby все типы являются объектами, даже сами классы. Каждый класс до конца выполнения программы остаётся открытым, а это значит, что в любой тип можно добавить собственные методы (или изменить поведение существующих). Каждый класс можно определять постепенно, в нескольких частях программы:
<
def sweep
end
Строка 444:
end
Broom.instance_methods #=> […, "sweep", …, "wash_lavatory_pan", …]</
Метод <code>.instance_methods</code> возвращает массив, который содержит имена методов, которые можно вызвать.
Строка 452:
Простейший пример — добавление метода классу <code>String</code>, выводящий только согласные буквы из строки:
<
def consonants
cons = []
Строка 460:
end
"Crazy brown fox jumps over a lazy dog".consonants #=> "Crzbwnfxjmpsvrldg"</
Операция расширения класса (добавление нового метода к существующему) по сути не отличается от создания нового класса.
Строка 466:
У объектов в Ruby есть методы класса и методы экземпляра. В нашем примере <code>consonants</code> — это именно метод экземпляра. При создании нового класса или изменении существующего создать метод класса можно, начав его имя с имени класса или с <code>self</code> и точки:
<
def self.consonants_from(string)
cons = []
Строка 474:
end
String.consonants_from("Crazy fox jumps over a lazy dog") #=> "Crzbwnfxjmpsvldg"</
Одним из специфических свойств Ruby является то, что классы сами по себе — экземпляры класса <code>Class</code>, и с ними можно работать как с обычными объектами. Специальный синтаксис для доступа к методам класса в Ruby не нужен. Классы можно хранить в переменных, передавать методам и так далее.
Строка 482:
Проиллюстрируем это простым примером. Как мы знаем, у класса <code>File</code> есть метод <code>open</code>. Создадим метод у класса <code>File</code>, дающий нам доступ к временному файлу, создаваемому в момент выполнения кода. Это такой же метод, но открывающий только файлы из директории <code>/tmp</code>:
<
class File
def self.temporary(&closure)
Строка 497:
end
File.temporary { |f| f << "Some info" } #=> #<File:/Tests/(irb)_1151198720.tmp (closed)></
{{info|Для управления временными файлами в Ruby существует класс <code>Tempfile</code>. Помимо других достоинств он гарантирует, что созданные временные файлы по завершении программы
Строка 506:
Добавим к классу <code>File</code> метод <code>myself</code>:
<
class << self
def myself
Строка 512:
end
end
end</
Если нужно добавить метод только к конкретному экземпляру, нужно выйти на его айгенкласс:
<
string = "Crazy brown fox jumps over a lazy dog"
other_string = "Three black witches"
Строка 527:
string.vowels #=> "ayoue"
other_string.vowels #=> NoMethodError: undefined method `vowels' for …</
Возможность добавлять и изменять устройство уже существующих классов — одно из основных свойств Ruby, обеспечивающих великую гибкость языка. Часто бывает, что метод возвращает не тот результат, который нам нужен — тогда при его изменении все программы, обращающиеся к данному методу будут получать изменённый результат.
Строка 535:
Как ни странно, изредка программисту приходится взять на себя позицию разрушителя — удалить существующий метод или константу. Метод <code>undef</code> позволяет сделать это:
<
def sweep
"Метём!"
Строка 557:
broom.sweep #=> "Метём!"
birch_broom.sweep #=> Ошибка NoMethodError — такого метода нет, хоть он и был унаследован</
Уничтожение класса несколько сложнее, но тоже возможно:
<
После этого <code>Broom</code> будет существовать только для объекта-экземпляра:
<
end
Строка 573:
Broom #=> Ошибка NameError: неизвестная константа Broom
whisk.class #=> Broom, всё ещё существует для экземпляра</
Это свойство Ruby крайне полезно, если нужно создать класс, наследующий от другого, но при этом имеющий другого родителя. Например:
<
class Connection < Socket
# много-много методов…
Строка 592:
end
# В итоге чужая программа будет использовать созданный нами Connection</
Полная замена чужих классов довольно опасна, но бывают ситуации, когда эта методика спасает.
Строка 609:
Писать класс не так уж и сложно. Простейший класс будет выглядеть так:
<
class NewClass
def initialize(a,b,c)
Строка 630:
#=> b = 20
#=> c = 30
</syntaxhighlight>
=== Наследовать или перемешать? ===
|