Что такое объекты и классы: 1‑я часть гайда по ООП
Почти всё современное программирование построено на принципах ООП, поэтому их должен понимать каждый разработчик. Узнайте основы из этой статьи.
Фото: Sonja Flemming / NBCUniversal / Getty Images
Это первая статья из серии, посвящённой объектно-ориентированному программированию. Она предназначена для тех, кто хочет понять суть этой парадигмы разработки, а не просто научиться использовать классы и объекты.
Цикл состоит из статей, посвящённых различным аспектам ООП:
- что такое классы и объекты;
- особенности работы с объектами;
- модификаторы доступа, инкапсуляция;
- перегрузка методов;
- полиморфизм;
- наследование и ещё немного полиморфизма;
- абстрактные классы и интерфейсы;
- практикум.
Все примеры в этой серии написаны на языке C#. Для наглядности они будут связаны с разработкой игр, потому что именно в играх (хотя далеко не только в них) активно используются объекты.
Перед тем как приступать к изучению ООП, убедитесь, что знакомы со следующими понятиями:
- переменные и типы данных,
- условные конструкции,
- циклы,
- коллекции (желательно).
Работа будет проходить в Visual Studio 2019, но вполне подойдёт и VS 2017.
В конце каждой статьи будут задания, которые помогут закрепить тему. Выполнять их необязательно, но имейте в виду, что осилить ООП без практики просто невозможно. Если же вам лень выполнять задания, можете просто посмотреть наш вариант решения. Итак, поехали!
Введение в объектно-ориентированное программирование:
Что такое ООП
Объектно-ориентированное программирование (сокращённо ООП) — это парадигма разработки программного обеспечения, согласно которой приложения состоят из объектов.
На объектах и классах строится всё ООП. Поэтому давайте чётко обозначим, чем они отличаются друг от друга.
Класс — это тип данных, созданный пользователем. Он содержит разные свойства и методы, как, например, тип String или Int.
Объект — это экземпляр класса, или его копия, которая находится в памяти компьютера. Например, когда вы создаёте переменную типа String и присваиваете ей значение «Строка», то в памяти создаётся экземпляр класса String.
По-другому можно сказать, что объекты — это сущности, у которых есть свойства и поведение. Обычно объекты являются экземплярами какого-нибудь класса. Например, в игре может быть класс Character («Персонаж»), а его экземплярами будут hero или npc.
Свойства — это данные, которые связаны с конкретным объектом:
- здоровье,
- очки,
- деньги,
- сила,
- ловкость,
- интеллект,
- скорость,
- координаты.
Поведение объекта определяется с помощью методов — специальных блоков кода, которые можно вызывать из разных частей программы. Например, у того же объекта Character могут быть следующие методы:
- идти,
- атаковать,
- говорить,
- подобрать,
- выбросить,
- использовать.
Используя эти свойства и методы, можно значительно ускорить разработку, сделать код более читаемым. К тому же самому программисту проще составлять код, если он думает с помощью объектов.
Разработчики не пишут какую-то функцию, которая будет делать что-то для программы в целом. Вместо этого они мысленно разделяют приложение на отдельные компоненты и продумывают их свойства и поведение.
Такую парадигму используют многие популярные языки:
- C#,
- Java,
- Python,
- JavaScript,
- PHP,
- Kotlin,
- Swift,
- Objective-C,
- C++.
У нас также есть статья по ООП на Python. Поэтому если вы не любите C#, то можете изучить главные принципы на Python.
Плюсы и минусы объектно-ориентированного программирования
Плюсы | Минусы |
---|---|
Легко читается. Не нужно выискивать в коде функции и выяснять, за что они отвечают | Потребляет больше памяти. Объекты потребляют больше оперативной памяти, чем примитивные типы данных |
Быстро пишется. Можно быстро создать сущности, с которыми должна работать программа | Снижает производительность. Многие вещи технически реализованы иначе, поэтому они используют больше ресурсов |
Проще реализовать большой набор функций. Так как на написание кода уходит меньше времени, можно гораздо быстрее создать приложение с множеством возможностей | Сложно начать. Парадигма ООП сложнее функционального программирования, поэтому на старт уходит больше времени |
Меньше повторений. Не нужно писать однотипные функции для разных сущностей | |
Основные принципы объектно-ориентированного программирования
Всё объектно-ориентированное программирование строится на четырёх понятиях:
Чтобы стало понятнее, представим, что у нас есть класс «Кошка». В нём присутствуют несколько атрибутов — например, «окрас», «порода» и «возраст», а также методов — например, «спать». И когда у нас есть класс, мы можем создать сколько угодно его экземпляров с разными свойствами. Например, мы можем добавить несколько пород кошек:
Теперь перейдём к принципам ООП.
Абстракция
При создании класса мы упрощаем его до тех атрибутов и методов, которые нужны в этом конкретном коде, не пытаясь описать его целиком и отбрасывая всё второстепенное. Скажем, все кошки теоретически умеют охотиться, но если наша программа не предназначена для ловли мышей, то и прописывать этот метод незачем.
Подробно об абстракции и абстрактных классах в ООП можно прочитать в другой нашей статье.
Инкапсуляция
Доступ к данным объекта должен контролироваться, чтобы пользователь не мог изменить их в произвольном порядке и что-то поломать. Поэтому для работы с данными программисты пишут публичные методы, которые составляют интерфейс объекта.
Возвращаясь к нашим кошечкам. Мы можем разрешить изменять атрибут «возраст», но только в большую сторону (к сожалению, с годами никто не молодеет), а атрибут «порода» лучше открыть только для чтения — ведь порода кошки не меняется.
Подробно об инкапсуляции с примерами кода читайте в гайде Skillbox Media.
Наследование
Классы могут передавать свои атрибуты и методы классам-потомкам. Например, мы хотим создать новый класс «Домашняя кошка». Он практически идентичен классу «Кошка», но у него появляются новые атрибуты — «хозяин» и «кличка», а также метод «клянчить вкусняшку». Достаточно объявить «Домашнюю кошку» наследником «Кошки» и прописать новые атрибуты и методы — вся остальная функциональность перейдёт от родителя к потомку.
Больше о наследовании, с примерами кода и полезными практическими советами, читайте в статье «Наследование и ещё немного полиморфизма: 6-я часть гайда по ООП».
Полиморфизм
Этот принцип позволяет применять одни и те же команды к объектам разных классов, даже если они выполняются по-разному. Например, помимо класса «Кошка», у нас есть никак не связанный с ним класс «Попугай» — и у обоих есть метод «спать». Несмотря на то, что кошки и попугаи спят по-разному (кошка сворачивается клубком, а попугай сидит на жёрдочке), для этих действий можно использовать одну команду.
Объекты и классы: как их использовать
Классами в C# является практически всё — строки, числа, массивы и так далее. У каждого из них есть свой набор свойств (например, количество символов в строке или размер типа данных), а также методы, которые позволяют удобно работать с объектами класса (например, отсортировать массив или сложить два числа).
На основе «базовых» классов из C#, мы можем создавать свои. К примеру, возьмём числа типа Int64 и создадим с помощью них числа с плавающей точкой. Такой класс, конечно, уже есть, но мы можем переопределить его по-своему.
Изучая C#, разработчик в первый же день сталкивается с классами и объектами. Например, вот как выглядит первая программа любого новичка:
Здесь создаётся класс Program, у которого есть метод Main() — с него начинается выполнение программы, поэтому его называют точкой входа.
Для вывода текста используется следующий оператор:
Тут программа обращается к объекту Console и вызывает метод WriteLine(), который выводит переданное значение в консоль.
Также у объекта Console есть разные свойства:
Если бы не было объекта, было бы сложно определить, цвет какого фона и какого шрифта будет указываться, потому что их в программе может быть несколько.
Вот что выведет программа:
Как создать класс
Чтобы создать класс, откройте в Visual Studio меню Project и выберите пункт Add Class…:
Затем введите его название и нажмите Add:
Программа создаст отдельный файл с таким кодом:
Разберём его по порядку.
Namespace — это пространство имён, в котором находится класс. Оно необходимо для того, чтобы не возникало конфликтов с именами классов и переменных из подключаемых библиотек. Например, можно создать свой класс Console, и это не будет ошибкой, потому что он будет находиться в другом пространстве имён.
Затем в коде следует ключевое слово class, которое говорит о том, что нужно создать класс с определённым именем. В данном случае — Character.
Запомните! Имена классов принято писать с заглавной буквы, а объектов — со строчной.
Всё, что находится внутри фигурных скобок, относится к этому классу. Несмотря на то, что он пустой, уже можно создать его экземпляр — объект. Это называется объявлением или инстанцированием.
Чтобы объявить объект, нужно перейти к методу main и использовать следующий код:
Это похоже на то, как создаются переменные, но вместо типа данных указывается название класса. После знака присваивания указываются ключевое слово new и конструктор — специальный метод, который позволяет создать объект (о нём читайте в блоке о методах).
Как использовать поля и свойства класса
Теперь можно добавить поля и свойства для класса. Поле объявляется как обычная переменная:
Теперь у объекта есть свои поля, но к ним нельзя обратиться извне, потому что закрыт доступ (подробнее об этом — в статье про инкапсуляцию). Чтобы его открыть, нужно поставить перед каждым полем ключевое слово public. То же слово нужно поставить перед словом class.
Теперь можно вернуться к созданному объекту и обратиться к его полю здоровья:
Вот что будет выведено:
Если доступ к полям открыт, то с ними можно проводить вычисления или просто получать их значения. Если же нужно запретить доступ к определённым полям — используйте свойства.
Это специальные конструкции, которые позволяют обращаться к полям. Чтобы создать свойства, нужно сначала закрыть доступ к полям с помощью уровня доступа private — тогда они будут доступны только изнутри класса:
Теперь нельзя узнать здоровье или координаты объекта или изменить их. Чтобы снова открыть к ним доступ, создайте свойства:
Тут указывается уровень доступа public, затем тип свойства и его имя. Имена свойств принято писать с заглавной буквы, и они должны соответствовать имени поля. Внутри свойства встречаются две конструкции:
- get (геттер — позволяет получить значение свойства) — возвращает значение, которое идёт после ключевого слова return;
- set (сеттер — позволяет изменить значение) — указывает поле, которое нужно изменить, используя значение value.
Также тут можно заметить ключевое слово this, которое обозначает, что поле принадлежит этому объекту. Использовать его необязательно, но оно делает код более читаемым.
Такие манипуляции нужны для того, чтобы доступ к полям осуществлялся только так, как это нужно разработчику. Например, можно создать свойство, значение которого будет зависеть от выполнения условия:
Значение, которое будет возвращено, зависит от здоровья персонажа. Если оно ниже нуля, то будет передано false, а если выше — true.
Вот как это работает в программе:
Вот что будет выведено на экран:
Как создать метод
Теперь можно приступить к работе с поведением объектов. Оно реализуется с помощью методов — специальных блоков кода, которые позволяют избежать повторений в проекте.
Методы являются аналогами функций (возвращают значение) и процедур (не возвращают), но с той разницей, что они являются частью какого-то класса. Например, можно в классе Character создать метод Move(), который будет отвечать за движение персонажа.
Сначала указывается уровень доступа public, затем тип возвращаемого значения (в данном случае используется void, что говорит компилятору о том, что ничего возвращать не нужно). Затем идёт название метода и круглые скобки.
Внутри скобок указываются аргументы, которые принимает метод (в данном случае направление движения), — от переданных аргументов зависит результат работы метода.
Вот пример вызова метода:
Пользователь видит свои координаты и вводит направление движения, а персонаж двигается с помощью метода Move(). Всё это повторяется, пока не будет введена команда exit.
Вот как это выглядит:
Если же нужно, чтобы метод что-то возвращал, то указывается его тип и используется оператор return:
Этот метод принимает в качестве аргумента объект класса Character и сравнивает координаты. Если они равны, то метод возвращает значение true, а иначе — false.
Вот как это можно использовать:
Приведённая выше программа сравнит координаты двух объектов и выведет результат. Так как объекты создаются с одинаковыми полями, то они будут равны:
Конструктор объекта
В примере выше объект создаётся с уже заданными значениями, но есть возможность указывать свои параметры. Для этого используются конструкторы — специальные методы, которые запускаются при инстанцировании экземпляра класса. С их помощью можно передать объекту параметры и провести необходимые операции.
Вот пример того же класса с конструктором:
Конструктор выглядит как метод, но ему не нужно указывать тип возвращаемых данных — он возвращает созданный объект. Теперь, чтобы объявить экземпляр класса, нужно вызвать конструктор с указанным количеством аргументов:
Вот что выведет программа:
Домашнее задание
Создайте консольную игру, в которой игрок сможет управлять персонажем с помощью команд. Возможности, которые должны быть в игре:
- перемещение;
- атака;
- получение опыта за победу над врагами;
- повышение уровня и здоровья персонажа.
Что запомнить
Это лишь вступление в ООП, и ещё многое предстоит изучить, чтобы начать применять его в полную силу. Например, нужно понять, как технически работают классы, как они хранятся в памяти, почему их называют ссылочными типами и так далее.
ООП — сложная, но эффективная парадигма программирования. Её стоит знать всем, кто хочет создавать программы и найти работу, потому что почти все популярные языки её поддерживают. И несмотря на то, что некоторые разработчики утверждают, будто «ООП умерло», потребность в программистах, которые владеют этим подходом, продолжает расти.
Больше интересного про код в нашем телеграм-канале. Подписывайтесь!