Introduction to Object-Oriented Programming with Elan

To undertake object-oriented programming (OOP) in Elan, you should first be reasonably familiar with procedural programming in Elan. Then, switch to the Object Oriented profile from the menu at the top of the IDE. This still allows you to do procedural programming, but changes two things:

- The list of demo programs will now show only programs that use OOP. A very good place to start is to look at the Snake - object-oriented demo, which is functionally identical to the (procedural) original, but it written using OOP design and coding patterns. We will be referring to code within this and other OOP demo programs within this text.
- It will change some of the options available on the new code menus in the editor:
  - At the global level, it will offer: concrete class, abstract class, interface, and enum. Each of these allows you to create a ‘user-defined type’ – that may then be used in a similar fashion to the standard types such `Int int int Integer int`, `String str string String String`, or `List list List List List`. (Enums - which are single-line instructions - are not directly associated with OOP, but because an enum is a very simple form of user-defined type, it is made available in the same profile.)
  - Within a concrete class, abstract class, or interface, the new code menu will show an entirely new list of instructions, collectively known as ‘members’ (plus comment) that may be defined within those three new global instructions.

# If you already understand OOP but are new to coding with Elan...

Be aware that Elan imposes a set of rules on the use of OOP, and which Elan enforces for all the supported languages - even though the individual languages, when used outside Elan, might not impose those constraints. These constraints are all good discipline: recognised by experts in OOP. So learning OOP within Elan will give you a better understanding and lead to fewer problems.

Before trying to writ

