Какие основные принципы ООП?

Ответ

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

  1. Инкапсуляция — сокрытие внутреннего состояния объекта и деталей реализации. Доступ к данным осуществляется только через публичный интерфейс (методы, свойства). Это защищает целостность данных и позволяет изменять реализацию, не ломая клиентский код.

    public class BankAccount
    {
        private decimal _balance; // Поле скрыто (инкапсулировано)
    
        public decimal Balance { get { return _balance; } } // Только чтение
    
        public void Deposit(decimal amount)
        {
            if (amount > 0) _balance += amount; // Валидация внутри класса
        }
    }
  2. Наследование — механизм, позволяющий создавать новый класс (производный) на основе существующего (базового), заимствуя его поля и методы. В C# поддерживается одиночное наследование классов.

    public class Vehicle // Базовый класс
    {
        public string Make { get; set; }
        public void StartEngine() { Console.WriteLine("Engine started."); }
    }
    
    public class Car : Vehicle // Производный класс
    {
        public int NumberOfDoors { get; set; }
    }
    // Car автоматически получает свойства Make и метод StartEngine.
  3. Полиморфизм — способность объектов с одинаковой спецификацией (например, один базовый класс) иметь различную реализацию. Позволяет работать с объектами производных классов через интерфейс базового класса.

    public abstract class Shape
    {
        public abstract double GetArea(); // Абстрактный метод
    }
    
    public class Circle : Shape
    {
        public double Radius { get; set; }
        public override double GetArea() => Math.PI * Radius * Radius;
    }
    
    public class Square : Shape
    {
        public double Side { get; set; }
        public override double GetArea() => Side * Side;
    }
    
    // Полиморфное поведение:
    List<Shape> shapes = new List<Shape> { new Circle { Radius = 5 }, new Square { Side = 4 } };
    foreach (var shape in shapes)
    {
        Console.WriteLine(shape.GetArea()); // Вызовется правильная реализация для каждого типа
    }
  4. Абстракция — выделение существенных характеристик объекта и игнорирование несущественных деталей. Реализуется через абстрактные классы и интерфейсы, которые определяют что должен делать объект, но не как.

    // Абстракция через интерфейс
    public interface ILogger
    {
        void Log(string message); // Контракт: любой логгер должен уметь логировать
    }
    
    public class FileLogger : ILogger { public void Log(string message) { /* запись в файл */ } }
    public class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine(message); } }

Эти принципы являются фундаментом для более продвинутых принципов проектирования, таких как SOLID.

Ответ 18+ 🔞

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

Вот смотри, представь, что ты делаешь не программу, а собираешь тачку в гараже. Все эти принципы — они как раз про то, чтобы не накосячить и чтобы потом не пришлось всё переделывать с бензопилой.

1. Инкапсуляция — это когда ты прячешь движок под капот, а не везешь его на прицепе.

Суть в том, чтобы спрятать всю свою внутреннюю кухню. Вот у тебя класс BankAccount. Ты же не оставляешь переменную с балансом на всеобщее обозрение, как голую жопу на пляже? Нет, блядь. Ты её в private засовываешь, чтобы любой мудак с улицы не мог прийти и написать account._balance = 1000000. Доступ только через методы, типа Deposit. А внутри метода ты уже можешь проверять: а положительная ли сумма? А не пытается ли тебя наебать? Всё это внутри, никто не видит. Меняй потом хоть весь движок — снаружи всё равно будут вызывать Deposit, и им похуй, как оно там внутри работает.

public class BankAccount
{
    private decimal _balance; // Спрятано, как золото в сейфе. Руками не трогать!

    public decimal Balance { get { return _balance; } } // Посмотреть можно, а потрогать — нет.

    public void Deposit(decimal amount)
    {
        if (amount > 0) _balance += amount; // А вот тут уже наша внутренняя логика, наше святое.
    }
}

2. Наследование — это когда ты берешь жигули, ставишь на них турбину и говоришь: «Это теперь спорткар».

Зачем с нуля ваять новую тачку, если можно взять готовую и добавить то, чего не хватает? Базовый класс Vehicle — это как голая жигулевская платформа: руль, колеса, движок. А Car от него наследуется — это уже конкретная модель. Она ВСЁ получает от родителя автоматически, плюс может добавить свои фишки, например, NumberOfDoors. Не нужно копипастить код про «завести двигатель» в каждый класс — он уже есть у родителя. Удобно, ёпта.

public class Vehicle // База, основа основ
{
    public string Make { get; set; }
    public void StartEngine() { Console.WriteLine("Движок заурчал."); }
}

public class Car : Vehicle // Наследник. Двоеточие — это как сказать «я всё от папочки взял»
{
    public int NumberOfDoors { get; set; } // И своё добавил
}
// Объект Car теперь умеет и StartEngine() (от папы), и имеет своё свойство.

3. Полиморфизм — это когда у тебя есть понятие «фигура», и ты говоришь «нарисуй», а оно само разбирается, круг это или квадрат.

Вот это, блядь, мощь. Ты создаёшь общий тип, например, Shape (фигура), и говоришь: «все, кто от тебя произойдут, обязаны уметь считать площадь». Но КАК именно считать — это их проблемы. Круг считает через Пи * R², квадрат — сторону на сторону. А ты в коде работаешь просто со списком Shape. И когда ты у каждого вызываешь GetArea(), он сам, по-своему, площадь и посчитает. Это и есть «много форм» (полиморфизм) у одного действия. Магия, но логичная.

public abstract class Shape // Абстрактный класс. Нельзя создать «просто фигуру», только конкретную.
{
    public abstract double GetArea(); // Контракт: «Дети, реализуйте этот метод, а то получите по ебалу!»
}

public class Circle : Shape
{
    public double Radius { get; set; }
    public override double GetArea() => Math.PI * Radius * Radius; // Вот так, пап, считаю!
}

public class Square : Shape
{
    public double Side { get; set; }
    public override double GetArea() => Side * Side; // А я вот так!

// А теперь полиморфизм в деле:
List<Shape> shapes = new List<Shape> { new Circle { Radius = 5 }, new Square { Side = 4 } };
foreach (var shape in shapes)
{
    // Вызывается один и тот же метод, но работает по-разному для круга и квадрата. Вот это сила.
    Console.WriteLine(shape.GetArea());
}

4. Абстракция — это когда ты говоришь «мне нужен инструмент, который забивает гвозди», а не «дай мне вот тот конкретный красный молоток с деревянной ручкой».

Ты откидываешь все лишние детали и оставляешь только суть. В коде это делается через интерфейсы или абстрактные классы. Ты определяешь ЧТО должно быть сделано, но не говоришь КАК. «Логгер должен уметь логировать» — вот и весь интерфейс ILogger. А как он это будет делать — в файл, в консоль или в облако — это уже проблемы классов FileLogger и ConsoleLogger. Тебе, тому кто использует логгер, похуй на детали. Главное — контракт выполняется.

// Абстракция. Не «журнал», а «то, что может записывать сообщения».
public interface ILogger
{
    void Log(string message); // Подпишись здесь, если хочешь называться логгером.
}

// Одна реализация
public class FileLogger : ILogger { public void Log(string message) { /* Пишем в файл, там свои заморочки */ } }
// Другая реализация
public class ConsoleLogger : ILogger { public void Log(string message) { Console.WriteLine($"Записал в консоль: {message}"); } }
// А в коде ты просто работаешь с ILogger, и тебе всё равно, кто там под капотом.

Вот эти четыре столпа, они, блядь, и держат всю эту вашу объектную архитектуру. А если их правильно применять, то код получается не как спагетти, а как нормальный, вменяемый конструктор. А потом на них уже нанизываются эти ваши SOLID-принципы, но это уже совсем другая история, на трезвую голову.

Видео-ответы