Ruby — один из самых популярных языков, используемых в сети. Мы начали новую серию скринкастов здесь, на Nettuts +, которая познакомит вас с Ruby, а также с отличными фреймворками и инструментами, которые сопровождают разработку на Ruby. На этом уроке мы подробнее рассмотрим операторов в Ruby и почему они отличаются от всего, что вы когда-либо видели.
операторы
Вы знакомы с операторами.
1
2
3
|
1 + 2 # 3
person[:name] = «Joe»
|
Операторы — это такие вещи, как знак плюс (один из арифметических операторов) или знак равенства (оператор присваивания). Эти вещи не сильно отличаются от тех, которые вы используете в JavaScript, PHP или любом другом языке. Но, как и большинство Ruby, здесь происходит гораздо больше, чем кажется на первый взгляд.
Вот секрет: операторы в Ruby на самом деле являются вызовами методов. Попробуй это:
1
|
1.+(2) # 3
|
Здесь мы вызываем оператор +
для объекта 1
, передавая объект 2
в качестве параметра. Возвращаем объект 3
. Мы можем сделать это и со строками:
1
2
3
4
5
|
name = «Joe»
name.+(» Smith») # «Joe Smith», but `name` is still «Joe»
name += » Smith» # name is now «Joe Smith»
|
Как видите, мы можем выполнить конкатенацию строк с помощью метода +
. В качестве бонуса, ruby определяет оператор + = на основе оператора + (примечание: вы не можете использовать + = в качестве метода).
Как вы понимаете, это дает нам невероятную силу. Мы можем настроить смысл добавления, вычитания и назначения объектов в наших пользовательских классах. Мы видели, как это работает со свойствами объектов в нашем уроке о классах (мы определили property
и метод property=
метод в классе и получили ожидаемый синтаксический сахар для их использования). То, на что мы здесь смотрим, делает еще один шаг вперед.
Создание наших собственных методов оператора
Давайте попробуем создать один из этих методов сами. Для этого примера давайте создадим объект-холодильник, к которому мы можем добавлять вещи с помощью оператора +
и извлекать вещи с помощью оператора -
.
Вот начало нашего класса:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
class Fridge
def initialize (beverages=[], foods=[])
@beverages = beverages
@foods = foods
end
def + (item)
end
def — (item)
end
end
|
Наша функция initialize
довольно проста: мы берем два параметра (которые возвращаются к пустым массивам, если ничего не дано) и присваиваем их переменным экземпляра. Теперь давайте создадим эти две функции:
1
2
3
4
5
6
7
|
def + (item)
if item.is_a?
@beverages.push item
else
@foods.push item
end
end
|
Это довольно просто. Каждый объект имеет is_a?
метод, который принимает один параметр: класс. Если объект является экземпляром этого класса, он вернет true; в противном случае он вернет false. Итак, это говорит о том, что если элемент, который мы добавляем в холодильник, это Beverage
, мы добавим его в массив @beverages
. В противном случае мы добавим его в массив @food
.
Это хорошо; как насчет того, чтобы вынуть вещи из холодильника? (Примечание: этот метод отличается от того, который показан на видео; это показывает, что эти операторные методы дают нам большую гибкость; на самом деле это просто обычные методы, с которыми вы можете делать что угодно. Кроме того, я думаю, что это лучшая версия метода, однако, она более сложная.)
01
02
03
04
05
06
07
08
09
10
11
12
13
|
def — (item)
ret = @beverages.find do |beverage|
beverage.name.downcase == item.downcase
end
return @beverages.delete ret unless ret.nil?
ret = @foods.find do |food|
food.name.downcase == item.downcase
end
@foods.delete ret
end
|
Вот что происходит, когда мы используем оператор минус. Параметр, который он принимает, является строкой с названием искомого элемента (кстати, мы скоро создадим классы Beverage
и Food
). Мы начнем с использования метода find
который есть у массивов. Есть несколько способов использовать этот метод; мы передаем это блок; этот блок говорит, что мы пытаемся найти элемент в массиве, у которого есть свойство name
которое совпадает со строкой, которую мы передали; обратите внимание, что мы конвертируем обе строки в нижний регистр, чтобы быть в безопасности.
Если в массиве найдется элемент, который будет сохранен в ret
; в противном случае ret
будет равен nil
. Далее мы вернем результат @beverage.delete ret
, который удаляет элемент из массива и возвращает его. Обратите внимание, что мы используем модификатор оператора в конце этой строки: мы делаем это, если только ret
равен nil
.
Вы можете удивиться, почему мы используем ключевое слово return
здесь, так как оно не требуется в Ruby. Если бы мы не использовали его здесь, функция еще не вернулась бы, так как в ней больше кода. Использование return
здесь позволяет нам возвращать значение из места, которое функция обычно не возвращает.
Если мы не вернемся, это означает, что элемент не был найден в @beverages
. Поэтому предположим, что это в @foods
. Мы сделаем то же самое, чтобы найти элемент в @foods
и затем вернуть его.
Перед тестированием нам понадобятся наши классы Food
и Beverages
:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
class Beverage
attr_accessor :name
def initialize name
@name = name
@time = Time.now
end
end
class Food
attr_accessor :name
def initialize name
@name = name
@time = Time.now
end
end
|
Обратите внимание, что в видео я не делал @name
доступным извне объекта. Здесь я делаю это с помощью attr_accessor :name
, чтобы мы могли проверить имя этих объектов, когда они находятся в холодильнике.
Итак, давайте проверим это в irb; начнем с того, что нам потребуется файл, содержащий код; затем, попробуйте классы; обратите внимание, что я добавил разрывы строк в выводе для облегчения чтения.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
> require ‘./lesson_6’
=> true
> f = Fridge.new
=> #<Fridge:0x00000100a10378 @beverages=[], @foods=[]>
> f + Beverage.new(«water»)
=> [#<Beverage:0x000001009fe8d0 @name=»water», @time=2011-01-15 13:20:48 -0500>]
> f + Food.new(«bread»)
=> [#<Food:0x000001009d3c98 @name=»bread», @time=2011-01-15 13:20:59 -0500>]
> f + Food.new(«eggs»)
=> [
#<Food:0x000001009d3c98 @name=»bread», @time=2011-01-15 13:20:59 -0500>,
#<Food:0x000001009746a8 @name=»eggs», @time=2011-01-15 13:21:04 -0500>
]
> f + Beverage.new(«orange juice»)
=> [
#<Beverage:0x000001009fe8d0 @name=»water», @time=2011-01-15 13:20:48 -0500>,
#<Beverage:0x00000100907cd8 @name=»orange juice», @time=2011-01-15 13:21:16 -0500>
]
> f
=> #<Fridge:0x00000100a10378
@beverages=[
#<Beverage:0x000001009fe8d0 @name=»water», @time=2011-01-15 13:20:48 -0500>,
#<Beverage:0x00000100907cd8 @name=»orange juice», @time=2011-01-15 13:21:16 -0500> ],
foods[
#<Food:0x000001009d3c98 @name=»bread», @time=2011-01-15 13:20:59 -0500>,
#<Food:0x000001009746a8 @name=»eggs», @time=2011-01-15 13:21:04 -0500> ]
> f — «bread»
=> #<Food:0x000001009d3c98 @name=»bread», @time=2011-01-15 13:20:59 -0500>
> f
=> #<Fridge:0x00000100a10378
@beverages=[
#<Beverage:0x000001009fe8d0 @name=»water», @time=2011-01-15 13:20:48 -0500>,
#<Beverage:0x00000100907cd8 @name=»orange juice», @time=2011-01-15 13:21:16 -0500>],
foods[#<Food:0x000001009746a8 @name=»eggs», @time=2011-01-15 13:21:04 -0500>]
|
По мере @beverages
вы можете видеть, как что-то добавляется в массивы @beverages
и @foods
, а затем удаляется.
Получить и установить операторы
Теперь давайте напишем методы для операторов get и set, используемых с хешами. Вы видели это раньше:
1
2
3
|
person = {}
person[:name] = «Joe»
|
Но, поскольку эти операторы являются методами, мы можем сделать это следующим образом:
1
2
3
|
person.[]=(:age, 35) # to set
person.[](:name) # to get
|
Это верно; это нормальные методы, со специальным сахаром для вашего использования.
Давайте попробуем; мы сделаем Club
класс. В нашем клубе есть члены с разными ролями. Однако мы можем захотеть иметь более одного члена с данной ролью. Таким образом, наш экземпляр Club
будет отслеживать участников и их роли с помощью хэша. Если мы попытаемся назначить второго члена на роль, вместо того, чтобы перезаписать первого, мы добавим его.
01
02
03
04
05
06
07
08
09
10
11
12
13
|
class Club
def initialize
@members = {}
end
def [] (role)
@members[role]
end
def []= (role, member)
end
end
|
Получить версию довольно просто; мы просто пересылаем его в массив @members
. Но набор немного сложнее:
1
2
3
4
5
6
7
8
9
|
def []== (role, member)
if @members[role].nil?
@members[role] = member
elsif @members[role].is_a?
@members[role] = [ @members[role], member ]
else
@members[role].push member
end
end
|
Если эта роль не была установлена, мы просто установим значение этого ключа для нашего хэша участника. Если он был задан как строка, мы хотим преобразовать его в массив и поместить в этот массив исходный элемент и новый элемент. Наконец, если ни одна из этих опций не верна, это уже массив, и мы просто помещаем элемент в массив. Мы можем проверить этот класс следующим образом:
01
02
03
04
05
06
07
08
09
10
11
|
c = Club.new
c[:chair] = «Joe»
c[:engineer] = «John»
c[:engineer] = «Sue»
c[:chair] # «Joe»
c[:engingeer] # [ «John», «Sue» ]
|
Вот и вы!
Другие операторы
Конечно, это не единственные операторы, с которыми мы можем сделать это. Вот весь список:
- Арифметические операторы:
+ - * \
- Операторы получения и установки:
[] []=
- Оператор лопаты:
<<
- Операторы сравнения:
== < > <= >=
- Оператор равенства регистра:
===
- Побитовый оператор:
| & ^
| & ^
Спасибо за прочтение!
Если у вас есть какие-либо вопросы об этом уроке или что-либо еще, что мы обсуждали в Ruby, задавайте их в комментариях!