Как упоминалось ранее, наш тип PropertyMetadata
имеет третью универсальную переменную, которой мы присваиваем индекс связанной переменной экземпляра. Основной вариант использования этого заключается в том, что мы можем затем использовать это для извлечения значения, которое представляет экземпляр метаданных, в сочетании с другим трюком.
Если вам интересно, нет, нет способа волшебным образом получить значение из воздуха только потому, что у нас есть индекс переменной экземпляра и TypeNode
типа, которому оно принадлежит. Для извлечения нам понадобится реальный экземпляр MyClass
. Чтобы учесть это, нам нужно добавить в PropertyMetadata
несколько дополнительных методов:
def value(obj : ClassType)
{% begin %}
obj.@{{ClassType.instance_vars[PropertyIdx].name.id}}
{% end %}
end
def value(obj) i : NoReturn
raise "BUG: Invoked default value method."
end
Другая хитрость, которая делает эту реализацию возможной, — это возможность прямого доступа к переменным экземпляра типа, даже если у них нет метода получения через синтаксис obj.@ivar_name
. В предисловии к этому я скажу, что вам не следует использовать это часто, если вообще когда-либо, за исключением очень специфических случаев использования, таких как этот. Это антишаблон, и его следует избегать, когда это возможно. В 99% случаев вам следует вместо этого определить метод получения, чтобы вместо этого предоставить значение переменной экземпляра.
С учетом вышесказанного реализация использует индекс переменной экземпляра для доступа к ее имени и использования его для создания предыдущего синтаксиса. Поскольку все это происходит во время компиляции, фактический метод, который добавляется, например, для переменной экземпляра name, будет выглядеть следующим образом:
def value(obj : ClassType)
obj.@name
end
Мы также определили еще одну перегрузку, которая вызывает исключение, если вы передаете экземпляр объекта, тип которого отличается от типа, представленного экземпляром метаданных. В основном это делается для того, чтобы компилятор был доволен, когда существует более одного типа Metadatable
. На практике этого никогда не должно происходить, поскольку конечный пользователь не будет напрямую взаимодействовать с этими экземплярами метаданных, поскольку это будет внутренней деталью реализации.
Мы можем пойти дальше и опробовать это, добавив в нашу программу следующее и запустив ее:
my_class = MyClass.new
pp MyClass.metadata["name"].value my_class
Вы должны увидеть значение свойства name, напечатанное на вашем терминале, которое в данном случае будет "Jim"
. У этой реализации есть один недостаток. Тип значения, возвращаемого методом #value
, будет состоять из объединения всех свойств, имеющих аннотацию данного типа. Например, typeof(name_value)
вернет (String | Time)
, что в целом приводит к менее эффективному представлению памяти.
Этот шаблон отлично подходит для реализации мощных внутренних API, но его следует использовать с осторожностью, не использовать в «горячем» пути приложения и даже не публиковать публично.
Если вы помните
Конечно, это, скорее всего, не тот шаблон, который вам понадобится очень часто, если вообще когда-либо понадобится, но полезно знать, если такая необходимость когда-нибудь возникнет. Это также хороший пример того, насколько мощными могут быть макросы, если вы мыслите немного нестандартно. В качестве дополнительного бонуса мы можем еще раз продвинуть эту модель на шаг дальше.
Моделирование всего класса
В предыдущем разделе мы рассмотрели, как можно использовать структуру для представления определенного элемента, например переменной экземпляра или метода, вместе с данными из примененной к нему аннотации. Другой шаблон предполагает создание специального типа для хранения этих данных вместо непосредственного использования массива или хеша. Этот шаблон может быть полезен для отделения метаданных о типе от самого типа, а также для добавления дополнительных методов/свойств без необходимости засорять фактический тип.