[MUD-Dev] (no subject)

John Buehler johnbue at msn.com
Wed Nov 22 12:27:33 CET 2000


>From: KevinL <darius at bofh.net.au>
>Date: Tue, 21 Nov 2000 19:11:06 +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.

The difference lies in the nature of the cooperation.  Components cooperate
fundamentally as peers.  One component calls another using a public
interface.  There are no 'private' interfaces per se.  In inheritance, there
is a trusted relationship between parent and child classes ('protected' in
C++).  And, as I've been saying, there is no contract clearly stated that
lies between parent and child in the general case.  As a child, I can modify
parent properties in ways that the parent may not have anticipated.
Obviously, good design says "don't do that", but over time, changes can be
made to both parent and child classes that do not take into account the
various ways that the loose interactions were being taken advantage of.
Eventually somebody violates somebody's loose contract and the system starts
to produce strange behaviors.

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

The component story is rather upside down from the inheritance story.  In
inheritance, you do define the child based on the parents.  In components,
you build your component and simply create other components that you want to
use in order to accomplish your end.  The fact that there is no strict
parent/child relationship opens up your design choices.

> > 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 ;)

If retrieval of properties is all that your components/classes are for, then
that means that you must have a big pile of monolithic code lying around
that actually does all the work.  In a component system, ALL code lies in
components, other than some bootstrapping needed to get from the operating
system paradigm up to the component paradigm.  In short, the main() creates
some component that implements the ICommandMethod interface or some such
thing.  That interface permits you to specify command arguments and to then
tell it to execute the command.  That command would be to run your
application, of course.  Your application would immediately start creating
all the components that it needs for correct operation.

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

It simmply means that whatever your parent has, you must have.  In
components, if you want to employ a component that implements one of the
behaviors that you need, but not others, you can still use that component.
In inheritance, it's all or nothing.  That's the nature of inheritance and
it's viewed as a 'good thing'.

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

Mutating classes on the fly is an interesting approach, but I'd never go
near such a thing for production code.  You can get it to work and it WILL
work, but I wouldn't go there.  Also, I don't want anything to do with
weakly-typed languages - aka script languages.  They are a nightmare to
debug when you get above some trivial number of lines of code.  All checking
happens at runtime and you have to rely on testing in order to find out if
your system works.  Fast and loose only works for one iteration of coding.
If you then want to build on that first iteration, you quickly find out how
messy things can get.

I liken components to bricks.  You build up your application layer by layer
and you know exactly what you have.  Other approaches, and particularly
weakly-typed languages, I liken to mud huts (no pun intended).  You can keep
piling on the mud, but you never know when it will settle, shift or outright
collapse on you.  You sure can't start piling bricks on top of it.

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

The fact that you can structure things as you like is very liberating.  What
happens is that decisions that you have to be aware of are in your face with
components.  In inheritance, the decisions are ones that you should be aware
of, but because so much happens automatically, you aren't forced to know
about them.  Only when strange things start to happen do you begin to
understand them.  Inheritance reminds me of the joke 'Ready...  FIRE!
Aim..."

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

The 'default' behavior can be provided by somebody who wrote a rock-solid
component with clearly-defined interfaces that you can use.  Components just
make sure that everything is clearly-stated and that everyone understands
what's really going on.  Components should end up being as simple as
clicking together Legos.  Note that clicking together these Legos is not a
means of glomming together a bunch of properties.  Clicking them together is
a means of defining the behavior of an application.  Hook up a printer
component to a streaming output component and you have the ability to print
documents.  Hook up a postscript viewer component to that same streaming
output component and you have the ability to view the printout on the
screen.  Hook up a fax component to it and you have the ability to
manufacture faxes.  And so on.

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

Version control is something that we looked at very hard - as have a number
of people.  Once a component is 'in use', it doesn't change.  Not to fix
bugs, not to add or remove methods, nothing.  If you need to fix a bug, you
can go to the original code and crank out a new component with a new
identification.  That's because the code using the buggy component might be
RELYING on the existence of that bug.  They may have code that works around
it.  You take out the bug and their code stops working correctly.  If the
team using the old component wants to upgrade to the new component with the
bug fixes, they can do that, but they do it consciously.  They have to alter
their code to deal with the fact that the behavior of the new component is
different from what they had.

Note that at the purist level, ANY change to a component constitutes some
small change in contract.  Maybe I was relying on the fact that there were
0x00004E56 bytes being occupied by the code when the component is loaded
into memory.  Maybe I'm relying on a certain series of instructions in the
component code.  Or the size of the component.  Or the speed of the
component.  All of these things are potential contracts that must be filled.
Some inordinately rare and some quite common.  ANY change can impact them.
If we're going to get serious about taking code into the next stage, we need
to understand these things.

I'm not talking about casual coding here.  I'm talking about commercial
products that are going to actually WORK.  Products that don't have goofs,
glitches and crashes all over the place.  This isn't going to sit well with
a whole world of cowboy coders who just like cranking out 'whatever' and get
paid exhorbitant amounts of money for 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?

That wrapper can be as transparent as handing out the interface from the
creator component to anyone who asks me for my creator interface.  That's if
you want to use the creator exactly as the creator component implemented
that functionality.  If you want to alter it in order to implement the
correct behavior for your component, you have to wrap it.

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

That's just a language mechanism.  I could give you a language where all you
do is say that you want to 'use' a certain component in the same way that
you would 'inherit' a certain class.

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

I'm obviously not being very articulate then.

Inheritance is a mechanism that establishes a relationship between two
chunks of code and data.  Those chunks can easily be components.  In
inheritance, the interactions between those chunks of code and data are
premitted to be loose, while in components they are normally tight as a
drum.  Because you can create a contract for ANY behavior, you can make the
interactions between components as loose as you like.  The looser you make
your contracts, the more rope that you wrap around your neck.

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

Obviously the word 'inherit' is the source of our woes:

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

Inheritance as a concept is the notion of handing out another component's
interfaces as your own.  This is used all the time in component systems.
Inheritance is just fine as a concept.  The implementation of it as the only
means of class construction is far too restrictive.  The particular
implementation in C++ and other languages that I've seen is too loose, from
a contractual standpoint.

JB


_______________________________________________
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