Re: Text terminal rendering design



H. S. Lahman wrote:

Responding to Guild...

Specifically, I have both a Terminal class and a SymbolFactory
class and all objects of both classes act as a combined facade
for the subsystem. I cannot see any harm in this design. Just
because the client invokes a method on a particular factory does
not mean the client knows even one detail about what happens in
response to that message. Correct me if I am wrong.

Terminal and SymbolFactory are part of the interface Facade?!?

How can that be a surprise to you? Didn't you know that the client is
calling methods of Terminal and SymbolFactory? I am sure I must have
mentioned that somewhere. Doesn't that make them interface objects by
definition?

Typically the subsystem interface (or application's programmatic
API) is a Facade pattern object. (The interface may even be
composed of multiple Facade objects.) But those objects implement
the subsystem /interface/, not the subsystem itself. The subsystem
itself is implemented with objects like Terminal and SymbolFactory
that the subsystem interface hides completely from the outside
world.

The nice thing about hidden objects is that the client has no way of
knowing anything about them. What the client doesn't know cannot hurt
him, right? Specifically, it makes no different whether those objects
even exist at all.

The subsystem interface is designed around the client's needs as best
as I've been able to so far, and as it happens that interface can
directly and easily be implemented in the interface objects
themselves. The client will never know the difference; I'm losing
exactly zero hiding by doing it this way.

I would expect Terminal and SymbolFactory to objects /within/ the
subsystem boundary that the Facade object talks to. They both seem
like problem space entities that are unique to the subject matter.
Those objects would not be visible at all to Client.

The Terminal object is how the client issues terminal-related
commands such as "Initialize yourself, I'm about to give you some
symbols to display" or "Shutdown, I don't need you anymore," or "I'm
doing giving you symbols for a moment, display what you have."

The SymbolFactory object is the clients interface onto the conversion
procedure that takes Symbols in various forms and converts it to the
form desired by the Terminal. If the Terminal object is giving a
symbol in the wrong format, the conversion will still be done;
SymbolFactory just allows it to happen at a time other than the
moment when the Symbol needs to be displayed. SymbolFactory's main
message from the client is "Convert this symbol for me now so that
there will be no delay when I say I want it displayed."

All of these messages have a rather simple implementation. If I could
see how better, more flexible, more maintainable, or any of those
good things by relaying these message to other objects that are more
suitable, I would do it, but what are those more suitable objects?

The subject matter of your software (as I understand it) is to
provide rendering of symbols on display terminals for clients in
an manner that isolates the client from rendering issues on
particular terminals.

That sounds exactly right.

As such the interface that subject matter provides to the client
should be as generic as the client's view of display. So I would
expect the interface to be at the level of requests like:

Display icon (id)
Change icon color (id, color)
Save new icon (id, ...)

That seems somewhat correct, though of course "Display icon (id)"
would need more than just an id as data; it would also need
coordinates at which to display the icon. Also, I don't know what
'Save new icon (id, ...)' would do. 'Change icon color (id, color)'
is currently an actual message that SymbolFactory accepts, returning
the id of the modified icon.

But I suppose what you are really saying is that the id should be
supplied by the client as the clients representation of the symbol
and then the UI subsystem is responsible for figuring out what the
client means by that id. In other words, the client gives the symbol
IDs which are the clients natural representation of the symbols and
the client somehow communicates the necessary conversion procedure
through messages.

So if the client wants to use ASCII, the id for a symbol would be an
ASCII code. If the client wants to represent symbols by vector
plotting, the id would be a list of points.

In that case, the primary SymbolFactory message would look like this:

factory.create(id)

Where id is some untyped data that the factory has no innate idea
what to do with. All that the factory would do would be to create a
blank symbol and associate it with the given id.

Then additional messages would tell the factory what id looks like as
follows:

factory.setASCII(id,code)
factory.setUnicode(id,code)
Which means in English: The shape of the symbol for this ID is the
same as the shape of this ASCII symbol or Unicode symbol.
factory.setColor(id,color)
Which indicates the color of the symbol, or is ignored for black &
white terminals.

And if we did want to represent a symbol by vectors, we could have
factory.addPoint(id,point)
which when called many times would build up an image of the symbol by
points.

