Este artigo discute uma das abordagens para o próximo estágio de desenvolvimento de OOP (programação orientada a objetos). A abordagem clássica para OOP é baseada no conceito de herança, que por sua vez impõe sérias restrições ao uso e modificação de código pronto. Ao criar novas classes, nem sempre é possível herdar de classes existentes (o problema da herança em forma de diamante ) ou modificar classes existentes das quais muitas outras classes já herdaram (uma classe base frágil (ou excessivamente inchada)). No desenvolvimento da linguagem de programação Delight, optou-se por uma abordagem alternativa para trabalhar as classes e sua composição - CPC (programação orientada a componentes).
Direto ao ponto
Devemos começar com o básico da linguagem, sua sintaxe e as regras do CPC. Mas isso é muito chato, então vamos direto para um exemplo de jogo específico. Para compreender todos os itens a seguir, é necessário um conhecimento completo de OOP, uma vez que a composição em si é construída nos mesmos princípios que OOP. Mais detalhes sobre como ele funciona podem ser encontrados na próxima seção após isso.
Vamos considerar um exemplo de um jogo condicional em que algumas criaturas podem se mover pelo mapa. Vamos escrever o código para o comportamento dessas criaturas. Vamos começar com as classes básicas.
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 - é responsável pelo comportamento básico da criatura, a classe em si não possui lógica, apenas as declarações necessárias.
PathBuilder é uma classe responsável por encontrar um caminho ao longo do solo (incluindo evitar obstáculos).
O modificador [shared] significa que este campo será compartilhado por todas as subclasses da classe final.
, :
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
Essa abordagem permite que você sobrecarregue as funções de uma classe com outra, ao mesmo tempo que está no mesmo nível da hierarquia. Graças a isso, as classes podem consistir em componentes prontos que se sobrepõem ou complementam as funções de outras pessoas, o que facilita muito a composição do código. Durante a composição, a principal funcionalidade da aula final é dividida em vários blocos básicos de construção, cuja combinação dá os resultados desejados. O Delight também oferece suporte à composição de código estático, mas isso já é material para outro artigo.