Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Do not use god classes

3 views
Skip to first unread message

Julia Donawald

unread,
Jun 3, 2001, 7:38:14 AM6/3/01
to
Hi,
some time ago I have read some parts of the book "Object-Oriented Design
Heuristics" written by Arthur J. Riel. While I read it, I was convinced,
that his design heuristics, are very useful. But now, as I start to design a
little complex project, some of his heuristics seemd to me as impossible to
realize. So now I want to know what you think about them and about the
realization:
1.) At the very beginning of the book, he told us the following theorem:
"Do not create god classes/objects in your system. Be very suspicious of a
class whose name contains Driver, Manager, System, or Subsystem."
Perhaps I do not really understand this heuristic, but in my project I need
a start point for my program, and thats in fact a class, which is something
like a Driver, Manager or System, because it have to deligate the work to
oher classes. I do not know, how I could develope a system usable in real
world, without a start point, where total encapsulated classes interact
together with a minimum interface. What is your opinion to this topic?
2.) "Beware of classes that have many accessor methods defined in their
public interface. Having many implies that related data and behavior are not
being kapt in one place". My question now is, when is it ok to use accessor
methods and when not? Furthermore should I access in the priavte methods of
my class, the private data of my class also with the accessor function, or
is it better to access them direct?

Thanks a lot in advance
Julia

Rich K

unread,
Jun 3, 2001, 8:23:00 AM6/3/01
to
HI Julia,

Drivers, handlers, controllers and the like are perfectly acceptable, and it
sounds like your planned use of them is ok. Yes, you will need a "start point"
- that is pefectly valid. As long as the driver class is not handling any
business logic, you are fine.

The time to be suspicious is when you start having drivers all over the place,
or if the drivers are incohesive (fat controllers). A bad sign is when you have
one huge class, and all the other classes are simple data classes. I think this
is Riel's main point.

I don't know about the details of your application, but in general terms, your
"start up" class should start to communicate with the business objects as soon
as something important (the realisation of a use case, if you're using them)
needs to happen.

In simple commercial systems, I always have a single controller for each use
case, and then a main controller that kicks the whole lot off. For example, the
main controller might ask the user what they want to do - this controller then
delegates the work to the appropriate use case controller (lets say, "create
purchase order", to use a dull example!)

The "Create Purhcase Order" controller then works with the business objects to
make that use case happen. There should be no controlling/gui logic inside the
business classes!

2) I don't use hard and fast rules for when and where to use gets and sets
(accessors and mutators). I only add them into a class if I am absolutely sure
that including them is the right thing to do, and that I am not unnecesarily
increasing coupling.

I think what Riel is saying is that if you find youself having to slap them in
all over the place, you have to stand back and check that your class design is
valid (are you missing some methods that would cut down on gets/ sets? Perhaps
that a class should be doing a job by itself that you've erroneously split
across three classes? That kind of thing.)

I don't think my message is very clear - sorry about that. Feel free to drop me
a line if you'd like more (or clearer information!)

Good luck!

Rich.
Cult...@aol.com.spamno

(remove spamno to reply)

Robert W Hand

unread,
Jun 3, 2001, 8:25:51 AM6/3/01
to
On Sun, 3 Jun 2001 13:38:14 +0200, "Julia Donawald"
<julia.d...@gmx.de> wrote:

>Hi,
>some time ago I have read some parts of the book "Object-Oriented Design
>Heuristics" written by Arthur J. Riel. While I read it, I was convinced,
>that his design heuristics, are very useful. But now, as I start to design a
>little complex project, some of his heuristics seemd to me as impossible to
>realize. So now I want to know what you think about them and about the
>realization:
>1.) At the very beginning of the book, he told us the following theorem:
>"Do not create god classes/objects in your system. Be very suspicious of a
>class whose name contains Driver, Manager, System, or Subsystem."

<snip>


>2.) "Beware of classes that have many accessor methods defined in their
>public interface. Having many implies that related data and behavior are not
>being kapt in one place". My question now is, when is it ok to use accessor
>methods and when not? Furthermore should I access in the priavte methods of
>my class, the private data of my class also with the accessor function, or
>is it better to access them direct?
>
>Thanks a lot in advance
>Julia

There is something inconsistent about his terminology or your
interpretation of his terminology. A heuristic relates to a method of
education in which the pupil proceeds along empirical lines using
rules of thumb. I would not use the word theorem as a synonym of
heuristic.

So I would interpret his book as giving you rules of thumb, guidelines
that you can follow most of the time and not follow in exceptional
circumstances.

I agree with his second point. What is the point of hiding data if it
is exposed with a rich interface of mutator and accessor methods?
Although such a method may be required in exceptional circumstances,
IMHO it usually reflects an inadequate design.

Best wishes,

Bob

>
>

Phlip

unread,
Jun 3, 2001, 11:28:54 AM6/3/01
to
[Follow-up set to comp.object]

Proclaimed Julia Donawald from the mountaintops:

> 1.) At the very beginning of the book, he told us the following theorem:
> "Do not create god classes/objects in your system. Be very suspicious of a
> class whose name contains Driver, Manager, System, or Subsystem."
> Perhaps I do not really understand this heuristic, but in my project I
> need a start point for my program, and thats in fact a class, which is
> something like a Driver, Manager or System, because it have to deligate
> the work to oher classes. I do not know, how I could develope a system
> usable in real world, without a start point, where total encapsulated
> classes interact together with a minimum interface. What is your opinion
> to this topic?

I'l take your confusion as a negative review of this Heuristics book.

Read the books /Design Patterns/, /Refactoring/, and these web pages:

http://c2.com/cgi/wiki?CodeUnitTestFirst
http://c2.com/cgi/wiki?DoSimpleThings
http://c2.com/cgi/wiki?RefactorMercilessly
http://c2.com/cgi/wiki?TestDrivenDesign

The pages will tell you to code unit tests before you code functionality.
Then put that functionality in the SIMPLEST design you can possibly type.
The fact that the test code checks the functionality is more important that
what the design might happen to be.

Then, after you added a few features and all the tests pass, read
/Refactoring/ and learn how to change the design on-the-fly, after it's
written. Your question implies this Heuristics book has taught you programs
must be designed first, then carved in stone. If this were true, a God
Object would indeed be a Bad Thing. But it would also be very hard to avoid!

