Object oriented progamming: constructors and properties
By Samir Tine, published on January 2023
Object constructors
Let's remember how we created an instance of the Fruct object :
apple = Fruct()
Under the hood, instance creation calls a special Object method called constructor
. But wait, we didn't provide such method to the Fruct object, so how was it possible to create the apple and banana instances ? Because every Object provides an internal constructor method if we don't create one.
Let's try to add such method to the Fruct object :
function Fruct:constructor(fruct_kind, fruct_color, fruct_shape)
self.kind = fruct_name
self.color = fruct_color
self.shape = fruct_shape
end
Here, we use the constructor to initialize our instance (represented by the implicit self). We also take advantage of this constructor to add a new attribute to the instance just created : the kind
attribute will contain the kind of fruit.
Let's try to create a new apple now :
redapple = Fruct("red apple", "red", "spherical")
redapple:describe() -- prints "I am a red and spherical fruct"
Great ! Let's update the describe function now that we have the new attribute name
set in the constructor :
function Fruct:describe()
print("I am a "..self.kind..", a "..self.color.." and "..self.shape.." fruct")
end
Object properties
The Fruct object improves as we learn new concepts of object programming with Luart.
Let's continue by changing the kind
attribute of the redapple
instance:
redapple.kind = "banana"
redapple:describe() -- prints "I am a banana, a red and spherical fruct"
Getter method
Let's start with a property in the Fruct object that controls the reading of an attribute :
function Fruct:get_name()
return self.kind
end
A property use a getter
method to read and return its value. Here we are defining a getter method for the property name
.
This property returns the value of the self.kind
attribute when we try to read the property.
Let's try to use it :
redapple = Fruct("red apple", "red", "spherical")
print(redapple.name) -- prints "red apple"
Setter method
Now let's add a setter method to control write access to the name
property:
function Fruct:set_name()
error("Fruct.name property is not writable)
end
Here, we have prefixed the property setter method by set_
. This method is called every time we try change the value of the name
property.
But remember that we don't want it to be changed, so we throw an error to indicate it. Let's see what it gives :
redapple.name = "banana" -- error !
Encapsulation is one of the key features of object-oriented programming. Encapsulation refers to the bundling of attributes and methods inside a single object. It prevents outer objects from accessing and changing attributes and methods of another object. This also helps to achieve data hiding, like here. But wait, the self.kind attribute is still there and can be modified :
redapple.kind = "banana"
print(redapple.name) -- prints "banana"
Accessing private data
function Fruct:constructor(fruct_kind, fruct_color, fruct_shape)
function self:get_name()
return fruct_name
end
self.color = fruct_color
self.shape = fruct_shape
end
We removed the kind
attribute in the Fruct constructor, and we defined the getter property method get_read
in the current scope of the constructor function.
There is no other way than creating a new Fruct instance to use another Fruct name :
redapple = Fruct("red apple", "red", "spherical")
print(redapple.name) -- prints "red apple"
redapple.name = "banana" -- error !
readapple.kind = "banana"-- we create a new field `kind`
print(redapple.name) -- prints "red apple" (the get_name() getter method don't use self.kind anymore !)
Lua scope rules permits to mimic private fields by declaring getter/setter method properties inside the constructor function.
But wait a minute, as the self.kind
attribute has been removed, we need to redefine the describe
method :
function Fruct:describe()
print("I am a "..self.name..", a "..self.color.." and "..self.shape.." fruct")
end
We now come to the end of this tutorial. To summarize :
- The getter and setter methods on an object provide an interface for accessing an instance attribute (called a "property" in this case)
- The getter returns the value of an internal attribute
- The setter sets a new value for an internal attribute