Skip to content
This repository

There are several ways you can make private parameters with middleclass.

Underscoring

The simplest one is just to precede your attributes with underscores. This is actually written on the Lua 5.1 reference, section 2.1, “Lexical conversions”, as a way to say “this is here, but please don’t use it”.

local class = require('middleclass')
MyClass = class('MyClass')

function MyClass:initialize()
  self._internalStuff = 1
  self.publicStuff = 2
end

However, this isn’t really making the properties “hidden”.

Private class attributes

In general, the way of “really” getting hidden functions or variables consists on using Lua’s scoping rules. The simplest way of using this is creating each of your classes on separate files, and then declaring any private variable or functions as local, on the “root” scope of the file.

Example:

-- File 'MyClass2.lua'
local class = require('middleclass')

MyClass2 = class('MyClass2')

local _internalClassCounter = 0

function MyClass2:initialize()
  _internalClassCounter = _internalClassCounter + 1
  self.publicStuff = 2
end

function MyClass2:getCount()
  return(_internalClassCounter)
end

The scope of local declarations on a lua file is the file itself. If you declare something “local” in one file it is not available on others, even if they “require” that file.

-- File 'main.lua'

require('MyClass2')

-- Try to change internal member...
_internalClassCounter = 4 -- Attempt to modify the _internalClassCounter variable

print(MyClass2:getCount()) -- prints "0"

Let me explain what happens here. The _internalClassCounter = 4 line is, in reality, creating a new global variable called internalClassCounter, and assigning it 4. The “really internal” one is “out of reach” on main.lua (unless someone does really tricky stuff with the environments). So getCount() works as expected.

Private instance methods

It is also possible to declare private methods. The trick here is not to “include” them on the class definition. On the following example, we will not declare it on Class3:secretMethod; instead we’ll create a local function. Since we’re not using the : operator any more, we have to make the “self” parameter explicit. Also, since we have to make it local, we have to deviate from the “usual” way of declaring Lua functions (the “usual” way of declaring functions makes them global):

-- File 'MyClass3.lua'
local class = require('middleclass')

MyClass3 = class('MyClass3')

local _secretMethod = function(self) -- notice the 'local' at the beginning, the = function and explicit self parameter
  return( 'My name is ' .. self.name .. ' and I have a secret.' )
end

function MyClass3:initialize(name)
  self.name = name
end

function MyClass3:shout()
  print( _secretMethod(self) .. ' You will never know it!' )
end

-- File 'Main.lua'
require('MyClass3')

peter = MyClass3:new('peter')
peter:shout() -- My name is peter and I have a secret. You will never know it!

print(_secretMethod(peter)) -- throws an error - _secretMethod is nil here.

This technique also allows the creation of private class methods. In MiddleClass, there’s really no difference between class methods and instance methods; the difference comes from what you pass to their ‘self’ parameter. So if you invoke _secretMethod like this: _secretMethod(MyClass3) it will be a class method.

A slightly more efficient way of creating a class method would be getting rid of the ‘self’ parameter and use MyClass3 directly on the method’s body:

MyClass3 = class('MyClass3')

local _secretClassMethod = function() -- self parameter out
  return( 'My name is ' .. MyClass3.name .. ' and I have a secret.' ) -- use MyClass3 directly.
end

-- Note that this alternative is only recommended for private class methods. Public class methods should follow the convention of adding one explicit 'class' parameter:
MyClass3 = class('MyClass3')

function MyClass3.classMethod(theClass)
  return( 'Being a public class named ' .. theClass.name .. ' is not a bad thing.' )
end

This gives a bit more of flexibility when overriding public class methods on subclasses.

Finally, a subtle point regarding recursive private methods. If you need to create a private method that calls himself, you will need to declare the variable first, and then (on the next line) initialize it with the function value. Otherwise the variable will not be available when the function is created

MyClass3 = class('MyClass3')

local _secretRecursiveMethod -- variable declared here
_secretRecursiveMethod= function(self, n) -- and initialized here
  if(n<=0) then
    print( 'Last recursion')
  else
    print ( 'Recursion level ' .. n )
    _secretRecursiveMethod(self, n-1)
  end
end

MyClass3:recurseOver(n)
  _secretRecursiveMethod(self, n)
end

m = MyClass3:new()
m:recurseOver(5)

Output:
Recursion level 5
Recursion level 4
Recursion level 3
Recursion level 2
Recursion level 1
Last recursion

Private Instance attributes

Instance attributes are a little bit trickier to implement, since we only have one scope to “hide stuff in”, and it has to cope with multiple instances.

One way to do this is using one private class variable as a ‘stash’. If you use one table instead of just a number, you can and hide there all the private information you may need. One problem with this approach is that you need to come out with a ‘key’ per ‘instance’.

Fortunately this is a very simple thing to do, since in lua you can use nearly any type of object as a key – So you can use the instances themselves as keys. In other words, we use ‘self’ as a key.

One problem with this approach is that instances might not be liberated by the garbage collector once they are not used any more (since there’s a reference to them on the ‘stash’ keys). In order to avoid this, we can make the ‘stash’ a weak table.

On the following example, the name attribute is public, but age and gender are private.

Our ‘secret stash’ in the following example will be called _private.

-By the way, the following example also shows how you can do “read-only-ish attributes”: you make them private, and make getters for them, but not setters.

-- File 'MyClass4.lua'
local class = require('middleclass')

MyClass4 = class('MyClass4')

local _private = setmetatable({}, {__mode = "k"})   -- weak table storing all private attributes

function MyClass4:initialize(name, age, gender)
  self.name = name
  _private[self] = {
    age = age,
    gender = gender
  }
end

function MyClass4:getName() -- shorter equivalent: MyClass4:getter('name')
  return self.name
end

function MyClass4:getAge()
  return _private[self].age
end

function MyClass4:getGender()
  return _private[self].gender
end

-- File 'main.lua'

require('MyClass4')

stewie = MyClass4:new('stewie', 2, 'male')

print(stewie:getName()) -- stewie
stewie.name = 'ann'
print(stewie.name) -- ann

print(stewie:getAge()) -- 2
stewie.age = 14        -- this isn't really modifying the age... it is creating a new public attribute called 'age'
-- the internal age is still unaltered
print(stewie:getAge()) -- 2
-- the newly created external age is also available.
print(stewie.age) -- 14

-- same goes with gender:

print(stewie:getGender()) -- 'male'
stewie.gender = 'female'
print(stewie:getGender()) -- 'male'
print(stewie.gender) -- 'female'

Private members on the same file

There’s also a way of creating private members that other classes/methods on the same file can’t access, if you ever had the need.

Just create an artificial scope with do … end, and declare private members as ‘local’ inside that block. Only the methods inside that block will have access to them:

-- File 'MyClass3.lua'
local class = require('middleclass')

MyClass3 = class('MyClass3')

function MyClass3:initialize(name)
  self.name = name
end

do
  local secretMethod = function(self) -- notice the explicit self parameter here.
    return( 'My name is ' .. self.name .. ' and I have a secret.' )
  end

  function MyClass3:shout()
    print( secretMethod(self) .. ' You will never know it!' )
  end
end

-- functions outside the do-end will not 'see' secretMethod, but they will see MyClass3.shout (because they see MyClass3)
Something went wrong with that request. Please try again.