20110921

Mixin Components and Fun

Recently I was clued in to a thing called Component-based architecture by one of my good friends. I'd never heard of it before, but I find some aspects of it very appealing.

I never thought that the strict inheritance model would work for a large system. It's hard to define multi-faceted things using a strict tree of inheritance (rather than, say, a DAG). In a nutshell, the component-based architecture dictates that classes themselves have no properties except for a collection of specialized pieces of functionality. The idea is that you can create new classes by dropping in different combinations of functionality, which offers better flexibility.

Ruby and mixins

As with many good ideas, multiple people come up with them independently. I actually use the spirit of this technique in certain places in the MUD. However, I take heavy advantage of the language features of Ruby as the mechanism for making the components work. There's a commonly used language feature of Ruby called mixins, wherein basically an interface is allowed to add instance data and methods to a class.

Here's a quick example:

# mixins are modules, not classes
module Reviewable
    def reviewScore()
        return @reviewScore
    end

    def reviewScore=(val)
        @reviewScore = val
    end

    # in Ruby you can use this shorthand for the getter and setter:
    # attr_accessor :reviewScore
end

class VideoGame
    include Reviewable
end

game1 = VideoGame.new()
game1.reviewScore = 5

class Shoes
    include Reviewable
end

vans = Shoes.new()
vans.reviewScore = 9

So the Reviewable mixin adds the reviewScore instance methods and data to a class--any class. Video games and shoes obviously have nothing to do with each other, but you could concievably review either, and the mixin lets you add that functionality very easily.

The component-based architecture model is a little different. In it, the class would have some kind of "reviewable" component, and whatever main loop needs to process the "reviewable" data would call some abstracted method on the object to fetch it. Maybe something like this?

class Reviewable
    def getData()
        return @reviewScore
    end

    def updateData(val)
        @reviewScore = val
    end
end

class VideoGame
    def initialize()
        self.components = [Reviewable.new()]
    end

    attr_accessor :components
end

def mainLoop()
    entities.each() { |ent|
        ent.components.each() { |comp|
            # consume the data from each entity's components
            print comp.getData()
        }
    }
end

"Real-world" examples

I mentioned I use the mixin model in my MUD. I'll give some examples. But there's a difference, which I'll touch on at the end. First, the examples.

Probably the best example is the Visible mixin, which provides a thing with the necessary data and attributes to be seen.

module Visible
    # initialization and such omitted for brevity
    attr_accessor :bareShortDescription, :bareLongDescription, :hugeDescription
end

More interestingly, the Visible module also provides all of the code needed to automatically load these values from XML data files (and save them back). This mixin is used for Mobs and Dealies (items like things you'd pick up and carry) alike without modifications needed, despite how dissimilar they are.

I have modules called InputFilter and OutputFilter that can be applied to any class to advertise that they know how to process text typed by the user or printed to the user's screen, respectively.

Actually, Ruby uses mixins for this kind of thing pretty broadly. The Comparable and Enumerable mixins advertise that particular objects implement functionality needed to compare or enumerate themselves, respectively, plus they add a bunch of additional methods implemented for you.

The difference... is all inside.

I claim that this use of mixins is similar to the component-based abstraction. In both cases, the objects store some state that is sequestered wholly inside some other class or functionality. In both cases, other objects would interact with this object through some abstraction that hides the nature of the containing class--either finding the component within the object to access or just accessing the mixin's functions directly.

Which technique you use doesn't seem too important. The thing that matters most is discipline and convention. If you want to reap the benefits of these techniques, you need to follow through with consistency better than I've done. I have a lot of places where state and functionality is given to a class rather than a mixin of a class.

For example, a Mob has a lifeState member, which tells if it's alive. But what if I want an animate sword that can be "killed?" I should put this functionality into, say, a Living module. Or a really good example: there are containers, which can contain other dealies, and there are mobs, which can also hold objects in their inventory, but they're handled differently. Instead, I could make a Container mixin and treat them the same.

There's a lot of work I could do to basically be more discliplined. I can't really promise I'll get to it, but I may in places it makes sense.

0 comments:

Post a Comment