Este artículo analiza uno de los enfoques para la siguiente etapa de desarrollo de OOP (programación orientada a objetos). El enfoque clásico de la programación orientada a objetos se basa en el concepto de herencia, que a su vez impone serias restricciones sobre el uso y modificación del código prefabricado. Al crear nuevas clases, no siempre es posible heredar de clases existentes (el problema de la herencia en forma de diamante ) o modificar clases existentes de las que muchas otras clases ya han heredado (una clase base frágil (o demasiado hinchada)). Al desarrollar el lenguaje de programación Delight, se eligió un enfoque alternativo para trabajar con las clases y su composición: CPC (programación orientada a componentes).
Directo al grano
Debemos comenzar con los conceptos básicos del lenguaje, su sintaxis y las reglas del CPC. Pero esto es bastante aburrido, así que vayamos directamente a un ejemplo de juego específico. Para comprender todo lo siguiente, se requiere un conocimiento completo de OOP, ya que la composición en sí se basa en los mismos principios que OOP. Se pueden encontrar más detalles sobre cómo funciona en la siguiente sección después de esto.
Consideremos un ejemplo de un juego condicional en el que algunas criaturas pueden moverse por el mapa. Escribamos el código para el comportamiento de estas criaturas. Comencemos con las clases base.
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: es responsable del comportamiento básico de la criatura, la clase en sí no tiene lógica, solo las declaraciones necesarias.
PathBuilder es una clase que se encarga de encontrar un camino a lo largo del suelo (incluida la evitación de obstáculos).
El modificador [shared] significa que este campo será compartido por todas las subclases de la clase 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
Este enfoque le permite sobrecargar funciones de una clase con otra, mientras se encuentra en el mismo nivel de la jerarquía. Gracias a esto, las clases pueden consistir en componentes prefabricados que se superponen o complementan las funciones de otras personas, lo que facilita enormemente la composición del código. Durante la composición, la funcionalidad principal de la clase final se divide en varios bloques de construcción básicos, cuya combinación da los resultados deseados. Delight también admite la composición de código estático, pero esto ya es material para otro artículo.