If you then happen to have one big class, but all your functionality
appears OnceAndOnlyOnce, and you have the lowest line count you possibly
could after refactoring, and your unit-tests all tested their features
before the features were added, and you can change the shape of the code at
any time at whim, then there's nothing wrong with a little bitty God Object
somewhere.

> 2.) "Beware of classes that have many accessor methods
> defined in their public interface. Having many implies that related data
> and behavior are not being kapt in one place". My question now is, when is
> it ok to use accessor methods and when not? Furthermore should I access in
> the priavte methods of my class, the private data of my class also with
> the accessor function, or is it better to access them direct?

Encapsulation is hierarchical. That means the best design exposes only
"relatively public" things - things only visible to the next layer.

Many tutorials say "never make raw data public". The exact rule actually is
"don't make primitive things globally public". If they are only visible
within their layer, that's okay.

When you code, do the simplest thing that could possibly work.

When you refactor, pay attention to "design smells". One bad smell is
"Behavior Here, Variables There". The /Refactoring/ book will tell you how
to put the behavior into the same class as the variables, on-the-fly using
code that already works.

This Heuristics book has taught you that design is hard, and then impressed
upon you how to make it harder. Many books say this:

- design
- code
- then test

Writing test code is easy, so why not put it first? Design is hard, so why
not put it last?

- test
- code
- then design

This is the only heuristic (search method for an optimal path thru a
decision tree) that a growing movement of programmers find they'l ever need.

--
Phlip phli...@my-deja.com
============== http://phlip.webjump.com ==============
-- Founding member of NuGWa -
Nudists for Global Warming --

Richard MacDonald

unread,
Jun 3, 2001, 12:20:13 PM6/3/01
to
"Julia Donawald" <julia.d...@gmx.de> wrote in message
news:9fd76n$3dvp4$1...@ID-55442.news.dfncis.de...
He is saying "Don't just write another procedural program in OO", i.e,. one
big God class along with a bunch of stupid get/set data classes. What he
may not have said was: "This is the extreme; its ok to have some of this,
but watch out." That is why it is a heuristic and not a law.

As for point 2, its ok to have an accessor when another object needs the
value. Its not ok if the other object gets the value, does something to it,
changes it and puts it back. This would be procedural. (Even here
there are exceptions, such as the Strategy pattern.) So if another object
needs the value for some "other purpose" that does not really belong to
the owner of the attribute, then its ok.

Aside: I use an OO-RDBMS mapping tool called Toplink. All my
persistent classes are written to and read from an RDBMS by some
other object, so they *all* need gets and sets. So I wind up having a
get/set for the RDBMS, and perhaps another get/set for the other
domain/application objects.

Final issue: Should your class use its own get/sets or should it use
direct access to its attributes. There is a lot if disagreement about this
between some very smart people. I side with the former. Its not a lot
of overhead, it buys you some indirection for making changes later
(say the attribute becomes lazy, i.e., is only calculated when needed),
and it assists you when you're browing through your code and want
to see who calls who (asking your IDE for all senders and implementors
of a particular method is easier than asking it for all users of a private
attribute.)


Paul Campbell

unread,
Jun 3, 2001, 12:16:14 PM6/3/01
to

As a begginer its OK to apply such heuristics rather blindly as take it as
read that there is some genuine benefit.

Now though that you are applying them for real you need to try to understand
the principals that the heuristics are based on.

Then decide whether those principals are relevent to your specific
circumstances. This may sound like a cop out but if you are asking "are
these heuristics really valid ?" and "how rigorously should I apply them ?"
then there are no answers to these questions that apply universally - you
have to make your own mind up for your specific situation.

Dont get hung up on getting it exactly right straight off becuase there
often is no "right" answer.
If for example you put too many public accesors in your classes you find
that it becomes harder to trace "cause and effect" dependencies in your
code, if OTOH you have too few you will find that you are constantly
changing your interfaces to allow more access. You can only learn to judge
this tradeoff better up front with more *experience*.

Give it your best shot and carry on with programming your app. If you have
missjudged, the worst that can happen is that some aspects of you code will
be a little harder to understand or maintain later than they might otherwise
have been.

Paul C.

"Julia Donawald" <julia.d...@gmx.de> wrote in message
news:9fd76n$3dvp4$1...@ID-55442.news.dfncis.de...

Todd Hoff

unread,
Jun 3, 2001, 12:24:36 PM6/3/01
to

Phlip wrote:

> Writing test code is easy, so why not put it first? Design is hard, so why
> not put it last?
>
> - test
> - code
> - then design
>
> This is the only heuristic (search method for an optimal path thru a
> decision tree) that a growing movement of programmers find they'l ever need.

Fire, ready, aim! How is writing test code any easier than writing any other
code? Writing code is writing code. I actually find test code usually more
challenging than any of the other code i write. It often takes more
thought/design/work
than any other code. When testing something other than a simple string class
it actually requires quite a dance of strategy and tactics.

Your nirvana is attained by placing test code in a privileged
position outside the system where it doesn't have to be designed, well thought
out,
verified, etc., so of course it is special. It's much like selecting axioms or
creating
a constitution in that you are asserting what is true and that first assertions
are somehow different than later thoughts. Tests must be testing something.
That something must come from somewhere. Tests must have structure.
Tests must come somewhere. You can avoid the labels, but design is
happening, a plan is being executed, albeit i suspect in a stream of conscious
sort of way in your case.


______________________________________________________________________
Posted Via Uncensored-News.Com - Still Only $9.95 - http://www.uncensored-news.com
With Seven Servers In California And Texas - The Worlds Uncensored News Source

Phlip

unread,
Jun 3, 2001, 1:46:23 PM6/3/01
to
Proclaimed Todd Hoff from the mountaintops:

> Your nirvana is attained by placing test code in a privileged
> position outside the system where it doesn't have to be designed, well
> thought out,
> verified,

Test code is one little function call followed by a list of assertions.

Easy.

Test code gets verified over and over again, including each time the code
changes.

Better, test code gets tested first. You always execute a test and predict
failure before adding the production code to attain success.

--
Phlip phli...@my-deja.com
============== http://phlip.webjump.com ==============

-- How old was Andy Williams when
Mighty Morphin' Power Rangers started? --

Daniel T.

unread,
Jun 3, 2001, 2:14:42 PM6/3/01
to
"Julia Donawald" <julia.d...@gmx.de> wrote:

>some time ago I have read some parts of the book "Object-Oriented Design
>Heuristics" written by Arthur J. Riel.
>

