Skip to content

Commit

Permalink
Added middleclass implementation for class behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
tredfern committed Jan 3, 2019
1 parent fc3ab35 commit c9488a7
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 17 deletions.
6 changes: 6 additions & 0 deletions ext/class/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-- Copyright (c) 2019 Redfern, Trevor <trevorredfern@gmail.com>
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT

return require "ext.class.middleclass"
183 changes: 183 additions & 0 deletions ext/class/middleclass.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
local middleclass = {
_VERSION = 'middleclass v4.1.1',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
MIT LICENSE
Copyright (c) 2011 Enrique García Cota
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
]]
}

local function _createIndexWrapper(aClass, f)
if f == nil then
return aClass.__instanceDict
else
return function(self, name)
local value = aClass.__instanceDict[name]

if value ~= nil then
return value
elseif type(f) == "function" then
return (f(self, name))
else
return f[name]
end
end
end
end

local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f

for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end

local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f

if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end

_propagateInstanceMethod(aClass, name, f)
end

local function _tostring(self) return "class " .. self.name end
local function _call(self, ...) return self:new(...) end

local function _createClass(name, super)
local dict = {}
dict.__index = dict

local aClass = { name = name, super = super, static = {},
__instanceDict = dict, __declaredMethods = {},
subclasses = setmetatable({}, {__mode='k'}) }

if super then
setmetatable(aClass.static, {
__index = function(_,k)
local result = rawget(dict,k)
if result == nil then
return super.static[k]
end
return result
end
})
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end

setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })

return aClass
end

local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")

for name,method in pairs(mixin) do
if name ~= "included" and name ~= "static" then aClass[name] = method end
end

for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method
end

if type(mixin.included)=="function" then mixin:included(aClass) end
return aClass
end

local DefaultMixin = {
__tostring = function(self) return "instance of " .. tostring(self.class) end,

initialize = function(self, ...) end,

isInstanceOf = function(self, aClass)
return type(aClass) == 'table'
and type(self) == 'table'
and (self.class == aClass
or type(self.class) == 'table'
and type(self.class.isSubclassOf) == 'function'
and self.class:isSubclassOf(aClass))
end,

static = {
allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,

new = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = self:allocate()
instance:initialize(...)
return instance
end,

subclass = function(self, name)
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")

local subclass = _createClass(name, self)

for methodName, f in pairs(self.__instanceDict) do
_propagateInstanceMethod(subclass, methodName, f)
end
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end

self.subclasses[subclass] = true
self:subclassed(subclass)

return subclass
end,

subclassed = function(self, other) end,

isSubclassOf = function(self, other)
return type(other) == 'table' and
type(self.super) == 'table' and
( self.super == other or self.super:isSubclassOf(other) )
end,

include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
}
}

function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end

setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })

return middleclass
27 changes: 16 additions & 11 deletions src/class.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,28 @@

local class = {}

function class:create(typename)
end

function class:new(tbl)
local instance = tbl or {}
setmetatable(instance, self)
function class:create(base)
local c = base or {}
setmetatable(c, self)
self.__index = self
return instance

function c:new(o)
local instance = o or {}
setmetatable(instance, c)
return instance
end
return c
end

function class:is(typeof)
return self.type_name == typeof.type_name
end

return function(className, base_class)
if base_class then
return class:create(base_class)
else
return class:create({ type_name = className })
end

return function(className)
return class:new({
type_name = className
})
end
21 changes: 16 additions & 5 deletions src/class_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@
-- https://opensource.org/licenses/MIT

describe("class", function()
local class = require "class"
local class = require "ext.class"

describe("type names", function()
it("requires a type name for a class", function()
local imp = class("type name")
assert.equals("type name", imp.type_name)
assert.equals("type name", imp.name)
end)

it("provides a method to check if object is of a type", function()
local imp = class("type name")
local instance = imp:new()
assert.is_true(instance:is(imp))
assert.is_true(instance:isInstanceOf(imp))
end)
end)

Expand All @@ -30,11 +30,22 @@ describe("class", function()

it("classes can have methods added to them that all instances get", function()
local imp = class("object")
imp.method1 = function(self) return 3 end
imp.method2 = function(self) return 4 end
imp.method1 = function() return 3 end
imp.method2 = function() return 4 end
local instance = imp:new()
assert.equals(3, instance:method1())
assert.equals(4, instance:method2())
end)
end)

describe("inheriting from base class", function()
it("supports the methods on the base class", function()
local base = class("base")
function base:base_method() return self.value end
local sub = class("sub", base)
local instance = sub:new()
instance.value = 67
assert.equals(67, instance:base_method())
end)
end)
end)
10 changes: 10 additions & 0 deletions src/scenes/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Copyright (c) 2019 Redfern, Trevor <trevorredfern@gmail.com>
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT

return {
title = require "scenes.title",
new_game = require "scenes.new_game",
game = require "scenes.game"
}
9 changes: 9 additions & 0 deletions src/scenes/new_game.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Copyright (c) 2019 Redfern, Trevor <trevorredfern@gmail.com>
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT

local class = require "ext.class"
local Scene = require "scenes.scene"
local NewGame = class("NewGame", Scene)
return NewGame
13 changes: 13 additions & 0 deletions src/scenes/scene.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-- Copyright (c) 2019 Redfern, Trevor <trevorredfern@gmail.com>
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT

local class = require "ext.class"
local Scene = class("Scene")

function Scene:activate()
Scene.current_scene = self
end

return Scene
14 changes: 14 additions & 0 deletions src/scenes/scene_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Copyright (c) 2019 Redfern, Trevor <trevorredfern@gmail.com>
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT

describe("Scene", function()
local Scene = require "scenes.scene"

it("when activating scene it sets itself to the current active scene", function()
local a = Scene:new()
a:activate()
assert.equals(a, Scene.current_scene)
end)
end)
9 changes: 8 additions & 1 deletion src/scenes/title.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT

local Title = {}
local class = require "ext.class"
local Scene = require "scenes.scene"
local NewGame = require "scenes.new_game"
local Title = class("Title", Scene)

function Title:draw()
love.graphics.print("Title Screen")
end

function Title:new_game()
NewGame:new():activate()
end

return Title
16 changes: 16 additions & 0 deletions src/scenes/title_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-- Copyright (c) 2019 Redfern, Trevor <trevorredfern@gmail.com>
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT

describe("Scenes - Title", function()
local Scene = require "scenes.scene"
local Title = require "scenes.title"
local NewGame = require "scenes.new_game"

it("can switch to a new game scene", function()
local t = Title:new()
t:new_game()
assert.is_true(Scene.current_scene:isInstanceOf(NewGame))
end)
end)

0 comments on commit c9488a7

Please sign in to comment.