Код
#статьи

Что такое объекты и классы: 1‑я часть гайда по ООП

Почти всё современное программирование построено на принципах ООП, поэтому их должен понимать каждый разработчик. Узнайте основы из этой статьи.

Фото: Sonja Flemming / NBCUniversal / Getty Images

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

Цикл состоит из статей, посвящённых различным аспектам ООП:

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

Перед тем как приступать к изучению ООП, убедитесь, что знакомы со следующими понятиями:

  • переменные и типы данных,
  • условные конструкции,
  • циклы,
  • коллекции (желательно).

Работа будет проходить в Visual Studio 2019, но вполне подойдёт и VS 2017.

В конце каждой статьи будут задания, которые помогут закрепить тему. Выполнять их необязательно, но имейте в виду, что осилить ООП без практики просто невозможно. Если же вам лень выполнять задания, можете просто посмотреть наш вариант решения. Итак, поехали!

Введение в объектно-ориентированное программирование:

Что такое ООП

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

На объектах и классах строится всё ООП. Поэтому давайте чётко обозначим, чем они отличаются друг от друга.

Класс — это тип данных, созданный пользователем. Он содержит разные свойства и методы, как, например, тип String или Int.

Объект — это экземпляр класса, или его копия, которая находится в памяти компьютера. Например, когда вы создаёте переменную типа String и присваиваете ей значение «Строка», то в памяти создаётся экземпляр класса String.

По-другому можно сказать, что объекты — это сущности, у которых есть свойства и поведение. Обычно объекты являются экземплярами какого-нибудь класса. Например, в игре может быть класс Character («Персонаж»), а его экземплярами будут hero или npc.

Наш класс — это «скелет». С помощью него мы можем создать разных персонажей: героя или NPC
Инфографика: Оля Ежак / Skillbox Media

Свойства — это данные, которые связаны с конкретным объектом:

  • здоровье,
  • очки,
  • деньги,
  • сила,
  • ловкость,
  • интеллект,
  • скорость,
  • координаты.
У нашего экземпляра есть собственные свойства
Инфографика: Оля Ежак / Skillbox Media

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

  • идти,
  • атаковать,
  • говорить,
  • подобрать,
  • выбросить,
  • использовать.
Наш герой может делать разные вещи — например, атаковать, говорить и отдыхать
Инфографика: Оля Ежак / Skillbox Media

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

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

Такую парадигму используют многие популярные языки:

  • C#,
  • Java,
  • Python,
  • JavaScript,
  • PHP,
  • Kotlin,
  • Swift,
  • Objective-C,
  • C++.

У нас также есть статья по ООП на Python. Поэтому если вы не любите C#, то можете изучить главные принципы на Python.

Плюсы и минусы объектно-ориентированного программирования

ПлюсыМинусы
Легко читается.
Не нужно выискивать в коде функции и выяснять, за что они отвечают
Потребляет больше памяти.
Объекты потребляют больше оперативной памяти, чем примитивные типы данных
Быстро пишется.
Можно быстро создать сущности, с которыми должна работать программа
Снижает производительность.
Многие вещи технически реализованы иначе, поэтому они используют больше ресурсов
Проще реализовать большой набор функций.
Так как на написание кода уходит меньше времени, можно гораздо быстрее создать приложение с множеством возможностей
Сложно начать.
Парадигма ООП сложнее функционального программирования, поэтому на старт уходит больше времени
Меньше повторений.
Не нужно писать однотипные функции для разных сущностей

Основные принципы объектно-ориентированного программирования

Всё объектно-ориентированное программирование строится на четырёх понятиях:

Чтобы стало понятнее, представим, что у нас есть класс «Кошка». В нём присутствуют несколько атрибутов — например, «окрас», «порода» и «возраст», а также методов — например, «спать». И когда у нас есть класс, мы можем создать сколько угодно его экземпляров с разными свойствами. Например, мы можем добавить несколько пород кошек:

Класс один — значения свойств разные
Инфографика: Оля Ежак / Skillbox Media

Теперь перейдём к принципам ООП.

Абстракция

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

А что ещё нужно домашним кошкам?
Инфографика: Оля Ежак / Skillbox Media

Подробно об абстракции и абстрактных классах в ООП можно прочитать в другой нашей статье.

Инкапсуляция

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

Возвращаясь к нашим кошечкам. Мы можем разрешить изменять атрибут «возраст», но только в большую сторону (к сожалению, с годами никто не молодеет), а атрибут «порода» лучше открыть только для чтения — ведь порода кошки не меняется.