>1.) At the very beginning of the book, he told us the following theorem:
>"Do not create god classes/objects in your system. Be very suspicious of a
>class whose name contains Driver, Manager, System, or Subsystem." Perhaps
>
>I do not really understand this heuristic, but in my project I need a
>start point for my program, and thats in fact a class, which is something
>like a Driver, Manager or System, because it have to deligate the work to
>oher classes. I do not know, how I could develope a system usable in real
>world, without a start point, where total encapsulated classes interact
>together with a minimum interface. What is your opinion to this topic?

Behavioral god classes have a nasty habit of checking the state of an
instance of class A and using the information to determine the fate of
an instance of class B.

Is your start point class doing this? All your start point class should
be doing is creating the necessary objects and then letting those
objects communicate amongst themselves. [see note at bottom]

>2.) "Beware of classes that have many accessor methods defined in their
>public interface. Having many implies that related data and behavior are
>not being kapt in one place". My question now is, when is it ok to use
>accessor methods and when not?

If an instance of class A (a) needs to modify its state based on the
state of an instance of class B (b), then it's ok for 'a' to use 'b's
accessors.

If an instance of class A (a) wants to modify the state of an instance
of class B (b), based on b's current state or based on the state of some
other instance of class B (b1), then it's not OK for 'a' to use 'b's or
'b1's accessors. Instead, class B should have a mutator defined.

If an instance of class A (a) wants to modify the state of an instance
of class B (b) based on the state of an instance of class C (c), then it
is not OK for 'a' to use 'c's accessors. Instead pass 'c' to 'b' and
have 'b' use 'c's accessors. [see not at bottom]

>Furthermore should I access in the priavte methods of my class, the
>private data of my class also with the accessor function, or is it better
>to access them direct?

Self-encapsulating fields can make it much easier to move them at a
later date, but usually self-encapsulating a field is quite easy. I
would say, don't encapsulate a field unless you find you need to move it
to a different class, but once it's encapsulated, keep it encapsulated.

[note at bottom]
There are some situations where you are unwilling or unable to have
objects of class B and C communicate amongst themselves (for eg. you
didn't create and can't modify classes B and C.) In those cases, you are
forced to create a behavioral god class. This is called the "mediator"
design pattern in the GoF book.

Cagdas Ozgenc

unread,
Jun 3, 2001, 3:52:53 PM6/3/01
to
These are very good questions. Here is the story from my point of view.

1) Think of a database where we can add knowledge, update, and remove
knowledge. Don't think of a database as a product, think of it logically.
Now as a person you are a database as well (actually a knowledge base). In
an ideal situation, the whole universe is a unified database with no
replication. However in practice it doesn't work that way. Therefore we need
to transfer knowledge from one DB to another. For example, you enter your
personal information to a web site. Now the knowledge is replicated, in
ideal terms you should have kept it in your head forever. Now how is this
all related to your 1st question? When a database is split into two, the OO
point of view breaks, because when you transfer knowledge from one DB to
another it is a single action either insert, update, or remove. This a
simple function rather than a function acting over data (the OO view). When
you start an application same thing happens, you break a knowledge base into
more parts. The only thing we can do to your application is call the main
function and pass the initialization parameters. This is like an insert to
the database. In "OO Design Heuristics", Riel mentions this problem as
"hitting an non OO boundary". This doesn't just happen when launching an
application. This happens in every situation where you need to clone an
object, and cause replication. For example, when you transfer objects over
the network. The knowledge base is split and the same object lives in two
different DBs.

2) Same situation. Why do you need getter and setter functions? You are
trying to move those objects away from their knowledge base. Instead a
better approach is to bring the functions closer to the knowledge, not move
the knowledge towards functionality. Getter and setter functions IMHO are
evil. They break encapsulation. People claim that getter and setter
functions are good because if the data schema changes you can still maintain
syntactic compatibility with the old clients. This is useless. Syntactic
compatibility will not get you anywhere, the important part is the semantic
compatibility.

"Julia Donawald" <julia.d...@gmx.de> wrote in message
news:9fd76n$3dvp4$1...@ID-55442.news.dfncis.de...

Todd Hoff

unread,
Jun 3, 2001, 3:53:18 PM6/3/01
to

Phlip wrote:

> Proclaimed Todd Hoff from the mountaintops:
>
> > Your nirvana is attained by placing test code in a privileged
> > position outside the system where it doesn't have to be designed, well
> > thought out,
> > verified,
>
> Test code is one little function call followed by a list of assertions.
>

Lucky you. I usually have to worry about threading, setting up multiple
nodes, testing different timing scenarios, testing throughput, realtime usage,

deadlock, priority inversion, queuing behaviour, etc. while having many usage
and
load scenarios and many data sets. Your world seems absurdly simple and
not characteristic of complicated applications. I don't think your experience
generalizes very well.

>
> Test code gets verified over and over again, including each time the code
> changes.

The same could be said about application code.

Stuart Golodetz

unread,
Jun 3, 2001, 3:57:41 PM6/3/01
to
"Phlip" <phli...@my-deja.com> wrote in message
news:9fdl3m$l...@dispatch.concentric.net...

Would this work as well:

-design
-write small amount of test code
-redesign (i.e. completely chuck old project, don't just fiddle with it) and
keep doing this until satisfied that your design is as good as you need/want
-write proper code

Phlip, expert opinion please? :o) This any good whatsoever?

Cheers,

Stuart.


Phlip

unread,
Jun 3, 2001, 6:46:52 PM6/3/01
to
Proclaimed Todd Hoff from the mountaintops:

> Lucky you.  I usually have to worry about threading, setting up multiple


> nodes, testing different timing scenarios, testing throughput, realtime
> usage, deadlock, priority inversion, queuing behaviour, etc. while having
> many usage and
> load scenarios and many data sets. Your world seems absurdly simple and
> not characteristic of complicated applications. I don't think your
> experience generalizes very well.

It does not. But fortunately the majority of the simple code that needs
tests only needs simple tests. When we have elaborate situations like the
above, we need more elaborate testing & proofing.

Now how do your elaborate situations require you to write the code before
the test?

--
Phlip phli...@my-deja.com
============== http://phlip.webjump.com ==============

-- It's a small Web, after all... --

Phlip

unread,
Jun 3, 2001, 6:47:42 PM6/3/01
to
Proclaimed Stuart Golodetz from the mountaintops:

