$ sudo teach IT
Модуль 8 · Функции

Урок 8.6 — Классы и объекты

Введение в ООП: классы, конструкторы, свойства, модификаторы доступа.

До сих пор мы писали код в одном классе Program, используя только статические методы. Но реальные программы состоят из множества взаимодействующих объектов. Объектно-ориентированное программирование (ООП) позволяет объединять данные и поведение в одной сущности — классе. Это как чертёж дома — по нему можно построить сколько угодно домов (объектов).

🏗️

Введение в ООП: объект = данные + поведение

Объектно-ориентированное программирование — это подход, в котором программа представляется как набор взаимодействующих объектов. Каждый объект содержит данные (поля, свойства) и поведение (методы). Это естественный способ моделирования реального мира.

Как это выглядит в реальном мире?

Представь автомобиль. У него есть данные: цвет, марка, скорость, уровень топлива. И есть поведение: завестись, ускориться, затормозить, повернуть. Объект Car в коде будет содержать и то, и другое.

Основные принципы ООП (их четыре):

  • Абстракция — выделение главных характеристик объекта, отбрасывание второстепенных.
  • Инкапсуляция — скрытие внутренних данных объекта, доступ только через методы/свойства.
  • Наследование — создание нового класса на основе существующего (is-a отношение).
  • Полиморфизм — возможность работать с разными классами через единый интерфейс.
💡 В этом уроке мы сосредоточимся на классах, объектах и инкапсуляции. Наследование и полиморфизм — тема отдельного урока.
📋

Синтаксис класса

Класс объявляется ключевым словом class. Внутри класса могут быть поля, свойства, методы, конструкторы и другие члены.

Базовая структура класса:

class ИмяКласса { поля; конструктор; свойства; методы; }

C# · Пример класса Student
public class Student
{
    // Поля (данные)
    public string Name;
    public int Age;
    private double grade;

    // Конструктор
    public Student(string name, int age)
    {
        Name = name;
        Age = age;
        grade = 0.0;
    }

    // Метод (поведение)
    public void SetGrade(double newGrade)
    {
        if (newGrade >= 0 && newGrade <= 5.0)
            grade = newGrade;
    }

    // Свойство
    public double Grade
    {
        get { return grade; }
        set { if (value >= 0 && value <= 5.0) grade = value; }
    }

    // Метод, выводящий информацию
    public void PrintInfo()
    {
        Console.WriteLine($"Студент: {Name}, возраст: {Age}, оценка: {grade:F1}");
    }
}
🔧

Конструктор — инициализация объекта

Конструктор — это специальный метод, который вызывается при создании объекта (оператор new). Имя конструктора совпадает с именем класса. Конструктор нужен для начальной установки полей объекта.

Особенности конструкторов:

  • Не имеют возвращаемого типа (даже void).
  • Могут быть перегружены (несколько конструкторов с разными параметрами).
  • Если ты не объявил ни одного конструктора, компилятор создаёт конструктор по умолчанию без параметров.
  • Можно вызвать один конструктор из другого через this().
C# · Конструкторы и перегрузка
public class Product
{
    public string Name { get; set; }
    public double Price { get; set; }
    public int Quantity { get; set; }

    // Конструктор по умолчанию (если не объявить — создаётся автоматически)
    public Product()
    {
        Name = "Неизвестный товар";
        Price = 0.0;
        Quantity = 0;
    }

    // Конструктор с параметрами
    public Product(string name, double price)
    {
        Name = name;
        Price = price;
        Quantity = 1;
    }

    // Ещё один конструктор
    public Product(string name, double price, int quantity)
    {
        Name = name;
        Price = price;
        Quantity = quantity;
    }

    // Вызов одного конструктора из другого через this
    public Product(string name) : this(name, 100.0, 1)
    {
        // this(name, 100.0, 1) вызовет третий конструктор
    }
}

