Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
public Manager(string fullName, int age, int empId,
float currPay, string ssn, int numbOfOpts)
{
// Это свойство определено в классе Manager.
StockOptions = numbOfOpts;
// Присвоить входные параметры, используя
// унаследованные свойства родительского класса.
Id = empId;
Age = age;
Name = fullName;
Pay = currPay;
PayType = EmployeePayTypeEnum.Salaried;
// Если свойство SSN окажется доступным только для чтения,
// тогда здесь возникнет ошибка на этапе компиляции!
SocialSecurityNumber = ssn;
}
Первая проблема с таким подходом связана с тем, что если любое свойство определено как допускающее только чтение (например, свойство SocialSecurityNumber), то присвоить значение входного параметра string данному полю не удастся, как можно видеть в финальном операторе специального конструктора.
Вторая проблема заключается в том, что был косвенно создан довольно неэффективный конструктор, учитывая тот факт, что в C# стандартный конструктор базового класса вызывается автоматически перед выполнением логики конструктора производного класса, если не указано иначе. После этого момента текущая реализация имеет доступ к многочисленным открытым свойствам базового класса Employee для установки его состояния. Таким образом, во время создания объекта Manager на самом деле выполнялось восемь действий (обращения к шести унаследованным свойствам и двум конструкторам).
Для оптимизации создания объектов производного класса необходимо корректно реализовать конструкторы подкласса, чтобы они явно вызывали подходящий специальный конструктор базового класса вместо стандартного конструктора. Подобным образом можно сократить количество вызовов инициализации унаследованных членов (что уменьшит время обработки). Первым делом обеспечьте наличие в родительском классе Employee следующего конструктора с шестью аргументами:
// Добавление в базовый класс Employee.
public Employee(string name, int age, int id, float pay, string empSsn,
EmployeePayTypeEnum payType)
{
Name = name;
Id = id;
Age = age;
Pay = pay;
SocialSecurityNumber = empSsn;
PayType = payType;
}
Модифицируйте специальный конструктор в классе Manager, чтобы вызвать конструктор Employee с применением ключевого слова base:
public Manager(string fullName, int age, int empId,
float currPay, string ssn, int numbOfOpts)
: base(fullName, age, empId, currPay, ssn,
EmployeePayTypeEnum.Salaried)
{
// Это свойство определено в классе Manager.
StockOptions = numbOfOpts;
}
Здесь ключевое слово base ссылается на сигнатуру конструктора (подобно синтаксису, используемому для объединения конструкторов одиночного класса в цепочку через ключевое слово this, как обсуждалось в главе 5), что всегда указывает производному конструктору на необходимость передачи данных конструктору непосредственного родительского класса. В рассматриваемой ситуации явно вызывается конструктор с шестью параметрами, определенный в Employee, что избавляет от излишних обращений во время создания объекта дочернего класса. Кроме того, в класс Manager добавлена особая линия поведения, которая заключается в том, что тип оплаты всегда устанавливается в Salaried. Специальный конструктор класса SalesPerson выглядит почти идентично, но только тип оплаты устанавливается в Commission:
// В качестве общего правила запомните, что все подклассы должны
// явно вызывать подходящий конструктор базового класса.
public SalesPerson(string fullName, int age, int empId,
float currPay, string ssn, int numbOfSales)
: base(fullName, age, empId, currPay, ssn,
EmployeePayTypeEnum.Commission)
{
// Это принадлежит нам!
SalesNumber = numbOfSales;
}
На заметку! Ключевое слово base можно применять всякий раз, когда подкласс желает обратиться к открытому или защищенному члену, определенному в родительском классе. Использование этого ключевого слова не ограничивается логикой конструктора. Вы увидите примеры применения ключевого слова base в подобной манере позже в главе при рассмотрении полиморфизма.
Наконец, вспомните, что после добавления к определению класса специального конструктора стандартный конструктор молча удаляется. Следовательно, не забудьте переопределить стандартный конструктор для классов SalesPerson и Manager. Вот пример:
// Аналогичным образом переопределите стандартный
// конструктор также и в классе Manager.
public SalesPerson() {}
Хранение секретов семейства: ключевое слово protected
Как вы уже знаете, открытые элементы напрямую доступны отовсюду, в то время как закрытые элементы могут быть доступны только в классе, где они определены. Вспомните из главы 5, что C# опережает многие другие современные объектные языки и предоставляет дополнительное ключевое слово для определения доступности членов — protected (защищенный).
Когда базовый класс определяет защищенные данные или защищенные члены, он устанавливает набор элементов, которые могут быть непосредственно доступны любому наследнику. Если вы хотите разрешить дочерним классам SalesPerson и Manager напрямую обращаться к разделу данных, который определен в Employee, то модифицируйте исходный класс Employee (в файле EmployeeCore.cs), как показано ниже:
// Защищенные данные состояния.
partial class Employee
{
// Производные классы теперь могут иметь прямой доступ к этой информации.
protected string EmpName;
protected int EmpId;
protected float CurrPay;
protected int EmpAge;
protected string EmpSsn;
protected EmployeePayTypeEnum EmpPayType;
...
}
На заметку! По соглашению защищенные члены именуются в стиле Pascal (EmpName), а не в "верблюжьем" стиле с подчеркиванием (_empName). Это не является требованием языка, но представляет собой распространенный стиль написания кода. Если вы решите обновить имена, как было сделано здесь, тогда не забудьте переименовать все поддерживающие методы в свойствах, чтобы они соответствовали защищенным свойствам с именами в стиле Pascal.
Преимущество определения защищенных членов в базовом классе заключается в том, что производным классам больше не придется обращаться к данным косвенно, используя открытые методы и свойства. Разумеется, подходу присущ и недостаток: когда производный класс имеет прямой доступ к внутренним данным своего родителя, то есть вероятность непредумышленного обхода существующих бизнес-правил, которые реализованы внутри открытых свойств. Определяя защищенные члены, вы создаете уровень доверия между родительским классом и дочерним классом, т.к. компилятор не будет перехватывать какие-либо нарушения бизнес-правил, предусмотренных