This is appealing because the given id is totally untyped, which
means that the client could literally give any value at all as the id
and not some special 'symbol id' type specified by the UI subsystem.
This idea seems a little strange to me, but it would mean that when
it comes time to draw, all the client has to do is say, "Draw this,"
and whatever "this" is, the UI subsystem will know how to draw it, so
long as the client has programmed that information into the UI
subsystem with the proper messages in advance.

It might be a little awkward for the client in the simplest cases,
however. For example, I am not terribly happy about forcing the
client to call
factory.setASCII('A','A')
before it can draw the letter A, but it is no worse than forcing it
to create a subsystem id for A.

What if we want to do some highlighting and draw a green A? We'd
better not call factory.setColor('A',green), because that would turn
every 'A' on the screen green. As the client, we would have to come
up with a new identifier for a green A.

Actually, I'm just thinking out loud. It wouldn't work because some
terminals would be completely incapable of accepting some of the more
advanced messages, like vector drawing, and would be left with no way
to display the symbol at all.

IOW, the client is messing with icons and wants to think of them
at that level of abstraction. The client invokes your UI service
to display those icons precisely because it doesn't care or want
to know how display is done.

Agreed.

It is the job of the Facade object that implements the UI
interface to interpret those requests in terms of the display
subject matter implementation and figure out who should respond to
them.

The GoF example implementation of the Facade object isn't a total
middle-man. It looks more like this

void Compiler::Compile(input, output)
{
Scanner scanner(input);
ProgramNodeBuilder builder;
Parser parser;
parser.Parse(scanner,builder);
RISCCodeGenerator generator(output);
ProgramNode* parseTree = builder.GetRootNode();
parseTree->Traverse(generator);
}

Is see it instantiating objects and relationships. I see it invoking
two behavioural methods in the order in which they should be invoked.
It's doing a lot more than just passing along the message.

That is about what mine is doing. My Terminal object passes the
messages almost directly to PDCurses, but it also acts as a GoF
Adapter when it needs to.

SymbolFactory might pass the messages the client gives on to some
other object if I could think of a situation in which that would help
in some way. Until and unless that happens, the SymbolFactory might
as well just do the conversion itself. Having SymbolFactory do the
job itself sometimes in no way interfers with my having it relay the
message other times.

Even if the client knows about objects in my subsystem, it does
not mean that it knows the implementations of those objects. There
can still be limitless layers of complexity that are hidden from
the client beneath those exposed objects. In fact, those exposed
objects could be nothing more than place-holders to satisfy what
the client expects from the subsystem interface while having no
role at all in the implementation of the subsystem.

At the scale of a subsystem, the implementation that is being
hidden /are/ the objects.

I'm saying that just because we expose some objects, interface
objects of Facade objects, doesn't mean that we cannot have hidden
objects that are used to implement the actual solution. We also have
the option of not having other objects and we are free to add or
remove hidden objects without the client being able to tell the
difference.

The objects in the subsystem are abstracted from the problem space
and tailored specifically to the subsystem's subject matter. That
abstraction and tailoring is nobody's business except the
subsystem's.

And no one that I am aware of is suggesting that we lay everything
out in the open for the client to see. If the abstraction from the
problem space happens to exactly match the best subsystem interface,
then the simplest course of action is to let the abstraction be the
interface, rather than duplicating it. It just becomes a facade onto
an empty subsystem. The client will not know or care that the
subsystem is empty.

But those Facade objects provide the interface rather than the
subsystem implementation. They still hide the subsystem objects,
regardless of how one subdivides the interface.

The facade always hides the implementation objects, even if the
Facade is one of those implementation objects. It is hidden because
the client cannot know that the facade is an implementation object.
In future versions of the subsystem maybe the Facade won't be an
implementation object, or maybe it will gain even more implementation
responsibilities. All of that is hidden from the client. Isn't it?

In addition, from a Systems Engineering viewpoint, subsystems
should be black boxes and the only way ensure that perspective is
to fully encapsulate them. Similarly, if one is to have any hope
of large-scale or component level reuse, the modules and
components must be fully encapsulated.

