multiple inheritance?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Skeletonxf
Citizen
Posts: 87
Joined: Tue Dec 30, 2014 6:07 pm

multiple inheritance?

Post by Skeletonxf »

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
User avatar
pgimeno
Party member
Posts: 3551
Joined: Sun Oct 18, 2015 2:58 pm

Re: multiple inheritance?

Post by pgimeno »

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:

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
Note that order matters, if there are overlapping methods.
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: multiple inheritance?

Post by Inny »

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.

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
Skeletonxf
Citizen
Posts: 87
Joined: Tue Dec 30, 2014 6:07 pm

Re: multiple inheritance?

Post by Skeletonxf »

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

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
Image

I don't quite get how the methods are being added in when none of the || logs, which is perhaps why things are breaking.
User avatar
Inny
Party member
Posts: 652
Joined: Fri Jan 30, 2009 3:41 am
Location: New York

Re: multiple inheritance?

Post by Inny »

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.
Skeletonxf
Citizen
Posts: 87
Joined: Tue Dec 30, 2014 6:07 pm

Re: multiple inheritance?

Post by Skeletonxf »

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 :/
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: multiple inheritance?

Post by airstruck »

Skeletonxf wrote:Inheritance so far goes as Point -> Box -> Button -> ImageButton & MoveableButton
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).

In general, the first thing you should consider when thinking about code reuse should be composition, not inheritance.
Skeletonxf
Citizen
Posts: 87
Joined: Tue Dec 30, 2014 6:07 pm

Re: multiple inheritance?

Post by Skeletonxf »

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 :D
User avatar
airstruck
Party member
Posts: 650
Joined: Thu Jun 04, 2015 7:11 pm
Location: Not being time thief.

Re: multiple inheritance?

Post by airstruck »

Skeletonxf wrote:it's a single point with a width and height
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
Citizen
Posts: 87
Joined: Tue Dec 30, 2014 6:07 pm

Re: multiple inheritance?

Post by Skeletonxf »

I've ran into a problem where trying to make Box composed of a point seems to over write its meta table.

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
Logging this has this weird behaviour

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>
    }
  }
}
If I remove the single line assigning self.point a point object the methods stay as for boxes like they should?
Post Reply

Who is online

Users browsing this forum: No registered users and 46 guests