Я создал довольно много модулей, которые я не собирался многократно использовать. Это облегчает тестирование группы связанных методов изолированно, и классы более читабельны, если их длина составляет всего несколько сотен строк, а не тысяч. Как всегда, нужно соблюдать баланс.
Я также создал модули, которые ожидают, что включающий класс определит методы экземпляра, чтобы методы, определенные в модуле, могли их использовать. Я бы не сказал, что это ужасно элегантно, но возможно, если вы делитесь кодом только между несколькими классами и хорошо документируете его. Вы также можете вызвать исключение, если класс не определяет необходимые методы:
module Aggregator
def aggregate
unless respond_to?(:collection)
raise Exception.new("Classes including #{self} must define #collection")
end
# ...
end
end
Я бы не решался зависеть от переменных общего экземпляра.
Самая большая проблема, которую я вижу при повторном открытии классов, - это просто управление исходным кодом. Вы бы в конечном итоге с несколькими копиями aggregator.rb
в разных каталогах? Определяется ли порядок загрузки этих файлов и влияет ли это на переопределение или вызов методов в классе? По крайней мере, для модулей порядок включения в источнике явный.
Обновление: В комментарии Стивен спросил о тестировании модуля, который должен быть включен в класс.
RSpec предлагает shared_examples
в качестве удобного способа проверки общего поведения. Вы можете определить поведение модуля в общем контексте, а затем объявить, что каждый из включающих классов также должен демонстрировать такое поведение:
# spec/shared_examples/aggregator_examples.rb
shared_examples_for 'Aggregator' do
describe 'when aggregating records' do
it 'should accumulate values' do
# ...
end
end
end
# spec/models/model_spec.rb
describe Model
it_should_behave_like 'Aggregator'
end
Даже если вы не используете RSpec, вы все равно можете создать простой класс-заглушку, включающий ваш модуль, а затем написать тесты для экземпляров этого класса:
# test/unit/aggregator_test.rb
class AggregatorTester
attr_accessor :collection
def initialize(collection)
self.collection = collection
end
include Aggregator
end
def test_aggregation
assert_equal 6, AggregatorTester.new([1, 2, 3]).aggregate
end