Что такое метаклассы. Для чего нужны метаклассы. Что такое метаобъектный протокол. Что такое Class::MOP. Для чего используется Class::MOP.
Что такое метаклассы?
В ООП метаклассом является класс, экземплярами которого являются не объекты, как обычно, а классы. Так же, как обычный класс определяет поведение некоторых объектов, метакласс определяет поведение некоторых классов и их экземпляров.
Не все языки программирования, поддерживающие парадигму ООП, поддерживают метаклассы. При этом, уровень поддержки метаклассов каждым языком может достаточно сильно отличаться. Отличаются так же и реализации протокола метаобъектов, принципы, которые управляют взаимодействием объектов, классов и метаклассов.
Среди самых популярных языков, метаклассы поддерживают: Objective-C, Python, Perl и Ruby.
Иногда идею метаклассов сравнивают с шаблоном проектирования "Фабрика".
Для чего нужны метаклассы?
Идея метаклассов возникает в объектно-ориентированных языках программирования. С ее помощью реализуется завершенный рекурсивный дизайн системы. Удобно при работе с иерархиями классов.
Для Perl в чистом виде, идея метаклассов абсолютно чужеродная. Хотя интересна с точки зрения архитектуры и реализации.
Что такое метаобъектный протокол?
Метаобъектный протокол - это механизм для определения и использования новых метаклассов.
Метаобъектный протокол определяет множество функций, которые содержат методы для классов. Программирование на уровне метаобъектного протокола - это определение новых классов вместе с методами для этих классов.
Метаклассы в perl
В чистом Perl 5 такие понятия, как пакет, класс, метод, конструктор не имеют четких различий и границ. Понятие атрибута вообще отсутствует.
Реализация метаклассов в Perl осуществляется с помощью метаобъектного протокола, который представлен системой Class::MOP. Class::MOP и метаклассы широко используются популярным Moose (Официальное определение: "Moose — расширение для Perl 5, позволяющее упростить реализацию ООП". Я не согласна с тем, что Moose что-то там упрощает. На мой взгляд, все совершенно наоборот).
Class::MOP четко разграничивает понятия классов, методов, пакетов, добавляет понятие атрибута. Moose, благодаря Class::MOP, приближает ООП в perl к общепринятым стандартам.
Т.е. по сути, Class::MOP - это больше, чем просто способ создавать метаклассы.
Метаобъектный протокол в perl, Class::MOP
Метаобъектный протокол создает абстрактные понятия классов, методов, объектов, атрибутов. Эти абстракции могут быть использованы для проверки и управления теми элементами системы, которые они описывают.
Для неподготовленного человека все вышенаписанное звучит, как полный бред. Поэтому, лучше всего начать разбирать метаклассы и метаобъектный протокол на примерах.
Метаобъектный протокол в perl реализован модулем Class::MOP и включает в себя 4 подпротокола:
- Class protocol - Средство управления классами. Реализуется через Class::MOP::Class .
- Attribute protocol - Реализует управление атрибутами классов. Реализуется через Class::MOP::Attribute . Может быть расширен с помощью наследования.
- Method protocol - Средство для работы с методами объектной системы. Реализуется через Class::MOP::Method .
- Instance protocol - Обеспечивает некоторый уровень абстракции для создания экземпляров объектов. Реализуется через Class::MOP::Instance
Instance protocol сегодня рассматриваться не будет.
Class protocol
Class::MOP::Class - класс метаобъекта. Данный протокол является самой большой и сложной частью Class::MOP.
Class::MOP::Class является подклассом Class::MOP::Module. Class::MOP::Module, в свою очередь, является подклассом Class::MOP::Package. Package Protocol предоставляет абстракцию для perl-пакетов и методы для работы с пространством имен.
Class::MOP::Class предоставляет множество методов, которые можно разделить по смысловым категориям:
-
создание класса
Методы этой категории создают объект Class::MOP::Class. Объект Class::MOP::Class является одиночкой и, либо представляет какой-нибудь существующий класс, либо используется для создания класса с нуля.
Созданный объект будет являться объектом метакласса.
Если вы попробуете повторно создать объект, то просто получите ссылку на уже созданный экземпляр.
-
информационные методы
Методы данной категории предназначены для получения информации о классе, над которым ведется работа.
-
методы для управления наследованием
Можно получить список всех подклассов, или, наоборот, стать наследником какого-то класса. Методы работают с @ISA.
-
методы для работы с методами класса
Можно использовать этот тип методов, для создания методов класса, их удаления и изменения. Данные методы в своей работе будут ссылаться на объекты Class::MOP::Method.
-
методы для работы с атрибутами классов
Методы будут работать с объектами класса Class::MOP::Attribute. Методы используются для задания, поиска, чтения и удаления атрибутов классов.
-
методы для заморозки
Методы данной категории "замораживают" класс. Класс становится стабильным, не изменяемым. После этого нельзя вызвать любые методы, которые будут изменять класс, добавлять или удалять новые атрибуты, методы класса и т.п.
-
модификаторы метода
Это возможность добавить некоторый код, который, при вызове метода класса будет исполняться до выполнения (before модификатор), после выполнения (модификатор after) и во время выполнения основного кода (around модификатор).
Пример использования Class::MOP::Class
Расширение функциональности существующего класса
Существующий класс, модуль Meta.pm:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package Meta; use strict; use Class::MOP; my $class = Class::MOP::Class->initialize(__PACKAGE__); sub new { my $type = shift; my $data = {}; my $obj = bless($data, $type); return $obj; } $class->add_method( 'get_name' => sub { return "ok"; } ); $class->get_all_methods(); 1; |
Подключили Class::MOP, затем использовали add_method для создания новых методов класса. Теперь можно использовать класс. Для юзера не будет видно никакой разницы, каким образом были созданы методы.
1 2 3 4 5 6 7 8 |
#!/usr/bin/perl use Meta; my $obj = Meta->new; my $res = $obj->get_name(); print $res."\n"; |
Создание нового класса
Можно создать новый класс. В одном модуле может быть создано несколько классов. А для того, чтобы задачка не была скучной, организуем наследование.
Файл модуля, Meta2.pm :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package Meta2; use strict; use Class::MOP; my $class = Class::MOP::Class->create('MyClass'); $class->add_method('get_info' => sub { return 'text info'; }); $class->get_all_methods(); my $ext_class = Class::MOP::Class->create('MyClassExt'); # делаем MyClassExt наследником класса MyClass $ext_class->superclasses('MyClass'); $ext_class->add_method('get_ext_info' => sub { return 'super info for you'; }); $ext_class->get_all_methods(); 1; |
Использование классов, файл meta2.pl:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#!/usr/bin/perl use strict; use Meta2; my $var = MyClass->get_info; print $var."\n"; $var = MyClassExt->get_ext_info; print $var."\n"; $var = MyClassExt->get_info; print $var."\n"; |
Результат запуска скрипта meta2.pl:
1 2 3 4 |
> perl meta2.pl text info super info for you text info |
Работает!
К тому моменту, концепция метаклассов уже стала вполне понятной.
Attribute protocol
Атрибутный протокол - это изобретение Class::MOP. Perl 5 не поддерживает атрибуты. Тем важнее разобраться, что это такое.
Атрибут класса или объекта - это переменная, связанная с классом или объектом. Доступ к атрибутам осуществляется по их имени. Этим атрибуты отличаются от данных, которые содержит объект, и доступ к которым осуществляется только через методы объекта.
У программиста perl закономерно возникает вопрос - нафига это нужно? Ответ: не все пользуются таким классным языком, как perl.
В некоторых объектно-ориентированных языках программирования (например, в Java) не существует глобальных переменных. В этом случае, атрибуты классов являются единственным способом хранения и использования глобальных данных.
Работа с атрибутами в Class::MOP::Class, на мой взгляд, уродлива и не удобна. Вот пример.
Файл модуля Meta4.pm:
1 2 3 4 5 6 7 8 9 10 11 |
package Meta4; use strict; use Class::MOP; my $class = Class::MOP::Class->create('MyClass'); $class->add_attribute(is_flag => (accessor => 'is_flag', default=> 1)); $class->get_all_attributes(); 1; |
Файл скрипта meta4.pl
1 2 3 4 5 6 7 8 9 10 11 12 |
use strict; use Meta4; my $class = MyClass->meta->get_attribute('is_flag')->associated_class; # задаем значение атрибуту MyClass->meta->get_attribute('is_flag')->set_value($class, '123'); # получаем значение атрибуту my $var = MyClass->meta->get_attribute('is_flag')->get_value($class); print $var."\n"; |
Вывод после запуска скрипта:
1 2 |
$ perl meta4.pl 123 |
Method protocol
Считается, что это это самый простой протокол. Реализован в классе Class::MOP::Method . Класс позволяет создать объект Class::MOP::Method, который содержит информацию о добавляемом методе класса.
В основном, методы объекта Class::MOP::Method просто возвращают техническую информацию. Если заглянуть в исходник Class::MOP::Method, можно увидеть, что документация к модулю занимает места больше, чем код модуля.
Простой пример, в котором я использую методы Class::MOP::Method . Класс Class::MOP::Class создает объект класса, и этот объект является "одиночкой". Чем я и злоупотребила в примере.
Модуль Meta3.pm
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
package Meta3; use strict; use Class::MOP; my $class = Class::MOP::Class->create('MyClass'); print "PACK: ".$class."\n"; $class->add_method('get_class' => sub { # данный код не создает новый объект, а вернет ссылку на уже существующий return Class::MOP::Class->create('MyClass'); }); $class->add_method('get_status' => sub { return 'OK'; }); $class->get_all_methods(); 1; |
Скрипт meta3.pl :
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/usr/bin/perl use strict; use Meta3; my $class_obj = MyClass->get_class(); print "SCRP: ".$class_obj."\n"; my $method = $class_obj->get_method('get_status'); print $method->package_name; |
В результате выполнения скрипта meta3.pl, мы получим данные, которые позволят убедиться, что объект класса, действительно, создается в единственном экземпляре:
1 2 3 4 |
> perl meta3.pl PACK: Class::MOP::Class=HASH(0x10ef624) SCRP: Class::MOP::Class=HASH(0x10ef624) MyClass |
Заключение
На этом закончим. После изучения Class::MOP, я стала лучше понимать принцип работы Moose. Хотя до сих пор не понимаю, в чем ее смысл. Если вам интересно более подробно разобрать концепцию метаклассов, рекомендую почитать документацию по системе Lisp CLOS, которая считается образцовой реализацией метаобъектного протокола.
Подпишитесь на обновления блога, если хотите получить руководство по Moose для начинающих, которое сейчас готовится к публикации.
Полезные ссылки
search.cpan.org: Class::MOP::Class
Dynamic data model creation using Class::MOP::Class not working
На мой взгляд, одна из целей всяких дополнительных реализаций объектных систем в Perl - это получение большего контроля над большой системой. Можно запретить программистам изменять некий аттрибут или допускать хранение в этом аттрибуте только чисел и т.п. Если в процессе работы программы произойдёт попытка сделать недопустимое - об это узнают до того, как проблема проявится где-то позже и более сложным образом.
Другая цель - это придание программе возможности к саморефлексии. Дать программе возможность получать информацию о каком-то объекте - поддерживается ли определённый метод, какие методы есть, можно ли в них писать и т.п. Это позволяет делать программы более динамичными и гибкими, т.к. благодаря этому становится меньше хардкода. В маленьких программах это может быть не столь важно, потому что программист может удержать в голове все эти знания и при необходимости перепроектировать программу, в больших - приобретает важность.