> -design
> -write small amount of test code
> -redesign (i.e. completely chuck old project, don't just fiddle with it)
> and keep doing this until satisfied that your design is as good as you
> need/want -write proper code

> Phlip, expert opinion please? :o) This any good whatsoever?

Great thing about a cycle is the first stop is also the last ;-)

--
Phlip phli...@my-deja.com
============== http://phlip.webjump.com ==============

-- Women: You can't live with them, and you can't get them to
dress up in a skimpy Nazi costume and beat you with a warm squash.
--Emo Phillips --

Andy Watson

unread,
Jun 3, 2001, 7:55:07 PM6/3/01
to
These are valid points. But it comes down to interpretation. I tend to
find that C++ programmers trying to write OO code have a rather
different bent from OO people writing C++ code. So you tend to get
arguments between the two groups as to what is right and what is
wrong. For myself, I am an OO person whom occaisionally writes C++
code which means that while there are lots of things you can do in
C++, not all of them are valid from an OO point. The two points in
your question cover some of them.


1). This comes down the the general rule that "Managers are Not
Objects". This is due to the fact that some people find it hard to
differentiate what is an object, and what is not.

Things with names like Driver, Manager, Sytem, Controller etc. Are not
objects, they are services.

Now I shall digress and raise a point which I am sure that 90% of the
readers will not agree with, but I find helps fit things in within an
Application Architecture point of view.

Objects are tangible things. That is - you can only have real world
instances of them. You tend to find that objects also cannot be
derived from. Some examples, an entry field, a form, a database, a com
port, a coke bottle, a vending machine etc. Note that in C++ objects
are the child level classes in an object framework. They may derive
from parent classes, but these are not objects, they are abstractions.
You cannot have an instance of an abtraction - you will have problems
if you do this (try to find an instance of a bottle in the real world
- you will find an instance of a coke bottle, or a beer bottle - but
not its abtraction - bottle - same with an entry field - its
abstraction window cannot have an instance).

You can see from the examples that you cannot derive anything new from
these objects. You can however, mix them together to form new objects
of a different kind. Normally, you would do this by aggregation, but
(and I will contradict myself here) you can also do it by inheritance.
The difference here is that when you do it by inheritence the new
class/object should not have any new methods added to it. Effectively
it is just an empty class that just inherits the public interface of
its parents - interface inheritance so to speak. If you find you must
add new methods, then it means that either one of your parents is not
an object, or has not been defined properly.

An example would be a bottle of coke. Its parents would be coke bottle
and coke and maybe bottle cap and label if you really want
completeness. But bottle of coke does not add anything new that its
parents don't already provide.

So now having done all this and created your object, you find that you
cannot do anything with them. Perhaps you may want to have more than
one bottle of coke in your application. So you place a Collection
class (bottle of coke manager) in your application. But where should
it reside you may ask. It cannot reside in you object hierachy,
because managers are not objects..

These things are services - classes that have methods but no data.
Services reside in the domain of the application framework (AF). An AF
is a group of classes made up solely of services and mixins (data
without methods). They may be organised in an inheritance model, or
may be stand alone. You can buy frameworks, such as MFC or Rogue Wave
tools. They tend to be linked in to your application as DLLs or
shared libraries.

So you will see that you will have your object framework which you
will probably package up into a DLL of some kind, and your Application
Framework which is also packaged up into a DLL. But now I have these
two frameworks, how do I write an application you might ask. Well,
you do this by writing normal application code that uses both of the
above packages and compile that into your .exe.

By taking this approach to an object definition, you now find that you
have two completely separate and reusable class libraries un-hindered
by the burdon of your application code and other hacks that you may
have put in.

2). This point applies only to the object framework above. Objects do
not have accessors. Why should they. They are encapsulated entities
that manipulate their own data. No other object should know about
another objects data. Object interfaces only have public methods like
-- doSomething() or haveYouDoneItYet()..

Mixins are classes that have data but no functions. You can identify a
mixin because it has lots of accessors but hardly any, if at all,
functions. A good example is classes that are used to put data in a
database or get it out.

Services may also contain accessors, usually used for getting and
setting flags or loading a mixin for the service to work on. They
should not have accessors to manipulate the mixins. Its ok, to expose
data in a service, so its better to provide a pointer to the mixin
itself, rather than double up the accessors.

Remember here, you are not dealing with objects, just classes. Do not
make the mistake of joining a service with a mixin and calling it an
object. It is not, it is just a service that has been badly inherited
or associated.


3). A final note. Develop your AF before the Object framework. This is
because the parent classes of your objects (or abstractions) will use
the AF to provide some of their functionality. Eg. to make an object
persistant one of your parent object classes may use internally your
AFs database class. Once you have an AF with its public methods well
defined and stable, you should find that its easier to develop your
object library.

I apologise if the above is not very clear, but hopefully it is enough
to give you something to think about.

Andy


On Sun, 3 Jun 2001 13:38:14 +0200, "Julia Donawald"
<julia.d...@gmx.de> wrote:

peter_douglass

unread,
Jun 3, 2001, 8:10:04 PM6/3/01
to

Andy Watson wrote in message

[snip]

Thank you for that interesting "objects come from the real world" point of
view. That point of view has always been alien to me, but perhaps it is
only becaues I've never learned to use it successfully. Is there a name
associated with your methodology? What books would you recommend to gain a
better appreciation of this point of view?

--PeterD


peter_douglass

unread,
Jun 3, 2001, 8:14:07 PM6/3/01
to
One more question. If services reside in classes, but are not objects, does
that mean that you never "instantiate" them? I.e. that they are accessed
through something like classname::method() rather than object.method or
object->method?

Thanks,
--PeterD


peter_douglass wrote in message <9fejjm$g9t$1...@taliesin.netcom.net.uk>...

Todd Hoff

unread,
Jun 3, 2001, 8:25:58 PM6/3/01
to

Phlip wrote:

> Now how do your elaborate situations require you to write the code before
> the test?
>

Require is a strong statement. There is absolutely no requirement.
The issue i have is with your test-code-design process is
that if you really think you aren't designing, that is planning, at every step
then you are fooling yourself. In writing a test you are planning/designing
otherwise you would/could do nothing. If this works for you then great.
In the simple case i personally see the test-first as equivalent to
develop-first because the efforts are virtually one-to-one.

