[Prev][Next][Index][Thread]

Re: Design Pattern in Dylan



ingjyehwang@my-deja.com wrote:
> 
> Recently, I try to read the book about design pattern (GoF).  I also
> read the article by Norvig in his website.   Is there any more detail
> design pattern article for Dylan, or someone like to discuss the known
> design pattern on web?

Here's a message that may be of interest...

------begin forwarded message------

From: Hugh Greene <hughg@harlequin.co.uk>
Subject: Re: Design patterns / virtual slot
Date: 05 Apr 1999 00:00:00 GMT
Newsgroups: comp.lang.dylan

Jeff Dalton wrote:
> 
> Andy Armstrong wrote:
> 
> > There is a general feeling that Dylan makes a lot of the design
> > patterns simpler to write than traditional languages, but there is
> > very little hard evidence ...  It would be really interesting to
> > start collecting code snippets for each pattern ...
> 
> I'm with Andy.

Me too :-)  I should really be off hacking on some non-Dylan stuff at
the moment, though, so here are some "seed" ideas for various GOF
patterns.  (These are all IHMO, of course -- correct me if I'm talking
nonsense!)


A couple of general points, which many of you know but are worth
repeating if we're trying to gather teaching material about patterns.

- In Dylan, you don't have to realise things in a class-centered way,
you can also or instead work in terms of functions and modules.

- Macros can be used to good effect, so you may only need to worry
about the guts of these patterns once, then use the macros lots of
times.


Creational

- Abstract Factory:

A natural implementation in Dylan is to realise the /AbstractFactory/
Participant as a combination of 'make', a 'what-to-make' GF and a
number of '<concrete-factory-/n/>' and '<concrete-product-/i/>' classes.
As Andy Armstrong already said(?), DUIM uses this internally, to work
out what concrete class to make for an abstract gadget class, in the
context of a given <frame-manager>.

A skeleton of one approach is:

----------------
define generic class-for-make-concrete-product
    (factory :: subclass(<factory>),
     abstract-product-class :: subclass(<abstract-product>))
 => (concrete-product-class :: subclass(<concrete-product>));