Мы сами можем ограничивать доступ к определённым свойствам
Инфографика: Оля Ежак / Skillbox Media

Подробно об инкапсуляции с примерами кода читайте в гайде Skillbox Media.

Наследование

Классы могут передавать свои атрибуты и методы классам-потомкам. Например, мы хотим создать новый класс «Домашняя кошка». Он практически идентичен классу «Кошка», но у него появляются новые атрибуты — «хозяин» и «кличка», а также метод «клянчить вкусняшку». Достаточно объявить «Домашнюю кошку» наследником «Кошки» и прописать новые атрибуты и методы — вся остальная функциональность перейдёт от родителя к потомку.

Классы-потомки наследуют свойства и методы классов-родителей, а также добавляют новые
Инфографика: Оля Ежак / Skillbox Media

Больше о наследовании, с примерами кода и полезными практическими советами, читайте в статье «Наследование и ещё немного полиморфизма: 6-я часть гайда по ООП».

Полиморфизм

Этот принцип позволяет применять одни и те же команды к объектам разных классов, даже если они выполняются по-разному. Например, помимо класса «Кошка», у нас есть никак не связанный с ним класс «Попугай» — и у обоих есть метод «спать». Несмотря на то, что кошки и попугаи спят по-разному (кошка сворачивается клубком, а попугай сидит на жёрдочке), для этих действий можно использовать одну команду.

Метод один и тот же, но действия разные
Инфографика: Оля Ежак / Skillbox Media

Объекты и классы: как их использовать

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

На основе «базовых» классов из C#, мы можем создавать свои. К примеру, возьмём числа типа Int64 и создадим с помощью них числа с плавающей точкой. Такой класс, конечно, уже есть, но мы можем переопределить его по-своему.

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

using System;

namespace OOPConsole
{
	class Program
	{
		static void Main(string[] args)
		{
			Console.WriteLine("Hello World!");
		}
	}
}

Здесь создаётся класс Program, у которого есть метод Main() — с него начинается выполнение программы, поэтому его называют точкой входа.

Для вывода текста используется следующий оператор:

Console.WriteLine("Hello, World!");

Тут программа обращается к объекту Console и вызывает метод WriteLine(), который выводит переданное значение в консоль.

Также у объекта Console есть разные свойства:

Console.ForegroundColor = ConsoleColor.Red; //Цвет шрифта — красный 
Console.BackgroundColor = ConsoleColor.Blue; //Цвет фона — синий

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

Вот что выведет программа:

Скриншот: Skillbox Media

Как создать класс

Чтобы создать класс, откройте в Visual Studio меню Project и выберите пункт Add Class…:

Скриншот: Skillbox Media

Затем введите его название и нажмите Add:

Скриншот: Skillbox Media

Программа создаст отдельный файл с таким кодом:

namespace OOPConsole
{
	class Character
	{
	}
}

Разберём его по порядку.

Namespace — это пространство имён, в котором находится класс. Оно необходимо для того, чтобы не возникало конфликтов с именами классов и переменных из подключаемых библиотек. Например, можно создать свой класс Console, и это не будет ошибкой, потому что он будет находиться в другом пространстве имён.

OOPConsole.Console.WriteLine(); //Класс, созданный в пространстве имён программы
System.Console.WriteLine(); //Класс из системного пространства имён

Затем в коде следует ключевое слово class, которое говорит о том, что нужно создать класс с определённым именем. В данном случае — Character.

Запомните! Имена классов принято писать с заглавной буквы, а объектов — со строчной.

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

Чтобы объявить объект, нужно перейти к методу main и использовать следующий код:

Character hero = new Character();

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

Как использовать поля и свойства класса

Теперь можно добавить поля и свойства для класса. Поле объявляется как обычная переменная:

class Character
{
	int health = 100;
	int x = 50;
	int y = 25;
}

Теперь у объекта есть свои поля, но к ним нельзя обратиться извне, потому что закрыт доступ (подробнее об этом — в статье про инкапсуляцию). Чтобы его открыть, нужно поставить перед каждым полем ключевое слово public. То же слово нужно поставить перед словом class.

public class Character
{
	public int health = 100;
	public int x = 50;
	public int y = 25;
}

Теперь можно вернуться к созданному объекту и обратиться к его полю здоровья:

Console.WriteLine(hero.health);

Вот что будет выведено:

Скриншот: Skillbox Media

Если доступ к полям открыт, то с ними можно проводить вычисления или просто получать их значения. Если же нужно запретить доступ к определённым полям — используйте свойства.

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

public class Character
{
	private int health = 100;
	private int x = 50;
	private int y = 25;
}

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

