multiple inheritance?
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
-
- Citizen
- Posts: 87
- Joined: Tue Dec 30, 2014 6:07 pm
multiple inheritance?
I've been working on some objects using plain lua rather than any library following the style I saw in PIL. I've now realised that I want a class that inherits both my ImageButton and MoveableButton. I'm not sure how to go about making an object inherit from both rather than just one of them?
https://github.com/Skeletonxf/menu-syst ... aster/menu
Inheritance so far goes as Point -> Box -> Button -> ImageButton & MoveableButton
https://github.com/Skeletonxf/menu-syst ... aster/menu
Inheritance so far goes as Point -> Box -> Button -> ImageButton & MoveableButton
Re: multiple inheritance?
There's a chapter on multiple inheritance in the PIL book: https://www.lua.org/pil/16.3.html
For just two superclasses, you can set the __index field in the metatable to something like:
Note that order matters, if there are overlapping methods.
For just two superclasses, you can set the __index field in the metatable to something like:
Code: Select all
__index = function (object, field)
-- cache it
object[field] = MoveableButton[field] and MoveableButton[field] or ImageButton[field]
-- return it
return object[field]
end
Re: multiple inheritance?
It's probably a better idea to use "Mixins" for multiple inheritance, rather than any delegation scheme via the __index metamethod.
A Mixin is pretty simple: Copy all of the methods from one table into another.
Yeah, that's it. In the class you're building, if there's any initialization logic that needs to happen, have the constructor call a method that it got via the mixin. Just to make life easier, name the init methods on the mixins to something pretty much assured to be distinct and different between them.
Here's some code, so you see what I'm getting at.
A Mixin is pretty simple: Copy all of the methods from one table into another.
Yeah, that's it. In the class you're building, if there's any initialization logic that needs to happen, have the constructor call a method that it got via the mixin. Just to make life easier, name the init methods on the mixins to something pretty much assured to be distinct and different between them.
Here's some code, so you see what I'm getting at.
Code: Select all
do
MovableMixin = {}
-- Remember to call from the constructors
function MovableMixin:initMovableMixin(some, parameters)
self.x = 27
end
function MovableMixin:moveIt()
self.x = self.x + 1
end
end
do
MovableImageButton = subclass(ImageButton)
-- This is how you mixin the MovableMixin table
for k, v in pairs(MovableMixin) do MovableMixin[k] = v end
function MovableImageButton:init()
ImageButton.init(self)
self:initMovableMixin()
end
end
do
-- example of how the button has the mixin's method
local iLikeToMoveIt = MovableImageButton:new()
iLikeToMoveIt:moveIt()
end
-
- Citizen
- Posts: 87
- Joined: Tue Dec 30, 2014 6:07 pm
Re: multiple inheritance?
I've tried that Mixins idea and it seems to be partially working. I can get the Image button to work if I don't try to add the Movement methods to work, and I can get half of the movement methods to work and to be added into the table of the object but this breaks the Image methods, even though they seem to still be there.
I found that the image method for drawing wasn't using the one from the image object in the merged object so I tried to only add in movement methods to the image object that the object didn't already have, to avoid an over write, but it seems the method adding code I'm using is very buggy because just crawling through the key and value pairs of the movement object seems to break the image one?
This is what logs using inspect.inspect() just after creating the MoveableImageObject using the following code
I don't quite get how the methods are being added in when none of the || logs, which is perhaps why things are breaking.
I found that the image method for drawing wasn't using the one from the image object in the merged object so I tried to only add in movement methods to the image object that the object didn't already have, to avoid an over write, but it seems the method adding code I'm using is very buggy because just crawling through the key and value pairs of the movement object seems to break the image one?
This is what logs using inspect.inspect() just after creating the MoveableImageObject using the following code
Code: Select all
Program starting as '"C:\Program Files\love\love.exe" "C:\Users\Skeletonxf\Documents\Lua\Tools"'.
Program 'love.exe' started in 'C:\Users\Skeletonxf\Documents\Lua\Tools' (pid: 4008).
!!x
!!imageHov
!!y
!!message
!!image
!!w
!!vertices
!!h
!!moveSpeed
{
h = 48,
image = <userdata 1>,
imageHov = <userdata 2>,
message = "test",
moveSpeed = 40,
vertices = { { 0, 24, 0, 0.5, 255, 255, 255 }, { 0, 0, 0, 0, 255, 255, 255 }, { 48, 0, 1, 0, 255, 255, 255 }, { 48, 24, 1, 0.5, 255, 255, 255 }, { 48, 48, 1, 1, 255, 0, 0 }, { 0, 48, 0, 1, 255, 0, 0 } },
w = 98,
x = -250,
y = 50,
<metatable> = <1>{
__index = <table 1>,
buttonClicked = <function 1>,
click = false,
moveSpeed = 30,
moveable = false,
setMoveSpeed = <function 2>,
update = <function 3>,
<metatable> = <2>{
__index = <table 2>,
animateButton = <function 4>,
buttonClicked = <function 5>,
checkActive = <function 6>,
checkCanClick = <function 7>,
clickRegister = false,
defaultActiveIf = "",
draw = <function 8>,
getState = <function 9>,
inRange = <function 10>,
makeActiveIf = <function 11>,
move = <function 12>,
moveCentreTo = <function 13>,
moveTo = <function 14>,
setDefaultActiveIf = <function 15>,
setSize = <function 16>,
setState = <function 17>,
state = true,
update = <function 18>,
<metatable> = <3>{
__index = <table 3>,
draw = <function 19>,
getArea = <function 20>,
getCentreX = <function 21>,
getCentreY = <function 22>,
getH = <function 23>,
getW = <function 24>,
getX2 = <function 25>,
getY2 = <function 26>,
h = 50,
isMouseOver = <function 27>,
setCentre = <function 28>,
setCentreX = <function 29>,
setCentreY = <function 30>,
setH = <function 31>,
setW = <function 32>,
w = 50,
<metatable> = <4>{
__index = <table 4>,
getMessage = <function 33>,
getX = <function 34>,
getY = <function 35>,
message = "",
moveX = <function 36>,
moveY = <function 37>,
new = <function 38>,
setMessage = <function 39>,
setX = <function 40>,
setXY = <function 41>,
setY = <function 42>,
x = 0,
y = 0
}
}
}
}
}
Code: Select all
local function addMoveMethods(args)
--!! shouldn't do anything
for k, v in pairs(moveableButton.new(args)) do
end
end
function moveableImageButton.new(args)
--local o = imageButton.new(args)
local o = imageButton.new(args)
--addMoveMethods(args)
for k, v in pairs(moveableButton.new(args)) do
-- copy all extra methods from moveable button into this instance of image button
print("!!" .. tostring(k))
if not o[k] then
print("||" .. tostring(k))
o[k] = v
end
end
return o
end
I don't quite get how the methods are being added in when none of the || logs, which is perhaps why things are breaking.
Re: multiple inheritance?
This is where the notions of Classical Inheritance and Classical Object Oriented Programming start to fall apart within a dynamically (duck) typed language. Since you don't have a static compiler vetoing your code from type mismatches, here's my other recommendation.
Duplicate code. Write each class separate of each other. I know that's a pain and it violates everything you've ever been taught, but the reality is you actually don't have a scenario where code sharing is possible and you're being misled into believing you do.
AFTER you've wrote the separate classes and realize there are patterns and snippets of code within your methods that can be shared, extract them out into a separate file. Try not to make these into objects/methods, though I still stand by my mixins advice before.
What's going on there is your Object Classes will now behave as interfaces and proxies to those functions which external code will not be using directly. This form of inheritance is the kind that you're always being told but never seemed to grok which is the following: Prefer Composition over Inheritance.
Duplicate code. Write each class separate of each other. I know that's a pain and it violates everything you've ever been taught, but the reality is you actually don't have a scenario where code sharing is possible and you're being misled into believing you do.
AFTER you've wrote the separate classes and realize there are patterns and snippets of code within your methods that can be shared, extract them out into a separate file. Try not to make these into objects/methods, though I still stand by my mixins advice before.
What's going on there is your Object Classes will now behave as interfaces and proxies to those functions which external code will not be using directly. This form of inheritance is the kind that you're always being told but never seemed to grok which is the following: Prefer Composition over Inheritance.
-
- Citizen
- Posts: 87
- Joined: Tue Dec 30, 2014 6:07 pm
Re: multiple inheritance?
I think I may cheat and make MoveableButton have a toggle over if it can be moved and then avoid repeating myself by just making MoveableImageButton instead of also ImageButton. Still, it's a shame there's no really clean way to do it and if I get something more complicated I guess I'll have to look into abstracting/extracting out code anyway :/
Re: multiple inheritance?
Whoa, whoa. Is a Box a specialized type of Point? Is a Button an even more specialized type of Point? You've already created a needlessly long, awkward inheritance chain. Try this -- assume any inheritance is an antipattern unless you can thoughtfully prove why it's better than composition in a particular case. A Box isn't a type of Point, but it may be composed of Points. A Button isn't a type of Box, but it may be composed of a Box and other things (like an Image, or a DragHandle maybe).Skeletonxf wrote:Inheritance so far goes as Point -> Box -> Button -> ImageButton & MoveableButton
In general, the first thing you should consider when thinking about code reuse should be composition, not inheritance.
-
- Citizen
- Posts: 87
- Joined: Tue Dec 30, 2014 6:07 pm
Re: multiple inheritance?
Ah. I actually did that for DropDown. In my case however Box really is a specialised Point rather than 4 points because it's a single point with a width and height (AABB). For Button though I probably should make it composition and then I'll be able to give it different shapes from Boxes in the future. Thanks, I hadn't really thought about composition all the much and it will probably get around the huge inheritance trees I would make in the future
Re: multiple inheritance?
Points don't have sizes, though. It would be more natural to model what you call a "point with a width and height" like you would model "eggs with grits and bacon." The bacon isn't part of the eggs, they are each components of a breakfast. In the same way a box can be composed of an origin point and dimensions.Skeletonxf wrote:it's a single point with a width and height
-
- Citizen
- Posts: 87
- Joined: Tue Dec 30, 2014 6:07 pm
Re: multiple inheritance?
I've ran into a problem where trying to make Box composed of a point seems to over write its meta table.
Logging this has this weird behaviour
If I remove the single line assigning self.point a point object the methods stay as for boxes like they should?
Code: Select all
function Box:givePoint(args)
print("giving box " .. (self.message or "") .. " a point")
self.point = point.new(args)
print(inspect.inspect(self))
end
--wrapper for public access to creating Boxes
function box.new(args)
local o = Box:new(args)
-- box is composed of a point
print("making box")
print(inspect.inspect(o))
o:givePoint(args)
return o
end
Code: Select all
making box
{
h = 50,
message = "ok",
w = 50,
x = 10,
y = 50,
--this are all the box methods as wanted
<metatable> = <1>{
__index = <table 1>,
draw = <function 1>,
getArea = <function 2>,
getCentreX = <function 3>,
getCentreY = <function 4>,
getH = <function 5>,
getW = <function 6>,
getX2 = <function 7>,
getY2 = <function 8>,
givePoint = <function 9>,
h = 50,
isMouseOver = <function 10>,
name = "box",
setCentre = <function 11>,
setCentreX = <function 12>,
setCentreY = <function 13>,
setH = <function 14>,
setW = <function 15>,
w = 50,
<metatable> = <2>{
__index = <table 2>,
name = "",
new = <function 16>
}
}
}
giving box ok a point
<1>{
h = 50,
message = "ok",
point = <table 1>,
w = 50,
x = 10,
y = 50,
-- and suddenly they are gone!, calling getW() is a nil value! these are the methods a point should have
<metatable> = <2>{
__index = <table 2>,
getMessage = <function 1>,
getX = <function 2>,
getY = <function 3>,
message = "",
moveX = <function 4>,
moveY = <function 5>,
name = "point",
new = <function 6>,
setMessage = <function 7>,
setX = <function 8>,
setXY = <function 9>,
setY = <function 10>,
x = 0,
y = 0,
<metatable> = <3>{
__index = <table 3>,
name = "",
new = <function 11>
}
}
}
Who is online
Users browsing this forum: Ahrefs [Bot], Google [Bot] and 3 guests