Лучшая практика: создание модуля с ожиданием типа, в который он включен?
При определении модуля в Ruby я видел примеры, в которых модуль ожидает класс (ы), в который он должен быть включен, чтобы иметь определенные определения и функциональность.
Например, модуль может вызвать метод, который сам по себе не содержит ожидание того, что класс, в который он включен, содержит определение этого метода.
Для меня это отличная парадигма от моей истории строго типизированных языков, в которых отсутствует возможность включать произвольный набор логики (Ruby в целом - совершенно другая парадигма в этом отношении).
Является ли это приемлемым стилем в соответствии со стандартами ruby или, по крайней мере, встречается достаточно часто в зрелых базах кода, которые можно считать "нормальными"?
Я понимаю, что это несколько субъективный вопрос, однако я смотрю, чтобы увидеть, происходит ли это достаточно часто, чтобы это можно было бы рассмотреть в рамках "нормы", если бы они построили модуль как таковой.
2 ответа
Поскольку Ruby очень свободно набирается, полагаясь больше на способность реагировать на методы, так называемую " утиную", чем на класс, трудно взять на себя ответственность за эти вещи.
Если ваш модуль включен в контекст, который несовместим, ваша задача как модуля - ничего не делать с этим. Заранее нельзя узнать, будет ли какой-либо вызов метода успешным или неуспешным, потому что после включения вашего модуля могут произойти другие вещи, которые сделают какую-то проверку преждевременной.
Учитывая эти ограничения, важно попытаться спроектировать вещи, чтобы избежать такой двусмысленности.
Возьмите Enumerable в качестве примера. Для правильной работы при включении необходимо определить несколько методов в контексте, к которому он относится. Если они не определены, модуль не будет работать, но ответственность за этот сбой возлагается не на Enumerable, а на модуль, который его включил.
В основном, перед вами include SomeModule
Вы должны быть уверены, что зависимости, если таковые имеются, для включения этого модуля выполнены. Как участник, вы также принимаете на себя всю ответственность за любые конфликты, которые это может вызвать.
Другие языки имеют более жесткие методы проверки целостности, такие как функция протоколов Objective C или наличие базового класса в C++. Поскольку в Ruby этого нет, чтобы убедиться, что все работает правильно, используются автоматизированные тестовые наборы. Вы можете заметить, что сообщество Ruby очень ориентировано на тестирование, и такая двусмысленность является одной из многих причин, почему это так.
Да, это приемлемо Вы видите такие вещи очень часто, даже в основной библиотеке, с такими модулями, как Comparable
а также Enumerable
оба ожидают, что определенные методы будут реализованы во включающем классе.
Такое ожидание можно сравнить с интерфейсами в Java, только с уткой против структурной типизации.
Помимо таких широко применимых модулей, вы видите это много со сложными классами, которые разбиты на более мелкие модульные задачи. Модули по-прежнему ожидают работы в контексте включающего класса, но код разбит на куски, что делает проблемы подключаемыми, а включающий класс - более управляемым размером.
Этот вид шаблона:
lib/
someclass.rb
someclass/
concern_a.rb
concern_b.rb
... где concern_a
а также concern_b
модули включены в someclass
, это довольно часто, и я думаю, что это достаточно интуитивно понятно, что вы не задаетесь вопросом, какие методы в concern_a
делают.
Конечно, эта гибкость не без цены. В частности, в Rails, учитывая обилие динамически определенных методов (за исключением greps) и магической автозагрузки, поиск искомого кода иногда может быть затруднен, особенно когда речь идет об унаследованном от не звездных версий коде, который вы унаследовали, например.