Одной из самых похвальных особенностей Ruby является его поддержка техники, известной как типирование утки . Несмотря на юмористическое название, Duck Typing позволяет разработчикам Ruby писать сжатый, чистый и читаемый код с минимальными усилиями — все то, что привлекло нас в Ruby.
«Что бы сделали Утка Доджерс?»
Основная предпосылка типизации утки проста. Если сущность выглядит, ходит и крякает как утка, во всех смыслах и целях справедливо предположить, что она имеет дело с членом вида anas platyrhynchos. В практических терминах Ruby это означает, что можно попробовать вызвать любой метод для любого объекта, независимо от его класса.
Чтобы продемонстрировать, вот простой сценарий: вы работаете с системой, которая отслеживает отгрузочные манифесты для каждого контейнера на корабле. Данные для каждого манифеста легко получить через веб-сервис RESTful:
# Fields:
# * package_count: number of packages in manifest (integer)
# * mass: total mass of all packages, in kilograms (BigDecimal)
class ShippingManifest
attr_accessor :package_count, :mass
end
Каждый ShippingManifest
Ваша задача — взять набор манифестов (предоставляется в Array
«Вы говорите, что монстр Лох-Несс живет в вашем джакузи?»
Проблема выглядит достаточно просто, верно? Возможно, мы могли бы разбить его на девять или десять строк кода:
def summarise_manifests
# Grab the latest set of Manifests (returns an Array)
manifest_set = ShippingManifest.all
# Initialize our counters
total_package_count = 0
total_mass = BigDecimal("0")
manifest_set.each do |m|
total_package_count += m.package_count
total_mass += m.mass
end
return [total_package_count, total_mass]
end
Легко, но многословно. Можно сократить этот метод до одной строки кода:
def summarise_manifests
ShippingManifest.all.sum
end
Как? Утка печатает!
«Ключ от скелета, а? Где ты это взял?
Наше однострочное решение использует одну из самых мощных функций Ruby: модуль Enumerable
Enumerable
Array
Hash.
В частности, мы используем метод, определенный в ActiveSupport
sum
, который доступен в каждом проекте Rails. Как следует из названия, он используется для расчета суммы набора объектов. Это делается с помощью inject
, функции, которая использует блок, чтобы свести набор объектов к одному значению. В случае sum
Это имеет смысл, когда мы говорим о числах: 1 + 1 = 2
Также легко увидеть, как его можно использовать для объединения строк:
['foo', 'bar', 'baz'].sum # => 'foobarbaz'
Но как Ruby знает, как добавить два экземпляра нашего класса ShippingManifest
Легко. Мы говорим это как.
«Это только часть того, кто я есть. Я на самом деле довольно сложный.
«Все является объектом» — одна из основных мантр Руби. Следствием этого является то, что (почти) каждый оператор на самом деле является вызовом метода для экземпляра объекта, что дает нам возможность определить нашу собственную реализацию для этого оператора. Это означает, что мы можем сделать это:
class ShippingManifest < ActiveResource::Base
# Returns a new instance of ShippingManifest containing the
# sum of both `mass` and `package_count`
def +(other)
self.dup.tap do |neu|
neu.mass = @mass + other.mass
neu.package_count = @package_count + other.package_count
end
end
end
Добавив несколько простых строк кода, мы добавили функциональность, которая сделала потенциальный ShippingManifest
«И позвольте мне еще раз напомнить вам, ребята, что вы слушаете Истину или… Аааа!»
Реализация пользовательского поведения для оператора +
Существуют другие операторы, для которых реализовано пользовательское поведение: &
*
-
<<.
Даже ==
Нужно отсортировать набор записей? Реализуйте оператор сравнения ( <=>
Enumerable.
Как всегда, некоторые примеры кода для этого поста доступны в нашей учетной записи GitHub — не стесняйтесь их раскошелиться и поэкспериментировать! Для начала попробуйте реализовать поведение для -
<=>
Enumerable.
Если у вас есть вопросы по любому другому аспекту Ruby, не стесняйтесь спрашивать в комментариях!