[MUD-Dev] (no subject)

KevinL darius at bofh.net.au
Tue Nov 21 19:11:06 CET 2000


Urk!  Apologies in advance for such a long email - I'm kind of thinking as I 
type, which is never a good thing (although I guess it's better than not 
thinking when I type...  I hope ;).

Hopefully, this all is useful to someone - I know it's proving useful to me.  
It will likely have design impact on Moebius, although I'm not quite sure how 
yet...

>>> "John Buehler" wrote
> >From: KevinL <darius at bofh.net.au>
> >Date: Tue, 21 Nov 2000 11:37:55 +1100
> 
> >Essentially, by inheriting, you're assuming that one of the parent objects
> >will take full responsibility for the behaviour - one parent or another
> will
> >win out wrt: what attributes are inherited, where they clash.  If you're
> >componentising, you can list all the different values for the different
> >attributes (in essence), and write something to control that.
> 
> But in an inheritance system, the parent and child classes cooperate in
> providing behavior.  Parent classes can provide utility methods that child
> classes can employ, child classes can ignore the parent implementation, or
> incorporate the parent implementation into their own behavior.

Which appears to be the same case as for a component system - other components 
provide methods than the child can employ, etc. etc.  The difference is you 
require the child to state the "routing" - how to manage such actions, which 
component to use for what activities, etc.  The parent/child setup provides an 
implicit (but potentially overridable) default routing for such things.

Um - by "routing" I refer to how a child object manages a request for an 
attribute/method/whatever - whether it passes that request up to the parent, 
handles it itself, queries multiple components and builds a response, etc.  
"child" is the final object you're building, "parent" and "component" I'm 
using interchangeably atm.  You talk about classes and objects and components 
- all of these I tend to interchange too, as where I'm coding there's very 
little difference.

> I hope that you're not using attributes as the definitive example of the
> sort of problem that needs to be addressed.  Attributes are a fairly simple
> problem.  Far more complicated is actual algorithmic behavior that the class
> is obligated to perform when its methods are invoked.  To take examples from
> the vector component I worked on, the insertion and removal of values is a
> bit more complicated than just modifying or retrieving a value.

Yup, that makes sense.  In python, as an easy example, you have __getattr__ 
and __setattr__ methods, which are hooks that can provide for this sort of 
thing - define __getattr__, and any request for an attribute that the child 
doesn't provide will be sent to this method - it can then perform whatever 
algorithmic juggling it needs to.

In Moebius, we don't _currently_ have a similar setup - although I have 
allowed for defining a method instead of an attribute, so if you call 
self.getAttr('str'), and your str is a method, it'll execute the method and 
return the results - if str is a number, it just returns the number.  That was 
intended to allow overriding in a similar sort of manner.  (Incidentally, we 
get actual methods with self.getMethod() - there's a paucity of words for 
use around some of these, it's a source of annoyance to me (and others who try 
to explain things to me ;)

> >The implementation aspects in inheritance are simply provided by one or the
> >other parent.  In component systems, each component gets a chance to have
> it's
> >say, but it's more likely that you're going to take those decisions out of
> the
> >hands of the components, and manage it elsewhere - probably in the main
> object
> >you're creating.
> 
> The relationship between base and derived classes is rather strict.  It
> results in the statement: I am an example of the base class, and I am
> obligated to deliver all aspects of that functionality.  The relationship
> between components is whatever you need it to be.  You get to employ other
> components in the construction of your component any way that you want.

Pah to this ;)  Sorry, but I'm used to working with mutable objects/classes - 
there's no reason I should feel obligated to deliver all aspects of the 
functionality of my parent at all, my parent simply defines a baseline to 
start from, and defaults for behaviour I don't deal with.

I don't really understand this "must deliver all functionality" approach - 
what's the difference between overriding my parent's method, and refusing to 
honour my parent's method?  Both are a form of changing the way the parent 
behaves - which is exactly what you want to do often when you're inheriting.

I understand that you might want strict typing and strict interface rules - 
but I guess I'm headed in a different direction, and am planning at some stage 
on back-tracking and filling that in with something akin to java's 
"interfaces".  But doing such in a highly dynamic language (python is the 
reference point here) is an interesting exercise in itself - classes are fully 
mutable (they are, in fact, simply objects themselves) during run-time, as is 
the class hierarchy.  My interfaces will have to control how objects are 
modified - deny the ability to remove certain methods/attributes/etc.

> The 'contains' relationship isn't really very interesting in this
> discussion.  We're mostly trying to figure out why components are more
> interesting when tackling the 'is' relationship.  The difference is that
> there is no clear contract between the code that sits in the base class and
> the code that sits in the derived class.  There IS a contract, but it hasn't
> been captured anywhere.  Making the base class and the derived classes into
> discrete components would turn an inheritance system into a component system
> (but a rather restricted one).  They would only be able to communicate with
> each other through specific interfaces.  Such a component system would still
> be limited to a tree composition, with single class representation in the
> tree.

It seems to me that if you use components, you require the child object to 
define explicitly how all requests are routed - if you use inheritance, you 
state a default routing for everything up front, and potentially allow the 
child to intercept that and do what it will.  That's the biggest difference I 
can see so far.