public int Health
{
	get
	{
		return this.health;
	}

	set
	{
		this.health = value;
	}
}

public int X {
	get
	{
		return this.x;
	}
}

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

  • get (геттер — позволяет получить значение свойства) — возвращает значение, которое идёт после ключевого слова return;
  • set (сеттер — позволяет изменить значение) — указывает поле, которое нужно изменить, используя значение value.

Также тут можно заметить ключевое слово this, которое обозначает, что поле принадлежит этому объекту. Использовать его необязательно, но оно делает код более читаемым.

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

public bool IsAlive
{
	get
	{
		return this.health > 0 ? true : false;
	}
}

Значение, которое будет возвращено, зависит от здоровья персонажа. Если оно ниже нуля, то будет передано false, а если выше — true.

Вот как это работает в программе:

Console.WriteLine($"Hero's health: {hero.Health} | Hero is alive: {hero.IsAlive}");
hero.Health = 0;
Console.WriteLine($"Hero's health: {hero.Health} | Hero is alive: {hero.IsAlive}");

Вот что будет выведено на экран:

Скриншот: Skillbox Media

Как создать метод

Теперь можно приступить к работе с поведением объектов. Оно реализуется с помощью методов — специальных блоков кода, которые позволяют избежать повторений в проекте.

Методы являются аналогами функций (возвращают значение) и процедур (не возвращают), но с той разницей, что они являются частью какого-то класса. Например, можно в классе Character создать метод Move(), который будет отвечать за движение персонажа.

public void Move(string direction)
{
	switch (direction)
	{
		case "forward":
			this.x++;
			break;
		case "backward":
			this.x--;
			break;
		case "up":
			this.y++;
			break;
		case "down":
			this.y--;
			break;
	}
}

Сначала указывается уровень доступа public, затем тип возвращаемого значения (в данном случае используется void, что говорит компилятору о том, что ничего возвращать не нужно). Затем идёт название метода и круглые скобки.

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

Вот пример вызова метода:

Character hero = new Character();

string command = string.Empty;

while (command != "exit")
{
	Console.WriteLine($"You are at {hero.Coordinates}. Where to go?");
	command = Console.ReadLine();

	hero.Move(command);
}

Пользователь видит свои координаты и вводит направление движения, а персонаж двигается с помощью метода Move(). Всё это повторяется, пока не будет введена команда exit.

Вот как это выглядит:

Скриншот: Skillbox Media

Если же нужно, чтобы метод что-то возвращал, то указывается его тип и используется оператор return:

public bool Collide(Character ch)
{
	if (ch.X == this.x && ch.Y == this.y)
	{
		return true;
	}
	else
	{
		return false;
	}
}

Этот метод принимает в качестве аргумента объект класса Character и сравнивает координаты. Если они равны, то метод возвращает значение true, а иначе — false.

Вот как это можно использовать:

Character hero = new Character();

Character npc = new Character();

bool collide = hero.Collide(npc);

if (collide)
{
	Console.WriteLine("Objects are on the same position.");
}
else
{
	Console.WriteLine("Objects are not on the same position.");
}

Console.ReadKey();

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

Скриншот: Skillbox Media

Конструктор объекта

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

Вот пример того же класса с конструктором:

public class Character
{
	private string name;
	private int health;
	private int x;
	private int y;

	public Character(string name, int x, int y)
	{
		this.name = name;
		this.x = x;
		this.y = y;
		this.health = 100;
		Console.WriteLine($"Object {this.name} was created at {this.Coordinates}.");
	}

	//Геттеры и сеттеры из прошлых примеров
}

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

Character hero = new Character("Player", 50, 20);
Character npc = new Character("Enemy", 20, 30);
Console.ReadKey();

Вот что выведет программа:

Скриншот: Skillbox Media

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

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

  • перемещение;
  • атака;
  • получение опыта за победу над врагами;
  • повышение уровня и здоровья персонажа.

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

Это лишь вступление в ООП, и ещё многое предстоит изучить, чтобы начать применять его в полную силу. Например, нужно понять, как технически работают классы, как они хранятся в памяти, почему их называют ссылочными типами и так далее.

ООП — сложная, но эффективная парадигма программирования. Её стоит знать всем, кто хочет создавать программы и найти работу, потому что почти все популярные языки её поддерживают. И несмотря на то, что некоторые разработчики утверждают, будто «ООП умерло», потребность в программистах, которые владеют этим подходом, продолжает расти.

Больше интересного про код в нашем телеграм-канале. Подписывайтесь!

Научитесь: Профессия Разработчик на C++ с нуля Узнать больше
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована