Теория
ООП е начин на мислене, при който програмата се изгражда от обекти — самостоятелни "същества", които имат данни (полета, свойства) и поведение (методи). Вместо разпилени променливи studentName1, studentGrade1, studentName2… създаваме клас Student и работим с обекти.
Класът е чертежът, обектът е реализацията. Класът описва каква структура и поведение ще има всеки студент; обектът е конкретен студент с конкретни стойности. От един клас създаваме колкото искаме обекти с ключовата дума new.
Конструкторът е специален метод с името на класа и без тип на връщане. Той се извиква автоматично при new и служи за първоначална настройка — гарантира, че обектът се ражда с валидни данни, а не "празен".
Инкапсулацията е златното правило на ООП: вътрешните данни (полетата) са private, а светът ги достъпва през public свойства (properties) с get и set. Така класът контролира как се четат и променят данните му — например може да отхвърли отрицателна оценка. Краткият запис public double Grade { get; set; } се нарича auto-property.
Ключовата дума this е препратка към "текущия обект" — обектът, върху който работи методът. Най-полезна е, когато параметър и поле имат еднакви имена: this.name = name;. Един клас може да има и няколко конструктора с различни параметри (предефиниране, точно като при методите).
Всеки клас в C# автоматично има метод ToString() — той определя как обектът се представя като текст. По подразбиране връща само името на класа, но можем да го заменим с public override string ToString() и тогава Console.WriteLine(obj) отпечатва нещо смислено. Това е първата среща с override — повече в модула за наследяване.
Златни правила
Клас срещу Обект
Класът е чертежът (напр. план на къща), обектът е реализацията (самата къща). От един клас могат да се направят много обекти.
Инкапсулация
Скривайте вътрешните данни! Полетата (данните) трябва да са private, а достъпът до тях става чрез public свойства (Properties).
Конструктор
Специален метод, който се извиква автоматично при създаване на нов обект (с ключовата дума new). Той служи за първоначална настройка на обекта.
Имената говорят
Класовете са съществителни в PascalCase (Student, BankAccount), методите — глаголи (Introduce, Deposit). Полетата са в camelCase, свойствата — в PascalCase.
Бърз справочник
Модификатори за достъп
| Модификатор | Описание |
|---|---|
public | Достъпен отвсякъде (от други класове и проекти). |
private | Достъпен само в рамките на класа, в който е дефиниран. (По подразбиране за полета.) |
Речник на ООП
| Термин | Какво е? | Пример |
|---|---|---|
| Клас (class) | Чертеж/шаблон за обекти | class Student { ... } |
| Обект (object) | Конкретна реализация на класа в паметта | new Student("Георги", 5.50) |
| Поле (field) | Private променлива вътре в класа | private string name; |
| Свойство (property) | Контролиран public достъп до данни | public string Name { get; set; } |
| Конструктор | Метод за първоначална настройка, име = името на класа | public Student(string name) { ... } |
| Метод (поведение) | Действие, което обектът умее | public void Introduce() { ... } |
Код за анализ и пренаписване
Клас Student: поле, свойство, конструктор и метод
using System;
namespace ModuleSix
{
// Дефиниция на класа
class Student
{
// Private поле (скрити данни)
private string name;
// Public свойство (Property)
public string Name
{
get { return name; }
set { name = value; }
}
public double Grade { get; set; } // Auto-property (съкратен запис)
// Конструктор
public Student(string studentName, double initialGrade)
{
Name = studentName;
Grade = initialGrade;
}
// Метод (Поведение)
public void Introduce()
{
Console.WriteLine($"Аз съм {Name} и успехът ми е {Grade}.");
}
}
class Program
{
static void Main(string[] args)
{
// Създаване на обекти
Student student1 = new Student("Георги", 5.50);
Student student2 = new Student("Елена", 6.00);
// Извикване на метод на обекта
student1.Introduce();
student2.Introduce();
}
}
}Какво се случва тук?
- Полето
nameеprivate— никой извън класа не може да го пипа директно. Това е инкапсулацията в действие. - Свойството
Nameе "вратата" към полето:getвръща стойността,setя записва (ключовата думаvalueе новата стойност). По-късно тук може да добавим валидация — напр. отказ на празно име. -
public double Grade { get; set; }е auto-property — компилаторът сам създава скритото поле. Това е съкратеният запис, който ползваме, когато няма нужда от специална логика. - Конструкторът
public Student(string studentName, double initialGrade)носи името на класа и няма тип на връщане. Извиква се автоматично приnew Student(...)и попълва данните. - В
Mainсъздаваме два различни обекта от един и същи клас — всеки със собствени стойности.student1.Introduce()печата данните на Георги,student2.Introduce()— на Елена: общ чертеж, различни реализации.
Валидация в свойство и ToString
using System;
namespace ModuleSixExtra
{
class Product
{
private double price;
public string Name { get; set; }
// Свойство с валидация - инкапсулацията в действие
public double Price
{
get { return price; }
set
{
if (value < 0)
{
Console.WriteLine("Цената не може да е отрицателна!");
return; // отказваме промяната
}
price = value;
}
}
public Product(string name, double price)
{
Name = name;
Price = price; // минава през set-а, т.е. през валидацията!
}
// Как обектът се представя като текст
public override string ToString()
{
return $"{Name} - {Price:F2} лв.";
}
}
class Program
{
static void Main(string[] args)
{
Product laptop = new Product("Лаптоп", 1500);
laptop.Price = -200; // отхвърля се от set-а
Console.WriteLine(laptop); // вика ToString() автоматично
}
}
}Какво се случва тук?
- Това е смисълът на инкапсулацията: полето
priceеprivate, а единствената "врата" към него — свойствотоPrice— проверява всяка нова стойност. Невалидните данни просто не могат да влязат. - В
setблокаvalueе подадената стойност. При-200печатаме предупреждение и излизаме сreturn— полето остава 1500. - Конструкторът присвоява на свойството (
Price = price), а не директно на полето — така и началната стойност минава през валидацията. Дребен детайл с голямо значение. -
ToString()се извиква автоматично отConsole.WriteLine(laptop)— без него щеше да се отпечата само "ModuleSixExtra.Product". -
overrideтук заменя наследената от базовия класobjectверсия наToString— първата глътка от наследяването, което ви чака в Модул 12.
Внимавай! Чести грешки
Грешките, които почти всеки прави в тази тема — виж разликата между грешния и правилния код.
Публични полета вместо свойства
Грешно
class Student
{
public string name;
public double grade; // всеки може да запише -3.50
}Правилно
class Student
{
public string Name { get; set; }
public double Grade { get; set; }
}Публичните полета нарушават инкапсулацията — няма как по-късно да добавите валидация, без да счупите кода наоколо. Дори без логика в get/set, свойството е правилната "врата" към данните.
Конструктор с тип на връщане
Грешно
class Student
{
public void Student(string name) { ... }
// това НЕ е конструктор, а обикновен метод!
}Правилно
class Student
{
public Student(string name) { ... }
}Конструкторът няма връщан тип — дори не void. Добавите ли void, той става обикновен метод със същото име и при new Student(...) няма да се извика (компилаторът ще се оплаче и от липсващ конструктор).
Използване на обект без new
Грешно
Student s;
s.Introduce(); // CS0165: неинициализирана променливаПравилно
Student s = new Student("Иван", 5.50);
s.Introduce();Декларацията създава само променливата-референция. Самият обект се ражда с new (памет + конструктор). Класическа грешка, която по-късно се проявява и като NullReferenceException при полета.
Извикване на метод на обект от static Main без обект
Грешно
class Program
{
void SayHello() { ... }
static void Main(string[] args)
{
SayHello(); // CS0120
}
}Правилно
class Program
{
static void SayHello() { ... }
static void Main(string[] args)
{
SayHello();
}
}Main е static и може да вика директно само static методи. Грешката CS0120 ("an object reference is required") значи: или направете метода static, или създайте обект и го извикайте през него.
Задачи за самостоятелна работа
Опитай първо сам — подсказката е там само ако наистина закъсаш.
Задача 1: Клас Car
Създайте клас Car с полета Марка, Модел и Година. Направете конструктор и метод StartEngine(), който отпечатва "Vroom!".
Задача 2: Клас BankAccount
Създайте клас BankAccount с поле Balance (баланс), което е private. Направете методи Deposit() (за добавяне на средства) и Withdraw() (за теглене), като Withdraw проверява дали има достатъчно пари преди теглене.
Задача 3: Клас Rectangle с валидация
Създайте клас Rectangle със свойства Width и Height, които не приемат стойности ≤ 0 (валидация в set). Добавете методи GetArea() и GetPerimeter() и override на ToString(), който описва правоъгълника.
Мини-тест за проверка
Отговори си наум (или на глас!) и чак тогава разкрий отговора.
1. Какво е името на метода, който създава и инициализира обекта в паметта?
2. Каква е разликата между class и object?
3. Какво точно прави ключовата дума new при Student s = new Student("Иван", 5.50)?
Речник на термините
Виж пълния речник на курсаnew, със собствени стойности.private — "суровите" данни на обекта.get и set — мястото за валидация.new и инициализира обекта.private) и достъп само през контролиран интерфейс (свойства, методи).this.name = name; различава полето от параметъра.public double Grade { get; set; } — компилаторът създава скритото поле сам.Провери знанията
Бърз тест с избор от отговори върху термините на модула — с точки и моментална обратна връзка.
Провери знанията си
8 въпроса върху термините от модула. Показваме определение — ти избираш правилния термин.
Препоръки за този модул
- Мислете в съществителни: прочетете условието на задачата и подчертайте съществителните — те са кандидати за класове, а глаголите — за методи.
- Създайте 2-3 обекта от класа си и ги накарайте да "говорят" — разликата клас/обект се усеща едва когато имате няколко обекта.
- Започвайте с auto-properties (
{ get; set; }) и преминавайте към пълния запис само когато трябва валидация вset. - Конструкторът трябва да оставя обекта в валидно състояние — ако нещо е задължително (име, баланс), искайте го като параметър.