• No results found

Multiple Inheritance

In document Programming in Lua 3ed (Page 187-191)

Object-Oriented Programming

16.3 Multiple Inheritance

Because objects are not primitive in Lua, there are several ways to do object-oriented programming in Lua. The approach that we have seen, using the index metamethod, is probably the best combination of simplicity, performance, and flexibility. Nevertheless, there are other implementations, which may be more appropriate for some particular cases. Here we will see an alternative implementation that allows multiple inheritance in Lua.

The key to this implementation is the use of a function for the metafield __index. Remember that, when a table’s metatable has a function in the __index field, Lua will call this function whenever it cannot find a key in the original table. Then,__index can look up for the missing key in how many parents it wants.

Multiple inheritance means that a class can have more than one superclass.

Therefore, we cannot use a class method to create subclasses. Instead, we will define a specific function for this purpose,createClass, which has as arguments the superclasses of the new class; see Listing 16.1. This function creates a table to represent the new class, and sets its metatable with an__index metamethod that does the multiple inheritance. Despite the multiple inheritance, each object instance still belongs to one single class, where it looks for all its methods.

Therefore, the relationship between classes and superclasses is different from the relationship between classes and instances. Particularly, a class cannot be the metatable for its instances and for its subclasses at the same time. In Listing 16.1, we keep the class as the metatable for its instances, and create another table to be the metatable of the class.

Let us illustrate the use ofcreateClass with a small example. Assume our previous classAccount and another class, Named, with only two methods: setname andgetname.

16.3 Multiple Inheritance 169

Listing 16.1. An implementation of multiple inheritance:

-- look up for 'k' in list of tables 'plist' local function search (k, plist)

for i = 1, #plist do

local v = plist[i][k] -- try 'i'-th superclass if v then return v end

endend

function createClass (...)

local c = {} -- new class local parents = {...}

-- class will search for each method in the list of its parents setmetatable(c, {__index = function (t, k)

return search(k, parents) end})

-- prepare 'c' to be the metatable of its instances c.__index = c

-- define a new constructor for this new class function c:new (o)

o = o or {}

setmetatable(o, c) return o

end

return c -- return new class end

To create a new classNamedAccount that is a subclass of both Account and Named, we simply callcreateClass:

NamedAccount = createClass(Account, Named) To create and to use instances, we do as usual:

account = NamedAccount:new{name = "Paul"}

print(account:getname()) --> Paul

Now let us follow how this last statement works. Lua cannot find the field

“getname” in account; so, it looks for the field __index of account’s metatable, which isNamedAccount. But NamedAccount also cannot provide a “getname” field, so Lua looks for the field __index of NamedAccount’s metatable. Because this field contains a function, Lua calls it. This function then looks for “getname”

first in Account, without success, and then in Named, where it finds a non-nil value, which is the final result of the search.

Of course, due to the underlying complexity of this search, the performance of multiple inheritance is not the same as single inheritance. A simple way to improve this performance is to copy inherited methods into the subclasses.

Using this technique, the index metamethod for classes would be like this:

setmetatable(c, {__index = function (t, k) local v = search(k, parents)

t[k] = v -- save for next access return v

end})

With this trick, accesses to inherited methods are as fast as to local methods (except for the first access). The drawback is that it is difficult to change method definitions after the system is running, because these changes do not propagate down the hierarchy chain.

16.4 Privacy

Many people consider privacy to be an integral part of an object-oriented lan-guage; the state of each object should be its own internal affair. In some object-oriented languages, such as C++ and Java, you can control whether an object field (also called an instance variable) or a method is visible outside the object.

Smalltalk, which popularizes object-oriented languages, makes all variables pri-vate and all methods public. Simula, the first ever object-oriented language, did not offer any kind of protection.

The main design for objects in Lua, which we have shown previously, does not offer privacy mechanisms. Partly, this is a consequence of our use of a general structure (tables) to represent objects. Moreover, Lua avoids too much redundancy and artificial restrictions. If you do not want to access something inside an object, just do not do it.

Nevertheless, another aim of Lua is to be flexible, offering to the program-mer meta-mechanisms that enable her to emulate many different mechanisms.

Although the basic design for objects in Lua does not offer privacy mechanisms, we can implement objects in a different way, so as to have access control. Al-though programmers do not use this implementation frequently, it is instructive to know about it, both because it explores some interesting aspects of Lua and because it can be a good solution for other problems.

The basic idea of this alternative design is to represent each object through two tables: one for its state and another for its operations, or its interface.

The object itself is accessed through the second table, that is, through the operations that compose its interface. To avoid unauthorized access, the table that represents the state of an object is not kept in a field of the other table;

instead, it is kept only in the closure of the methods. For instance, to represent our bank account with this design, we could create new objects running the following factory function:

function newAccount (initialBalance) local self = {balance = initialBalance}

16.4 Privacy 171

local withdraw = function (v)

self.balance = self.balance - v end

local deposit = function (v)

self.balance = self.balance + v end

local getBalance = function () return self.balance end return {

withdraw = withdraw, deposit = deposit, getBalance = getBalance end}

First, the function creates a table to keep the internal object state and stores it in the local variable self. Then, the function creates the methods of the object. Finally, the function creates and returns the external object, which maps method names to the actual method implementations. The key point here is that these methods do not getself as an extra parameter; instead, they access self directly. Because there is no extra argument, we do not use the colon syntax to manipulate such objects. We call their methods just like regular functions:

acc1 = newAccount(100.00) acc1.withdraw(40.00)

print(acc1.getBalance()) --> 60

This design gives full privacy to anything stored in the self table. After newAccount returns, there is no way to gain direct access to this table. We can access it only through the functions created insidenewAccount. Although our example puts only one instance variable into the private table, we can store all private parts of an object in this table. We can also define private methods: they are like public methods, but we do not put them in the interface. For instance, our accounts may give an extra credit of 10% for users with balances above a certain limit, but we do not want the users to have access to the details of that computation. We can implement this functionality as follows:

function newAccount (initialBalance) local self = {

balance = initialBalance, LIM = 10000.00,

}

local extra = function ()

if self.balance > self.LIM then return self.balance*0.10 elsereturn 0

endend

local getBalance = function () return self.balance + extra() end

<as before>

Again, there is no way for any user to access theextra function directly.

In document Programming in Lua 3ed (Page 187-191)