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