Статьи

Рубин для новичков: операторы и их методы

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, задавайте их в комментариях!