In terms of mud building, the inheritance option seems (to me at least) to sit 
more easily with the idea of allowing clueless people to build - people like 
myself won't have to write up the default behaviour, or make sure they've 
accounted for addition of further components down the track.  More - 
functionality changes to the parents/components would seem to be "safer" in an 
inheritance setup, because the default way of passing all those requests 
through is the same across the board.

What happens in a component system if a component is modified to include new 
methods?  Do the children have to be modified to know about the new 
functionality from the components?  If I include both key and monster 
components, and a "talkto()" method is added to the monster component, do I 
have to track down all objects using that component and alter them to pass
requests for the talkto() method to the monster component?

> >edit, set - which all belong to a single object.  Inherit from that object,
> >and you become a creator.  The base class is responsible for anything the
> >child chooses not to take responsibility for - which strikes me as the same
> as
> >component systems.
> 
> Yup, that's the same sort of thing that you do in a component system, except
> that you'd use the 'creator' class as a tool in the construction of your
> class.  The 'creator' methods that you wanted to use, you'd call from your
> methods, and the ones you want to supercede, you'd just code up and ignore
> the 'creator' class.  And you manually instantiate the 'creator' class when
> and if you need it.

Ok, I think the "manually instantiate" is language-specific again - it makes 
little sense in my small area of the world ;)  But it sounds like you're 
writing a wrapper for the creator component.  Why wrap it, if it's there 
already in the first place?

If you do wrap it, why not simply inherit from it and use the child to mask 
out/supercede the bits you want to change?

Ah, I think I've just realised something - you're assuming that classes aren't 
"live" - they don't exist/can't operate until they've been instantiated - 
whereas I'm used to objects either existing or not, and very little space 
between classes and instances.  Components provide a local state for each 
object using them - that makes more sense...

> >But I'm still not clear on what's meant by "implementation".
> 
> I was referring to the code sitting in the base class versus the derived
> class.  Some classes do not inherit cleanly, requiring serpentine call
> chains that pass between parent and child one or more times.  It's times
> like that when not having clear contracts can start to get you into trouble.

Ah, ok - if I call parent.blah(), the blah() is called on the child, not on 
the parent.  All my parents do is provide functionality for their children 
(unless you manipulate them directly).  So those serpentine calls don't exist, 
unless someone really went out of their way to explicitly play with their 
parents.

> I'm implementing an 'is a' structure using that service component.
> Inheritance IS a composition technique.  It's just clunky and limited.  It
> happens to have an implementation in a widely-used language, so it is what
> we use.  Components don't resolve 'collision', they avoid having them in the
> first place.  Components also permit multiple instances of a class to be
> involved in the implementation of a new class, and ensure that strict
> contractual separation is always retained between pieces of software.  The
> strict separation is invaluable when you want to upgrade your system to have
> improved characteristics.

Ok, I don't use C++ (it's ugly, IMNSHO ;).  Components seem to avoid collision 
by forcing the coder to resolve it manually when they create the object/import 
the components.  Multiple instances I can understand being easier to manage, 
but they'd be managed the same way I would currently - if you put multiple 
locks on a chest, you'd override is_locked() to check each lock in turn.  
_However_, if you instantiated each component separately when you included it, 
then each one could hold local state itself, whereas in my current inheritance 
scheme, those are just pushed into the local object to deal with - which would 
be awkward.  I'd use multiple objects in this case, and bind them all together 
somehow - which is the primitive component setup I've noted previously.

> Inheritance works for a certain class of design problems.  Components work
> for a superset of design problems.  Why bother with inheritance?

Possibly because, for what I can see, inheritance requires less and applies 
more easily to the set of problems it addresses - it's a simpler concept for 
building, and I'm all for simple concepts ;)  Additionally, the people I've 
been talking to seem to find it useful in certain places - they'd inherit from 
a base orc to produce a bunch of orcs, for instance, but the base orc would be 
made up of a heap of components that provide various services.

I'm not convinced components are a "superset" - or if they are, they seem to 
have certain conceptual blocks in them for certain problems.

> By the way, components are not necessarily about 'has' relationships.  I can
> implement an inheritance system using only components behind the scenes,
> delivering all the 'is' relationships that you need.  Further, the 'has'
> relationship is not far removed from the 'is' relationship.  Possession of a
> characteristic can be an 'is' relationship.  For example, a vehicle with
> four wheels 'is' a four-wheeled-vehicle.  I can write a contract that a
> component can implement (and that another component can require) that
> ensures at compile time that what is being communicated between two pieces
> of software is a vehicle that 'has' four wheels.  Because it 'is' a
> four-wheeled vehicle.

But then when you built a bunch of cars, would you use the four-wheeled 
vehicle as a component and write more wrapping code, or would you just inherit 
from the four-wheeled vehicle and make local changes as necessary?

I'm seeing overlap, but I''m still thinking that inheritance is a useful 
concept.

KevinL


_______________________________________________
MUD-Dev mailing list
MUD-Dev at kanga.nu
https://www.kanga.nu/lists/listinfo/mud-dev



More information about the mud-dev-archive mailing list