Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   Python (http://www.velocityreviews.com/forums/f43-python.html)
-   -   Mixin way? (http://www.velocityreviews.com/forums/t959354-mixin-way.html)

andrea crotti 04-03-2013 02:04 PM

Mixin way?
 
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?


Steven D'Aprano 04-03-2013 03:47 PM

Re: Mixin way?
 
On Wed, 03 Apr 2013 15:04:51 +0100, andrea crotti wrote:

> 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.



--
Steven

andrea crotti 04-03-2013 04:29 PM

Re: Mixin way?
 
2013/4/3 Steven D'Aprano <steve+comp.lang.python@pearwood.info>

> [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...


Neil Cerutti 04-03-2013 05:16 PM

Re: Mixin way?
 
On 2013-04-03, andrea crotti <andrea.crotti.0@gmail.com> wrote:
> 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.

--
Neil Cerutti


All times are GMT. The time now is 04:06 PM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.