Пример работы с замещающими
классами
Замещение класса выполняется на уровне типа, для этого создается производный класс, который помечается атрибутом [Override]. При запросе экземпляра базового типа через ClassFactory.Get<T>() система возвращает экземпляр последнего замещающего класса в цепочке наследования.
1. Замещающий класс без параметров конструктора
Рассмотрим пример инициализации замещающего класса с конструктором по умолчанию и параметризированным конструктором. При этом у замещаемого класса только один конструктор по умолчанию.
/// <summary>
/// Замещаемый (базовый) класс.
/// </summary>
public class BpmReplacedClass
{
/// <summary>
/// Свойство класса.
/// </summary>
public int OriginalValue { get; private set; }
/// <summary>
/// Конструктор по умолчанию.
/// </summary>
public BpmReplacedClass() {
// Инициализация поля класса.
OriginalValue = 5;
}
/// <summary>
/// Метод, который может быть переопределен в замещающем классе.
/// </summary>
public virtual int GetMultipliedValue(){
return OriginalValue * 5;
}
}
/// <summary>
/// Замещающий (производный) класс.
/// </summary>
// Атрибут Override указывается для построения дерева зависимостей замещающих и замещаемых типов.
// Атрибут указывает на то, что данный класс являются замещающим.
[BPMSoft.Core.Factories.Override]
public class BpmReplacementClass : BpmReplacedClass
{
/// <summary>
/// Свойство класса BpmReplacementClass.
/// </summary>
public int AdditionalValue { get; private set; }
/// <summary>
/// Конструктор по умолчанию, который инициализирует свойство AdditionalValue значением 15.
/// </summary>
public BpmReplacementClass()
{
AdditionalValue = 15;
}
/// <summary>
/// Конструктор с параметром, который инициализирует свойство AdditionalValue значением, переданным в качестве параметра
/// </summary>
public BpmReplacementClass(int additionalValue)
{
AdditionalValue = additionalValue;
}
/// <summary>
/// Замещение родительского метода. Метод возвращает AdditionalValue, умноженное на 3.
/// </summary>
public override int GetMultipliedValue()
{
return AdditionalValue * 3;
}
}
Для получения экземпляра замещающего класса воспользуемся фабрикой классов:
var replacementClass = ClassFactory.Get<BpmReplacedClass>();
Переменная OriginalValue содержит значение 5, а переменная AdditionalValue содержит значение 15. Обе переменные были инициализированы конструктором по умолчанию своих классов. Конструктор по умолчанию запускается в неявном виде. Метод GetMultipliedValue вернет значение 45.
Для инициализации замещающего класса с параметризированным конструктором воспользуемся следующей конструкцией:
var replacementClassWithParameters = ClassFactory.Get<BpmReplacedClass>(new ConstructorArgument("additionalValue", 20));
Конструктор класса ConstructorArgument принимает два параметра: название параметра конструктора производного класса; значение этого параметра.
Переменная OriginalValue в данном случае будет содержать значение 5, а переменная AdditionalValue содержит 20. Метод GetMultipliedValue вернет значение 60.
2. Замещающий класс с параметрами конструктора
Рассмотрим пример инициализации замещающего класса с параметризированным конструктором. При этом у замещаемого класса тоже параметризированный конструктор.
/// <summary>
/// Замещаемый (базовый) класс.
/// </summary>
public class BpmReplacedClass
{
/// <summary>
/// Свойство класса.
/// </summary>
public int OriginalValue { get; private set; }
/// <summary>
/// Конструктор по умолчанию.
/// </summary>
public BpmReplacedClass(int originalValue) {
// Инициализация поля класса.
OriginalValue = originalValue;
}
/// <summary>
/// Метод, который может быть переопределен в замещающем классе.
/// </summary>
public virtual int GetMultipledValue(){
return OriginalValue * 5;
}
}
/// <summary>
/// Замещающий (производный) класс.
/// </summary>
// Атрибут Override указывается для построения дерева зависимостей замещающих и замещаемых типов.
// Атрибут указывает на то, что данный класс являются замещающим.
[BPMSoft.Core.Factories.Override]
public class BpmReplacementClass : BpmReplacedClass
{
/// <summary>
/// Свойство класса BpmReplacementClass
/// </summary>
public int AdditionalValue { get; private set; }
/// <summary>
/// Конструктор по умолчанию, который инициализирует свойство AdditionalValue значением 15.
/// </summary>
public BpmReplacementClass()
{
AdditionalValue = 15;
}
/// <summary>
/// Конструктор с параметром, который инициализирует свойство AdditionalValue значением, переданным в качестве параметра.
/// </summary>
public BpmReplacementClass(int additionalValue) : base(additionalValue + 1)
{
AdditionalValue = additionalValue;
}
/// <summary>
/// Замещение родительского метода. Метод возаращает AdditionalValue, умноженное на 3.
/// </summary>
public override int GetMultipliedValue()
{
return AdditionalValue * 3;
}
}
Проинициализируем экземпляр замещающего класса. В данном случае нам необходимо использовать инициализацию через параметризированный конструктор, так как при инициализации замещающего класса конструктором по умолчанию возникнет ошибка, поскольку у замещаемого класса есть только параметризированный конструктор.
var replacementClassWithParameters = ClassFactory.Get<BpmReplacedClass>(new ConstructorArgument("additionalValue", 10));
Переменная OriginalValue в данном случае будет содержать значение 11, а переменная AdditionalValue содержит 10. Метод GetMultipliedValue вернет значение 30.
Примеры замещения методов
После создания замещающего (производного) класса и применения атрибута [Override] можно использовать различные способы замещения методов в зависимости от структуры базового класса и задач. На практике используются три сценария: переопределение virtual-методов, сочетание [Override] с ClassFactory для подмены типа при создании, а также скрытие невиртуальных методов через new (при наличии ограничений базового кода).
1. Переопределение virtual-методов (override)
Если метод базового класса объявлен как virtual, его можно переопределить в производном классе с помощью override. Для корректного переопределения метод в наследнике обязан иметь точно такую же сигнатуру, как виртуальный метод базового класса. Вызов будет выполняться по фактическому типу объекта во время выполнения, а при необходимости можно обратиться к базовой реализации через base.MethodName().
public class Shape
{
public virtual double GetArea()
{
return 0;
}
}
public class Rectangle : Shape
{
public double Width { get; }
public double Height { get; }
public Rectangle(double width, double height)
{
Width = width;
Height = height;
}
public override double GetArea()
{
return Width * Height;
}
}
Shape s1 = new Rectangle(3, 4); Console.WriteLine(s1.GetArea()); // 12
2. [Override] + ClassFactory.Get<T> и вызов переопределенных методов
Атрибут [Override] применяется к классу и указывает, что этот класс замещает свой базовый класс. При создании объекта через ClassFactory.Get<T>() возвращается экземпляр замещающего класса, даже если запрашивается базовый тип. Далее поведение методов определяется правилами C#: если метод базового класса является virtual, то при вызове будет использоваться реализация замещающего класса (при условии, что метод переопределен через override).
public class EmailSender
{
public virtual void SendEmail(string message)
{
Console.WriteLine(message);
}
}
[Override]
public class CustomEmailSender : EmailSender
{
public override void SendEmail(string text)
{
// При необходимости можно вызвать базовую реализацию:
// base.SendEmail(text);
Console.WriteLine($"{text} (Отправлено с особой пометкой!)");
}
}
// Создание через фабрику — будет возвращен замещающий тип.
var sender = ClassFactory.Get<EmailSender>(); // фактически CustomEmailSender
sender.SendEmail("Привет"); // Привет (Отправлено с особой пометкой!)
// Создание напрямую — подмена не применяется.
var baseSender = new EmailSender();
baseSender.SendEmail("Привет"); // Привет
3. Скрытие невиртуальных методов (new)
Если метод базового класса не объявлен как virtual, переопределение через override невозможно. В таких случаях можно объявить в производном классе метод с той же сигнатурой и ключевым словом new, чтобы скрыть базовую реализацию.
Следует учитывать, что new не включает полиморфизм времени выполнения. Метод в наследнике не переопределяет базовый, а скрывает его. Выбор вызываемого метода выполняется на этапе компиляции и зависит от статического типа переменной. Поэтому обращение через переменную базового типа приведет к вызову базового метода, даже если фактически создан объект производного класса.
public class BaseNotifier
{
public void Notify(string message)
{
Console.WriteLine($"Базовое уведомление: {message}");
}
}
public class AdvancedNotifier : BaseNotifier
{
public new void Notify(string message)
{
Console.WriteLine($"Расширенное уведомление: {message}");
}
}
BaseNotifier notifier1 = new BaseNotifier();
BaseNotifier notifier2 = new AdvancedNotifier();
AdvancedNotifier notifier3 = new AdvancedNotifier();
notifier1.Notify("Привет!"); // Базовое уведомление: Привет!
notifier2.Notify("Привет!"); // Базовое уведомление: Привет!
notifier3.Notify("Привет!"); // Расширенное уведомление: Привет!
При совместном использовании с атрибутом [Override], данный механизм позволяет изменять поведение невиртуальных методов базовых классов без модификации исходного кода. Атрибут [Override] обеспечивает подмену типа при создании объекта через ClassFactory, а ключевое слово new скрывает реализацию метода базового класса в наследнике. Таким образом, базовый класс фактически перестает использоваться напрямую, а вся работа ведется с замещающим типом.
В данном случае замещение поведения достигается не средствами полиморфизма CLR, а за счет инфраструктурной подмены типа на этапе создания объекта и последующего скрытия базовой реализации метода.