...
public class BenefitPackage {
public double ComputePayDeduction() {return 125.0;}
}
}
Глубина вложения может быть любой. Предположим, что мы хотим создать перечень BenefitPackageLevel, указывающий различные уровни льгот, которые может выбрать работник. Чтобы программно реализовать связь между Employee, BenefitPackage и BenefitPackageLevel, можно вложить перечень так, как показано ниже.
// Employee содержит BenefitPackage.
public class Employee {
// BenefitPackage содержит BenefitPackageLevel.
public class BenefitPackage {
public double ComputePayDeduction() {return 125.0;}
public enum BenefitPackageLevel {
Standard, Gold, Platinum
}
}
}
С учетом отношений вложении обратите внимание на то, как приходится иcпользовать этот перечень.
Static void Main(string[] args) {
// Создание переменной BenefitPackageLevel.
Employee.BenefitPackage.BenefitPackageLevel myBenefitLevel = Employee.BenefitPackage.BenefitPackageLevel.Platinum;
…
}
Третий принцип: поддержка полиморфизма в C#
Теперь давайте рассмотрим заключительный принцип ООП – полиморфизм. Напомним, что базовый класс Employee определил метод GiveBonus(), который был реализован так.
// Предоставление премий работникам.
public class Employee {
…
public void GiveBonus(float amount) { currPay += amount;}
}
Ввиду того, что этот метод является открытым, вы теперь имеете возможность выдать премии продавцам и менеджерам (а также продавцам, занятым неполный рабочий день).
static void Main(string[] args) {
// Премии работникам.
Manager chucky = new Manager("Сhucky", 50, 92, 100000, "333-23-2322", 9000);
chucky.GiveBonus(300);
chucky.DisplayStats();
SalesPerson fran = new SalesPerson("Fran", 43, 93, 3000, "932-32- 3232 " , 31);
fran.GiveBonus(200); fran.DisplayStats();
Console.ReadLine();
}
Недостаток данного варианта в том, что наследуемый метод GiveBonus() действует одинаково для всех подклассов. В идеале премия продавца, как и продавца, работающего на неполную ставку, должна зависеть от числа продаж. Возможно, менеджеры должны получать льготы в дополнение к денежному вознаграждению, В связи с этим возникает интересный вопрос: "Каким образом родственные объекты могут по-разному реагировать на одинаковые запросы?"
Ключевые слова virtual и override
Полиморфизм обеспечивает подклассам возможность задать собственную реализацию методов, определенных базовым классом. Чтобы соответствующим образом изменить наш проект, мы должны рассмотреть применение ключевых слов C# virtual и override. Если в базовом классе нужно определить метод, допускающий переопределение подклассом, то этот метод должен быть виртуальным.
public class Employee {
// GiveBonus() имеет реализацию, заданную по умолчанию,
// но дочерние классы могут переопределить это поведение.
public virtual void GiveBonus(float amount) {currPay += amount;}
…
}
Чтобы в подклассе переопределить виртуальный метод, используется ключевое слово override. Например, SalesPerson и Manager могут переопределить GiveBonus() так, как показано ниже (мы предполагаем, что PTSalesPerson переопределяет GiveBonus() примерно так же, как SalesPerson),
public class SalesPerson: Employee {
// Премия продавца зависит от числа продаж.
public override void GiveBonus(float amount) {
int salesBonus = 0;
if (numberOfSales ›= 0 && numberOfSales ‹= 100) salesBonus = 10;
else
if (numberOfSales ›= 101&& numberOfSales ‹= 200) salesBonus = 15;
else salesBonus = 20; // Вcе, что больше 200.
base.GiveBonus(amount * salesBonus);
}
}
public class Manager: Employee {
// Менеджер в дополнение к денежному вознаграждению
// получает некоторое число опционов.
public override void GiveBonus(float amount) {
// Прибавка к зарплате.
base.GiveBonus(amount);
// И получение опционов…
Random r = new Random();
numberOfOptions += (ulong)r.Next(500);
}
…
}
Обратите внимание на то, что переопределенный метод будет использовать поведение, принятое по умолчанию, если указать ключевое слово base. При этом нет необходимости снова реализовывать логику GiveBonus(), а можно снова использовать (и, возможно, расширить) поведение родительского класса, принятое по умолчанию.
Также предположим, что Employee.DisplayStats() был объявлен виртуально и переопределен каждым подклассом, чтобы учесть число продаж (для продавцов) и текущее состояние опционов (для менеджеров). Теперь, когда каждый подкласс может по-своему интерпретировать виртуальные методы, каждый экземпляр объекта ведет себя более независимо.
static void Main (string[] args) {
// Лучшая система премиальных!
Manager chucky = new Manager("Chucky", 50, 92, 100000, "333-23-2322", 9000);
chucky.GiveBonus(300);
chucky.DisplayStats();
SalesPerson fran = new SalesPerson("Fran", 43, 93, 3000, "932-32-3232", 31);
fran.GiveBonus(200);
fran.DisplayStats();
}
Снова о ключевом слове sealed
Ключевое слово sealed может также применяться к членам типа, чтобы запретить переопределение таких виртуальных членив в производных типах. Это оказывается полезным тогда, когда нужно изолировать не весь класс, а только несколько его методов или свойств.
Например, если (по некоторой причине) классу PTSalesPerson требуется разрешить расширение другими классами, но нужно гарантировать, чтобы эти классы не могли переопределять виртуальный метод GiveBonus(), можно использовать следующий вариант программного кода.
// Этот класс можно расширить,
// но GiveBonus() не может переопределяться производным классом.
public class PTSalesPerson: SalesPerson {
…
public override sealed void GiveBonus(float amount) {
…
}
}
В данный момент базовый класс Employee скомпонован так, что он может поставлять своим потомкам защищенные члены-переменные, а также два виртуальных метода (GiveBonus() и DisplayStats()), которые могут переопределяться производным классом. Все это хорошо, но данный вариант программного кода имеет один недостаток: вы можете непосредственно создавать экземпляры базового класса Employee.
// Что же это значит?
Employee X = new Employee ();
В данном примере единственной целью базового класса Employee является определение общих полей и членов для всех подклассов. Вероятно, вы не предполагали, что кто-то будет непосредственно создавать экземпляры класса, поскольку тип Employee (работник) является слишком общим. Например, если я приду к вам и скажу "Я работаю!", то в ответ я, скорее всего, услышу вопрос "Кем вы работаете?" (консультантом, инструктором, ассистентом администратора, редактором, представителем Белого Дома и т.п.).
Ввиду того, что многие базовые классы оказываются чем-то вроде "небожителей", для нашего примера лучше всего запретить возможность непосредственного создания новых объектов Employee. В C# это можно сделать программными средствами, используя ключевое слово abstract.
// Обозначение класса Employee, как абстрактного,
// запрещает непосредственное создание его экземпляров.
abstract public class Employee {…}
Если вы теперь попытаетесь создать экземпляр класса Employee, то получите ошибку компиляции.
// Ошибка! Нельзя создать экземпляр абстрактного класса.
Employee X = new Employee();
Превосходно! К этому моменту мы построили очень интересную иерархию служащих. Мы добавим новые функциональные возможности в это приложение немного позже, когда будем рассматривать правила классификации в C#. Иерархия типов, определенных на данный момент, показана на рис. 4.9.
Исходный код. Проект Employees размещен в подкаталоге, соответствующем главе 4.
Принудительный полиморфизм: абстрактные методы
Если класс является абстрактным базовым классом, он может определять любое число абстрактных членов (их аналогами в C++ являются "чистые" виртуальные функции). Абстрактные методы могут использоваться тогда, когда требуется определить метод без реализации, заданной по умолчанию. В результате производным классам придется использовать полиморфизм, поскольку им придется "уточнять" детали абстрактных методов. Здесь сразу же возникает вопрос, зачем это нужно. Чтобы понять роль абстрактных методов, давайте снова рассмотрим иерархию форм, уже упоминавшуюся в этой главе и расширенную так, как показано на рис. 4.10.