Проблема с открытыми данными заключается в том, что сами по себе они неспособны "понять", является ли присваиваемое значение допустимым с точки зрения текущих бизнес-правил системы. Как известно, верхний предел значений для типа int в C# довольно высок (2 147 483 647), поэтому компилятор разрешит следующее присваивание:
// Хм... Ничего себе мини-новелла!
Book miniNovel = new Book();
miniNovel.numberOfPages = 30_000_000;
Хотя границы типа данных int не превышены, понятно, что мини-новелла объемом 30 миллионов страниц выглядит несколько неправдоподобно. Как видите, открытые поля не предоставляют способа ограничения значений верхними (или нижними) логическими пределами. Если в системе установлено текущее бизнес-правило, которое регламентирует, что книга должна иметь от 1 до 1000 страниц, то совершенно неясно, как обеспечить его выполнение программным образом. Именно потому открытым полям обычно нет места в определениях классов производственного уровня.
На заметку! Говоря точнее, члены класса, которые представляют состояние объекта, не должны помечаться как public. В то же время позже в главе вы увидите, что вполне нормально иметь открытые константы и открытые поля, допускающие только чтение.
Инкапсуляция предлагает способ предохранения целостности данных состояния для объекта. Вместо определения открытых полей (которые могут легко привести к повреждению данных) необходимо выработать у себя привычку определять
• определение пары открытых методов доступа и изменения;
• определение открытого свойства.
Независимо от выбранного приема идея заключается в том, что хорошо инкапсулированный класс должен защищать свои данные и скрывать подробности своего функционирования от любопытных глаз из внешнего мира. Это часто называют
Инкапсуляция с использованием традиционных методов доступа и изменения
В оставшейся части главы будет построен довольно полный класс, моделирующий обычного сотрудника. Для начала создайте новый проект консольного приложения под названием EmployeeApp и добавьте в него новый файл класса по имени Employee.cs. Обновите класс Employee с применением следующего пространства имен, полей, методов и конструкторов:
using System;
namespace EmployeeApp
{
class Employee
{
// Поля данных.
private string _empName;
private int _empId;
private float _currPay;
// Конструкторы.
public Employee() {}
public Employee(string name, int id, float pay)
{
_empName = name;
_empId = id;
_currPay = pay;
}
// Методы.
public void GiveBonus(float amount) => _currPay += amount;
public void DisplayStats()
{
Console.WriteLine("Name: {0}", _empName); // имя сотрудника
Console.WriteLine("ID: {0}", _empId); // идентификационный
// номер сотрудника
Console.WriteLine("Pay: {0}", _currPay); // текущая выплата
}
}
}
Обратите внимание, что поля класса Employee в текущий момент определены с использованием ключевого слова private. Учитывая это, поля empName, empID и currPay не будут доступными напрямую через объектную переменную. Таким образом, показанная ниже логика в коде приведет к ошибкам на этапе компиляции:
Employee emp = new Employee();
// Ошибка! Невозможно напрямую обращаться к закрытым полям объекта!
emp._empName = "Marv";
Если нужно, чтобы внешний мир взаимодействовал с полным именем сотрудника, то традиционный подход предусматривает определение методов доступа (метод get) и изменения (метод set). Роль метода get заключается в возвращении вызывающему коду текущего значения лежащих в основе данных состояния. Метод set позволяет вызывающему коду изменять текущее значение лежащих в основе данных состояния при условии удовлетворения бизнес-правил.