In complex scenarios multiple things must happen at once for testing so test-
first is difficult because the setup and the development of the setup are
usally larger efforts than the code being developed. My database testing
code actually dwarfs the database code itself, which makes sense if you
think about it. The database test code itself must be developed which
means the test-first approach can not bootstrap itself unless you
assume the most simplest of scenarios, which is what you have had
to assume.

I've tried test-first and didn't care for it and i religiously write
unit/system tests for
everything. Personally i do best when i first write well documented class
interfaces.
This works for me because my capabilities favor mentally simulating
the system, making the system tell a story. Others work differently. XPers
seem to forget that there are different types of people who work very
differently, yet
effectively. It's a mistake to generalize from ones own experience and
say X is the best way and hint at X is the only way. I've noticed
some XP masters say they like short books. I like long books. My guess is
we could list a lot more personality differences as well that strongly
influence our styles.

The paths to truth are many.

Phlip

unread,
Jun 3, 2001, 10:18:08 PM6/3/01
to
Proclaimed Todd Hoff from the mountaintops:

> In complex scenarios multiple things must happen at once for testing so


> test- first is difficult because the setup and the development of the
> setup are usally larger efforts than the code being developed.

Sounds to me like elaborate multi-threaded code needs to be designed in
terms of how it will be tested. Otherwise you'd get those dreaded
unreproducable bugs from timing issues.

> My database
> testing code actually dwarfs the database code itself, which makes sense
> if you think about it. The database test code itself must be developed
> which means the test-first approach can not bootstrap itself unless you
> assume the most simplest of scenarios, which is what you have had
> to assume.

Per the official XP party line, these are Acceptance Tests. They are not
test-first because they are not versioned synchronous with the low-level
code.

> The paths to truth are many.

I find your lack of faith disturbing.

--
Phlip phli...@my-deja.com
============== http://phlip.webjump.com ==============

-- Wanted: Marriage counselor who also keeps pet rats --

Andreas Klimas

unread,
Jun 4, 2001, 5:06:13 PM6/4/01
to
Todd Hoff wrote:
>

[snip]


> In complex scenarios multiple things must happen at once for testing so test-
> first is difficult because the setup and the development of the setup are
> usally larger efforts than the code being developed.

that's the point. if you are willing to build unit tests you have to
think how to realize this strategy - and it's not easy.
so I will make at first an test and application overview design. after
that it's not really importantin which order you develop, I think it's
important to get UnitTest's. you are right, there are many situations
in where simple UnitTesting isn't possible, I try to master those
situations
and sometimes I'm failed.

I think UnitTests are important and really helpful.

--
Andreas Klimas

Andreas Klimas

unread,
Jun 4, 2001, 5:10:35 PM6/4/01
to
Richard MacDonald wrote:

[snip]


> Final issue: Should your class use its own get/sets or should it use
> direct access to its attributes. There is a lot if disagreement about this
> between some very smart people. I side with the former. Its not a lot
> of overhead, it buys you some indirection for making changes later
> (say the attribute becomes lazy, i.e., is only calculated when needed),
> and it assists you when you're browing through your code and want
> to see who calls who (asking your IDE for all senders and implementors
> of a particular method is easier than asking it for all users of a private
> attribute.)

and you might inherit an accessor to perform additional behavior

--
Andreas Klimas

Tom Keaveny

unread,
Jun 4, 2001, 10:30:40 PM6/4/01
to
"Daniel T." wrote:

> [note at bottom]
> There are some situations where you are unwilling or unable to have
> objects of class B and C communicate amongst themselves (for eg. you
> didn't create and can't modify classes B and C.) In those cases, you are
> forced to create a behavioral god class. This is called the "mediator"
> design pattern in the GoF book.

An example of this would be a CONTROLLER class that encapsulates
SENSOR and ACTUATOR classes. The CONTROLLER would sample data from its
SENSORs, and stimulate the ACTUATORs based on this data. The CONTROLLER
would specify an appropriate interface for the task at hand.

A practical example would be a THERMOSTAT controller class which would
employ
TEMPERATURE_SENSORs and BLOWER, HEATER and COOLER actuators. Each
encapsulated element is independent of each other (no coupling). The
THERMOSTAT establishses their relationships, and could provide its
own accessor methods for controlling the temperature.

Of course, there is nothing to prevent the HEATER, for example, from
having its own "private" SENSORs, for detecting certain over-temperature
conditions.

==
tom k

Sean Kelly

unread,
Jun 4, 2001, 10:50:53 PM6/4/01
to
"Richard MacDonald" <macdo...@att.net> wrote in message
news:1ttS6.2060$Ji.2...@bgtnsc04-news.ops.worldnet.att.net...

>
> Aside: I use an OO-RDBMS mapping tool called Toplink. All my
> persistent classes are written to and read from an RDBMS by some
> other object, so they *all* need gets and sets. So I wind up having a
> get/set for the RDBMS, and perhaps another get/set for the other
> domain/application objects.

I guess it depends on the implementation, but frankly I don't see persisting
to a database requiring exposing the internal data elements at all. I do
this kind of thing fairly often using an ODBC classlib I wrote (that looks
similar to ADO) and all I do is pass an open database connection as a
parameter in the object's constructor and let it take over from there. I
keep the query code embedded in the class and if for some reason the query
fails I throw an exception out of the constructor, thus guranteeing that the
class is never constructed.

> Final issue: Should your class use its own get/sets or should it use
> direct access to its attributes. There is a lot if disagreement about this
> between some very smart people. I side with the former. Its not a lot
> of overhead, it buys you some indirection for making changes later

Certainly. Though I think using gets/sets is only slightly better than
exposing the data members of a class directly, which is to say not so great
a solution. I tend to either create classes which provide no direct access
to data (either by making it public or exposing via gets/sets) or just
making the object a struct and keeping the number of builtin functions to a
minimum. I think it's a mark of good OO design to define an object by its
behavior rather than its data elements.


Sean


H. S. Lahman

unread,
Jun 4, 2001, 11:49:53 PM6/4/01
to
Responding to Donawald...

