Модул 6

Основи на ООП (Обектно-ориентирано програмиране)

Първа среща с обектно-ориентираното програмиране — какво е клас и какво е обект, как конструкторът инициализира обекта, защо скриваме данните (инкапсулация) и как свойствата дават контролиран достъп.

Теория

ООП е начин на мислене, при който програмата се изгражда от обекти — самостоятелни "същества", които имат данни (полета, свойства) и поведение (методи). Вместо разпилени променливи 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: поле, свойство, конструктор и метод

Program.cs
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

Program.cs
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 — "суровите" данни на обекта.
Свойство (Property)
Контролиран публичен достъп до данните с get и set — мястото за валидация.
Конструктор
Специален метод с името на класа и без връщан тип; извиква се автоматично при new и инициализира обекта.
Инкапсулация
Скриване на вътрешните данни (private) и достъп само през контролиран интерфейс (свойства, методи).
this
Препратка към текущия обект — this.name = name; различава полето от параметъра.
Auto-property
Съкратен запис public double Grade { get; set; } — компилаторът създава скритото поле сам.

Провери знанията

Бърз тест с избор от отговори върху термините на модула — с точки и моментална обратна връзка.

Провери знанията си

8 въпроса върху термините от модула. Показваме определение — ти избираш правилния термин.

Препоръки за този модул

  • Мислете в съществителни: прочетете условието на задачата и подчертайте съществителните — те са кандидати за класове, а глаголите — за методи.
  • Създайте 2-3 обекта от класа си и ги накарайте да "говорят" — разликата клас/обект се усеща едва когато имате няколко обекта.
  • Започвайте с auto-properties ({ get; set; }) и преминавайте към пълния запис само когато трябва валидация в set.
  • Конструкторът трябва да оставя обекта в валидно състояние — ако нещо е задължително (име, баланс), искайте го като параметър.

Начален курс по C# — учебен наръчник и бърз справочник за студенти.

Натисни Ctrl K или / отвсякъде, за да търсиш.