define method make
    (abstract-product-class :: subclass(<abstract-product>),
     #rest initargs,
     #key factory :: subclass(<factory>))
 => (concrete-product)
  apply(make,
        class-for-make-concrete-product(factory,
abstract-product-class),
        initargs)
end method;
----------------

(Note that the different kinds of Factory don't actually have to be
classes, unless they also need extra state; unique constants would do
just as well.  The same goes for the kinds of Products, but it seems
more natural to make them classes and allows us to use the standard
'make' generic for creation of the ConcreteProducts.  The class
<concrete-product> might be a subclass of <abstract-product>, but I
don't think it's strictly necessary, if you use a GF other than 'make'.)

The main Dylan language feature shown off here is 'classes as objects':
this gives the syntactic convenience of the standard creation protocol
but also reduces the number of language-level objects involved in
implementing the pattern, since we don't need separate CreateProductX
functions.  (Of course, we're trading this off against a loss of
static type info, but that's Dylan for you.)

We can also see 'GFs separating behaviour from classes' making the
implementation of "extensible factories" easier (as Implementation note
3 describes for Smalltalk).

- Builder: Not a lot to say here.

- Factory Method: Seems very close to Abstract factory, in Dylan
implementation if not in intent.

- Prototype: Pretty straightforward, but nothing very interesting in
terms of Dylan magic.  (Hmm: Dylan comes with a 'shallow-copy' GF but
not the 'deep-copy' needed for this pattern.  I wonder if that's worth
adding to the dylan or common-extensions libraries in future?)

- Singleton: Enough already ;-)  (Although I'll reply to one of Lyman's
messages later.)


Structural

- Adapter

No great advantage for Dylan in the basic implementation, but a possible
advantage and some caveats.

On the plus side, if you have a loosely-typed program with the GFs to be
adapted being defined on <object>, it may be enough to write new methods
on those for the Target, rather than create an Adapter subclass.

The first caveat is that, since Dylan doesn't strictly separate
interface from implementation, you have to be careful with the "class
adapter" approach, because your Adapter can behave equally as a Target
and an Adaptee, which you might not want.

Secondly, since GFs are separate from classes (making it harder to
enforce interfaces), some new library might add extra operations on
your Target, which aren't implemented in terms of operations you've
adapted.  In general, to get the appropriate behaviour for your Adaptee,
you may need extra implementation code which knows about the
implementations of both the new Target behaviour and the innards of your
Adapter and/or Adaptee classes.  So we're gaining extensibility at the
possible cost of substitutability(?).  In languages like C++, new
behaviour would usually be added in a subclass, from which your Adapter
doesn't inherit, so the mismatch could be spotted at compile time.  The
same danger arises for the Proxy pattern.

- Bridge: Nothing special here.

- Composite:

Straightforward again, and Dylan already "emphasize[s] transparency
over [static type-]safety" (Implementation note 4).  This is another
pattern where the separation of GFs from classes is advantageous: it's
easy to add another operation applicable to Components if some subclass
requires it.

- Decorator: Basically straighforward; Implementation note 1 need not
apply (that ConcreteDecorator classes must have a common ancestor).

- Facade:

Straightforward, though worth noting that a Facade might be
realised as a module exporting various functions (and maybe classes)
in Dylan, rather than as a class, since those are the focus of
namespaces.  OTOH, if you might want to parameterise the Facade with
different "back-ends", you probably want a class or other unique
object as the focus for that (rather than, say, relying solely on
out-of-language magic like DLL loading).

- Flyweight:

It would be natural enough to implement this in Dylan using the C++
style (of an extra explicit context argument) shown in the Sample Code
for this pattern.  Alternatively, a combination of global state and
the '\dynamic-bind' macro can hide this context syntactically, which
works well with Dylan's 'slot access as function call' mechanism.

----------------
// As it stands, this relies on a not-necessarily-portable extension
// to \dynamic-bind; however, the same thing can be done portably by
// splitting the state encoded on <glyph-context> into a number of
// separate module variables.
//
// I've omitted the method on glyph-height-dynamic-binder
//
// Both <row> and <character> inherit from <glyph>, which is the root
// for the glyph-height GF.

define thread variable *glyph-context* :: false-or(<glyph-context>) =
#f;

define method draw (window :: <window>, row :: <row>) => ()
  let row-height = /* determine row height */;
  dynamic-bind (*glyph-context*.glyph-height = row-height)
    for (character in row.row-characters)
      draw(window, character);
    end;
  end;
end method;

define method draw (window :: <window>, character :: <character>) => ()
  // Do something involving "character.glyph-height"
end method;

define method glyph-height (character :: <character>) => (height)
  *glyph-context*.glyph-height
end method;
----------------

- Proxy:

Mostly straightforward.  Having 'slot-access as function call' means
we can proxy state as well as behaviour with syntactic ease.

As an exercise, how about doing a "copy-on-write" Proxy?  Having garbage
collection means we don't need to reference-count the copied object. 
Oh,
and this might be a good candidate pattern for showing off macros: how
about a version of '\class-definer' with a some kind of proxy adjective
for slots :-?

See also the caveat above under "Adapter".


Behavio(u)ral

- Chain of Responsibility:

Straightforward enough.  Regarding Implementation note 3, the decoupling
of functions from classes means that an open-ended set of
Requests can be achieved more naturally in the language, with new
functions rather than new "request codes".  Alternatively, Requests can
be represented as closures in Dylan; in that case, though, it seems to
me that Handlers could only decide to handle (i.e., call) all Requests
or none, since closures don't carry much distinguishing information.

This pattern appears in-language in Dylan in the guise of generic
functions and their methods.  Okay, most OO languages with inheritance
will do something like this, but Dylan is a bit closer because the set
of Handlers (applicable methods) may be determined only at runtime and
may be changed at runtime, too.  Exception handlers use this pattern
also.

- Command:

Commands can be represented directly as functions/closures (DUIM does
this for its 'XXX-callback:' functions) or, if you want undo, as a pair
of them.  As functions are first-class objects, you can queue these in
event loops etc.  However, you often want more information than a simple
closure can provide (e.g., who asked for it to be executed), so you may
need to package things up in a more structured object.

- Interpreter:

Easy enough in Dylan.  

Dylan's macro system can help here, as long as the input language is
sufficiently Dylan-like.  (I'm sure I saw a simple macro for finite-
-state machines floating by in email internally.)  Indeed, you could
include the behaviour (as method bodies) as well as the structure
in the macro call.

The ability to create classes dynamically means you could generate the
XxxExpression classes based on user input, though I'm not sure how
useful that'd be, because it's harder to base the behaviour of the
Interpret action on runtime input.

- Iterator:

Dylan does this using iteration protocols, which are "tuples" of
functions and state, and this works really nicely with the '\for' macro.
I'm not entirely sure why Dylan doesn't have separate <iterator>
objects,
but I suppose it could be because: (a) it improves possibilities for
compiler optimisations (not that I'm a compiler hacker at all); or (b)
closures are harder to introspect into, so it's easier to hide state
(although it's also easier to pass the wrong state to the wrong
functions
if they're not all in one object).

- Mediator: Nothing special here.

Much DUIM code I've seen makes the <frame> the Mediator for various
<gadget>s inside it.  I sometimes wonder whether it would be better to
have a separate <mediator> object referred to by the frame (or parts of
it).  But it'd be easy enough (in terms of design, if not of effort) to
change the interface to look like this and have the <frame> as an
Adapter for the <mediator>.

- Memento:

Straightforward again.  Regarding Implementation note 1, Dylan doesn't
provide public and private interfaces to a class, but it does allow
as many different modules as you like to export different aspects.

Shame we don't have a serialisation facility like Java; that'd make this
pattern even easier to implement, in its basic form.

- Observer:

Easy enough to do in its simplest form.  (You could get a bit cleverer
and abstract out a general broadcast communication mechanism, though
that's not Dylan-specific.)

Consequence 3 notes that, if Observers can't tell *what* changed, they
may have to do extra work to decide what to do as a result.  Since
functions are first class objects in Dylan, it would be easy to extend
the Update action to include a sequence of (setter-function, new-value)
pairs :-)

Dylan's 'slot-access as function call' mechanism means that you can
implement this such that clients don't have to rewrite any code (and
probably not even recompile) if you switch between "direct lists of
Observers in each Subject" and "a table mapping Subjects to Observers".

Dylan's macro system might make it possible to easily define classes
whose slots should be observable (or maybe not -- I haven't fleshed out
that idea).  Also, you could use macros to make it simple to write
something like this:

----------------
  with-delayed-notification (foo)
    foo.slot1 := x;
    foo.slot2 := y;
  end;
----------------

... an exercise for the reader ;-)  Extra points if it's thread-safe.

- State: Can't think of anything to say here.

- Strategy: Nothing interesting to say here.  If you need to pass state
from the Context to the Strategy, an obvious Dylan approach would be to
make both Participants be arguments to an /AlgorithmInterface/ GF.

- Template Method:

Straightforward to do as described.  One Dylan twist, which I've thought
of but don't remember seeing, is that, if you have a bunch of generics
with related functionality like this:

  perform-operation(x, y, ...)
  can-perform-operation?(x, y, ...)
  begin-perform-operation(x, y, ...)
  end-perform-operation(x, y, ...)

you could instead write them like this:

  operation(x, y, ...)
  can-perform?(operation, x, y, ...)
  begin-perform(operation, x, y, ...)
  end-perform(operation, x, y, ...)

and have methods on the latter GFs where the first argument specialises
on 'singleton(operation)'.  Seems a neat bit of factoring-out to me :-)

