Фабрика объектов замещающих классов
Glossary Item Box
Общие сведения
Разработка на платформе Creatio базируется на основных приемах объектно-ориентированного программирования, в частности, принципа открытости/закрытости. Согласно ему все сущности (классы, модули, функции) должны быть закрыты для изменения и открыты для расширения. Это означает, что новое поведение и функциональность должны реализовываться путем добавления новых сущностей, а изменением существующих. Такой подход лежит в основе механизма пакетов Creatio.
Конфигурационные элементы, расположенные в предустановленных пакетах, недоступны для изменения. Разработка дополнительной функциональности и модификация существующей выполняются исключительно в пользовательских пакетах. Описание работы с пакетами содержится в блоке статей "Инструменты разработки. Пакеты".
При доработке конфигурации могут возникнуть задачи, которые требуют изменения или расширения базовой функциональности. Вносить изменения в логику предустановленных объектов запрещено на уровне системы, поэтому для решения подобных задач должен использоваться механизм замещения.
Чтобы изменить поведение предустановленного объекта, в пользовательском пакете создается новый объект, унаследованный от предустановленного. Для созданного пользовательского объекта устанавливается признак, что он должен замещать своего родителя в иерархии. Все модификации, которые необходимо применить к предустановленному объекту, реализуются в созданном замещающем объекте. В дальнейшем при обращении к предустановленному объекту система будет выполнять логику объекта, который его замещает.
Замещение одного и того же базового объекта можно выполнять в нескольких пользовательских пакетах. При этом конечная реализация замещающего объекта в скомпилированной конфигурации определяется иерархией пакетов, содержащих таккие замещающие объекты.
Описание механизма замещения
Создание замещающего конфигурационного элемента
Для создания замещающего конфигурационного элемента необходимо выполнить следующие действия:
- Для пользовательского пакета, в котором будут созданы замещающие сущности, установить зависимость от пакета, содержащего замещаемую сущность.
- Создать замещающий объект или замещающую страницу. Для создания замещающего объекта необходимо в разделе [Конфигурация] ([Configuration]) выполнить действия [Добавить] —> [Замещающий объект] ([Add] —> [Replacing Object]). Для создания замещающей страницы необходимо в разделе [Конфигурация] ([Configuration]) выполнить действия [Добавить] —> [Замещающая страница] ([Add] —> [Replacing Page]).
Создание замещающего класса
Специфические задачи по доработке конфигурации могут потребовать изменения программного кода схем типа [Исходный код] ([Source Code]). В этом случае в качестве замещаемых элементов выступают классы, реализованные в схемах других пакетов.
Принцип замещения классов аналогичен принципу замещения конфигурационных элементов, однако создание и использование экземпляров замещаемых классов в конфигурации имеют специфические особенности.
Для создания замещающего класса необходимо выполнить следующие действия:
- Для пользовательского пакета, в котором будет создан замещающий класс, установить зависимость от пакета, содержащего замещаемый класс.
- В пользовательском пакете создать класс, который является наследником замещаемого класса.
- Отметить замещающий класс атрибутом [Override].
- Реализовать логику замещающего класса (например, добавление новых свойств и методов, расширяющих функциональность замещаемого класса, либо перегрузку методов замещаемого класса с целью изменения его поведения и т.д.).
Инстанцирование замещающего класса выполняется через фабрику объектов замещающих классов. У фабрики запрашивается экземпляр замещаемого типа, в результате чего возвращается экземпляр соответствующего замещающего типа, который вычисляется фабрикой по дереву зависимостей типов в схемах исходных кодов.
Ниже более подробно рассмотрены особенности реализации и применения фабрики, а также приведен кейс по созданию и использованию объектов замещающих классов.
Фабрика объектов замещающих классов
Атрибут Override
Тип атрибута [Override] описан в пространстве имен Terrasoft.Core.Factories и является прямым наследником базового типа System.Attribute. Атрибут [Override] может применяться только к классам. Классы, помеченные этим атрибутом, участвуют в построении дерева зависимостей замещаемых и замещающих типов фабрики.
Применение атрибута [Override] к классу MySubstituteClass, который замещает класс SubstitutableClass, содержится ниже.
[Override] public class MySubstituteClass : SubstitutableClass { // Реализация класса. }
Принцип работы фабрики. Класс ClassFactory
Статический класс ClassFactory реализует фабрику по созданию замещающих объектов Creatio. В своей работе фабрика использует open-source фреймворк внедрения зависимостей Ninject. По сути, при инициализации фабрика собирает информацию обо всех замещаемых типах конфигурации, соответствующим образом конфигурирует ядро фреймворка, который на основании сконфигурированных зависимостей возвращает экземпляры требуемых типов.
Инициализация фабрики осуществляется в момент первого обращения к ней, то есть при первой попытке получить экземпляр замещающего типа. При этом выполняется следующее:
-
Фабрика выполняет анализ типов конфигурационной сборки на наличие замещающих типов. Класс, помеченный атрибутом [Override], интерпретируется фабрикой как замещающий, а родитель этого класса — как замещаемый. В результате формируется дерево зависимостей типов в виде списка пар значений [Замещаемый тип] —> [Замещающий тип]. При этом в дереве иерархии замещения не учитываются промежуточные типы. В конечном итоге исходный класс замещается последним наследником в иерархии замещения.
Иерархия классов содержится ниже.
// Исходный класс. public class ClassA { } // Класс, который замещает ClassА. [Override] public class ClassB : ClassA { } // Класс, который замещает ClassВ. [Override] public class ClassC : ClassB { }
По этой иерархии фабрика построит следующее дерево зависимостей:
ClassA —> ClassC
ClassB —> ClassC
Тогда иерархия замещения типов ClassA —> ClassB —> ClassC. То есть ClassA будет замещаться типом ClassC, который является последним наследником в иерархии замещения, а не промежуточным типом ClassB. Таким образом, при запросе экземпляра типа ClassA или ClassB фабрика будет возвращать экземпляр ClassC. - Средствами Ninject фабрика выполняет привязку типов замещения в соответствии с построенным деревом зависимостей типов.
Создание экземпляра замещаемого типа
Для получения экземпляра замещающего типа фабрика ClassFactory предоставляет один публичный статический параметризованный метод Get<T>. В качестве обобщенного параметра метода выступает замещаемый тип.
Получение экземпляра типа, который замещает тип SubstitutableClass, содержится ниже. При этом нет необходимости явно указывать тип создаваемого экземпляра. Благодаря предварительной инициализации, фабрика определяет, какой именно тип замещает запрашиваемый тип и создает соответствующий экземпляр.
var substituteObject = ClassFactory.Get<SubstitutableClass>();
В качестве параметров метод Get<T> может принимать массив объектов ConstructorArgument. Каждый из этих объектов представляет собой аргумент конструктора класса, создаваемого с помощью фабрики. Таким образом, фабрика позволяет инстанцировать замещающие объекты с параметризированными конструкторами. При этом ядро фабрики самостоятельно разрешает все зависимости, необходимые для создания или работы объекта.
В общем случае рекомендуется, чтобы конструкторы замещающих классов имели сигнатуру, совпадающую с сигнатурой родительского класса. Если логика реализации замещающего класса требует объявления конструктора с пользовательской сигнатурой, важно соблюдать следующие правила создания и вызова конструкторов замещаемого и замещающего классов:
- Если замещаемый класс не имеет явно реализованного параметризированного конструктора (имеет только конструктор по умолчанию), то в замещающем его классе допускается явная реализация своего конструктора без каких-либо ограничений. При этом соблюдается стандартный порядок вызовов конструкторов родительского и дочернего классов. В этом случае необходимо помнить, что при инстанцировании исходного (замещаемого) класса через фабрику ей необходимо передавать правильные параметры для инициализации свойств класса, который в итоге будет замещать исходный. Несоблюдение этого правила приведет к ошибке времени выполнения.
- Если замещаемый класс имеет конструктор с параметрами, то в замещающем классе в обязательном порядке должен быть реализован конструктор. Конструктор замещающего класса должен явно вызывать параметризированный конструктор своего родителя (то есть замещаемого класса), которому передаются параметры для корректной инициализации родительских свойств. При этом конструктор замещающего класса может выполнять инициализацию своих свойств либо оставаться пустым. Несоблюдение этого правила приведет к ошибке времени выполнения. В итоге ответственность за корректность инициализации свойств замещаемого и замещающего объектов лежит на разработчике. Ниже приведен ряд примеров, демонстрирующих различные кейсы инстанцирования замещаемых типов с параметризированными конструкторами.
Примеры
Пример 1
Замещаемый класс SubstitutableClass имеет один конструктор по умолчанию. В замещающем классе SubstituteClass объявлены два конструктора — конструктор по умолчанию и параметризированный. Замещающий класс переопределяет родительский метод GetMultipliedValue(). Варианты инстанцирования и вызова метода GetMultipliedValue() замещающего класса с конструктором по умолчанию и параметризированным конструктором представлены ниже.
// Объявление замещаемого класса. public class SubstitutableClass { // Свойство класса, инициализация которого будет выполняться в конструкторе. public int OriginalValue { get; private set; } // Конструктор по умолчанию, который инициализирует свойство OriginalValue значением 10. public SubstitutableClass() { OriginalValue = 10; } // Метод, который возвращает значение OriginalValue, умноженное на 2. Этот метод может быть // переопределен в замещающих классах. public virtual int GetMultipliedValue() { return OriginalValue * 2; } } // Объявление класса, замещающего SubstitutableClass. [Terrasoft.Core.Factories.Override] public class SubstituteClass : SubstitutableClass { // Свойство класса SubstituteClass. public int AdditionalValue { get; private set; } // Конструктор по умолчанию, который инициализирует свойство AdditionalValue значением 15. Здесь можно не // вызывать конструктор родительского класса SubstitutableClass, так как в родительском классе // объявлен только конструктор по умолчанию (он вызывается неявно при создании экземпляра SubstituteClass). public SubstituteClass() { AdditionalValue = 15; } // Конструктор с параметром, который инициализирует свойство AdditionalValue значением, переданным в // качестве параметра. Здесь аналогично явно не вызывается родительский конструктор. public SubstituteClass(int paramValue) { AdditionalValue = paramValue; } // Замещение родительского метода. Метод будет возвращать значение AdditionalValue, умноженное на 3. public override int GetMultipliedValue() { return AdditionalValue * 3; } }
Варианты получения экземпляра замещающего класса через фабрику представлены ниже.
// Получение экземпляра класса, который замещает SubstitutableClass. // Фабрика вернет экземпляр SubstituteClass, инициализированный конструктором без параметров. var substituteObject = ClassFactory.Get<SubstitutableClass>(); // Переменная будет содержать значение 10. Свойство OriginalValue инициализировано родительским // конструктором по умолчанию, который был неявно вызван при создании экземпляра замещающего класса. var originalValue = substituteObject.OriginalValue; // Здесь будет вызван метод замещающего класса, который будет возвращать значение AdditionalValue, // умноженное на 3. Переменная будет содержать значение 45, так как AdditionalValue было // инициализировано значением 15. var additionalValue = substituteObject.GetMultipliedValue(); // Получение экземпляра замещающего класса, который инициализирован параметризированным // конструктором. При этом имя параметра ConstructorArgument должно совпадать с именем параметра в // конструкторе класса. var substituteObjectWithParameters = ClassFactory.Get<SubstitutableClass>( new ConstructorArgument("paramValue", 20)); // Переменная будет содержать значение 10. var originalValueParametrized = substituteObjectWithParameters.OriginalValue; // Переменная будет содержать значение 60, так как свойство AdditionalValue инициализировано // значением 20. var additionalValueParametrized = substituteObjectWithParameters.GetMultipliedValue();
Пример 2
Замещаемый класс SubstitutableClass имеет один параметризированный конструктор. Замещающий класс SubstituteClass также имеет один параметризированный конструктор.
// Объявление замещаемого класса. public class SubstitutableClass { // Свойство класса, инициализация которого будет выполняться в конструкторе. public int OriginalValue { get; private set; } // Параметризированный конструктор, который инициализирует свойство OriginalValue переданным в качестве // параметра значением. public SubstitutableClass(int originalParamValue) { OriginalValue = originalParamValue; } // Метод, который возвращает значение OriginalValue, умноженное на 2. Этот метод может быть // переопределен в замещающих классах. public virtual int GetMultipliedValue() { return OriginalValue * 2; } } // Объявление класса, замещающего SubstitutableClass. [Terrasoft.Core.Factories.Override] public class SubstituteClass : SubstitutableClass { // Свойство класса SubstituteClass. public int AdditionalValue { get; private set; } // Конструктор с параметром, который инициализирует свойство AdditionalValue значением, переданным // в качестве параметра. Здесь необходимо явно вызывать родительский конструктор для инициализации // родительского свойства. Если этого не сделать, то возникнет ошибка компиляции. public SubstituteClass(int paramValue) : base(paramValue + 8) { AdditionalValue = paramValue; } // Замещение родительского метода. Метод будет возвращать значение AdditionalValue, умноженное на 3. public override int GetMultipliedValue() { return AdditionalValue * 3; } }
Создание и использование экземпляра замещающего класса через фабрику представлены ниже.
// Получение экземпляра замещающего класса, который инициализирован параметризированным // конструктором. При этом имя параметра ConstructorArgument должно совпадать с именем параметра в // конструкторе класса. var substituteObjectWithParameters = ClassFactory.Get<SubstitutableClass>( new ConstructorArgument("paramValue", 10)); // Переменная будет содержать значение 18. var originalValueParametrized = substituteObjectWithParameters.OriginalValue; // Переменная будет содержать значение 30. var additionalValueParametrized = substituteObjectWithParameters.GetMultipliedValue();
Пример 3
Замещаемый класс SubstitutableClass имеет один параметризированный конструктор. В замещающем классе SubstituteClass не добавляется никаких новых свойств, а только переопределяется метод GetMultipliedValue(), который будет возвращать фиксированное значение. Класс SubstituteClass не требует первичной инициализации своих свойств, в нем должен быть явно объявлен конструктор, который вызывает родительский конструктор с параметрами для корректной инициализации родительских свойств.
// Объявление замещаемого класса. public class SubstitutableClass { // Свойство класса, инициализация которого будет выполняться в конструкторе. public int OriginalValue { get; private set; } // Параметризированный конструктор, который инициализирует свойство OriginalValue переданным в качестве // параметра значением. public SubstitutableClass(int originalParamValue) { OriginalValue = originalParamValue; } // Метод, который возвращает значение OriginalValue, умноженное на 2. Этот метод может быть // переопределен в замещающих классах. public virtual int GetMultipliedValue() { return OriginalValue * 2; } } // Объявление класса, замещающего SubstitutableClass. [Terrasoft.Core.Factories.Override] public class SubstituteClass : SubstitutableClass { // Пустой конструктор по умолчанию, который явно вызывает конструктор родительского класса для корректной // инициализации родительских свойств. public SubstituteClass() : base(0) { } // Также можно использовать пустой конструктор с параметрами для того, чтобы передать эти параметры в конструктор // родительского класса. public SubstituteClass(int someValue) : base(someValue) { } // Замещение родительского метода. Метод будет возвращать фиксированное значение. public override int GetMultipliedValue() { return 111; } }
Пример разработки замещающего класса содержится в статье "Создание замещающих классов в пакетах".