Объектно-ориентированное программирование. Часть 1: что такое классы и объекты

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

Введение

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

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

  1. Введение в ООП: создание классов и объектов.
  2. Особенности работы с объектами.
  3. Инкапсуляция и зависимости.
  4. Полиморфизм.
  5. Наследование, абстрактные классы, интерфейсы.

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

Перед чтением этой серии статей вам нужно ознакомиться с такими понятиями, как:

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

Работа будет происходить в Visual Studio 2019, но вполне подойдет и VS 2017. В конце каждой статьи будут задания, которые помогут закрепить тему. 

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

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


Что такое ООП

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

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

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

  • здоровье;
  • очки;
  • деньги;
  • сила;
  • ловкость;
  • интеллект;
  • скорость;
  • координаты.

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

  • идти;
  • атаковать;
  • говорить;
  • подобрать;
  • выбросить;
  • использовать.

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

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

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

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

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

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

Как использовать классы и объекты

Изучая 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; //Цвет фона — синий

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

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

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

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

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

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

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);

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

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

Это специальные конструкции, которые позволяют обращаться к полям. Чтобы создать свойства, нужно сначала закрыть доступ к полям с помощью уровня доступа 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, затем тип свойства и его имя. Имена свойств принято начинать с заглавной буквы, и они должны соответствовать имени поля. Внутри свойства встречаются две конструкции:

  1. get (геттер — позволяет получить значение свойства). В ней возвращается значение, которое идет после ключевого слова return;
  2. 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}");

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

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

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

Методы являются аналогами функций (возвращают значение) и процедур (не возвращают), но с той разницей, что они являются частью какого-то класса. Например, можно в классе 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.

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

Если же нужно, чтобы метод что-то возвращал, то указывается его тип и используется оператор 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();

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

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

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

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

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();

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

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

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

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

Итог

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

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

Курс

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


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

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