ООП Часть 3: модификаторы доступа, инкапсуляция

Классы, методы и поля не всегда могут постоять за себя. Рассказываем, как быть защитником в объектно-ориентированном программировании.

Инкапсуляция (от лат. in capsule — в оболочке) — это заключение данных и функционала в оболочку. В объектно-ориентированном программировании в роли оболочки выступают классы: они не только собирают переменные и методы в одном месте, но и защищают их от вмешательства извне.

Это может быть достаточно сложной концепцией для понимания. Поэтому, чтобы быстрее разобраться, в этой статье мы рассмотрим инкапсуляцию на примере уровней доступа.

Евгений Кучерявый

Пишет о разработке сайтов, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.


Модификатор доступа public

Первый уровень, с которым сталкиваются все разработчики, — публичный. Чтобы сказать компилятору, что что-то должно быть доступно для всех, используется ключевое слово public.

Рассмотрим на примере класса Item:

public class Item
{
    public string name;
    public int cost;
 
    public Item(string name, int cost)
    {
        this.name = name;
        this.cost = cost;
    }
}

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

Item sword = new Item("Sword of Destiny", 500000);
 
Console.WriteLine($"You've got {sword.name}! It costs {sword.cost} rubles.");

Так как поля публичные, в консоли они отобразятся без каких-либо проблем:

Это удобно, потому что можно в любой момент выполнить любое действие над объектом и его данными. Но в этом и кроется проблема: объект становится беззащитен перед любым вмешательством. Например, можно просто взять и изменить его цену:

sword.cost = 50;
Console.WriteLine($"You've got {sword.name}! It costs {sword.cost} rubles.");

Из-за того, что поле публичное, оно изменится:

Это плохо по нескольким причинам:

  • Если поле можно изменить в любом месте программы, сложно отслеживать ошибки, потому что не всегда понятно, где что-то пошло не так.
  • Если вдруг в программу во время работы попадёт чужой код, он сможет изменять и читать любые поля любых объектов.

Разумеется, это не лучшее, что может случиться с приложением.

Модификатор доступа private

Чтобы поля были защищены от вмешательства, используется ключевое слово private — оно делает члены класса доступными только внутри самого класса.

public class Item
{
    private string name;
    private int cost;
 
    public Item(string name, int cost)
    {
        this.name = name;
        this.cost = cost;
    }
}

Теперь эти поля нельзя будет изменить нигде, кроме как в методах этого класса. Но и получить их значение извне тоже не получится, а попытка вывести приведёт к ошибке:

Есть два способа сделать поле доступным только для чтения. Первый — использовать ключевое слово readonly, но оно запрещает менять значение вообще.

Второй способ заключается в том, чтобы передавать значения приватного члена класса через публичный. Например, с помощью методов:

public string GetName()
{
    return this.name;
}
 
public int GetCost()
{
    return this.cost;
}

К такой практике прибегают Java-разработчики, но в C# есть более элегантный способ — свойства.

public string Name 
{ //Объявление свойства. Свойства принято называть так же, как и поля, но начинаются они с заглавной буквы
    get //Конструкция get (также она называется геттером) позволяет определить логику получения значения
    {
        return this.name;
    }
 
    set //Конструкция set (сеттер) позволяет определить логику изменения значения
    {
        this.name = value; //value — это значение, которое передаётся свойству
    }
}

Теперь, чтобы получить данные, нужно обратиться к свойству, а не к полю:

sword.Name = "Sword of Protection"; //Значения для свойства передаются так же, как и для обычной переменной
 
Console.WriteLine($"You've got {sword.Name}!");

Преимущество этого в том, что можно разрешить получать данные, но запретить их менять. То есть прописать только геттер:

public int Cost
{
    get;
}

Обратите внимание, что можно просто написать set; или get; если не требуется дополнительная логика. Это сработает, если у поля и свойства одинаковые имена и если это примитивный тип (int, float, char, double и другие). Со ссылочными типами (объекты и строки) это не работает.

Также можно менять логику работы со значением:

public int Cost
{
    get
    {
        return this.cost;
    }
 
    set
    {
        if(value > 0)
        {
            this.cost = value;
        }
    }
}

Здесь поле будет изменено только в том случае, если ему пытаются указать значение, которое выше нуля.

То есть если запустить вот такой код:

sword.Cost = 50;
sword.Cost = -1;
 
Console.WriteLine($"Sword's cost: {sword.Cost}!");

То выведено будет 50, а не -1:

Также можно создавать свойства без поля:

public bool IsExpensive
{
    get
    {
        if(this.cost > 5000)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

Это свойство вернёт true, если цена выше 5000, и false, если ниже.

Ключевое слово private можно также применять и к методам. Это делает их доступными только внутри класса.

private void Wipe()
{
    this.name = "";
    this.cost = 0;
}

Также приватным можно сделать сам класс, если он находится внутри другого класса:

public class Char
{
 
    private Collider collider = new Collider();
 
    private class Collider
    {
 
        public int x = 5;
        public int y = 10;
 
        public int width = 15;
        public int height = 25;
 
    }
}

Модификатор доступа internal

Иногда нужно сделать компонент доступным только внутри одного файла — например, в Program.cs, Item.cs или любом другом. Для этого используется ключевое слово internal.

class Program
{
    static void Main(string[] args)
    {
        Backpack b = new Backpack();
        Console.WriteLine($"{b.itemsCount} items in the backpack.");
    }
}
 
internal class Backpack
{
    public int itemsCount = 10;
}

Класс Backpack можно будет использовать только внутри файла Program.cs, и попытка объявить его внутри другого файла приведёт к ошибке.

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

Статичность относится не совсем к уровням доступа, но тоже помогает заключить реализацию функционала в оболочку класса. Статичность позволяет обращаться к методам или полям, не создавая объект.

Например:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"5 + 6 = {Calc.Sum(5, 6)}");
    }
}
 
class Calc
{
    public static int Sum(int a, int b) //Создание статичного метода
    {
        return a + b;
    }
}

Метод Sum() используется в классе Program, хотя экземпляр класса Calc не создавался. При этом можно сделать статичным как отдельный метод или свойство, так и весь класс. В этом случае все поля и методы тоже должны быть статичными.

Это может быть нужно, чтобы создать набор инструментов, который будет использоваться в других частях программы. Хороший пример — класс Console, который тоже является статичным.

Другой пример — класс Math. Его можно использовать, чтобы выполнять различные математические операции (получение квадратного корня, модуляция, получение синуса, косинуса и так далее). У него много методов, а также он хранит различные константы вроде числа пи.

Домашнее задание

Напишите класс GameObject, в котором будут храниться координаты объекта. Координаты должны быть доступны для чтения, а их изменение должно происходить в методе Move().

Заключение

Есть и другие ключевые слова:

  • abstract;
  • protected;
  • private protected;
  • protected internal;
  • sealed и другие.

Они будут рассмотрены в статье о наследовании.

Большая часть курса «Профессия С#-разработчик» посвящена именно ООП —
не только теории, но и практике. Вы научитесь писать программы, подбирая нужные инструменты — от инкапсуляции до полиморфизма. К концу курса у вас будет портфолио из нескольких проектов, а также все знания и навыки, которые нужны для получения первой работы.

Курс

Профессия С#-разработчик


130 часов — и вы научитесь писать программы на языке, созданном Microsoft. Вы создадите 5 проектов для портфолио, даже если до этого никогда не программировали. После обучения — гарантированное трудоустройство.

Хочешь получать крутые статьи по программированию?
Подпишись на рассылку Skillbox