- Visitor:

The classic "Dylan is better" pattern, since every function is already
a Visitor.

Consequence 2 notes that Visitors group related functionality: I'd
expect this to be done using modules (or just groups of similar names)
in Dylan.

Consequence 4 points out that the various ConcreteElement classes to be
visited needn't be related by inheritance.  Taking GFs as Visitors, you
can achieve this in Dylan by using 'type-union's for the argument types.

Consequence 5 says that Visitor objects can double as state
accumulators;
in Dylan, using GFs as Visitors, you'd need to pass an extra object or
use globals, as they suggest.  (You could implement Visitors as non-
-function objects if you wanted, of course.)

Consequence 6 says that you may need to compromise encapsulation to give
Visitors enough access to the visited objects to do their jobs.  In
Dylan, multiple interfaces to one object (via different modules) can
help lessen this problem (by making it clearer what access you're
getting, and allowing several kinds).

> I once suggested, years ago by now, that someone take the "gang of 4"
> patterns book and write up briefly how to do all of the patterns in
> Dylan (or explain why Dylan didn't need them).

Howzat? :-)

> ... since Java does not have full multiple-inheritance and lacks
> multi-methods, some things are rather awkward to do.  ...

Yep.  OTOH, since Java and C++ force functionality to be directly and
textually associated with classes, they make things like Proxy easier
to keep track of.  I still think I'd rather use Dylan, though ;-)

> ... if, as I suspect, Dylan makes a number of things
> significantly easier, Dylan could look pretty good to these
> programmers.

I think Dylan makes things easier, but there's a little more ease to be
gleaned from its syntax, so all of the above will make more convincing
stories if we have some code fragments (or even larger, non-trivial
examples).



References: