Page 1 of 25

middleclass & extras: middleclass 3.0 is out!

Posted: Wed Nov 11, 2009 12:48 am
by kikito
Hi everyone,

Version 3.0 is out. Details on this post: viewtopic.php?f=5&t=1053

Download: https://github.com/kikito/middleclass

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Mon Feb 08, 2010 10:21 am
by kikito
I've started the MindState documentation on the wiki. Since this post can be used for asking questions about both, I've changed its title.

The example there is specially dedicated to one of the LÖVE developers. I'll try to add more examples later on.

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Thu Feb 11, 2010 3:20 pm
by giniu
was looking trough MC and have one question - is it possible to have private fields in classes? Say not necessary methods, but variables? Maybe there is some universal solution or recommendation for private use with MC?

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Thu Feb 11, 2010 4:47 pm
by kikito
It is quite funny that you mention it.

That was the next thing I was to include on the MiddleClass wiki.

...

Done! Please give it another look and let me know what you think.

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Thu Feb 11, 2010 5:46 pm
by giniu
It's good description, I like it :) As of use cases you presented there I think it's more than enough for normal uses of private scope for class

Btw. I was thinking about what other examples could be cool - you can also give example of Singleton implementation ;)

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Sat Feb 13, 2010 3:16 pm
by giniu
One small question... in wiki you say that :new part is implicit, i.e. there is __call for classes, right? But when I try:

Code: Select all

> require 'MiddleClass'
> Game = class('Game')
> game1 = Game:new()
> game2 = Game()
stdin:1: attempt to call global 'Game' (a table value)
stack traceback:
	stdin:1: in main chunk
	[C]: ?
it gives me that I attempt to call table. What am I missing here? :)

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Sat Feb 13, 2010 5:34 pm
by kikito
Hi there!

No that was a bug. :? Thanks for spotting it.

I've fixed MiddleClass, implicit instantiation should be working again.

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Sat Feb 13, 2010 5:57 pm
by giniu
yup, works now :)

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Sat Feb 13, 2010 6:44 pm
by giniu
I just wonder, what do you think about this Singleton implementation?

Code: Select all

Game = class('Game')
local _GameInstance = nil

function Game:instance()
  return _GameInstance or Game:new()
end

function Game:initialize()
  assert(not _GameInstance, "Game is a singleton")
  _GameInstance=self
end

getmetatable(Game).__call = Game.instance
I was thinking about it, because in your MindState example you showed the Game state management - in the example there is Game class and bunch of states classes - the issue in example is though as far as I understand it (you write game:gotoState('OptionsMenu'), each state would have to know which instance of Game class it's running with - natural way to walk around this is by using singleton pattern - but is there any better way than above to build singleton using MC? :)

Re: MiddleClass & MindState: Object Orientation for LUA

Posted: Sun Feb 14, 2010 11:57 am
by kikito
Hmm I think you have made me spot another weakness on middleclass.

The _call parameter is calling Object.new, but it should really be invoking the class:new. In other words when you do this:

Code: Select all

 game = Game()
It is really doing this:

Code: Select all

 game = Object.new(Game)
It should be doing this instead:

Code: Select all

 game = Game:new() 
This way, if you override Game.new, the Game() call would just use that new implementation - you would not have to repeat the overriding on _call.

I'll try to make that change this evening.

Now, to your question: singletons. Here's my view of them: their implementation requirements are very dependent on their target users.

On your example, you will be the only one using your Game object, so in reality, you would be just fine without singletons; you can just create one single global variable and trust that you will not create another one later on. Small improvement just for the laughts: making the whole Game class private, and a game instance public.

Code: Select all

-- File 'Game.lua'
local Game = class('Game', StatefulObject) -- notice the local here
function Game:initialize()
...
end
...-- states, etc

game = Game:new()
Now you can't create other games. Well, not directly: You can still do game.class:new() ... so you might probably want to override new() method to throw an error after the game variable is created (after the fix I'll do today you will not have to mess around with _call).

Code: Select all

game = Game:new()
function Game:new()
  error('Explicit creation of games is forbidden. Use the game global variable instead')
end
But again, if you are going to be the only user of your class, those security measures aren't really necessary.

But what about Singletons in general? What would be the best way to create them with MiddleClass?

Well, consider the following: Every time you create a new singleton you would have to do the same things: create the class, then create a single private instance variable, then override create a getter for that private instance, and then override the new function so it throws an error.

Well, I'm a very big fan of code reuse. This repetitiveness suggests that it should be probably managed with code.

Mixins are a great tool for this job. Consider the following Singleton Mixin:

Code: Select all

-- File 'Singleton.lua'

local private = {}

Singleton = {
  included=function(class, ...)
    private[class] = { instance = class:new(...) }
  end
}
function Singleton:getInstance()
  return private[self].instance
end
function Singleton:new()
  error("Object creation is forbidden on singletons")
end
The 'included()' method is executed when the Mixin is included in a class (by the way I need to add the '...' parameters to middleClass). This is saying: when the mixin is included, generate a private variable for that class called 'instance' invoking class:new.

The other two functions are just adding a new function to class (getInstance) that returns the private variable and overriding new() so it throws an error.

Here's how you would use it with Game:

Code: Select all

-- File 'Game.lua'
require('singleton.lua')
Game = class('Game', StatefulObject)
function Game:initialize()
...
end
...-- states, etc

-- It is important that this is done at the end, or at least after the Game:initialize() function is defined. Otherwise it will not work
Game:includes(Singleton, ...) -- the ... means 'parameters for creating the game instance, if any'
Finally, a couple comments about nomenclature - These are just recommendations, feel free to not adhere to them.
  • I think methods should be "verbal" when possible. I would have called the "instance" method "getInstance"
  • Uppercases are better left for classes and mixins. Attributes and methods, lowercased. So I would have called the instance gameInstance instead of GameInstance.
This evening I'll make the two code changes in MiddleClass, and I'll add one Naming convention section to the wiki.