<<Метапрограммирование на Ruby
Лекция 4: ООП и контейнеры. Примесь Еnumerable
Давайте определим метод sum для массивов:
class Array
def sum
inject{|a,b| a+b}
end
end
[1,2,3,4].sum #=> 10
['a', 'b', 'c'].sum #=> 'abc'
Мы здесь к существующему стандартному классу Array добавили новый метод.
Изменение определенных классов называется monkey-patching (обезьяны тут не причем, правильнее этот термин называть партизанскими патчами - guerillas patching).
Метод
sum
было бы хорошо иметь для всех контейнеров.
Мы можем написать такой
цикл по классам:
require 'set'
[Set, Array, Range].each do |klass|
klass.class_eval do
def sum
inject{|a,b| a+b}
end
end
end
[1,2,3,4].sum #=> 10
s = Set[1,2,1,1,2,3,1]
s.sum #=> 6
(1..10).sum # => 55
Но это неправильный подход.
Правильно использовать примеси, которые являются альтернативой множественному наследованию.
module SumMixin
def sum
inject{|a,b| a+b}
end
end
[Set, Array, Range].each do |klass|
klass.class_eval do
include SumMixin
end
end
Но дело в том, что уже есть примесь Enumerable, которая включена во все стандартные классы контейнеров.
И правильное решение выглядит так:
module Enumerable
def sum
inject{|a,b| a+b}
end
end
Чтобы подчеркнуть тот факт, что модуль Enumerable уже определен до нас и мы его лишь патчим, пишем так
Enumerable.module_eval do
def sum
inject{|a,b| a+b}
end
end
Если бы модуль Enumerable не был определен, то последний код (в отличие от предыдущего) кинул бы Exception.
Важные утверждения:
- Абстрактных классов в Ruby нет, и они и не нужны. Кстати, зачем нужны абстрактные классы в C++ (интерфейсы в Java)?
- Множественного наследования в Ruby нет. А оно и не нужно, если есть примеси. Кстати, зачем оно нужно в C++? Приведите примеры уместного использования множественного наследования в C++.
- Примеси более адекватный способ модуляризации функционала в языках, где основой является Duck Typing.
- Duck Typing
- идиомы:
* "не утка, а крякает как утка"
* "подкинуть утку"
- Определение: методология языка программирования, позволяющая не указывать типы аргументов у методов. От аргументов требуется, чтобы они имели ряд instance-методов.
- Эта методология позволила нам написать метод sum, работающий как для чисел (суммирования), так и для строк (конкатенация).
- Примеси это функционал, примешиваемый к уткам (различным классам). Примесям нужно лишь, чтобы классы, к которым их примешали, имели определенный набор методов. Так, примеси Enumerable нужен лишь метод each, итерирующий элементы контейнера (в подарок за это она предоставляет методы max, min, sort, each_with_index, inject). Это позволяет не создавать интерфейсы для каждого хоть сколько-нибудь целостного набора методов.
ObjectSpace.each_object(Class) do |klass|
puts klass if klass.include?(Enumerable)
end