// Использование:
Product p1 = new Product();                    // Name="Неизвестный товар"
Product p2 = new Product("Книга", 500.0);      // Name="Книга", Price=500
Product p3 = new Product("Ноутбук", 80000, 2); // полные данные
Product p4 = new Product("Часы");              // Price=100, Quantity=1
📦

Поля и свойства (prop)

Поля — это переменные, которые хранят данные объекта. Свойства — это специальные члены класса, которые управляют доступом к полям. Они выглядят как поля извне, но внутри содержат логику (геттеры и сеттеры).

Поля (fields)

Поле — это обычная переменная, объявленная внутри класса. Может быть с модификатором доступа (public, private, protected).

C# · Поля класса
public class User
{
    public string Name;          // поле — все могут читать и писать
    private int age;             // поле — доступно только внутри класса
    internal string email;        // поле — доступно в той же сборке
    protected string phone;       // поле — доступно в наследниках
}

Свойства (properties)

Свойство — это гибрид поля и метода. Оно предоставляет контролируемый доступ к данным. Внутри свойства могут быть get (чтение) и set (запись). value — это ключевое слово, обозначающее присваиваемое значение.

C# · Свойства (полная запись)
public class BankAccount
{
    private decimal balance;  // приватное поле

    // Полное свойство с логикой
    public decimal Balance
    {
        get { return balance; }
        set
        {
            if (value >= 0)
                balance = value;
            else
                Console.WriteLine("Баланс не может быть отрицательным!");
        }
    }

    // Свойство только для чтения
    public string AccountNumber { get; private set; }

    // Авто-свойство (компилятор сам создаёт поле)
    public string OwnerName { get; set; }
}

Авто-свойства (auto-implemented properties)

Если логика в get/set не нужна, можно использовать авто-свойства. Компилятор сам создаст приватное поле. Синтаксис: { get; set; }.

C# · Авто-свойства
public class Person
{
    // Авто-свойства — самый популярный способ
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }

    // Авто-свойство с инициализацией
    public DateTime CreatedAt { get; set; } = DateTime.Now;

    // Свойство только для чтения (set приватный)
    public int Id { get; private set; }

    // Вычисляемое свойство (только get)
    public string FullName => $"{FirstName} {LastName}";
}
⚙️

Методы внутри класса

Метод — это блок кода, который выполняется при вызове. Методы определяют поведение объекта. В C# методы могут быть экземплярными (вызываются на конкретном объекте) и статическими (принадлежат классу в целом).

C# · Методы в классе
public class Calculator
{
    // Экземплярный метод — работает с данными объекта
    public int Add(int a, int b)
    {
        return a + b;
    }

    // Статический метод — принадлежит классу, а не объекту
    public static int Multiply(int a, int b)
    {
        return a * b;
    }

    // Метод, изменяющий состояние объекта
    private int lastResult;
    public void StoreResult(int result)
    {
        lastResult = result;
        Console.WriteLine($"Сохранён результат: {lastResult}");
    }

    // Метод, возвращающий данные объекта
    public int GetLastResult()
    {
        return lastResult;
    }
}

// Использование:
Calculator calc = new Calculator();
int sum = calc.Add(5, 3);            // экземплярный метод
int product = Calculator.Multiply(5, 3);  // статический метод — через класс
calc.StoreResult(sum);               // сохранение состояния

Методы могут быть перегружены (одинаковое имя, разные параметры):

C# · Перегрузка методов
public class Logger
{
    public void Log(string message)
    {
        Console.WriteLine($"[LOG] {message}");
    }

    public void Log(string message, int level)
    {
        Console.WriteLine($"[LOG-{level}] {message}");
    }

    public void Log(string message, Exception ex)
    {
        Console.WriteLine($"[LOG-ERROR] {message}: {ex.Message}");
    }
}
👈

Ключевое слово this

this — это ссылка на текущий объект. Она используется внутри класса для обращения к его членам. Основные случаи применения:

  • Разрешение конфликта имён (параметр метода совпадает с полем).
  • Вызов одного конструктора из другого.
  • Передача текущего объекта в качестве аргумента.