But don't objects encapsulate their own implementations? Objects that
are exposed to the client are still objects whether they are what you
would call facade objects or not. Suppose you are the client and
suppose you are given a subsystem so that you can see only the
details needed to use it, the interfaces of all the public classes.

Now explain to me how you would tell whether those classes are
exposed implementation or facade classes? If there is a hole in the
encapsulation, then surely you can look through that hole. Otherwise,
is it not just as much a black box?

Suppose I give you my Terminal class with its 'initialize',
'shutdown' and 'refresh' messages. If I didn't tell you that those
messages were being implemented directly in the objects of that
class, how would you know? What is actually being exposed here?

The interface identifies the public responsibilities _of the
subsystem_. How those responsibilities are implemented is nobody
else's business.

Fortunately, no one is suggesting exposing that implementation.

Finally, as a practical matter one wants to use the subsystem
interface to raise the level of abstraction for access to the
subsystem services. Ideally one wants a subsystem interface that
will work for any client needing those services. To provide that
sort of client-independent interface one needs to capture the
invariants of the subject matter in the interface. Including a
subsystem object's interface in the subsystem Facade pretty much
trashes that possibility.

You seem to be looking at it from a perspective that is entirely
opposite to mine. I always start with the interface and then design
the implementation. How could I know what needs to be done if I don't
have an interface to implement?

I take the view of the client and I try to figure out what I want to
be able to do with this subsystem. I design the interface to meet my
needs. Then I just fill in the insides with implementation.

It often happens that the abstract concepts that I thought I needed
as a client are also highly useful for implementation. These are
client concepts that will always be part of the interface (since it
quickly becomes too late to change it once the client is written). I
cannot raise the level of abstraction for that reason alone; it has
nothing to do with the fact that I am also using those concepts
within the implementation.

On the utility of handles:

I think I can see why you would think that, but I have concerns.
Your suspicion that the client already has an identifier for
symbols is not always correct. In fact, in the practical
implementation that I am working on, often the client uses a
factory to create a symbol constructively out of several parts
and then takes the resulting Symbol as its only identifier for
the new symbol. If the UI subsystem did not provide the
identifier the client would be forced to create a new arbitrary
identifier in that case.

First, I hope you meant to say 'icon' (the term you used
previously to describe the client) here rather than 'symbol'. B-)
We are having enough disconnects without confusing the semantics
of rendering with the client's view of the world.

'Icon' was just the name of an example client class, just to give it
a name. I could have chosen anything for that name and the client is
sure to have many classes besides 'Icon'. There is nothing special
about the word. Icon was a class that uses a symbol in one particular
example, the word 'Icon' was not meant to be synonymous with symbol,
though it tends to be in most cases.

Plus, the client is not actually forced to keep track of what
each Symbol object represents. It is not impossible for it to
request that the factory create a new copy of a Symbol that it
has already created. So the client does not need keep a table
mapping 'A' to one Symbol and 'B' to another and 'C' to another,
it can just order the creation of those Symbols each time it
needs them.

Letting the client keep track of the Symbols that it needs also
allows the client to discard Symbols that it no longer needs,
while the subsystem would have to maintain a list of every symbol
ever created. (Or else it would need a dispose(symbolID) method,
which works but requires special garbage collection effort from
the client.)

These two paragraphs seem in conflict for the situation where
conversions between multiple Symbols are needed. I was nodding my
head, Yeah, yeah, yeah when I read the first paragraph (up to the
last clause). But the last clause and the second paragraph only
makes sense to me if one is talking about icons in the client
domain rather than symbols in the UI implementation. Otherwise
there is ambiguity about which Symbol one is talking about
("natural" or "terminal-ready").

Unfortunately, this ambiguity is not clear to me. I am sure I would
have seen it by now if merely telling me that it exists was enough to
help me see it.

As I understand your terms, a natural symbol is one that the client
creates to show the UI subsystem what should be displayed. The
terminal-ready symbol is the one that the SymbolFactory creates for
the Terminal to display directly, without further conversion.

So then, SymbolFactory always gives a terminal-ready symbol. That is
surely unambiguous, isn't it? Any symbol that the client comes up
with by any means other than the SymbolFactory is going to be a
natural symbol. Could you suggest an example of a situation where the
ambiguity that you see and I don't see would cause a problem?
.


Quantcast