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
+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
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 SquareSquareSquareSquareSquare
+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 AppleAppleAppleAppleApple
+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 SnakeSnakeSnakeSnakeSnake
Within the same class the value of a property may be read, or written to using the following syntax (taken from SnakeSnakeSnakeSnakeSnake):
reassign this.priorTailvariableName? to this.body[0]value or expression?0
self.priorTailvariableName? = self.body[0]value or expression? # reassign variable0
this.priorTailvariableName? = this.body[0]value or expression?; // reassign variable0
Me.priorTailvariableName? = Me.body[0]value or expression? ' reassign variable0
this.priorTailvariableName? = this.body[0]value or expression?; // reassign variable0
where the value of the property named priorTailpriorTailpriorTailpriorTailpriorTail is being changed to hold the first element from the list of squares held in the property named bodybodybodybodybody.
In both cases the keyword thisthisthisthisthis 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 SquareSquareSquareSquareSquare:
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.
the value of the properties xxxxx and yyyyy 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 thisthisthisthisthis.
The constructor for SnakeSnakeSnakeSnakeSnake 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 SquareSquareSquareSquareSquare, passing in the arguments 2020202020 and 1515151515, 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 tailtailtailtailtail (an instance of SquareSquareSquareSquareSquare), and is passed
the value of currentDircurrentDircurrentDircurrentDircurrentDir as an argument. Looking at the definition of getAdjacentSquare within the class SquareSquareSquareSquareSquare:
+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
we see that the direction (assigned to the parameter ddddd) is used together with the square's xxxxx and yyyyy properties to identify the coordinates
newXnewXnewXnewXnewX and newYnewYnewYnewYnewY for the adjacent square in the specified direction, and use those to create and return a new instance of SquareSquareSquareSquareSquare 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 AppleAppleAppleAppleApple:
+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
reassign this.locationvariableName? to new Square(ranX, ranY)value or expression?6
+if not snake.bodyCovers(this.location)condition? then
7
reassign 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? # reassign variable6
+if not snake.bodyCovers(self.location)condition?:
7
changePositionvariableName? = Falsevalue or expression? # reassign variable8
+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?; // reassign variable6
+if (!snake.bodyCovers(this.location)condition?) {
7
changePositionvariableName? = falsevalue or expression?; // reassign variable8
} // if
} // while
} // procedure
+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? ' reassign variable6
+If Not snake.bodyCovers(Me.location)condition? Then
7
changePositionvariableName? = Falsevalue or expression? ' reassign 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?; // reassign variable6
+if (!snake.bodyCovers(this.location)condition?) {
7
changePositionvariableName? = falsevalue or expression?; // reassign variable8
} // if
} // while
} // procedure
changes the locationlocationlocationlocationlocation to a new SquareSquareSquareSquareSquare - 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 bodyCoversbodyCoversbodyCoversbodyCoversbodyCovers, defined on SnakeSnakeSnakeSnakeSnake.
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 StringstrstringStringString. 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
+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
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 IntintintIntegerint, FloatfloatdoubleDoubledouble, 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 StringstrstringStringString 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