> some time ago I have read some parts of the book "Object-Oriented Design
> Heuristics" written by Arthur J. Riel. While I read it, I was convinced,
> that his design heuristics, are very useful. But now, as I start to design a
> little complex project, some of his heuristics seemd to me as impossible to
> realize. So now I want to know what you think about them and about the
> realization:
> 1.) At the very beginning of the book, he told us the following theorem:
> "Do not create god classes/objects in your system. Be very suspicious of a
> class whose name contains Driver, Manager, System, or Subsystem."
> Perhaps I do not really understand this heuristic, but in my project I need
> a start point for my program, and thats in fact a class, which is something
> like a Driver, Manager or System, because it have to deligate the work to
> oher classes. I do not know, how I could develope a system usable in real
> world, without a start point, where total encapsulated classes interact
> together with a minimum interface. What is your opinion to this topic?

I will give you basically the same advice as several others, albeit with
a different spin. First, the use of 'heuristics' in Riel's title is not
accidental. He is describing sensible guidelines rather than dogma.
Usually the only time such classes are justified is when they correspond
to some real role in the problem space.

The reason that Manager objects are not a good idea is that they are
often artificial. They fill a niche in a functional decomposition tree
that does not correspond to anything in the problem space. That view is
inconsistent with the way OT handles functionality. In OT we isolate
inherent, intrinsic, standalone behavior in objects and then solve a
problem by connecting various behaviors them together in the proper
sequence.

Ideally one should be able to solve a similar problem by reconnecting
the intrinsic behaviors in a different sequence. The <limited> analogy
is a Tinker Toy kit where the wheels are object behaviors and the spokes
represent messages. Connect them in a particular way and one gets a
particular shape. Change the message connections and you get a
different shape.

The key is that this mode of connecting isolated, inherent behaviors
with messages is essentially a breadth-first technique. Behaviors are
directly connected in the proper order. Functional decomposition is
inherently a depth-first technique. The behaviors must be executed in
an order dictated by the limbs of the tree.

The basic reason Riel advises against Manager objects is that they are
often a product of functional decomposition, which is fundamentally
incompatible with the usual OO approach for combining behaviors that are
intrinsic to problem space structures.

A second reason is related to why OT is devoted to problem space
abstractions. The idea here is that customers don't like change any
more than developers. They will accommodate change in a manner that
causes least disruption to their environment's structures. The notion
underlying OT is that if the software structure faithfully imitates the
customer's structure, it will be more maintainable. This is because
when the change drifts down to the software the customer will have
already figured out how to accommodate it with minimal disruption. If
so, then if the software mimics the customer's structure, it should be
minimally disrupted as well.

> 2.) "Beware of classes that have many accessor methods defined in their
> public interface. Having many implies that related data and behavior are not
> being kapt in one place". My question now is, when is it ok to use accessor
> methods and when not? Furthermore should I access in the priavte methods of
> my class, the private data of my class also with the accessor function, or
> is it better to access them direct?

I don't think Riel is criticizing the use of accessors. I believe he is
citing a symptom of a different problem -- lack of cohesion. The
knowledge is in one object but its related behavior is someplace else.
In fact, in that section he later says, "[accessors] are dangerous
because they indicate poor encapsulation of related data and behavior."

I believe his basic point about accessors in that section is really
about the granularity of the interface. An interface with a lot of fine
grained accessors may be symptomatic that it is missing behavior to
address the details. Therefore the clients of the class are supplying
that behavior, particularly god classes. The question to ask is: why
can't the detailed interface be replaced with a more general one that
has higher level methods?

If the answer is that the necessary behavior to coalesce the details is
elsewhere, then the follow-up question is: is it more logically related
to the place where it is or to this object? Riel is offering odds that
the answer will be that the behavior is in the wrong place. That is,
both objects would be more cohesive if it were moved.

Riel sees this as a god object problem but I think it is more general
than that. The behavior could be spread over several objects. This
situation is common when one has not got the level of abstraction
right. Providing a detailed interface may force all the clients of the
object to deal with it in a detailed (less abstract) way when they
really could have used a high level interface. That makes them less
cohesive because they have to have behavior to merge the details.

A classic example is the legendary ComplexNumber class. There are
exceptions but the vast majority of applications only need to know about
the real and imaginary parts when the instance is created. They will
Just Work if one simply overloads the basic arithmetic operators to
handle complex numbers and doesn't expose the real/imaginary parts
except in the ctor. So one should always try to develop the program
without exposing them.

That will force other objects to deal with it abstractly as a scalar to
protect their cohesion. It will also encourage ComplexNumber to provide
interface methods only at the scalar level of abstraction. While the
ComplexNumber example may seem trivial and obvious, the same notions of
correct interface abstraction apply in much more complicated and subtle
situations.

*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
h...@pathfindersol.com
Pathfinder Solutions -- We Make UML Work
http://www.pathfindersol.com
(888)-OOA-PATH

Andy Watson

unread,
Jun 5, 2001, 4:24:28 AM6/5/01
to
Originally I learned it back in about 1984/85 - the technique or a
similar one was used by Taligent some years ago and I used something
similar during my days at IBM.

Services are just ordinary C++ classes (using C++ for a point of
reference).. You instantiate them just like any other class. Same with
mixins.

They have the unique name of Service simply because thats what they do
- provide services for other classes to use. Wheras objects don't
provide anything to anyone - the mean beggers do it all themselves. :)

Remember tho' that objects often use services and mixin's internally..

Andy

On Sun, 3 Jun 2001 20:14:07 -0400, "peter_douglass" <bai...@gis.net>
wrote:

Daniel T.

unread,
Jun 5, 2001, 8:28:24 AM6/5/01
to
t...@rtl.rose.agilent.com wrote:

Indeed. In fact, later in the book Riel recommends the use of god
classes (4.14) "Objects which share lexical scope, should not have uses
relationships between them." Early in the book, he also states that some
of the heuristics are contradictory, and spicifically refuses to weight
them.

So, what's a poor designer to do? Here is a heuristic of my own: If the
objects that share lexical scope have a large scope in relation to the
program as a whole, then Riel's heuristic 3.2 should be followed,
otherswise follow 4.14.

Better would be Mr. Lahman's advice, if the Controller class makes sense
from a domain level point of view, the follow heuristic 4.14, otherwise
follow heuristic 3.2.

Richard MacDonald

unread,
Jun 5, 2001, 7:21:22 PM6/5/01
to
"Sean Kelly" <ken...@pacbell.net> wrote in message
news:%NXS6.2316$Fs2.1...@news.pacbell.net...