C# · Использование this
public class Employee
{
    private string name;
    private int age;

    // Разрешение конфликта имён
    public Employee(string name, int age)
    {
        this.name = name;  // this.name — поле, name — параметр
        this.age = age;
    }

    // Вызов конструктора через this
    public Employee(string name) : this(name, 18)
    {
        // this(name, 18) вызывает первый конструктор
    }

    // Передача текущего объекта
    public void Register(Company company)
    {
        company.AddEmployee(this);  // передаём себя
    }

    // Явное обращение к полю (даже без конфликта)
    public void PrintInfo()
    {
        Console.WriteLine($"Имя: {this.name}, Возраст: {this.age}");
    }
}
🆕

Создание экземпляра: new ClassName()

Чтобы создать объект класса, используется оператор new. Он выделяет память и вызывает конструктор.

C# · Создание объектов
public class Car
{
    public string Model { get; set; }
    public int Year { get; set; }
    public string Color { get; set; }

    public Car(string model, int year)
    {
        Model = model;
        Year = year;
        Color = "Белый";  // цвет по умолчанию
    }

    public void Honk()
    {
        Console.WriteLine($"{Model}: Бип-бип!");
    }
}

// Создание объектов (экземпляров класса)
Car car1 = new Car("Toyota Camry", 2022);
Car car2 = new Car("Honda Civic", 2024);
car2.Color = "Красный";

// Вызов методов на объектах
car1.Honk();   // → Toyota Camry: Бип-бип!
car2.Honk();   // → Honda Civic: Бип-бип!

// Доступ к свойствам
Console.WriteLine($"У меня {car1.Model} {car1.Year} года, цвет {car1.Color}");
// → У меня Toyota Camry 2022 года, цвет Белый

Объекты можно создавать с помощью инициализатора объектов (без явного вызова конструктора):

C# · Инициализатор объектов
// Если в классе есть конструктор по умолчанию (без параметров)
// или авто-свойства, можно использовать инициализатор:

Car car3 = new Car
{
    Model = "Tesla Model 3",
    Year = 2023,
    Color = "Синий"
};

// Или с конструктором + инициализатор:
Car car4 = new Car("BMW X5", 2025)
{
    Color = "Чёрный"
};
🔐

Модификаторы доступа: public, private, protected

Модификаторы доступа определяют, какие части программы могут видеть и использовать члены класса. Это основа инкапсуляции — скрытия внутренней реализации.

Модификатор

  • public
  • private
  • protected
  • internal
  • protected internal
  • private protected

Доступен

  • Везде
  • Только внутри класса
  • Внутри класса и в наследниках
  • Внутри текущей сборки (.exe/.dll)
  • Сборка + наследники
  • Наследники внутри сборки
C# · Пример модификаторов доступа
public class BankAccount
{
    // public — видно всем
    public string AccountNumber { get; set; }

    // private — видно только внутри класса
    private decimal balance;

    // protected — видно в классе и наследниках
    protected decimal InterestRate;

    // public метод — точка доступа к приватным данным
    public void Deposit(decimal amount)
    {
        if (amount > 0)
            balance += amount;
    }

    public decimal GetBalance()
    {
        return balance;  // доступ к private полю
    }
}

// Наследник имеет доступ к protected, но не к private
public class SavingsAccount : BankAccount
{
    public void ApplyInterest()
    {
        // balance += 100;  // ОШИБКА! balance — private
        InterestRate += 0.01m;  // ОК! InterestRate — protected
    }
}
💡 Хорошая практика: поля делай private, а доступ к ним предоставляй через public свойства с логикой в get/set. Это и есть инкапсуляция.
🛡️

Инкапсуляция: { get; set; }

Инкапсуляция — это принцип ООП, при котором данные объекта скрыты от внешнего кода, а доступ к ним осуществляется только через публичные методы и свойства. Это защищает данные от некорректного использования.

