Composition instead of inheritance in the Delight programming language

This article discusses one of the approaches to the next stage of development of OOP (object-oriented programming). The classical approach to OOP is based on the concept of inheritance, which in turn imposes serious restrictions on the use and modification of ready-made code. When creating new classes, it is not always possible to inherit from existing classes (the problem of diamond-shaped inheritance) or modify existing classes from which many other classes have already inherited (a fragile (or overly bloated) base class). When developing the Delight programming language, an alternative approach was chosen for working with classes and their composition - CPC (component-oriented programming).





Straight to the point

We should start with the basics of the language, its syntax and the rules of the CPC. But this is pretty boring, so let's go straight to a specific game example. To understand all of the following, full knowledge of OOP is required, since the composition itself is built on the same principles as OOP. More details on how it works can be found in the next section after this.





Let's consider an example from a conditional game where some creatures can move around the map. Let's write the code for the behavior of these creatures. Let's start with the base classes.





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 - is responsible for the basic behavior of the creature, the class itself has no logic, only the necessary declarations.





PathBuilder is a class that is responsible for finding a path along the ground (including obstacle avoidance).





The [shared] modifier means that this field will be shared by all subclasses of the final class.





, :





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
      
      



This approach allows you to overload functions of one class with another, while being at the same level of the hierarchy. Thanks to this, classes can consist of ready-made components that overlap or complement other people's functions, which greatly facilitates the composition of the code. During composition, the main functionality of the final class is broken down into several basic building blocks, the combination of which gives the desired results. Delight also supports static code composition, but this is already material for another article.








All Articles