Mixin way?

A

andrea crotti

I have some classes that have shared behaviours, for example in our
scenario an object can be "visited", where something that is visitable
would have some behaviour like

--8<---------------cut here---------------start------------->8---
class Visitable(Mixin):
FIELDS = {
'visits': [],
'unique_visits': 0,
}

def record_view(self, who, when):
self.visits += {'who': who, 'when': when}
self.unique_visits += 1
--8<---------------cut here---------------end--------------->8---

Where the Mixin class simply initialises the attributes:

--8<---------------cut here---------------start------------->8---
class Mixin(object):
def __init__(self, **kwargs):
for key, val in self.FIELDS.items():
setattr(self, key, val)

for key, val in kwargs.items():
if key in self.FIELDS:
setattr(self, key, val)
--8<---------------cut here---------------end--------------->8---


So now I'm not sure how to use it though.
One way would be multiple subclasses

class MyObjectBase(object):
pass

class MyObj(MyObjectBase, Visitable):
pass

for example.
This solution is probably easy, but at the same time disturbing because
MyObjectBase is semantically quite different from Visitable, so
subclassing from both seems wrong..

The other solution (which is what is partially done now) is to use
another class attribute:

class ObjectWithMixin(CouchObject):
MIXINS = [Visitable]

and then do all the smart things needed:
- at object construction time
- when setting attributes and so on..

This solution is more complicated to implement but maybe is more
flexible and more "correct", what do you think?
 
S

Steven D'Aprano

I have some classes that have shared behaviours, for example in our
scenario an object can be "visited", where something that is visitable
would have some behaviour like

[snip mixins]

By the way, it's a common convention to name mixin classes as "SpamMixin".

So now I'm not sure how to use it though.
One way would be multiple subclasses

class MyObjectBase(object):
pass

class MyObj(MyObjectBase, Visitable):
pass

for example.

What's the purpose of MyObjectBase? Either your example is too simple, or
it actually has no purpose, and you should simply write:

class MyObj(object, VisitableMixin):
# code goes here


instead of having one extra layer in the inheritance hierarchy.

This solution is probably easy, but at the same time disturbing because
MyObjectBase is semantically quite different from Visitable, so
subclassing from both seems wrong..


Not really. From what you describe, this seems to be exactly the use-case
for mixins. Yes, mixins are by definition somewhat of an abuse of Object-
Oriented concepts. Mixins don't so much represent a "type of thing" as a
convenient bundle of encapsulated behaviour and/or state.

E.g. MyObj represents some sort of "MyObj thing" (or at least it
*should*, if your OO classes are well-planned), but Visitable(Mixin) does
not represent a thing at all.

But here's a way to look at it to justify the concept of mixins. Think of
regular classes and subclasses as representing trees of descent, like
biological groups:

Animal -> Mammal -> Rodent -> Porcupine
Animal -> Monotreme -> Echidna

Then mixins represent convergent evolution of some trait:

Animal -> Mammal -> Rodent + QuillsMixin -> Porcupine
Animal -> Monotreme + QuillsMixin -> Echidna


The other solution (which is what is partially done now) is to use
another class attribute:

class ObjectWithMixin(CouchObject):
MIXINS = [Visitable]

and then do all the smart things needed:
- at object construction time
- when setting attributes and so on..

This solution is more complicated to implement but maybe is more
flexible and more "correct", what do you think?

I think that's worse.

You seem to have *almost* stumbled onto the design pattern known as
"composition" or "delegation", only not quite.

Normal class-based design models a "is-a" relationship. "Lassie is a
Dog", so we would do:

lassie = Dog()

Composition models a "has-a" relationship. For example, both cars and
boats have engines, so we might be tempted to use a mixin:

class Car(EngineMixin):
pass

class Boat(EngineMixin):
pass


which isn't an awful solution. But a conceptually cleaner solution might
be to do this:

class Car:
def __init__(self):
self.engine = Engine()
def go(self):
print "Fasten your seat belt"
self.engine.start()
self.handbrake = False
self.change_gear("drive")
self.accelerate()

class Boat:
def __init__(self):
self.engine = Engine()
def go(self): ...

and then both cars and boats can *delegate* behaviour to the engine
object.


So, if you think of "Visitable" as a gadget that can be strapped onto
your MyObj as a component, then composition is probably a better design.
But if you think of "Visitable" as a mere collection of behaviour and
state, then a mixin is probably a better design.
 
A

andrea crotti

2013/4/3 Steven D'Aprano said:
[snip]

So, if you think of "Visitable" as a gadget that can be strapped onto
your MyObj as a component, then composition is probably a better design.
But if you think of "Visitable" as a mere collection of behaviour and
state, then a mixin is probably a better design.
Well I can explain better the situation to make it more clear.

We are using CouchDb and so far it has been (sigh) a brutal manipulation of
dictionaries everywhere, with code duplication and so on.

Now I wanted to encapsulate all the entities in the DB in proper objects,
so I have a CouchObject:


class CouchObject(object) :
"""
Encapsulate an object which has the ability to be saved to a couch
database.
"""
#: list of fields that get filled in automatically if not passed in
AUTO = ['created_datetime', 'doc_type', '_id', '_rev']
#: dictionary with some extra fields with default values if not
# passed in the constructor the default value gets set to the attribute
DEFAULTS = {}
REQUIRED = []
OPTIONAL = []
TO_RESOLVE = []
MIXINS = []

Where every subclass can redefine these attributes to get something
done automatically by the constructor for convenience.

Now however there is a lot of behaviour shared between them, so I want
to encapsulate it out in different places.

I think the MIXINS as I would use it is the normal composition
pattern, the only difference is that the composition is done per class
and not per object (again for lazyness reasons), right?

Probably subclassing might be fine as well, and makes it simpler, but
I don't like too much to do subclass from multiple classes...
 
N

Neil Cerutti

Well I can explain better the situation to make it more clear.

We are using CouchDb and so far it has been (sigh) a brutal
manipulation of dictionaries everywhere, with code duplication
and so on.

Now I wanted to encapsulate all the entities in the DB in
proper objects, so I have a CouchObject:


class CouchObject(object) :
"""
Encapsulate an object which has the ability to be saved to a couch
database.
"""
#: list of fields that get filled in automatically if not passed in
AUTO = ['created_datetime', 'doc_type', '_id', '_rev']
#: dictionary with some extra fields with default values if not
# passed in the constructor the default value gets set to the attribute
DEFAULTS = {}
REQUIRED = []
OPTIONAL = []
TO_RESOLVE = []
MIXINS = []

Where every subclass can redefine these attributes to get
something done automatically by the constructor for
convenience.

Hopefully someone with experience with them can help you further,
but this seems like a job for a metaclass.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top