> "Richard MacDonald" <macdo...@att.net> wrote in message
> news:1ttS6.2060$Ji.2...@bgtnsc04-news.ops.worldnet.att.net...
> >
> > Aside: I use an OO-RDBMS mapping tool called Toplink. All my
> > persistent classes are written to and read from an RDBMS by some
> > other object, so they *all* need gets and sets. So I wind up having a
> > get/set for the RDBMS, and perhaps another get/set for the other
> > domain/application objects.
>
> I guess it depends on the implementation, but frankly I don't see
persisting
> to a database requiring exposing the internal data elements at all. I do
> this kind of thing fairly often using an ODBC classlib I wrote (that looks
> similar to ADO) and all I do is pass an open database connection as a
> parameter in the object's constructor and let it take over from there. I
> keep the query code embedded in the class and if for some reason the query
> fails I throw an exception out of the constructor, thus guranteeing that
the
> class is never constructed.

You're letting the instance read/write itself to the database.
Toplink uses a different object to do this.
It can be done both ways..

> > Final issue: Should your class use its own get/sets or should it use
> > direct access to its attributes. There is a lot if disagreement about
this
> > between some very smart people. I side with the former. Its not a lot
> > of overhead, it buys you some indirection for making changes later
>
> Certainly. Though I think using gets/sets is only slightly better than
> exposing the data members of a class directly, which is to say not so
great
> a solution. I tend to either create classes which provide no direct
access
> to data (either by making it public or exposing via gets/sets) or just
> making the object a struct and keeping the number of builtin functions to
a
> minimum. I think it's a mark of good OO design to define an object by its
> behavior rather than its data elements.

But there are many applications/objects whose behavior is to manage data
elements :-)


Robert C. Martin

unread,
Jun 11, 2001, 9:33:12 PM6/11/01
to
On Sun, 3 Jun 2001 13:38:14 +0200, "Julia Donawald"
<julia.d...@gmx.de> wrote:

>Hi,


>some time ago I have read some parts of the book "Object-Oriented Design
>Heuristics" written by Arthur J. Riel. While I read it, I was convinced,
>that his design heuristics, are very useful. But now, as I start to design a
>little complex project, some of his heuristics seemd to me as impossible to
>realize. So now I want to know what you think about them and about the
>realization:

>1.) At the very beginning of the book, he told us the following theorem:
>"Do not create god classes/objects in your system. Be very suspicious of a
>class whose name contains Driver, Manager, System, or Subsystem."
>Perhaps I do not really understand this heuristic, but in my project I need
>a start point for my program, and thats in fact a class, which is something
>like a Driver, Manager or System, because it have to deligate the work to
>oher classes. I do not know, how I could develope a system usable in real
>world, without a start point, where total encapsulated classes interact
>together with a minimum interface. What is your opinion to this topic?

Arthur is just saying that the intelligence of the system should be
spread out amongst many classes rather than concentrated into one.

>2.) "Beware of classes that have many accessor methods defined in their
>public interface. Having many implies that related data and behavior are not
>being kapt in one place". My question now is, when is it ok to use accessor
>methods and when not? Furthermore should I access in the priavte methods of
>my class, the private data of my class also with the accessor function, or
>is it better to access them direct?

If you have lots of accessors, look for methods in other classes that
use those accessors to manipulate the variables in *your* class. If
you find any, move them into your class.

Access the private variables directly.
Robert C. Martin | "Uncle Bob" | Software Consultants
Object Mentor Inc. | rma...@objectmentor.com | We'll help you get
PO Box 5757 | Tel: (800) 338-6716 | your projects done.
565 Lakeview Pkwy | Fax: (847) 573-1658 | www.objectmentor.com
Suite 135 | | www.XProgramming.com
Vernon Hills, IL, | Training and Mentoring | www.junit.org
60061 | OO, XP, Java, C++, Python|

"One of the great commandments of science is:
'Mistrust arguments from authority.'" -- Carl Sagan

Peter Garrone

unread,
Jun 29, 2001, 10:59:09 PM6/29/01
to
Possibly he is saying that if you have poorly defined classes with very large
numbers of access methods you are no longer being as OO as you possibly could
be, and that said classes should be logically broken up into smaller
interacting classes with better definition.

OTOH an object could be well defined conceptually and also have a large number
of access methods.

Julia Donawald wrote:

> Hi,
> some time ago I have read some parts of the book "Object-Oriented Design
> Heuristics" written by Arthur J. Riel. While I read it, I was convinced,
> that his design heuristics, are very useful. But now, as I start to design a
> little complex project, some of his heuristics seemd to me as impossible to
> realize. So now I want to know what you think about them and about the
> realization:
> 1.) At the very beginning of the book, he told us the following theorem:
> "Do not create god classes/objects in your system. Be very suspicious of a
> class whose name contains Driver, Manager, System, or Subsystem."
> Perhaps I do not really understand this heuristic, but in my project I need
> a start point for my program, and thats in fact a class, which is something
> like a Driver, Manager or System, because it have to deligate the work to
> oher classes. I do not know, how I could develope a system usable in real
> world, without a start point, where total encapsulated classes interact
> together with a minimum interface. What is your opinion to this topic?

> 2.) "Beware of classes that have many accessor methods defined in their
> public interface. Having many implies that related data and behavior are not
> being kapt in one place". My question now is, when is it ok to use accessor
> methods and when not? Furthermore should I access in the priavte methods of
> my class, the private data of my class also with the accessor function, or
> is it better to access them direct?
>

Bob Hablutzel

unread,
Jun 30, 2001, 7:52:06 AM6/30/01
to
Not having read the book, I'm going to comment anyhow.

It sounds like he is trying to avoid having a lot of structures and calling
them objects. In a number of (bad) designs I've seen, there are both large
number of drivers/managers/executors which perform all the logic of an
equally large number of essentially data-only structures. While object
oriented in form, it isn't really object oriented in design.

I don't have a problem with accessors per se; it's when accessors are
required so that internals can be exposed to allow for the logical
manipulations of the objects that I start to have an issue.

Having a small number of manager classes isn't bad - especially for well
defined, meta-object operations (such as kicking off the main application).

hab


"Peter Garrone" <pgar...@acay.com.au> wrote in message
news:3B3D407D...@acay.com.au...

Warren R. Zeigler Sr

unread,
Jul 3, 2001, 5:51:57 PM7/3/01
to
I attended a one-day class of his while the book was being published.

He is stating that in bad designs, there is only one object really running
things. I agree. This is often seen in the first few designs made by people
switching to objects.

