Хотя все это хорошо, но необходимо также принимать во внимание и то, что обычно происходит внутри конструктора класса. Конструктор получает входные параметры, проверяет данные на предмет допустимости и затем присваивает значения внутренним закрытым полям. Пока что главный конструктор не проверяет входные строковые данные на вхождение в диапазон допустимых значений, а потому его можно было бы изменить следующим образом:
public Employee(string name, int age, int id, float pay)
{
/// Похоже на проблему. ..
if (name.Length > 15)
{
Console.WriteLine("Error! Name length exceeds 15 characters!");
// Ошибка! Длина имени превышает 15 символов!
}
else
{
_empName = name;
}
_empId = id;
_empAge = age;
_currPay = pay;
}
Наверняка вы заметили проблему, связанную с таким подходом. Свойство
Name
и главный конструктор выполняют одну и ту же проверку на наличие ошибок. Реализуя проверки для других элементов данных, есть реальный шанс столкнуться с дублированием кода. Стремясь рационализировать код и изолировать всю проверку, касающуюся ошибок, в каком-то центральном местоположении, вы добьетесь успеха, если для получения и установки значений внутри класса
всегда будете применять свойства. Взгляните на показанный ниже модифицированный конструктор:
public Employee(string name, int age, int id, float pay)
{
// Уже лучше! Используйте свойства для установки данных класса.
// Это сократит количество дублированных проверок на предмет ошибок.
Name = name;
Age = age;
ID = id;
Pay = pay;
}
Помимо обновления конструкторов для применения свойств при присваивании значений рекомендуется повсюду в реализации класса использовать свойства, чтобы гарантировать неизменное соблюдение бизнес-правил. Во многих случаях прямая ссылка на лежащие в основе закрытые данные производится только внутри самого свойства. Имея все сказанное в виду, модифицируйте класс
Employee
:
class Employee
{
<b> // Поля данных.</b>
private string _empName;
private int _empId;
private float _currPay;
private int _empAge;
<b> // Конструкторы.</b>
public Employee() { }
public Employee(string name, int id, float pay)
:this(name, 0, id, pay){}
public Employee(string name, int age, int id, float pay)
{
Name = name;
Age = age;
ID = id;
Pay = pay;
}
<b> // Методы.</b>
public void GiveBonus(float amount) => Pay += amount;
public void DisplayStats()
{
Console.WriteLine("Name: {0}", Name); // имя сотрудника
Console.WriteLine("ID: {0}", Id);
// идентификационный номер сотрудника
Console.WriteLine("Age: {0}", Age); // возраст сотрудника
Console.WriteLine("Pay: {0}", Pay); // текущая выплата
}
<b> // Свойства остаются прежними...</b>
...
}
Свойства, допускающие только чтение
При инкапсуляции данных может возникнуть желание сконфигурировать свойство, допускающее только чтение, для чего нужно просто опустить блок
set
. Например, пусть имеется новое свойство по имени
SocialSecurityNumber
, которое инкапсулирует закрытую строковую переменную
empSSN
. Вот как превратить его в свойство, доступное только для чтения:
public string SocialSecurityNumber
{
get { return _empSSN; }
}
Свойства, которые имеют только метод
get
, можно упростить с использованием членов, сжатых до выражений. Следующая строка эквивалентна предыдущему блоку кода:
public string SocialSecurityNumber => _empSSN;
Теперь предположим, что конструктор класса принимает новый параметр, который дает возможность указывать в вызывающем коде номер карточки социального страхования для объекта, представляющего сотрудника. Поскольку свойство
SocialSecurityNumber
допускает только чтение, устанавливать значение так, как показано ниже, нельзя:
public Employee(string name, int age, int id, float pay, string ssn)
{
Name = name;
Age = age;
ID = id;
Pay = pay;
// Если свойство предназначено только для чтения, это больше невозможно!