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 section 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.
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: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
class in Snake defines:
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
Selecting concrete class from the new code menu inserts a template like this:
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:
Note that the type of each variable is determined by the class of the instance so that the variable
may subsequently be reassigned to a different instance of 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:
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):
to the property named of the
object named .
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 , , or -
which are automatically initialised to the default value of each type - , , or respectively.
All other properties must be explicitly initialised within the constructor. Here is simple example of properties being initialised
within the constructor for in the Snake demo:
"""""""""" and a list to any empty list of the appropriate member-type,
for example new List
Code does not parse as Elan. new List
Code does not parse as Elan. new List
Code does not parse as Elan. new List
Code does not parse as Elan. new List
Code does not parse as Elan. - though you may choose any value.Square Square Square Square Square for example takes the form variable tail name ? set to new Square (20 , 15 )value or expression ? 0 tail name ? = Square (20 , 15 )value or expression ? # variable definition 0 var tail name ? = new Square (20 , 15 ) value or expression ? ;0 Dim tail name ? = New Square (20 , 15 )value or expression ? ' variable definition 0 var tail name ? = new Square (20 , 15 ) value or expression ? ;0 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 class shows
both kinds of association (only relevant parts of the class are shown only here):
Snake Snake Snake Snake Snake is always initialised to the same values, so the constructor does not need to define any parameters.currentDir currentDir currentDir currentDir currentDir property is of type Direction Direction Direction Direction Direction (which is an enum) and is initialised to Direction .right Direction .right Direction .right Direction .right Direction .right .body body body body body property is a multiple association - defined as a list of type Squaretail
Code does not parse as Elan.Squaretail
Code does not parse as Elan.Squaretail
Code does not parse as Elan.Squaretail
Code does not parse as Elan.Squaretail
Code does not parse as Elan. used only for the purpose of initialising properties.
It is first used to initialise the body body body body body as a list containing only the tail tail tail tail tail and
is finally used to define the priorTail priorTail priorTail priorTail priorTail which is a single association of type Square Square Square Square Square .
In between it is used in an expression to determine the initial value for the head head head head head property using a function (more on this in the next heading).
In the last code example calls a function method, using 'dot syntax' on the
the variable , which is an instance of class . Defining a function method, or a procedure method,
on a class has these advantages:
Properties on a class define the data (or 'state') for each instance of the class; methods define the behaviour for the object.
Function methods defined on a class, are similar to global functions in that they act like 'queries', returning information that is determined by
the values of the parameters. The difference is that a function method defined on a class, may additionally make use of information obtained from
the properties of the object (directly or indirectly) - to determine the information to be returned. But like a global function, a function method
defined on a class may not cause any side effects, which means that it may not update any property. The body of the
and properties and uses this, together
with the parameter to create and return a new instance of with different and
properties:
The """""""""".
If you do intend to print an object in text form then you can define the string to be returned, incorporating
one or more of the object's properties to help identify the particular object. For example, you might edit the method
defined on the class to:
into the main routine.)
Procedure methods, like function methods, may define parameters and may also read the values of the object's properties.
Like global procedures they may also generate side effects, and this includes changing the values of the object's properties,
and may call other procedures, on the same object, or associated objects. For example, the
class, changes the position of various
segments, and, if the now covers the same space as the changes the position of
the apple (passed into the method as a parameter) by calling the :
The word 'encapsulation' is often used in descriptions of object-oriented programming. But that word has two rather different meanings in plain English. Fortunately, both of the meanings of 'encapsulation' apply.
The first meaning of encapsulation is similar to 'represents' - as in 'Our school motto encapsulates the ethos and culture of the school'.
(The second meaning will be addressed in the next principle.)
Thus, objects should represent, or encapsulate, the principal concepts in the application domain.
A rule of thumb is that the classes defined in a program should correspond to the main nouns used when describing the purpose
and operation of the program in plain English. You can see this in the Snake demo, where the code defines three classes, named , , and - all nouns that
would be recognised by anyone who understands how to play the game Snake, but who does not necessarily know anything about programming.
One of the claimed benefits of this is that it makes programs easier to read, and to update in response to new requirements, because the structure of the code corresponds more closely to the language of the application domain. If the user, or the business customer that owns the game, says that they want the snake to be a different colour, or for there to be two apples at once (in different positions) - it will be easier for the programmer to identity where within the program the code must be changed that in a procedural implementation. You can try this for yourself, by asking a user to specify similar small changes to the presentation and/or game rules, and testing how long it takes you to update the procedural, and object-oriented, versions of the Snake program as required.
Importantly, the class does not just represent all the information associated with the noun - it also encapsulates the behaviour needed from that thing, in the form of methods. This is sometimes stated as representing the know-whats for the thing (as properties), and the know how-to's as methods.
This principle corresponds to the second meaning of 'encapsulation': sealing. Think of the related word: 'capsule'. You can only access the capabilities of an object through its public members. All the 'members' defined on a class (that is: its properties and methods) are public - although properties are public only for reading, not for modifying. However, it is possible to make any property or method private - such that it can be accessed from code within the class, but not from outside. To do this, you bring up the context menu on the instruction (by right-mouse-click or Ctrl-m) and select make private (a change that can be reversed with make public).A method might be made private because it just provides help to one or more public methods, but is not expected to be useful in its own right. However, a property is often made private to implement the principle of 'information' hiding.
Some textbooks suggest that every property - for example - should be private, and that access should be via a 'getter' -
a function method typically named and properties are private, but the function method class, to ensure that the next apple is not created
directly underneath the snake.
In many applications two or more of the classes will be seen as having quite a lot in common. When this situation identified, there might be a case for making them inherit from a common 'abstract' class. For example, in a school administration system, you might define classes named Pupil, Teacher, and Parent, but these might all inherit from a common abstract class named Individual. The Person class can then be described as the 'superclass' of the other three, and they as the 'subclasses' of Individual.
An abstract class is created, at global level, with an abstract class instruction, and the template requires a name (beginning with a capital letter) to be specified.
The main differences between an abstract and a concrete super-class are:
new Individual ()Individual ()new Individual () New Individual ()new Individual () you will get a compile-error message: Individual must be concrete to create instance.
Earlier we learned about association between objects. Association may be characterised by the 'has a' relationship, as in a 'Student has a personal tutor' (meaning that the Student class defines a property named 'tutor' - which is of type Teacher. Bt contrast inheritance may be characterised as an 'is a' relationship, as in 'a Student is an Individual'.
There are two principal motivations from making two or more classes inherit from a common abstract class:
Some textbooks emphasise the first of those two motivations, but it is the second one that is far more important. Indeed, inheritance is the most over-used, and the most frequently abused, concepts in OOP. For that reason, irrespective of the programming language being used, Elan deliberately enforces principles of good OOP from the outset. For example:
The last point is controversial - since all the supported programming languages allow overriding. But overriding (of concrete methods) breaks the 'O' of the widely-quoted 'SOLID' principles of oop: that classes should be 'Open for extension, but closed for modification'.
In Elan, an object-oriented program still needs a procedural main method to execute.
The Java language is unusual in that even the main routine must defined within a class - Elan handles this by including a class named Global
Code does not parse as Elan.Global
Code does not parse as Elan.Global
Code does not parse as Elan.Global
Code does not parse as Elan.Global
Code does not parse as Elan. as part of the file boilerplate,
but treats the instructions directly within this class as being 'global' in nature. Any user-defined classes are added within this, and Elan does not allow properties to be added
directly within that Global
Code does not parse as Elan.Global
Code does not parse as Elan.Global
Code does not parse as Elan.Global
Code does not parse as Elan.Global
Code does not parse as Elan. class since these would be, effectively, just global variables in disguise.
In general good OOP design means aiming to keep the functionality well-distributed among the user-defined classes. Inevitably, some classes are going to define more methods, and/or more-complex methods, than others. But you should be wary of a situation where one class is doing most of the work, and the other classes act as little more than data structures without encapsulated behaviour. All too often that one big class is instantiated only once (this pattern is known as a 'singleton'), by the main routine. While singletons are not always a bad idea, a singleton that does most of the work of the program - sometimes irreverently referred to as a 'god class' - is really just a procedural program in disguise.
A useful check on your design is to look at the distribution of instructions in the program (not counting instructions within tests, which are not part of the executable program) as in the the following summary of the Snake - object-oriented demo:
| Class | Properties | Methods (excl. constructor & toString) | No. of instructions | Notes |
|---|---|---|---|---|
| 4 | 657 | singleton - but an extended version of the game might have multiple instances. | |
| 1 | 216 | ditto | |
| 2 | 222 | Many instances created in the game. Both Snake and Apple have associations to one or more Squares | |
| Instructions not in a user-defined class | 12 | 13 | e.g. in main method, enums, or global procedures/functions, excluding tests | |
| Total instructions | 96 | excluding tests |
Coding with Elan using the object-oriented profile offers all the global instructions uses in the procedural profile plus two new ones: concrete class and abstract class
From the Snake - object-oriented demo:
From the Blackjack demo:
From the Pathfinder demo:
variable apple name ? set to new Apple ()value or expression ? 0 apple name ? = Apple ()value or expression ? # variable definition 0 var apple name ? = new Apple () value or expression ? ;0 Dim apple name ? = New Apple ()value or expression ? ' variable definition 0 var apple name ? = new Apple () value or expression ? ;0 or variable start name ? set to new Point (5 , 5 )value or expression ? 0 start name ? = Point (5 , 5 )value or expression ? # variable definition 0 var start name ? = new Point (5 , 5 ) value or expression ? ;0 Dim start name ? = New Point (5 , 5 )value or expression ? ' variable definition 0 var start name ? = new Point (5 , 5 ) value or expression ? ;0 .From the Blackjack demo, the abstract class - which is inherited by the concrete classes
and
C C C C C , which inherits from type A A A A A .If you want sub-classes to be able to define their own implementation of a common method, then that method should be made abstract. If it turns out that several of the sub-classes need the same, or very similar, implementations (and duplication of code is always to be avoided, according to the DRY principle - Don't Repeat Yourself) then you use the pattern of 'delegation'. This means defining the method as abstract, but also providing a private implementation (or part-implementation) with a slightly different name, which sub-classes may call from their own implementations. You can see an example of this in the Blackjack demo:
Player Player Player Player Player defines abstract procedure newHand()
Code does not parse as Elan.abstract procedure newHand()
Code does not parse as Elan.abstract procedure newHand()
Code does not parse as Elan.abstract procedure newHand()
Code does not parse as Elan.abstract procedure newHand()
Code does not parse as Elan..newHandHelper newHandHelper newHandHelper newHandHelper newHandHelper .HumanPlayer HumanPlayer HumanPlayer HumanPlayer HumanPlayer defines its implementation of call this .newHandHelper procedureName ? (arguments ? )0 self .newHandHelper procedureName ? (arguments ? ) # call procedure 0 this .newHandHelper procedureName ? (arguments ? ); // call procedure 0 Me .newHandHelper procedureName ? (arguments ? ) ' call procedure 0 this .newHandHelper procedureName ? (arguments ? ); // call procedure 0 .Dealer Dealer Dealer Dealer Dealer defines a more complex implementation of call this .newHandHelper procedureName ? (arguments ? )0 self .newHandHelper procedureName ? (arguments ? ) # call procedure 0 this .newHandHelper procedureName ? (arguments ? ); // call procedure 0 Me .newHandHelper procedureName ? (arguments ? ) ' call procedure 0 this .newHandHelper procedureName ? (arguments ? ); // call procedure 0
to do the common part of the task.Coding with Elan using the object-oriented profile offers the same statement instructions used in the procedural profile.
Member instructions (also referred to simply as 'members') may be added only with a concrete class or abstract class. Hence, these method instructions may be created only when the profile is set to object-oriented (or functional - which includes all OOP capabilities).
From the Snake - object-oriented demo, where the class defines this constructor:
which requires the programmer to provide values for parameters and when instantiating a ,
and assigns these parameters to the corresponding properties of the object.
From the Blackjack demo, where the class defines this constructor:
Note that the single parameter defined in the instructor does not correspond directly to the any property defined on ,
but is used to create a new instance of for its own (association) property.
Its property is a 'multiple association' and is initialised to a new (empty) list of type .
reassign this .x variableName ? to x value or expression ? 0 self .x variableName ? = x value or expression ? # reassign variable 0 this .x variableName ? = x value or expression ? ; // reassign variable 0 Me .x variableName ? = x value or expression ? ' reassign variable 0 this .x variableName ? = x value or expression ? ; // reassign variable 0 -
the prefix means that you can have a parameter with the same name, with no risk of ambiguity.From the Snake - object-oriented demo, where the class defines these properties:
When a class property is optional, it is defined with the type .
Before reading the property's value, you must test if it has a value with function .
If is returned, then you retrieve the value with function is returned, then it has no current value,
and using
To update the property's value, use function
Here a pupil (of class
From the Roman Numerals Turing Machine demo, where the Turing Machine
Code does not parse as Elan.Turing Machine
Code does not parse as Elan.Turing Machine
Code does not parse as Elan.Turing Machine
Code does not parse as Elan.Turing Machine
Code does not parse as Elan. class defines these two function methods:
this .methodName ()self .methodName ()this .methodName ()Me .methodName ()this .methodName ().From the Roman Numerals Turing Machine demo, where the Turing Machine
Code does not parse as Elan.Turing Machine
Code does not parse as Elan.Turing Machine
Code does not parse as Elan.Turing Machine
Code does not parse as Elan.Turing Machine
Code does not parse as Elan. defines these procedure methods (among others):
This is the simplest form of a procedure method, which allows the property (a string) to be set to a new value
by code outside the class.
This method made use of a function method on the same class - .
call this .methodName procedureName ? (arguments ? )0 self .methodName procedureName ? (arguments ? ) # call procedure 0 this .methodName procedureName ? (arguments ? ); // call procedure 0 Me .methodName procedureName ? (arguments ? ) ' call procedure 0 this .methodName procedureName ? (arguments ? ); // call procedure 0 .From the Blackjack demo, where the abstract class defines this abstract function:
which is implemented (as a concrete function method) by the subclasses, including on :
which makes use of a method ).
From the Blackjack demo, where the abstract class defines this abstract function:
which is implemented (as a concrete function method) by the subclass using simple logic to implement the
standard rules that a Blackjack dealer must always follow:
and on the subclass by code that usesinput/output methods to obtain the next action from the user:
See Comments
Elan Language Reference go to the top