Без инкапсуляции (плохо):

public class BadAccount
{
    public decimal Balance;  // публичное поле — все могут менять

    // Проблема: никто не мешает установить отрицательный баланс!
}

// Использование:
BadAccount acc = new BadAccount();
acc.Balance = -1000m;  // ❌ Это не имеет смысла, но C# разрешает

С инкапсуляцией (хорошо):

public class GoodAccount
{
    private decimal balance;  // скрытое поле

    public decimal Balance    // свойство с проверкой
    {
        get { return balance; }
        set
        {
            if (value >= 0)
                balance = value;
            else
                throw new ArgumentException("Баланс не может быть отрицательным");
        }
    }

    public void Deposit(decimal amount)
    {
        if (amount > 0)
            balance += amount;
    }

    public bool Withdraw(decimal amount)
    {
        if (amount > 0 && balance >= amount)
        {
            balance -= amount;
            return true;
        }
        return false;
    }
}

// Использование:
GoodAccount acc = new GoodAccount();
acc.Deposit(500);
acc.Withdraw(200);
Console.WriteLine(acc.Balance);  // → 300
// acc.Balance = -100;  // ❌ ArgumentException
❓

Мини-тест

Вопрос 1: Что выведет код? var p = new Person("Анна"); Console.WriteLine(p.Name);

A) Анна
B) null
C) Ошибка компиляции
D) Зависит от конструктора

Правильный ответ: D (зависит от реализации конструктора)

Вопрос 2: Какой модификатор доступа делает член видимым только внутри класса?

A) public
B) private
C) protected
D) internal

Правильный ответ: B

Вопрос 3: Что такое свойство (property)?

A) Поле класса
B) Член класса с get и set для управления доступом к данным
C) Статический метод
D) Конструктор

Правильный ответ: B

Вопрос 4: Для чего используется ключевое слово this?

A) Для вызова статического метода
B) Для ссылки на текущий объект
C) Для объявления нового класса
D) Для импорта пространства имён

Правильный ответ: B

Вопрос 5: Что выведет код?

C#
class Test { public int X = 5; }
var t = new Test();
t.X = 10;
Console.WriteLine(t.X);
A) 5
B) 10
C) Ошибка компиляции
D) NullReferenceException

Правильный ответ: B

🧪

Задание

Библиотека книг

Создай консольное приложение, моделирующее библиотеку:

  1. Создай класс Book с полями/свойствами: Title, Author, Year, IsAvailable (bool).
  2. Добавь конструктор с параметрами и конструктор по умолчанию.
  3. Добавь метод PrintInfo() — вывод всей информации о книге.
  4. Создай класс Library с полем List<Book> Books.
  5. Методы Library: AddBook(Book book), FindBook(string title), BorrowBook(string title) (меняет IsAvailable), ReturnBook(string title), PrintAllBooks().
  6. Используй инкапсуляцию: поле со списком книг сделай private, доступ — через методы.
  7. В Main() создай библиотеку, добавь несколько книг, возьми книгу, верни, выведи весь список.

Цель

Отработать создание классов, конструкторов, свойств, методов и инкапсуляцию.

Сложность

Средняя

📌

Что важно запомнить

1️⃣
Класс — это шаблон для создания объектов. Содержит поля, свойства, методы, конструкторы.
2️⃣
Конструктор — вызывается при new, инициализирует объект. Имя совпадает с именем класса, нет возвращаемого типа.
3️⃣
Свойства — контролируют доступ к данным через get и set. Авто-свойства { get; set; } — самый простой способ.
4️⃣
Модификаторы доступа: public (всем), private (только классу), protected (класс + наследники).
5️⃣
Инкапсуляция — скрытие данных (private) с предоставлением контролируемого доступа через public свойства или методы.

Тест: 8.6: Классы и объекты

4 вопроса

Классы и объекты

Premium