Первые два довольно очевидны. Третий метод отмечен звездочкой, так как здесь есть одна проблема, которую мы обсудим чуть позже в этой главе. Четвертое заслуживает дальнейшего объяснения. По сути, это означает, что вы можете использовать комбинацию первых трех, чтобы отфильтровать нужные вам типы. Примером этого может быть перебор всех типов, которые наследуются от определенного базового класса
Самый распространенный способ перебора типов — через подклассы родительского типа. Это могут быть либо
Итерация подклассов типа
Прежде чем мы перейдем к более сложным примерам, давайте сосредоточимся на более простом варианте использования перебора подклассов типа с использованием следующего дерева наследования:
abstract class Vehicle; end
abstract class Car < Vehicle; end
class SUV < Vehicle; end
class Sedan < Car; end
class Van < Car; end
Первое, что нам нужно, это TypeNode
родительского типа, подклассы которого мы хотим перебрать. В нашем случае это будет Vehicle
, но это не обязательно должен быть самый верхний тип. Мы могли бы с тем же успехом выбрать Car
, если бы она лучше соответствовала нашим потребностям.
Если вы помните первую главу этой части, мы смогли получить TypeNode
с помощью специальной макропеременной @type
. Однако это будет работать только в том случае, если мы хотим перебирать типы в контексте типа Vehicle
. Если вы хотите выполнить итерацию за пределами этого типа, вам нужно будет использовать полное имя родительского типа.
Когда у нас есть TypeNode
, мы можем использовать два метода в зависимости от того, что именно мы хотим сделать. TypeNode#subclasses
можно использовать для получения прямых подклассов этого типа. TypeNode#all_subclasses
можно использовать для получения всех подклассов этого типа, включая подклассы подклассов и так далее. Например, добавьте в файл следующие две строки вместе с показанным ранее деревом наследования:
{{pp Vehicle.subclasses}}
{{pp Vehicle.all_subclasses}}
В результате компиляции программы на консоль будут выведены две строки: первая — [Car, SUV]
, а вторая — [Car, Sedan, Van, SUV]
. Вторая строка длиннее, поскольку она также включает подклассы типа Car
, который не включен в первую строку, поскольку Van
и Sedan
не являются прямыми дочерними элементами типа Vehicle
.
Также обратите внимание, что массив содержит как конкретные, так и абстрактные типы. На это стоит обратить внимание, поскольку если бы вы захотели перебрать типы и создать их экземпляры, это не удалось бы, поскольку был бы включен абстрактный тип Car
. Чтобы этот пример работал, нам нужно отфильтровать список типов до тех, которые не являются абстрактными. Оба метода в предыдущем примере возвращают ArrayLiteral(TypeNode)
. По этой причине мы можем использовать метод ArrayLiteral#reject
для удаления абстрактных типов. Код для этого будет выглядеть так:
{% for type in Vehicle.all_subclasses.reject &.abstract? %}
pp {{type}}.new
{% end %}
Запуск этого в конечном итоге приведет к печати нового экземпляра типов Sedan
, Van
, и SUV
. Мы можем пойти дальше в этой идее фильтрации и включить более сложную логику, например, использование данных аннотаций для определения того, следует ли включать тип.
Например, предположим, что мы хотим получить подмножество типов, имеющих аннотацию, исключая те, у которых есть определенное поле аннотации. В этом примере мы будем использовать следующие типы:
annotation MyAnnotation; end
abstract class Parent; end
@[MyAnnotation(id: 456)]
class Child < Parent; end
@[MyAnnotation]
class Foo; end
@[MyAnnotation(id: 123)]
class Bar; end
class Baz; end
У нас пять занятий, включая одно реферативное. Мы также определили аннотацию и применили ее к некоторым типам. Кроме того, некоторые из этих аннотаций также включают поле id
, в котором установлено некоторое число. Используя эти классы, давайте переберем только те, у которых есть аннотация и либо нет поля id
, либо ID
является четным числом.