Найти тему
IT. Как это работает?

От транзистора до фреймворка. Часть 16. ООП на языке Си

Оглавление

Видео: YouTube

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

Программист как строитель

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

Силовые элементы конструкции
Силовые элементы конструкции

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

Принципы ООП

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

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

Разнотипные структуры
Разнотипные структуры

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

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

Общий предок у двух разных типов
Общий предок у двух разных типов

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

На рисунке приведена так называемая диаграмма классов.

"Чертеж" интерфейса у объектов
"Чертеж" интерфейса у объектов

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

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

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

Полиморфизм

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

Использование паттерна Посредник

Пока это рыцарь и король можно придумать дюжину методов внутри объектов для взаимодействия между ними.  Представим, что мы описали все тонкости их взаимодействия. Очевидно, что при развитии программы будут появляться все новые и новые объекты. Описание всех взаимодействий внутри юнитов будет трудозатратным мероприятием. Опыт решения многочисленных похожих задач закреплен в таком понятии как паттерны проектирования, они же шаблоны проектирования. Это не руководство к действию, это рекомендации как можно поступить при возникновении той или иной проблемы. Чтобы организовать взаимодействие между различными объектами хорошо подходит паттерн Посредник. Кто же такой посредник? Это такой объект, который берет на себя все проблемы взаимодействия между объектами. Юниты могут не знать о существовании друг друга. Они знают, что есть посредник и к нему можно обратиться за помощью. Посредник же знает сколько юнитов есть в наличии и у него есть методы, которыми можно рассудить все взаимоотношения объектов.После разрешения всех проблем посредник отдает все необходимые распоряжения через интерфейс взаимодействия с юнитами.

"Чертеж" паттерна Посредник
"Чертеж" паттерна Посредник

Если бы объектно-ориентированного подхода не было, то даже не знаю как можно было бы объяснить решение такого рода задач. С чего бы начать рассмотрение всего того, что изображено на этой диаграмме? Начнем с юнитов. Они повторяют друг за другом. Во-первых, у них есть указатель на объект посредник. Появился метод walkToNearest() , это передвигаться к ближайшему юниту. Поскольку у юнита нет информации кто вообще на карте есть, то этот метод переадресует вопрос посреднику.

Запрос Посреднику на передвижение
Запрос Посреднику на передвижение

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

Перемещение объекта через интерфейс
Перемещение объекта через интерфейс

Метод strike() наносит удар и решить было ли попадание по другому юниту может только посредник. Поэтому strike() перенаправит вопрос посреднику. Метод hit() есть в интерфейсе и каждый юнит реализует его по своему уменьшив здоровье на какое-то значение.

Нанесение повреждения через интерфейс
Нанесение повреждения через интерфейс

Методы getX() и getY() возвращают координаты юнита. Они есть в интерфейсе и нужны для того, чтобы посредник узнал о местонахождении юнита.
Теперь рассмотрим посредника.  Посредник хранит интерфейсы всех юнитов в массиве. Имя массива —
warriors. Чтобы знать точно сколько имеется юнитов есть свойство warriorsCount. Метод warriorsWalkRequest() обрабатывает запрос на перемещение юнита, рассчитывает в каком направлении двигаться и передают новые координаты через интерфейс. Метод warriorStrike() обрабатывает запрос на совершение удара юнитом. Если рядом не было соседа, то ничего не произойдет. Если рядом стоял юнит, то для него через интерфейс вызовется метод hit(). Что означает попадание. Как мы уже знаем, юнит вычтет у себя часть здоровья. Схема пока не идеальна, но в ней уже заложены большие возможности по расширению функционала юнитами и дальнейшему развитию игровой логики. Все дело в применение известного паттерна.

Исходный код примера на языке Си

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

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

Недостаток ООП

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