Капсулация
Проблем
Минус 500 €
В миналия урок сложихме проверката за теглене в метод. Само че полето е отворено и всеки може да пише в него направо:
Account account = new Account("Мария", 100);account.Withdraw(500);account.Balance = -500;Console.WriteLine(account.Balance);-500Методът правилно отказа да изтегли 500 €. Следващият ред просто записа баланса на ръка. Проверката е там, но може да се заобиколи. Трябва начин да кажем „балансът се пипа само от сметката“.
Идеята
private: само класът пипа
Сложи пред полето private вместо public:
class Account{ private string owner; private double balance; // ...}Сега до полето стига само кодът вътре в класа Account. Отвън редът
account.balance = -500;вече не се компилира:
error CS0122: 'Account.balance' is inaccessible due to its protection levelprivate и public се казват модификатори за достъп. Те казват кой може да вижда поле, метод или конструктор. Класът крие данните си и ги показва само през свои методи. Това се казва капсулация.
В C# скритите полета се пишат с малка буква: balance, owner. Затова в конструктора пишем this.balance = balance;: полето и параметърът вече се казват еднакво.
По-точно, за любопитните
- Без модификатор полето или методът в C# са
private. Затова до този урок пишехмеpublicнавсякъде. - Клас без модификатор е
internal: видим в целия проект (сборката), но не и от други проекти. Затоваclass Productработи навсякъде в нашата програма. - Има и
protected: видимо в класа и в наследниците му. Ще го срещнем в модул „Наследяване и полиморфизъм“.
Идеята
Да четеш, без да пипаш
Отвън вече не можем и да прочетем баланса. Затова класът дава метод, който само го връща:
class Account{ private string owner; private double balance;
public Account(string owner, double balance) { this.owner = owner; this.balance = balance; }
public string Owner { get { return owner; } }
public double Balance { get { return balance; } }
public void Deposit(double amount) { if (amount > 0) { balance += amount; } }
public bool Withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; return true; } return false; }}
class Program{ static void Main() { Account account = new Account("Мария", 100); account.Withdraw(30); account.Deposit(50); Console.WriteLine(account.Owner + ": " + account.Balance + " €"); }}Мария: 120 €Balance е свойство (property). Отвън се ползва като поле: account.Balance. Вътре е метод: get връща стойността на скритото поле balance. Това е getter-ът на C#. Сменя се балансът само през Deposit и Withdraw, а те проверяват.
Withdraw вече връща bool: дали тегленето е минало. Така който тегли, разбира дали е успял.
Account account = new Account("Мария", 100);account.Withdraw(500);account.Deposit(50);Console.WriteLine(account.Balance);Стъпка по стъпка, с думи
- balance е private: стъклото значи, че отвън до полето няма път.
- Тегленето минава през проверката в метода: 500 е повече от 100. Отказано, балансът не се пипа.
- Внасянето също се проверява: 50 е положителна сума. Методът я добавя.
- Свойството Balance дава навън копие на баланса. Самото поле остава скрито.
Пробвай
Отказано
Какво ще отпечата програмата?
Account account = new Account("Мария", 100);Console.WriteLine(account.Withdraw(150));account.Deposit(-20);Console.WriteLine(account.Balance);150 € е повече от баланса, затова Withdraw връща False и не пипа баланса. Внасянето на -20 € също се отказва, защото сумата трябва да е положителна. Балансът остава 100.
Идеята
Setter: промяна с проверка
Понякога полето трябва да може да се сменя отвън, но не с каквато и да е стойност. Възрастта на животно може да се промени, но не може да е отрицателна:
class Animal{ private int age;
public int Age { get { return age; } set { if (value >= 0) { age = value; } } }}
Animal cat = new Animal();cat.Age = 4;cat.Age = -3;Console.WriteLine(cat.Age);4Свойството вече има и set: това е setter-ът. При cat.Age = -3 се изпълнява кодът в set, а value е стойността отдясно на =, тук -3. Проверката не минава и възрастта остава 4.
Идеята
Кое поле да има setter?
Не всяко поле има нужда от setter, а някои нямат нужда и от getter. Помисли какво трябва да може да се прави с продукта отвън:
- името и цената се задават при създаването и после само се четат: само getter;
- бройката в количката се сменя, но поне 1: getter и setter с проверка.
class Product{ private string name; private double price; private int quantity;
public Product(string name, double price) { this.name = name; this.price = price; this.quantity = 1; }
public string Name { get { return name; } }
public double Price { get { return price; } }
public int Quantity { get { return quantity; } set { if (value >= 1) { quantity = value; } } }}
Product coffee = new Product("Кафе", 7.50);coffee.Quantity = 3;coffee.Quantity = 0;Console.WriteLine(coffee.Name + " x" + coffee.Quantity);Кафе x3Правилото е: започни с private за всичко и отваряй само каквото трябва. Setter, който записва всичко без проверка, връща проблема от първата карта.
Идеята
Свойства накратко
Свойство, което само чете и записва поле без проверка, може да се напише на един ред. Скритото поле C# прави сам:
class Product{ public string Name { get; set; } public double Price { get; set; } public int Quantity { get; set; } = 1;}
Product coffee = new Product { Name = "Кафе", Price = 7.50 };Console.WriteLine(coffee.Name + " x" + coffee.Quantity + ": " + coffee.Price + " €");Кафе x1: 7.5 €{ get; set; } е автоматично свойство. С = 1 то има стойност по подразбиране. new Product { Name = "Кафе", Price = 7.50 } е инициализатор на обект: създава обекта и задава свойствата му в едни скоби.
Можеш да скриеш само записа. { get; private set; } се чете отвсякъде, а се променя само в класа. { get; } се задава само в конструктора:
class Account{ public string Owner { get; } public double Balance { get; private set; }
public Account(string owner, double balance) { Owner = owner; Balance = balance; }
public void Deposit(double amount) { if (amount > 0) { Balance += amount; } }}
Account account = new Account("Мария", 100);account.Deposit(20);Console.WriteLine(account.Owner + ": " + account.Balance + " €");Мария: 120 €Ред account.Balance = -500; в Main не се компилира:
error CS0272: The property or indexer 'Account.Balance' cannot be used in this context because the set accessor is inaccessibleПробвай
Кое липсва?
class Account
{
double balance;
}private: полето е видимо само за кода в Account.Пробвай
Намери грешката
Полето е private, а балансът пак става -500:
Account account = new Account();account.Balance = 100;account.Withdraw(30);account.Balance = -500;Console.WriteLine(account.Balance);-500Кой ред в класа пробива защитата?
Избери реда с грешката.
set { balance = value; } записва всяка стойност без проверка. Полето е скрито, но публичният set го отваря отново. Махни го (или го направи private set) и задавай началния баланс в конструктора. Сменя се само с Deposit и Withdraw.
Упражнение
Скрити данни
Реши задачите в редактора си. Всички полета са private.
1. Човек. Person с име и възраст, конструктор, свойства Name и Age само за четене и Introduce().
Мария е на 34 г.Здравей, аз съм Мария и съм на 34 г.2. Сметка без минус. Account със собственик и баланс. Внася се само положителна сума. Тегленето връща дали е минало и не пуска баланса под 0. Балансът се чете, но не се записва отвън.
Account account = new Account("Петър", 50);account.Deposit(25);account.Deposit(-10);Console.WriteLine(account.Withdraw(100));Console.WriteLine(account.Withdraw(40));Console.WriteLine("Баланс: " + account.Balance + " €");FalseTrueБаланс: 35 €3. Животно. Animal с вид и възраст. Видът се задава в конструктора и само се чете. Възрастта има set, който отказва отрицателно число и печата съобщение. Конструкторът също минава през тази проверка.
Animal cat = new Animal("котка", 3);cat.Age = 4;cat.Age = -1;Console.WriteLine(cat.Species + ", " + cat.Age + " г.");Невалидна възраст: -1котка, 4 г.Още задачи
- Оценка. Клас
Gradeс име на предмет и оценка от 2 до 6. Setter-ът отказва всичко извън тези граници. Пробвай с 5, 7 и 1. - Продукт с отстъпка. Към
Productот урока добави метод за отстъпка в проценти, който приема само от 1 до 90%. Цената няма setter.
Тест