Pattern, Abstract Factory / Factory

Pascal Bourguignon pjb at informatimago.com
Sat Feb 6 16:35:15 UTC 2021


Le 06/02/2021 à 14:45, Manfred Bergmann a écrit :
> Hello.
> 
> I am trying to discover design patterns and how they are implemented in Common Lisp.

Using defmacro.

Have a look at:
http://groups.google.com/group/comp.lang.lisp/msg/ee09f8475bc7b2a0
http://groups.google.com/group/comp.programming/msg/9e7b8aaec1794126



> According to Peter Norvig (http://norvig.com/design-patterns/design-patterns.pdf) and this source (https://wiki.c2.com/?AreDesignPatternsMissingLanguageFeatures) some design pattern are more simple in Common Lisp, or are actually not needed at all because the language can be extended to achieve the same thing: extendability, separation, abstraction.
> 
> Taking an Abstract Factory for example, the sources say that metaclasses can help do this.
> I’ve read a bit about metaclasses and the MOP but I fail to see how.
> I could probably come up with something.
> 
> A GUI framework for example. I could create something like this :
> 
>> (defclass meta-button (standard-class)
>    ((gui-button-backend :initform nil
>                         :accessor gui-button-backend)))
> 
> (defmethod closer-mop:validate-superclass
>      ((class meta-button)
>       (superclass standard-class))
>    t)
> 
> (defclass button () () (:metaclass meta-button))
> (defun make-button ()
>    (make-instance 'button))
>
This code is incomplete.  It doesn't specify the concrete 
gui-button-backend, and how it's used to make an instance of button.

Also, it doesn't really implement the abstract factory design pattern, 
because defclass being a macro, that specifies that the metaclass IS the 
name of the metaclass, not that it is EVALUATED to a name of a 
metaclass, you cannot create a class with a metaclass in a variable (not 
directly at least; you could do it using eval and building a defclass 
form at run-time).  Fundamentally defclass is a compilation-time 
operator, while the abstract factory design pattern is designed to 
select the concrete classes at run-time.


> And configure a QT or GTK, Motif (or whatever) button backend to the metaclass on startup (or so).
> But I think this would just be the pattern somehow turned into a metaclass.
> Doesn’t feel like the right thing.
> 
> Does anyone know what Peter Norvig had in mind when he said that metaclasses can be used instead of Abstract Factory pattern?

As an abstraction mechanism, metaclasses can probably help to implement 
some design patterns.  However, the code presented above doesn't 
demonstrate it.

That said, I won't try to define such a metaclass, because in lisp there 
are easier ways to define the abstract factory design pattern.

First, let's remember that design patterns were invented to paper over 
deficiencies in OO programming languages.  Lisp doesn't have a  lot of 
deficiencies, and it has defmacro which let you add features to the lisp 
language easily.

Next, concerning the Asbtract Factory design pattern, it seems to me 
that an important part of this design pattern is the abstract classes of 
the factory, and of the concrete classes.  Abstract classes are 
important in a number of OO programming languages because 1) methods are 
attached to classes, and 2) they are a way to define interfaces.

However, some OO programming languages have other abstractions to define 
interfaces (eg. @protocols in Objective-C), or just don't need an 
abstract superclass to have a bunch of concrete classes implementing the 
same set of methods (or even methods implemented generically for all of 
them).  OO programming languages that allow duck typing such as Python 
for example, or indeed, Common Lisp CLOS where the methods are attached 
to generic functions, not to classes and where all classes are subclass 
of the standard-object class.

So when you remove the abstract superclasses, it remains only the 
parameterisation of the concrete classes that must be created.

In the case of CLOS, the MAKE-INSTANCE generic function already takes 
the class as parameter!!!   There is absolutely no need for an asbtract 
factory design pattern in CLOS, because CLOS doesn't have the deficiency 
in the first place!

There's a last aspect of that design pattern (all design patterns 
actually), which is the participants and collaboration structure.  But 
note that in the case of the Abstract Factory design pattern, the 
description given is actually a meta design pattern, because the 
participants and collaborations are given as a mere example, not in a 
concrete form. When you actually implement the design patter, you will 
choose the exact set of participants (the concrete classes) and their 
relationships.  So the question is how you want to represent this aspect.

It can be just a variable containing the class.  Perhaps it could be a 
list of classes.  Or a structure or clos object with a set of classes in 
slots.  Or a hash-table or other map such as a-list.

Of course, you can also define a class, and define a bunch of methods 
returning the concrete class names.  There would be little reason to do 
that, unless  for some reason this class would need attributes (slots) 
used to compute the concrete class names in the various methods of the 
various generic functions.

Let's also remember that you can specialize generic function on specific 
lisp objects, not only on classes.

To recapitulate, here are various examples of code:

(defclass x11-window ()
   ((title :initarg :title)
    (subviews :initarg :subviews)))
(defclass x11-pane ()
   ())
(defclass x11-button ()
   ((title :initarg :title)))
(defclass x11-textfield ()
   ((value :initarg :value)))

;;; Using a mere variable to store the concrete class:

