[Программирование, ООП] Композиция вместо наследования в языке программирования Delight
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В данной статье рассматривается один из подходов к следующей ступени развития ООП (объектно-ориентированного программирования). Классический подход к ООП строиться на концепции наследования, что в свою очередь накладывает серьезные ограничения по использованию и модификации уже готового кода. Создавая новые классы, не всегда получается наследоваться от уже существующих классов (проблема ромбовидного наследования) или модифицировать существующие классы от которых уже унаследовалось множество других классов (хрупкий (или чрезмерно раздутый) базовый класс). При разработке языка программирования Delight был выбран альтернативный подход для работы с классами и их композицией - КОП (компонентно-ориентированное программирование).Сразу к делуНачать следовало бы с основ языка, его синтаксиса и правил работы КОП. Но это довольно скучно, так что сразу перейдем к конкретному игровому примеру. Для понимания всего нижеизложенного, требуется полноценное знание ООП, так как сама композиция строиться на тех же принципах что и ООП. Более детально о том, как это все-таки работает, можно прочитать в следующем после этого разделе.Рассмотрим пример из условной игры, где некие существа могут передвигаться по карте. Напишем код поведения этих существ. Начнем с базовых классов.
class BaseBehavior
unitPos: UnitPos [shared]
fn DoTurn [virtual]
class PathBuilder
unitPos: UnitPos [shared]
fn Moving:boolean [virtual]
...
fn BuildPath(x:int, y:int) [virtual]
...
// ... and some more helper functions ...
BaseBehavior - отвечает за базовое поведение существа, сам класс не имеет логики, только необходимые декларации. PathBuilder - класс который отвечает за поиск пути по земле (включая обход препятствий). Модификатор [shared] означает что это поле будет общим для всех подклассов финального класса.Дальше пропишем классы, в которых уже есть непосредственно логика поведения:
class SimpleBehavior
base: BaseBehavior [shared]
path: PathBuilder [shared]
fne DoTurn // override of BaseBehavior.DoTurn
if path.Moving = false
path.BuildRandomPath
class AgressiveBehavior
open SimpleBehavior [shared]
fne DoTurn // override of SimpleBehavior.DoTurn
d: float = path.GetDistance(player.x, player.y) // get distance from this unit to player
if d < 30
path.BuildPath(player.x, player.y) // run to player
else
nextFn // inherited call to next DoTurn
class ScaredBehavior
open SimpleBehavior [shared]
fne DoTurn // override of SimpleBehavior.DoTurn
d: float = path.GetDistance(player.x, player.y) // get distance from this unit to player
if d < 50
path.BuildPathAwayFrom(player.x, player.y) // run away from player
else
nextFn // inherited call to next DoTurn
Здесь все просто:SimpleBehavior - существо будет перемещаться по карте по случайным координатам.AgressiveBehavior - если игрок находиться близко, то существо бежит к нему. Иначе управление передаеться в SimpleBehavior. ScaredBehavior - если игрок находиться недалеко, то существо отбегает от него или двигаеться согласно SimpleBehavior. open - означает открытое поле класса заданного типа но без имени. fne - перегрузка (override) виртуальной функции. nextFn - виртуальный вызов следующей в цепочке функции.Пока что весь код можно отобразить и с помощью обычного ООП, но для следующего поведения используем КОП:
class UncertainBehavior
open AgressiveBehavior [shared]
open ScaredBehavior [shared]
Здесь уже включается "магия" композиции. В этом простом коде, при вызове DoTurn, управление сначала передаётся в AgressiveBehavior.DoTurn. Если игрок близко, то существо побежит к нему. Если нет, то управление переходит к ScaredBehavior.DoTurn - если игрок недалеко, то существо убегает от него. Если нет, то дальше вызывается SimpleBehavior.DoTurn и существо просто бродит по карте.На этом коде уже можно создать существ Волка (AgressiveBehavior), Зайца (ScaredBehavior) и Кошку (UncertainBehavior). Но что делать для других видов существ? Летающих или плавающих? Или комбинированных? В ООП подобная иерархия уже не сработает. Зато очень помогает композиция. Сначала создадим новые классы для поиска пути в разных средах:
class PathBuilder_air // поиск пути по воздуху
path: PathBuilder [shared]
fne BuildPath(x:int, y:int)
...
class PathBuilder_water // поиск пути в воде
path: PathBuilder [shared]
fne BuildPath(x:int, y:int)
...
А дальше просто подменим этими классами уже существующий код поведения:
class Shark
open PathBuilder_water [shared]
open AgressiveBehavior [shared]
В этом классе "Акулы", сначала создаётся класс поиска пути по воде, дальше используется код с AgressiveBehavior, только учитывая, что класс PathBuilder общий (shared), то в AgressiveBehavior (как и в SimpleBehavior) будет использоваться PathBuilder_water (так как он был объявлен ранее чем обычный PathBuilder). Соответственно вся логика AgressiveBehavior сохранилась, но поиск пути будет работать уже по воде. Таким же способом, просто перебирая классы-компоненты и используя минимум кода, можно создать существ с разным поведением в разных средах обитания:
class Fish
open PathBuilder_water [shared]
open ScaredBehavior [shared]
class Eagle
open PathBuilder_air [shared]
open UncertainBehavior [shared]
class Pigeon
open PathBuilder_air [shared]
open ScaredBehavior [shared]
class Wolf
open AgressiveBehavior [shared]
Как видим, суть компонентно-ориентированного программирования состоит в создании небольших классов-компонентов и правильной комбинации этих классов в финальном объекте-сущности.Основы композиции в DelightОбъявление простого класа в Delight выглядит следующим образом:
class NonVirtualClass
val: OtherClass
fn SomeFn
Trace('Hello world')
Здесь val является обычным членом класса, с типом OtherClass.Функции, как и в ООП языках могут быть виртуальными, для этого используется модификатор [virtual]
fn SomeFn [virtual]
Trace('Hello virtual world')
и перегружаться/дополняться с помощью ключевого слова fne (вместо fn)
fne SomeFn
Trace('Hello overrided world')
А вот синтаксис наследования (вернее композиции) сильно отличается от классических языков. В случае, если класс хочет перегрузить функцию своего базового класса, он должен объявить базовый класс с модификатором [shared] (общий), и использовать fne для перегрузки функции:
class BaseClass
fn SomeFn [virtual]
Trace('Hello virtual world')
class NewClass
base: BaseClass [shared]
fne SomeFn
Trace('Hello overrided world')
nextFn
Ключевое слово nextFn вызовет следующую функцию в цепочке виртуальных вызовов.Для примера похожий (но не эквивалентный) код на С++
class BaseClass
{
public:
virtual void SomeFn()
{
Trace('Hello virtual world');
}
};
class NewClass : public virtual BaseClass
{
virtual void SomeFn() override
{
Trace('Hello overrided world');
BaseClass::SomeFn();
}
};
В классе может быть множество полей с модификатором [shared], что соответствует концепции множественного наследования. Более того, shared поля одного типа могут повторяться в любом месте иерархии класса, но при этом в финальном объекте, независимо от количества [shared] деклараций одного типа, создастся только один общий объект этого типа, а все [shared] поля соответствующего типа во всех общих классах будут содержать только ссылки на этот объект (вернее запись в vtable).Таким образом в коде:
class Base
val: int
class ClsA
base: Base [shared]
class ClsB
base: Base [shared]
class ClsC
a: ClsA [shared]
b: ClsB [shared]
при создании класса ClsC, этот объект будет содержать три базовых объекта в единичном экземпляре (Base, ClsA, ClsB) и значение val будет всегда одинаковым (общим) для всех этих объектов. Подобный подход соответствует виртуальному наследованию классов, которое используется в С++.Как видно из синтаксиса, у общих полей при композиции задаются также имена (в отличие от классического ООП, где при наследовании, достаточно указать тип), и последующее обращение к такому полю должно начинаться с имени поля. Однако в Delight есть синтаксический сахар в виде декларации полей через ключевое слово open (делает поле открытым). Для компилятора это ничего не меняет, но программисту не нужно будет каждый раз обращаться к такому полю по имени (все поля и функции открытого класса могут быть доступны просто через this).
class ClsA
open Base [shared]
fne Constructor
val = 10
Обход классов и функцийПоскольку такая декларация классов представляет собой зацикленный граф, то обход и построение финального класса будет сложнее чем построение дерева иерархии в ООП. По сути, при строительстве класса используются следующие правила:
- Класс проходиться сверху вниз, если находиться новый общий (shared) класс, то происходит строительство этого класса;
- Если в поле общий (shared) класс такого типа уже был построен, то используется указатель на уже построенный класс.
Таким же способом строиться цепочка вызовов виртуальных функций, но с двумя важным деталями:
- В цепочку вызовов сначала попадают функции из тела текущего класса, и только после них функции из общих полей класса;
- Декларация функции всегда будет стоять в конце цепочки вызовов.
Для вызова следующей в цепочке виртуальной функции, используется оператор nextFn. Важно понимать, что этот оператор по сути является виртуальным вызовом (virtual call), в отличие от статического вызова перегруженной функции в классическом ООП (inherited call).Например, такой код:
class Base
fn SomeVirtFn [virtual]
Trace('Base')
class ClsA
open Base [shared]
fne SomeVirtFn
Trace('ClsA')
class ClsB
open Base [shared]
fne SomeVirtFn
Trace('ClsB')
class ClsC
open ClsA [shared]
open ClsB [shared]
fne SomeVirtFn
Trace('ClsC')
....
fn Main
c: ClsC
c.SomeVirtFn
выдаст:
ClsC
ClsA
ClsB
Base
Такой подход позволяет перегружать функции одного класса, другим, находясь при этом на одном уровне иерархии. Благодаря этому, классы могут состоять из готовых компонентов, которые перекрывают или дополняют чужие функции, что существенно облегчает композицию кода. При композиции, основной функционал финального класса разбит на несколько базовых кирпичиков, комбинация которых и дает нужные результаты. Также Delight поддерживает статическую композицию кода, но это уже материал для другой статьи.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование, .NET, C#, Разработка под Windows] Создаём по-настоящему надёжные плагины на платформе Managed Add-In Framework
- [Программирование, Управление разработкой, Лайфхаки для гиков] Проблема XY, или как правильно задавать вопросы (перевод)
- [Python, Программирование, Машинное обучение] Продвинутое использование библиотеки PYTORCH: от подготовки данных до визуализации
- [Программирование, Rust] С лёгким налётом ржавчины или немного о владении (перевод)
- [Разработка веб-сайтов, CSS, Программирование, Java] От студента до учителя: как разобраться в веб-разработке, если это не твой профиль
- [Программирование, Конференции] Современный фронтенд без ошибок и костылей. 8 полезных докладов конференции DUMP
- [Разработка веб-сайтов, Программирование, .NET, Тестирование веб-сервисов] Как я решил протестировать нагрузочную способность web сервера
- [Программирование, Проектирование и рефакторинг, Разработка под Android, Kotlin] Пишем под android с Elmslie
- [Программирование, ReactJS, Управление разработкой, TypeScript] Wrike переходит с Dart на новый стек. Какой?
- [Программирование микроконтроллеров, Производство и разработка электроники, Интернет вещей, DIY или Сделай сам] Разработка защищённого WEB интерфейса для микроконтроллеров
Теги для поиска: #_programmirovanie (Программирование), #_oop (ООП), #_oop (ООП), #_kop (КОП), #_delight, #_metaide, #_programmirovanie (
Программирование
), #_oop (
ООП
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:26
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В данной статье рассматривается один из подходов к следующей ступени развития ООП (объектно-ориентированного программирования). Классический подход к ООП строиться на концепции наследования, что в свою очередь накладывает серьезные ограничения по использованию и модификации уже готового кода. Создавая новые классы, не всегда получается наследоваться от уже существующих классов (проблема ромбовидного наследования) или модифицировать существующие классы от которых уже унаследовалось множество других классов (хрупкий (или чрезмерно раздутый) базовый класс). При разработке языка программирования Delight был выбран альтернативный подход для работы с классами и их композицией - КОП (компонентно-ориентированное программирование).Сразу к делуНачать следовало бы с основ языка, его синтаксиса и правил работы КОП. Но это довольно скучно, так что сразу перейдем к конкретному игровому примеру. Для понимания всего нижеизложенного, требуется полноценное знание ООП, так как сама композиция строиться на тех же принципах что и ООП. Более детально о том, как это все-таки работает, можно прочитать в следующем после этого разделе.Рассмотрим пример из условной игры, где некие существа могут передвигаться по карте. Напишем код поведения этих существ. Начнем с базовых классов. class BaseBehavior
unitPos: UnitPos [shared] fn DoTurn [virtual] class PathBuilder unitPos: UnitPos [shared] fn Moving:boolean [virtual] ... fn BuildPath(x:int, y:int) [virtual] ... // ... and some more helper functions ... class SimpleBehavior
base: BaseBehavior [shared] path: PathBuilder [shared] fne DoTurn // override of BaseBehavior.DoTurn if path.Moving = false path.BuildRandomPath class AgressiveBehavior open SimpleBehavior [shared] fne DoTurn // override of SimpleBehavior.DoTurn d: float = path.GetDistance(player.x, player.y) // get distance from this unit to player if d < 30 path.BuildPath(player.x, player.y) // run to player else nextFn // inherited call to next DoTurn class ScaredBehavior open SimpleBehavior [shared] fne DoTurn // override of SimpleBehavior.DoTurn d: float = path.GetDistance(player.x, player.y) // get distance from this unit to player if d < 50 path.BuildPathAwayFrom(player.x, player.y) // run away from player else nextFn // inherited call to next DoTurn class UncertainBehavior
open AgressiveBehavior [shared] open ScaredBehavior [shared] class PathBuilder_air // поиск пути по воздуху
path: PathBuilder [shared] fne BuildPath(x:int, y:int) ... class PathBuilder_water // поиск пути в воде path: PathBuilder [shared] fne BuildPath(x:int, y:int) ... class Shark
open PathBuilder_water [shared] open AgressiveBehavior [shared] class Fish
open PathBuilder_water [shared] open ScaredBehavior [shared] class Eagle open PathBuilder_air [shared] open UncertainBehavior [shared] class Pigeon open PathBuilder_air [shared] open ScaredBehavior [shared] class Wolf open AgressiveBehavior [shared] class NonVirtualClass
val: OtherClass fn SomeFn Trace('Hello world') fn SomeFn [virtual]
Trace('Hello virtual world') fne SomeFn
Trace('Hello overrided world') class BaseClass
fn SomeFn [virtual] Trace('Hello virtual world') class NewClass base: BaseClass [shared] fne SomeFn Trace('Hello overrided world') nextFn class BaseClass
{ public: virtual void SomeFn() { Trace('Hello virtual world'); } }; class NewClass : public virtual BaseClass { virtual void SomeFn() override { Trace('Hello overrided world'); BaseClass::SomeFn(); } }; class Base
val: int class ClsA base: Base [shared] class ClsB base: Base [shared] class ClsC a: ClsA [shared] b: ClsB [shared] class ClsA
open Base [shared] fne Constructor val = 10
class Base
fn SomeVirtFn [virtual] Trace('Base') class ClsA open Base [shared] fne SomeVirtFn Trace('ClsA') class ClsB open Base [shared] fne SomeVirtFn Trace('ClsB') class ClsC open ClsA [shared] open ClsB [shared] fne SomeVirtFn Trace('ClsC') .... fn Main c: ClsC c.SomeVirtFn ClsC
ClsA ClsB Base =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_oop ( ООП ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:26
Часовой пояс: UTC + 5