10 steps to Object-Oriented Programming with Elan

Getting started

To undertake object-oriented programming (OOP) with Elan, you should first be reasonably familiar with procedural programming with Elan. Start by switching to the object-oriented profile from the menu at the top of the IDE. This will still allow you to do procedural programming, but changes two things:

In the following sections we will describe 10 principles that define the essence of OOP, illustrated with example code - where possible from the demo programs.

Important. Every textbook on OOP differs slightly in the definition of core principles, and every language that supports OOP differs in the rules that it enforces. The following 10 principles are based on widely recognised best practices in OOP. When coding with Elan, in whichever of the supported languages - Python, VB, C#, or Java (or the Elan Reference Language) - these principles are, wherever possible, enforced by the Elan tooling. The resulting code is always valid syntax for the chosen language - but may depend upon common library methods defined in the Elan library. Also, if you go on to write code in any of those languages outside Elan, you will find that the same principles are not necessarily enforced. Though that might appear to offer you more freedom, sticking voluntarily to the principles that you learned in Elan will result in better code and fewer problems.

Principle 1: A class is a user-defined data structure type

In procedural programming with Elan, there is a fixed set of types available to you to represent data - the only exception being enums which offer a very limited form of 'user-defined' type (UDT). OOP makes it easy to define far richer forms of UDTs. The simplest and most common way to do this is to define a concrete class which is one of the new options made available at global (file) level when using Elan with the Object Oriented

profile selected.

In the Snake object-oriented demo you can find three examples of concrete classes. Only the 'signature' of each class is shown here - not the instructions contained within the classes:
+class SnakeName? inheritance?1 +constructor(parameter definitions?) 2 new code end constructor +function toStringname?(parameter definitions?) returns StringType? 3 return "undefined"value or expression?4 end function end class +class AppleName? inheritance?5 +constructor(parameter definitions?) 6 new code end constructor +function toStringname?(parameter definitions?) returns StringType? 7 return "undefined"value or expression?8 end function end class +class SquareName? inheritance?9 +constructor(parameter definitions?) 10 new code end constructor +function toStringname?(parameter definitions?) returns StringType? 11 return "undefined"value or expression?12 end function end class
+class SnakeName? inheritance? # concrete class1 +def __init__(self: Snake) -> None: 2 new code +def toStringname?(self: Snake) -> strType?: # function method3 return "undefined"value or expression?4 +class AppleName? inheritance? # concrete class5 +def __init__(self: Apple) -> None: 6 new code +def toStringname?(self: Apple) -> strType?: # function method7 return "undefined"value or expression?8 +class SquareName? inheritance? # concrete class9 +def __init__(self: Square) -> None: 10 new code +def toStringname?(self: Square) -> strType?: # function method11 return "undefined"value or expression?12
+class SnakeName? inheritance? {1 +public Snake(parameter definitions?) { 2 new code } // constructor +public stringType? toStringname?(parameter definitions?) { // function method3 return "undefined"value or expression?;4 } // function method } // class +class AppleName? inheritance? {5 +public Apple(parameter definitions?) { 6 new code } // constructor +public stringType? toStringname?(parameter definitions?) { // function method7 return "undefined"value or expression?;8 } // function method } // class +class SquareName? inheritance? {9 +public Square(parameter definitions?) { 10 new code } // constructor +public stringType? toStringname?(parameter definitions?) { // function method11 return "undefined"value or expression?;12 } // function method } // class
+Class SnakeName? inheritance? inheritance?1 +Sub New(parameter definitions?) 2 new code End Sub +Function toStringname?(parameter definitions?) As StringType? 3 Return "undefined"value or expression?4 End Function End Class +Class AppleName? inheritance? inheritance?5 +Sub New(parameter definitions?) 6 new code End Sub +Function toStringname?(parameter definitions?) As StringType? 7 Return "undefined"value or expression?8 End Function End Class +Class SquareName? inheritance? inheritance?9 +Sub New(parameter definitions?) 10 new code End Sub +Function toStringname?(parameter definitions?) As StringType? 11 Return "undefined"value or expression?12 End Function End Class
+class SnakeName? inheritance? inheritance? {1 +public Snake(parameter definitions?) { 2 new code } // constructor +public StringType? toStringname?(parameter definitions?) { // function method3 return "undefined"value or expression?;4 } // function method } // class +class AppleName? inheritance? inheritance? {5 +public Apple(parameter definitions?) { 6 new code } // constructor +public StringType? toStringname?(parameter definitions?) { // function method7 return "undefined"value or expression?;8 } // function method } // class +class SquareName? inheritance? inheritance? {9 +public Square(parameter definitions?) { 10 new code } // constructor +public StringType? toStringname?(parameter definitions?) { // function method11 return "undefined"value or expression?;12 } // function method } // class

Within the definition of each class you will find several property instructions. Each property has unique name (within the class). A class may defined multiple properties of the same type, for example, the SquareSquareSquareSquareSquare class in Snake defines:

+constructor(parameter definitions?) 0 new code end constructor
property x as Int Code does not parse as Elan.
property x as Int Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property x as Int Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property y as Int Code does not parse as Elan.
property y as Int Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property y as Int Code does not parse as Elan.
or properties may have different types, for example the TuringMachineTuringMachineTuringMachineTuringMachineTuringMachine class in the Roman Numerals Turing Machine demo has these:- the class may have multiple properties of the same type but with different names, or define
+constructor(parameter definitions?) 0 new code end constructor
property initialState as String Code does not parse as Elan.
property initialState as String Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property initialState as String Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property currentState as String Code does not parse as Elan.
property currentState as String Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property currentState as String Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property headPosition as Int Code does not parse as Elan.
property headPosition as Int Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property headPosition as Int Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property haltState as String Code does not parse as Elan.
property haltState as String Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property haltState as String Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property rules as List Code does not parse as Elan.
property rules as List Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property rules as List Code does not parse as Elan.
+constructor(parameter definitions?) 0 new code end constructor
property tape as String Code does not parse as Elan.
property tape as String Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property tape as String Code does not parse as Elan.

Defining a class

Selecting concrete class from the new code menu inserts a template like this:

+class NameName? inheritance?1 +constructor(parameter definitionsparameter definitions?) 2 new code end constructor +function toStringname?(parameter definitionsparameter definitions?) returns StringType? 3 return "undefined"value or expression?4 end function end class
+class NameName? inheritance? # concrete class1 +def __init__(self: Name) -> None: 2 new code +def toStringname?(self: Name) -> strType?: # function method3 return "undefined"value or expression?4
+class NameName? inheritance? {1 +public Name(parameter definitionsparameter definitions?) { 2 new code } // constructor +public stringType? toStringname?(parameter definitionsparameter definitions?) { // function method3 return "undefined"value or expression?;4 } // function method } // class
+Class NameName? inheritance? inheritance?1 +Sub New(parameter definitionsparameter definitions?) 2 new code End Sub +Function toStringname?(parameter definitionsparameter definitions?) As StringType? 3 Return "undefined"value or expression?4 End Function End Class
+class NameName? inheritance? inheritance? {1 +public Name(parameter definitionsparameter definitions?) { 2 new code } // constructor +public StringType? toStringname?(parameter definitionsparameter definitions?) { // function method3 return "undefined"value or expression?;4 } // function method } // class
where the only thing you must provide is a unique name for the class, which must start with a capital letter, followed by other letters, digits or underscores. (We will look at the roles of the constructor, and the toString function later on.)

Principle 2: An object is an instance of a class

Every object in code is an instance of a specific class. The class may be thought of as a 'template' for defining instances. Each instance holds its own specific value for each of the properties defined in the class.

An new object may be created and assigned to a variable definition, for example:

variable applename? set to new Apple()value or expression?0
applename? = Apple()value or expression? # variable definition0
var applename? = new Apple()value or expression?;0
Dim applename? = New Apple()value or expression? ' variable definition0
var applename? = new Apple()value or expression?;0
variable snakename? set to new Snake()value or expression?0
snakename? = Snake()value or expression? # variable definition0
var snakename? = new Snake()value or expression?;0
Dim snakename? = New Snake()value or expression? ' variable definition0
var snakename? = new Snake()value or expression?;0

Note that the type of each variable is determined by the class of the instance so that the appleappleappleappleapple variable may subsequently be reassigned to a different instance of AppleAppleAppleAppleApple but may not be reassigned to an instance of a different class (nor any other type).

A new instance may also be created as part of an expression without having to be assigned to a variable, for example:

return new Square(newX, newY) Code does not parse as Elan.
return new Square(newX, newY) Code does not parse as Elan.
return new Square(newX, newY) Code does not parse as Elan.
return new Square(newX, newY) Code does not parse as Elan.
return new Square(newX, newY) Code does not parse as Elan.

Reading properties

Wherever you have a reference to an object, your code may read and make use of the publicly-visible properties of that object using 'dot syntax', for example (from the Pathfinder demo):

+if distViaCurrent < neighbour.distFromStartcondition? then 0 new code end if
+if distViaCurrent < neighbour.distFromStartcondition?: 0 new code
+if (distViaCurrent < neighbour.distFromStartcondition?) { 0 new code } // if
+If distViaCurrent < neighbour.distFromStartcondition? Then 0 new code End If
+if (distViaCurrent < neighbour.distFromStartcondition?) { 0 new code } // if
is comparing the value of the variable distViaCurrentdistViaCurrentdistViaCurrentdistViaCurrentdistViaCurrent to the property named distFromStartdistFromStartdistFromStartdistFromStartdistFromStart of the object named neighbourneighbourneighbourneighbourneighbour.

Principle 3: The constructor initialises the properties

When coding with Elan every class must define a constructor, the template for which is created when you define a new class, and which may be edited, though not deleted. The role of the constructor is to initialise the properties whenever an instance of that class is created.

The only properties that don't need to be initialised are properties of type IntintintIntegerint, FloatfloatdoubleDoubledouble, or BooleanboolboolBooleanbool - which are automatically initialised to the default value of each type - 00000, 0.00.00.00.00.0, or falseFalsefalseFalsefalse respectively. All other properties must be explicitly initialised within the constructor. Here is simple example of properties being initialised within the constructor for SquareSquareSquareSquareSquare in the Snake demo:

constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.
constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.
constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.
constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.
constructor(x as Int, y as Int) reassign this.x to x reassign this.y to y end constructor Code does not parse as Elan.

Note:

Principle 4: Objects may have associations to other objects

So far we have looked at properties where the type is one of the standard library types. However, a class may define one or more properties where the type is a class - either of the same type as the class in which the property is defined, or - more commonly - of a different class. Each such property is referred to as an 'association' or 'associated object'. If the type of a property is a list, or other data structure, where the member type is a class, then it is often referred to as a 'multiple association' - with the ordinary kind of association referred to as 'single association'. The SnakeSnakeSnakeSnakeSnake class shows both kinds of association (only relevant parts of the class are shown only here):

+class SnakeName? inheritance?1 +constructor(parameter definitionsparameter definitions?) 2 variable tailname? set to new Square(20, 15)value or expression?3 reassign this.currentDirvariableName? to Direction.rightvalue or expression?4 reassign this.bodyvariableName? to [tail]value or expression?5 reassign this.headvariableName? to tail.getAdjacentSquare(this.currentDir)value or expression?6 reassign this.priorTailvariableName? to tailvalue or expression?7 end constructor private property currentDirname? as DirectionType?8 private property headname? as SquareType?9 private property bodyname? as List<of Square>Type?10 private property priorTailname? as SquareType?11 +function toStringname?(parameter definitions?) returns StringType? 12 return "undefined"value or expression?13 end function end class
+class SnakeName? inheritance? # concrete class1 +def __init__(self: Snake) -> None: 2 tailname? = Square(20, 15)value or expression? # variable definition3 self.currentDirvariableName? = Direction.rightvalue or expression? # reassign variable4 self.bodyvariableName? = [tail]value or expression? # reassign variable5 self.headvariableName? = tail.getAdjacentSquare(self.currentDir)value or expression? # reassign variable6 self.priorTailvariableName? = tailvalue or expression? # reassign variable7 currentDirname?: DirectionType? # private property8 headname?: SquareType? # private property9 bodyname?: list[Square]Type? # private property10 priorTailname?: SquareType? # private property11 +def toStringname?(self: Snake) -> strType?: # function method12 return "undefined"value or expression?13
+class SnakeName? inheritance? {1 +public Snake(parameter definitionsparameter definitions?) { 2 var tailname? = new Square(20, 15)value or expression?;3 this.currentDirvariableName? = Direction.rightvalue or expression?; // reassign variable4 this.bodyvariableName? = [tail]value or expression?; // reassign variable5 this.headvariableName? = tail.getAdjacentSquare(this.currentDir)value or expression?; // reassign variable6 this.priorTailvariableName? = tailvalue or expression?; // reassign variable7 } // constructor private DirectionType? currentDirname? {get; private set;} // private property8 private SquareType? headname? {get; private set;} // private property9 private List<Square>Type? bodyname? {get; private set;} // private property10 private SquareType? priorTailname? {get; private set;} // private property11 +public stringType? toStringname?(parameter definitions?) { // function method12 return "undefined"value or expression?;13 } // function method } // class
+Class SnakeName? inheritance? inheritance?1 +Sub New(parameter definitionsparameter definitions?) 2 Dim tailname? = New Square(20, 15)value or expression? ' variable definition3 Me.currentDirvariableName? = Direction.rightvalue or expression? ' reassign variable4 Me.bodyvariableName? = {tail}value or expression? ' reassign variable5 Me.headvariableName? = tail.getAdjacentSquare(Me.currentDir)value or expression? ' reassign variable6 Me.priorTailvariableName? = tailvalue or expression? ' reassign variable7 End Sub Private Property currentDirname? As DirectionType?8 Private Property headname? As SquareType?9 Private Property bodyname? As List(Of Square)Type?10 Private Property priorTailname? As SquareType?11 +Function toStringname?(parameter definitions?) As StringType? 12 Return "undefined"value or expression?13 End Function End Class
+class SnakeName? inheritance? inheritance? {1 +public Snake(parameter definitionsparameter definitions?) { 2 var tailname? = new Square(20, 15)value or expression?;3 this.currentDirvariableName? = Direction.rightvalue or expression?; // reassign variable4 this.bodyvariableName? = [tail]value or expression?; // reassign variable5 this.headvariableName? = tail.getAdjacentSquare(this.currentDir)value or expression?; // reassign variable6 this.priorTailvariableName? = tailvalue or expression?; // reassign variable7 } // constructor private DirectionType? currentDirname?; // private property8 private SquareType? headname?; // private property9 private List<Square>Type? bodyname?; // private property10 private SquareType? priorTailname?; // private property11 +public StringType? toStringname?(parameter definitions?) { // function method12 return "undefined"value or expression?;13 } // function method } // class

Note:

Principle 5: Classes provide behaviour through methods

In the last code example tail.getAdjacentSquare(this.currentDir)tail.getAdjacentSquare(self.currentDir)tail.getAdjacentSquare(this.currentDir)tail.getAdjacentSquare(Me.currentDir)tail.getAdjacentSquare(this.currentDir) was calling a function method, using 'dot syntax' on the the variable tailtailtailtailtail which is an instance of class SquareSquareSquareSquareSquare. A class can define both function methods and procedure methods, for the following reasons:

  • these methods are invoked on an instance using 'dot-syntax' as we shown in the previous example
  • they have access to all the properties belonging to that same instance, and they can also invoke other methods on the same object, prefixed by thisselfMethisMe dot.

This may be seen in the implementation of getAdjacentSquare in the SquareSquareSquareSquareSquare class:

+function getAdjacentSquarename?(d as Directionparameter definitions?) returns SquareType? 1 variable newXname? set to this.xvalue or expression?2 variable newYname? set to this.yvalue or expression?3 +if d is Direction.leftcondition? then 4 reassign newXvariableName? to this.x - 1value or expression?5 elif d is Direction.rightcondition? then6 reassign newXvariableName? to this.x + 1value or expression?7 elif d is Direction.upcondition? then8 reassign newYvariableName? to this.y - 1value or expression?9 elif d is Direction.downcondition? then10 reassign newYvariableName? to this.y + 1value or expression?11 end if return new Square(newX, newY)value or expression?12 end function
+def getAdjacentSquarename?(d: Directionparameter definitions?) -> SquareType?: # function1 newXname? = self.xvalue or expression? # variable definition2 newYname? = self.yvalue or expression? # variable definition3 +if d == Direction.leftcondition?: 4 newXvariableName? = self.x - 1value or expression? # reassign variable5 elif d == Direction.rightcondition?: # else if6 newXvariableName? = self.x + 1value or expression? # reassign variable7 elif d == Direction.upcondition?: # else if8 newYvariableName? = self.y - 1value or expression? # reassign variable9 elif d == Direction.downcondition?: # else if10 newYvariableName? = self.y + 1value or expression? # reassign variable11 return Square(newX, newY)value or expression?12
+static SquareType? getAdjacentSquarename?(Direction dparameter definitions?) { // function1 var newXname? = this.xvalue or expression?;2 var newYname? = this.yvalue or expression?;3 +if (d == Direction.leftcondition?) { 4 newXvariableName? = this.x - 1value or expression?; // reassign variable5 } else if (d == Direction.rightcondition?) {6 newXvariableName? = this.x + 1value or expression?; // reassign variable7 } else if (d == Direction.upcondition?) {8 newYvariableName? = this.y - 1value or expression?; // reassign variable9 } else if (d == Direction.downcondition?) {10 newYvariableName? = this.y + 1value or expression?; // reassign variable11 } // if return new Square(newX, newY)value or expression?;12 } // function
+Function getAdjacentSquarename?(d As Directionparameter definitions?) As SquareType? 1 Dim newXname? = Me.xvalue or expression? ' variable definition2 Dim newYname? = Me.yvalue or expression? ' variable definition3 +If d = Direction.leftcondition? Then 4 newXvariableName? = Me.x - 1value or expression? ' reassign variable5 ElseIf d = Direction.rightcondition? Then6 newXvariableName? = Me.x + 1value or expression? ' reassign variable7 ElseIf d = Direction.upcondition? Then8 newYvariableName? = Me.y - 1value or expression? ' reassign variable9 ElseIf d = Direction.downcondition? Then10 newYvariableName? = Me.y + 1value or expression? ' reassign variable11 End If Return New Square(newX, newY)value or expression?12 End Function
+static SquareType? getAdjacentSquarename?(Direction dparameter definitions?) { // function1 var newXname? = this.xvalue or expression?;2 var newYname? = this.yvalue or expression?;3 +if (d == Direction.leftcondition?) { 4 newXvariableName? = this.x - 1value or expression?; // reassign variable5 } else if (d == Direction.rightcondition?) {6 newXvariableName? = this.x + 1value or expression?; // reassign variable7 } else if (d == Direction.upcondition?) {8 newYvariableName? = this.y - 1value or expression?; // reassign variable9 } else if (d == Direction.downcondition?) {10 newYvariableName? = this.y + 1value or expression?; // reassign variable11 } // if return new Square(newX, newY)value or expression?;12 } // function

Function and procedure methods defined on a class have similaries and differences to global functions and procedures.

TO STRING

Function method

Where properties define state, methods define behaviour Function & procedure methods – distinction (like globals) & commonality – optional params EXAMPLES Invoked on an instance using dot syntax EXAMPLES Implementation of methods Why put behaviour onto methods? The toString method

Procedure method

Principle 6: Object represent identifiable concepts in the application domain

This is encapsulation meaning 1 It is never a complete representation, but it is complete for the purposes of the application Most of the objects It not only represents its state, but its behaviour. By making objects the fundamental building blocks of programs they are easier to change and extend

Principle 7: Objects should hide state behind methods, where possible

Does not mean getter and setter methods This is encapsulation meaning 2 Private properties, public methods – why and how Can make methods private too – where they are called by other methods in the same class.

Principle 8: Allow objects of different classes to be handled through a common interface

May be handled through interfaces or inheritance Where appropriate it is possible to deal with instances or two or more separate classes without having to know which type each one is A class can implement multiple interfaces, as long as they don’t clash An interface cannot be instantiated – but a concrete class that implements that interface can be An interface may extend another interface Each class that implements an interface must provide an implemented of each method, but the implementation may differ (EXAMPLES). It is likely that more than one class might have exactly the same implementation, and by the DRY principle it is undesirable to repeat the code. So you delegate, in part or in whole

Principle 9: Common implementations may be shared through delegation or inheritance

An abstract class … An abstract class is like an interface, but more powerful that it may also define concrete implementations of methods, with some limitations The limitation is that a concrete class may (optionally) inherit only from one abstract class – though it may implement additional interfaces An abstract class may inherit from another abstract class and/or implement interfaces Elan’s model of inheritance – what you can and can’t do -

Principle 10: Application logic should be well-distributed across classes

As much as possible of the code should be defined in classes With a minimal main and/or standalone (global) constructs The logic should be distributed as evenly as possible across the classes Be wary of singletons In particular of a singleton that is doing most of the work. This pattern is often used to avoid global variables, but be wary of this