(defparameter *button-class* 'button)

(defun configure (backend)
   (ecase backend
     (x11 (setf *button-class* 'x11-button))
     (macos (setf *button-class 'macos-button))
     ...))

(make-instance *button-class* :title "OK")

;;; Of course you can define a bunch of variables for several concrete 
classes.



;;; Using a map (here an a-list) for several concrete classes:

(defparameter *gui-factory* '((window    . x11-window)
                               (pane      . x11-pane)
                               (button    . x11-button)
                               (textfield . x11-textfield)))

(defun aget (alist key)
   (cdr (assoc key alist)))

(defun window-class (factory) (aget factory 'window))
(defun pane-class (factory) (aget factory 'pane))
(defun button-class (factory) (aget factory 'button))
(defun textfield-class (factory) (aget factory 'textfield))

(make-instance (window-class *gui-factory*)
                :title "Example Window"
                :subviews (list
                           (make-instance (textfield-class *gui-factory*)
                                          :value "Is it a good example?")
                           (make-instance (button-class *gui-factory*)
                                          :title "Yep")
                           (make-instance (button-class *gui-factory*)
                                          :title "Nope")))


;;; Using a structure (or a clos object, it would be similar):

(defclass classes ()
   ((window :initarg :window :reader window-class)
    (pane :initarg :pane :reader pane-class)
    (button :initarg :button :reader button-class)
    (textfield :initarg :textfield :reader textfield-class)))

(defparameter *gui-factory* (make-instance 'classes
                                            :window    'x11-window
                                            :pane      'x11-pane
                                            :button    'x11-button
                                            :textfield 'x11-textfield))

(make-instance (window-class *gui-factory*)
                :title "Example Window"
                :subviews (list
                           (make-instance (textfield-class *gui-factory*)
                                          :value "Is it a good example?")
                           (make-instance (button-class *gui-factory*)
                                          :title "Yep")
                           (make-instance (button-class *gui-factory*)
                                          :title "Nope")))

;; It's actually the same as with the map, in both cases, instead of
;; using aget or structure or clos instance accessors, we can define a
;; function implementing a common API.


;;; Using methods:

(defgeneric window-class (factory))
(defgeneric pane-class (factory))
(defgeneric button-class (factory))
(defgeneric textfield-class (factory))

;; the you can define an abstract factory class, and implement the methods:

(defclass x11-factory ()
   ())

(defmethod window-class ((factory x11-factory)) 'x11-window)
(defmethod pane-class ((factory x11-factory)) 'x11-pane)
(defmethod button-class ((factory x11-factory)) 'x11-button)
(defmethod textfield-class ((factory x11-factory)) 'x11-textfield)

(defparameter *gui-factory* (make-instance 'x11-factory))

(make-instance (window-class *gui-factory*)
                :title "Example Window"
                :subviews (list
                           (make-instance (textfield-class *gui-factory*)
                                          :value "Is it a good example?")
                           (make-instance (button-class *gui-factory*)
                                          :title "Yep")
                           (make-instance (button-class *gui-factory*)
                                          :title "Nope")))

;; But as you can see, the methods are constant functions, the classes
;; are hard-coded.  So we could avoid the useless factory class, and
;; dispatch only on a value, for example a symbol:

(defmethod window-class ((factory (eql 'x11))) 'x11-window)
(defmethod pane-class ((factory (eql 'x11))) 'x11-pane)
(defmethod button-class ((factory (eql 'x11))) 'x11-button)
(defmethod textfield-class ((factory (eql 'x11))) 'x11-textfield)

(defparameter *gui-factory* 'x11)

(make-instance (window-class *gui-factory*)
                :title "Example Window"
                :subviews (list
                           (make-instance (textfield-class *gui-factory*)
                                          :value "Is it a good example?")
                           (make-instance (button-class *gui-factory*)
                                          :title "Yep")
                           (make-instance (button-class *gui-factory*)
                                          :title "Nope")))

;; As you can see, no difference.



Now, where would defmacro enter the scene?  We could define a few macros 
and supporting functions to be able to define factories with their set 
of concrete classes; the macro would expand to the boilerplate code, 
such as the generic and methods, according to the implementation choice 
(alist, factory class, factory symbol, etc).


(define-abstract-factory gui-factory
    (window pane button textfield))

(define-factory x11-factory gui-factory
    (x11-window x11-pane x11-button x11-textfield))

(define-factory x11-factory gui-factory
    (macos-window macos-pane macos-button macos-textfield))

(defparameter *gui-factory* (make-factory 'x11-factory))

(make-instance (window-class *gui-factory*)
                :title "Example Window"
                :subviews (list
                           (make-instance (textfield-class *factory*)
                                          :value "Is it a good example?")
                           (make-instance (button-class *factory*)
                                          :title "Yep")
                           (make-instance (button-class *factory*)
                                          :title "Nope")))

(define-abstract-factory enterprise-factory
    (employee invoice tax))

(define-factory incorporated-enterprise (enterprise-factory)
    (inc-employee inc-invoice inc-tax))

(define-factory limited-enterprise (enterprise-factory)
    (ltd-employee ltd-invoice ltd-tax))

(defparameter *enterprise-factory* (make-factory 'incorporated-enterprise))

(make-instance (employee-class *enterprise-factory*) :name "John Doe"))



In effect, the abstract factory meta design pattern is thus implemented 
in lisp as a define-abstract-factory macro, and the concrete abstract 
factory design pattern as a define-factory macro.


-- 
__Pascal Bourguignon__



More information about the pro mailing list