# Exploring the 'Snake

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 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 main()
+class SnakeName? inheritance? {1 +public Snake(parameter definitions?) { 2 new code } +public stringType? toStringname?(parameter definitions?) { // function method3 return "undefined"value or expression?;4 } } +class AppleName? inheritance? {5 +public Apple(parameter definitions?) { 6 new code } +public stringType? toStringname?(parameter definitions?) { // function method7 return "undefined"value or expression?;8 } } +class SquareName? inheritance? {9 +public Square(parameter definitions?) { 10 new code } +public stringType? toStringname?(parameter definitions?) { // function method11 return "undefined"value or expression?;12 } }
+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 } +public StringType? toStringname?(parameter definitions?) { // function method3 return "undefined"value or expression?;4 } } +class AppleName? inheritance? inheritance? {5 +public Apple(parameter definitions?) { 6 new code } +public StringType? toStringname?(parameter definitions?) { // function method7 return "undefined"value or expression?;8 } } +class SquareName? inheritance? inheritance? {9 +public Square(parameter definitions?) { 10 new code } +public StringType? toStringname?(parameter definitions?) { // function method11 return "undefined"value or expression?;12 } }

This example illustrates an important principle of OOP: an object-oriented program should largely consist of the definition of types, the names and definitions of which correspond to entities in program's 'domain'. Here the domain is a game of 'snake' - which incorporate the concepts of a snake, an apple, and many squares. The program might included classes to represent, game, player, board, and others. The decision as to which entities from the domain should represented in code comes with experience - based on how much value would be added to the program by defining such a class - and some programmers will opt for more than others. The important principle is that each class should sound familiar to someone who is familiar with the problem domain (in this case: with the game of 'snake') but is not necessarily a programmer.

## Instances

Each concrete class may be thought of as a template, from which one or more 'instances' are created. It is valid to define a concrete class that is instantiated only once within the program, but most classes that you define should be instantiated multiple times. When a class is instantiated, the instance is assigned to a variable, and we can see two (of more) separate examples of this from Snake here:

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
variable tailname? set to new Square(20, 15)value or expression? 0
tailname? = Square(20, 15)value or expression? # variable definition0
var tailname? = new Square(20, 15)value or expression?;0
Dim tailname? = New Square(20, 15)value or expression? ' variable definition0
var tailname? = new Square(20, 15)value or expression?;0

## Members

Each concrete classes contains other instructions., collectively known as the 'members' of the class. The following different kinds of members can be seen:

### Property

Most classes define several properties. A property has name (like a variable) and a type, which might be a standard simple type or data structure, but can also be a user-defined type - which indicates that the property contains a reference to an instance of that type. (This pattern is often referred to as an 'association' between instances. Like a variable, the value of a property may change during the program run. However, each instance holds its own value for each of the properties defined on its class. Here are some examples of properties being defined (the examples are taken from different classes), showing the name and type of each:

+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.
taken from `Square Square Square Square Square`
+constructor(parameter definitions?) 0 new code end constructor
property location as Square Code does not parse as Elan.
property location as Square Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
property location as Square Code does not parse as Elan.
taken from `Apple Apple Apple Apple Apple`
+constructor(parameter definitions?) 0 new code end constructor
private property body as List Code does not parse as Elan.
private property body as List Code does not parse as Elan.
+Sub New(parameter definitions?) 0 new code End Sub
private property body as List Code does not parse as Elan.
taken from `Snake Snake Snake Snake Snake`

Within the same class the value of a property may be read, or written to using the following syntax (taken from `Snake Snake Snake Snake Snake`):

set this.priorTailvariableName? to this.body[0]value or expression? 0
self.priorTailvariableName? = self.body[0]value or expression? # re-assign variable0
this.priorTailvariableName? = this.body[0]value or expression?; // re-assign variable0
Me.priorTailvariableName? = Me.body[0]value or expression? ' re-assign variable0
this.priorTailvariableName? = this.body[0]value or expression?; // re-assign variable0

where the value of the property named `priorTail priorTail priorTail priorTail priorTail` is being changed to hold the first element from the list of squares held in the property named `body body body body body`. In both cases the keyword `this this this this this` refers to the current instance (i.e. the instance to which the code is applied). It is followed by a dot and the name of the property - an example of 'dot syntax'.

### Constructor

Each class must define one, and only one, constructor. The role of the constructor is to 'initialise' (set the initial values) of the properties. The constructor is called each time an instance of a class is created. In the constructor for `Square Square Square Square Square`:

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

the value of the properties `x x x x x` and `y y y y y` are set to the values defined by the two parameters. Although the parameters have the same names as the properties they ambiguity is avoided because the property is always prefixed by `this this this this this`.

The constructor for `Snake Snake Snake Snake Snake` does not define any parameters, because a snake is instantiated only once within each game, and always to the same size and initial position:

constructor()
constructor()
constructor()
constructor()
constructor()

Note, however, that within the constructor, it creates a new instance of `Square Square Square Square Square`, passing in the arguments `20 20 20 20 20` and `15 15 15 15 15`, which the square's constructor requires. It also makes used of getAdjacentSquare

which is an example of using a method - which we shall explore next.

### Methods

The properties on a class define the state of an instance; the methods define its behaviour. Methods on a concrete class have two distinct forms: function method and procedure method. These mirror the distinction between a (global) function and procedure. A function is a 'query' that returns information based on the parameters that are supplied to it, but in the case of a function method (defined within a class), in addition to the parameter values, it has access to the property values of the instance - which is legitimate because it is only ever called via that instance. Thus, in the example above, getAdjacentSquare is invoked on the variable `tail tail tail tail tail` (an instance of `Square Square Square Square Square`), and is passed the value of `currentDir currentDir currentDir currentDir currentDir` as an argument. Looking at the definition of getAdjacentSquare within the class `Square Square Square Square Square`:

+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 set newXvariableName? to this.x - 1value or expression? 5 elif d is Direction.rightcondition? then6 set newXvariableName? to this.x + 1value or expression? 7 elif d is Direction.upcondition? then8 set newYvariableName? to this.y - 1value or expression? 9 elif d is Direction.downcondition? then10 set 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? # re-assign variable5 elif d == Direction.rightcondition?: # else if6 newXvariableName? = self.x + 1value or expression? # re-assign variable7 elif d == Direction.upcondition?: # else if8 newYvariableName? = self.y - 1value or expression? # re-assign variable9 elif d == Direction.downcondition?: # else if10 newYvariableName? = self.y + 1value or expression? # re-assign variable11 return Square(newX, newY)value or expression? 12 main()
+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?; // re-assign variable5 } else if (d == Direction.rightcondition?) {6 newXvariableName? = this.x + 1value or expression?; // re-assign variable7 } else if (d == Direction.upcondition?) {8 newYvariableName? = this.y - 1value or expression?; // re-assign variable9 } else if (d == Direction.downcondition?) {10 newYvariableName? = this.y + 1value or expression?; // re-assign variable11 } return new Square(newX, newY)value or expression?;12 }
+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? ' re-assign variable5 ElseIf d = Direction.rightcondition? Then6 newXvariableName? = Me.x + 1value or expression? ' re-assign variable7 ElseIf d = Direction.upcondition? Then8 newYvariableName? = Me.y - 1value or expression? ' re-assign variable9 ElseIf d = Direction.downcondition? Then10 newYvariableName? = Me.y + 1value or expression? ' re-assign 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?; // re-assign variable5 } else if (d == Direction.rightcondition?) {6 newXvariableName? = this.x + 1value or expression?; // re-assign variable7 } else if (d == Direction.upcondition?) {8 newYvariableName? = this.y - 1value or expression?; // re-assign variable9 } else if (d == Direction.downcondition?) {10 newYvariableName? = this.y + 1value or expression?; // re-assign variable11 } return new Square(newX, newY)value or expression?;12 }

we see that the direction (assigned to the parameter `d d d d d`) is used together with the square's `x x x x x` and `y y y y y` properties to identify the coordinates `newX newX newX newX newX` and `newY newY newY newY newY` for the adjacent square in the specified direction, and use those to create and return a new instance of `Square Square Square Square Square` with those two new coordinates.

Similarly a procedure method is like a global procedure except that it is defined within a class, is accessed only via an instance of that class, using dot syntax, and also has internal access to the properties of that instance. Both kinds of procedure may be thought of as 'commands': they make changes to the state of the system. A common use of procedure methods is to change the values of one or more properties and/or to make changes outside the object. The method newRandomPosition defined on the class `Apple Apple Apple Apple Apple`:

+procedure newRandomPositionname?(snake as Snakeparameter definitions?) 1 variable changePositionname? set to truevalue or expression? 2 +while changePositioncondition? 3 variable ranXname? set to randint(0, 39)value or expression? 4 variable ranYname? set to randint(0, 29)value or expression? 5 set this.locationvariableName? to new Square(ranX, ranY)value or expression? 6 +if not snake.bodyCovers(this.location)condition? then 7 set changePositionvariableName? to falsevalue or expression? 8 end if end while end procedure
+def newRandomPositionname?(snake: Snakeparameter definitions?) -> None: # procedure1 changePositionname? = Truevalue or expression? # variable definition2 +while changePositioncondition?: 3 ranXname? = randint(0, 39)value or expression? # variable definition4 ranYname? = randint(0, 29)value or expression? # variable definition5 self.locationvariableName? = Square(ranX, ranY)value or expression? # re-assign variable6 +if not snake.bodyCovers(self.location)condition?: 7 changePositionvariableName? = Falsevalue or expression? # re-assign variable8 main()
+static void newRandomPositionname?(Snake snakeparameter definitions?) { // procedure1 var changePositionname? = truevalue or expression?;2 +while (changePositioncondition?) { 3 var ranXname? = randint(0, 39)value or expression?;4 var ranYname? = randint(0, 29)value or expression?;5 this.locationvariableName? = new Square(ranX, ranY)value or expression?; // re-assign variable6 +if (!snake.bodyCovers(this.location)condition?) { 7 changePositionvariableName? = falsevalue or expression?; // re-assign variable8 } } }
+Sub newRandomPositionname?(snake As Snakeparameter definitions?) ' procedure1 Dim changePositionname? = Truevalue or expression? ' variable definition2 +While changePositioncondition? 3 Dim ranXname? = randint(0, 39)value or expression? ' variable definition4 Dim ranYname? = randint(0, 29)value or expression? ' variable definition5 Me.locationvariableName? = New Square(ranX, ranY)value or expression? ' re-assign variable6 +If Not snake.bodyCovers(Me.location)condition? Then 7 changePositionvariableName? = Falsevalue or expression? ' re-assign variable8 End If End While End Sub
+static void newRandomPositionname?(Snake snakeparameter definitions?) { // procedure1 var changePositionname? = truevalue or expression?;2 +while (changePositioncondition?) { 3 var ranXname? = randint(0, 39)value or expression?;4 var ranYname? = randint(0, 29)value or expression?;5 this.locationvariableName? = new Square(ranX, ranY)value or expression?; // re-assign variable6 +if (!snake.bodyCovers(this.location)condition?) { 7 changePositionvariableName? = falsevalue or expression?; // re-assign variable8 } } }

changes the `location location location location location` to a new `Square Square Square Square Square` - which requires it to be a procedure method. (A second reason is that it uses the system method randint - which depends upon, and changes, data held in the system.) Note that this procedure method makes use of the function method `bodyCovers bodyCovers bodyCovers bodyCovers bodyCovers`, defined on `Snake Snake Snake Snake Snake`.

A function method may also use other function methods and global functions, but may not call any procedure, whether global or procedure method.

### toString method

Every class must define a function method named toString, which defines no parameters and returns a `String str string String String`. The returned string is considered to be a representation of the instance: toString is called automatically when an instance printed, or is used within a field in an interpolated string. The string might be as generic as 'a Person', use one or more properties to identify the instance as e.g 'Charlie Smith', or even list, or summarise all the data held by the object.

## Creating a new concrete class

A new concrete class is added into your code, at global level, with the concrete class instruction. The template includes an empty constructor, and a default implementation of toString:

+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 main()
+class NameName? inheritance? {1 +public Name(parameter definitionsparameter definitions?) { 2 new code } +public stringType? toStringname?(parameter definitionsparameter definitions?) { // function method3 return "undefined"value or expression?;4 } }
+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 } +public StringType? toStringname?(parameter definitionsparameter definitions?) { // function method3 return "undefined"value or expression?;4 } }

You must complete the template, by giving the class a name, which must start with a capital letter (because a class defines a type), followed by any alphabetic or numeric characters, or underlines. In toString you should, at some point, replace `"undefined" "undefined" "undefined" "undefined" "undefined"` with something more specific.

Then via the new code prompt, which will offer you the appropriate options, add properties and methods. All properties must be initialised in the constructor, except those of types `Int int int Integer int`, `Float float double Double double`, which take on the default values of the type - although such properties may optionally be initialised to different values. Note in particular that properties of type `String str string String String` must be initialised, even if only to an empty string `"" "" "" "" ""`.

# Inheritance

A concrete class may inherit from a single abstract class, and/or from one or more interfaces. It may not inherit from more than one abstract class. Nor may it inherit from another concrete class. Abstract class An abstract class may not be instantiate directly, and hence neither needs, nor allows, a constructor. It may define the following types of member - Property, Function method, Procedure method - Abstract Property, abstract function, abstract Intro What is OOP and its advantages Allows you to work with custom data structures that represent the domain of your application in a way that it easier to read, write, and modify. Example: Snake OOP vs. Snake Procedural Particularly well suited to simulations and games, graphics, or complex business applications – give examples of objects. You can define and use objects where needed within a procedural programming, but the OOP paradigm means using the ‘object model’ as the primary design pattern. Class and instance The example programs We will be referring to the following demo programs: … Class defines type and may be thought of as a template, used to create one or more instances. Class is defined in one place And instantiated, and subsequently used, in other places Example of Snake OOP Snake Defined here (the code within the definition is not shown here) Instantiated here and assigned to variable named snake Apple Defined here (the code within the definition is not shown here) Instantiated here and assigned to the variable named apple Square Instantiated many times to show each segment of the snake’s body and the apple. Whenever the snake moves a new instance of Square is added to the front (the ‘head’) and the one at the tail end is discarded. Class definition Adding a new class Select class from new code menu – at global (file) level show You must give it a name. Because a class defines a type the name must start with a capital letter. Examples of valid class names including use of camel case. Within the class, two members are defined in the template (because every class must have them both) : a constructor and a toString method. If you look at each of the classes in Snake_OOP you will see that each has these somewhere, but with some additions/modifications. Properties Almost all classes define one or more properties – which define the data items. Each has a name and type. See examples Properties are like variables, except that they are associated separately with each instance. Thus the class X might define a property y, which will contain z1 for one instance, and z2 for another. Properties are defined within a class using the the property instruction. Example A property of a numeric type (float or integer) will automatically be initialised to the value 0, and a Boolean type to false. All other properties must be explicitly initialised within the constructor. Whenever a value is specified for a property, it is done with a change variable instruction. In the following example the property x is initialised to …. Or you can allow value(s to be specified as parameters in the constructor, and then these values are used within the constructor to initialise one or more properties, for example: If the constructor defines parameters (as above) then appropriate values must be provided when you create a new instance. For example: Then, you can read the properties of an object instance that is held in Using property values within the toString method ToString is a function method that returns a string, but takes no parameters. However, like other methods it can make use of the instance’s properties To string is used: - When printing an object e.g. - Referencing an object within an interpolated string e.g. By default the toString returns ‘undefined’. You can edit this so that it returns `a student` but it is often more useful more useful to return a string that is derived from one or more of the instances properties, for example: - Returning a single identifier - Returning type plus identifier - Summarising all the properties Updating properties of an object Although you can read the value of an ordinary property from code that it outside the class (for example in main) you cannot directly update a property from outside the class. For example, if you try to write this code: You will get the error Xxx If you want to be able to update an property from code outside you need to write a method Private property What this looks like What it means Encapsulation and information hiding Associations Basic types e.g. numbers, strings, Boolean Single association Other UDT (class) or less-commonly another instance of the same type e.g. Best friend Multiple association List of value types, or of associated objects - referrerd Optional association Methods A method (more completely called an ‘instance method’) is a function or procedure that is defined within a class. These methods (unless defined as private) can be Function methods Think of them as queries on the object. They can return information from object’s Procedure methods Make changes to the object and/or depend upon the system. So they f