My thoughts:
In OO software, major functionality is realized through the collaborations
of objects. In other words, objects work together as a team. If this is not
designed in, major functionality is realized by one or more "god" objects,
running everything.

This is not to say that there will not be any functions "running" a
collaboration.

Think of a design for a kitchen.
*If the kitchen is dedicated to one recipe, each object in the kitchen
understands the objects around it, knowing how to receive and then send on
things being processed.
*If the kitchen is a normal at-home kitchen, an environment for processing
is created, then recopies are followed. This is seen in the analysis pattern
using "Boundary, Control and Entity" classes. (See UML documentation. This
pattern is directly supported by the UML.)
Both methods are OO. The first has no small procedures "running" things. The
objects interact and are highly coupled. The second uses the control classes
as "transactions", running a collaboration. This decouples the objects,
placing knowledge specific to the entity classes in the control classes and
not in other entity or boundary classes.
(The control class also presents a high level API, and it runs the
lower-level API presented by the entity classes. For example, a bank
transaction control class running a method "Deposit" will have an API using
the terminology that a bank customer uses - deposit and withdraw. It will
manipulate the entity class "account" with debits and credits.)

Bottom line - even with control classes running a collaboration's
transactions as a short procedure, the environment is still OO, just with
added decoupling. Why? The objects are still collaborating. With a "god"
class, all other objects are passive, not collaborating.

Where is the line between OO and procedural here? It is a matter of
granularity.
--
Warren R. Zeigler Sr
http://www.UnderstandingObjects.com


"Peter Garrone" <pgar...@acay.com.au> wrote in message
news:3B3D407D...@acay.com.au...

Robert O'Dowd

unread,
Jul 3, 2001, 8:30:29 PM7/3/01
to
Peter Garrone wrote:
>
> Possibly he is saying that if you have poorly defined classes with very large
> numbers of access methods you are no longer being as OO as you possibly could
> be, and that said classes should be logically broken up into smaller
> interacting classes with better definition.

That's addresses the question about number of accessors, I think.

>
> OTOH an object could be well defined conceptually and also have a large number
> of access methods.

Sure. If you have a class with behaviour controlled by a lot of
parameters, and need to be able to access those values from
outside the class, this is true. However, it is often possible to
have classes that contain subsets of those parameters.


>
> Julia Donawald wrote:
>
> > Hi,
> > some time ago I have read some parts of the book "Object-Oriented Design
> > Heuristics" written by Arthur J. Riel. While I read it, I was convinced,
> > that his design heuristics, are very useful. But now, as I start to design a
> > little complex project, some of his heuristics seemd to me as impossible to
> > realize. So now I want to know what you think about them and about the
> > realization:
> > 1.) At the very beginning of the book, he told us the following theorem:
> > "Do not create god classes/objects in your system. Be very suspicious of a
> > class whose name contains Driver, Manager, System, or Subsystem."
> > Perhaps I do not really understand this heuristic, but in my project I need
> > a start point for my program, and thats in fact a class, which is something
> > like a Driver, Manager or System, because it have to deligate the work to
> > oher classes. I do not know, how I could develope a system usable in real
> > world, without a start point, where total encapsulated classes interact
> > together with a minimum interface. What is your opinion to this topic?

I agree with the book, although it's often easier said than done.

A few years ago, I worked on a moderately large C++ project that had
two layers: the "infrastructure layer" that was based on a
granular object mode, and a GUI front end. Those two layers were
written by different people. In the GUI there was a "Globals" class,
that among other things, stored pointers to every window object and
stored instances of objects from the infrastructure at global scope.

In principle, the GUI objects (eg buttons, menus) delegated the work
to the infrastructure. In practice, they found the object they
delegated to by looking in the Globals object. The creator of
the GUI argued that this structure gave a means for decoupling
the GUI objects from the infrastructure, and also [if necessary]
allowed tracking of messages from GUI to infrastructure. In
practice, the GUI objects were highly coupled with that Globals
object, which was in turn highly coupled with particular objects
in the infrastructure.

Whenever new capability was added to the infrastructure (eg new
classes), the Globals object and it's methods had to be
updated to make use of it. This had a ripple effect on the
GUI: a small change in the infrastructure resulted in a
complete build of the GUI (eg recompilation of *all*
source files). This gave the interesting effect that
the infrastructure was about 500K lines of code, the
GUI about 200K lines of code. However, compilation times
for the GUI were about 2 hours, compared with 15 minutes for
the infrastructure.

The Globals object became fairly large (lots of accessors,
but some functionality as well). It did not have a clearly
defined purpose (other than being a kitchen sink of all
sorts of stuff). This made it a challenge to maintain.
It also made it difficult to identify links between objects
in the GUI and the infrastructure objects they interacted
with; the design did not provide that information, and it
wasn't easy to get it by simply reading source code.
The only way was really by stepping through with a debugger,
which tends to be difficult with event based GUI code
anyway (if you step through the event loop, you walk through
a lot of events before you find the one of interest. But
the alternate is setting break points in (potentially) lots
of little callback functions that correspond to specific
events or GUI objects). The end result was that a lot of
fairly simple errors were hard to track down with anything
other than a debugger.

> > 2.) "Beware of classes that have many accessor methods defined in their
> > public interface. Having many implies that related data and behavior are not
> > being kapt in one place". My question now is, when is it ok to use accessor
> > methods and when not? Furthermore should I access in the priavte methods of
> > my class, the private data of my class also with the accessor function, or
> > is it better to access them direct?
> >

In general, it is suitable to have accessors if objects in the
"outside world" need to set or display your attributes. If you
can't make that case, don't have accessors. For example, a method
that does a particular action may set 5 attributes and then
perform a series of contributing actions (presumably by
calling other methods).

I personally tend to steer away from having private methods use
accessor functions. There is potentially a performance hit,
unless the accessors are inline. More significantly, the
private methods of your class are usually private because they
(temporarily at least) violate preconditions and postconditions
associated with the public interface. That can be a source
of obscure errors that are painful to track down.

Geoff Sobering

unread,
Jul 4, 2001, 10:44:28 PM7/4/01
to
Another excellent description of this principle is in the first couple of
chapters of Martin Fowler's book "Refactoring". He has a very nice example of
transforming a highly-coupled set of classes into a much better, less coupled,
system (and explains why the latter is better much more clearly than I could
ever hope to).

Cheers,

Geoff S.

0 new messages