Re: Text terminal rendering design



I've been thinking about this for a while. The issues raised here are
very interesting and the disconnects especially give me thoughts to
chew on. I believe that I now understand many things, especially
relating to my primary concerns in the design of this subsystem and
the causes of some of these disconnects.

I believe that one source of disconnect is the blurring of two issues
that are actually quite separate but have unfortunately been
sometimes confused in these recent posts. You view them both as
problems in my design and so it sometimes seems you are pushing back
against one when you are really pushing back against the other. I'm
not sure if they are blurred together in your thoughts, but on my end
they appear to be blurred so I will carefully separate them in this
post.

The first issue is a fundamental principle of my subsystem interface
design. The idea is that I have the client coordinate the conversion
of symbols from its view of the symbols to the terminal-ready version
as an extra step in addition to actually telling the subsystem to
display a symbol. You have described it as three steps, but in fact
it is only two steps. First we convert from the clients view to the
terminal's view, then we display. I believe that I have solid reason
for this design that you may not have considered, but I will go into
that later in this post.

The second issue is entirely separate. I want to make that very
deliberately clear since separating these issues is the main point of
this post. I am referring to giving the client implementation objects
rather than facade objects. I am very aware of how horrible you
consider that in a subsystem design. What I want to make clear is
that it is orthogonal to the first issue: I could have the problem
represented by the first issue without exposing implementation
objects and I could expose implementation objects without the first
issue.

I believe that the fact that I have both problems in my design is a
point of confusion since you seem to push back against them both as
one, when they each need separate attention in my mind. I am not at
all certain that they are both truly problems or that I should
eliminate both issues instead of just one or the other. You have
given plenty of reasons for eliminating both issues already, so I
don't expect you to respond by giving more of them right away. I
intend to supply new and careful justifications for each issue
individually and you might prefer to respond to those if you see them
as prompting something new to say.

My other realization is about the most important design feature of
this subsystem. It needs to be able to be extended with new
encodings, including new encodings at the level of the client. As I
develop support for new terminals I will also inevitably need to
support new encodings. I do not just mean new output encodings for
the hardware to consume; I need to support new encodings for the
client. This is critical because the client might be internally
dealing with symbols in exactly the encoding that the hardware needs,
and in that case my subsystem needs to take that encoding somehow and
display it with absolute faithfulness.

If I use an intermediate encoding internally or if I force the client
to perform a conversion to comply with my subsystem interface, then I
have to be sure that it is completely lossless, even for encodings
that I am not currently aware of and even if it requires an expansion
of the subsystem interface. The usefulness of the subsystem would be
fatally crippled if the subsystem client could produce better results
by talking to the hardware directly.

H. S. Lahman wrote:
The only way you can reveal an object without defining its
properties is as a handle or referential attribute. Your client is
invoking specific responsibilities of the objects. To do that
there must be a full class definition, even if it is an abstract
base class in a generalization. That class definition defines
every public responsibilities that every object member in every
descendant subclass share.

Despite all my careful thought, this is one point that I have been
unable to comprehend. In what sense do you mean that the client is
invoking responsibilities of the objects? In the basest physical
reality of the implementation the client is actually invoking methods
and nothing more. You make it sound as if the client must be aware of
what those methods do. In fact, later in the post you make it sound
as if the header file itself reveals more than just the names of the
methods. You seem to say that it reveals the actual purpose of the
class and its methods within the subsystem.

However, in the cold light of an uninformed observer, the header file
seems to be nothing more than a list of names. When I look at it
without knowing what exactly the methods will do, all I see are
message names just like the messages I might send to any subsystem.
You are pushing a wedge into this disconnect by making it sound as if
I cannot look at the header file without immediately realizing the
responsibilities of these methods.

So here is what my thinking has given me to help reconnect us. The
purpose of a facade is to relay messages from some client to the
appropriate implementation objects to cause the subsystem to respond
as it should to the message. In the simplest case it could be nothing
more than a table that maps messages to method invocations.

Now suppose one particular Facade happens to invoke methods always on
the same object. Further suppose, however unlikely, that the methods
that are being invoked have the same names and take the same
arguments as the Facade methods that were used by the client to give
the facade the messages. In this case the facade would be as trivial
as it could be, but it is still a facade just as you have been
suggesting that I use.

The tricky part comes next. Let me suggest that as an implementation
decision I might choose to not actually instantiate a real facade
object in the above simple case. Instead I would have the
implementation object be a subclass of facade class and then give the
client a pointer to the implementation object, so that the virtual
function table would play the role of the facade by directing the
messages to the appropriate methods.

I can imagine you wanting to eat your hat in frustration while
reading that above paragraph. After all we've discussed, how could I
possibly think that suggestion would seem anything but foolish to
you. You are worried about coupling with the client violation of
encapsulation and a million other horrors and needless trouble. I
have a plan to dismiss your worries completely and I have great
confidence in it. All you have to do is notice just how minor my
little trick was: I can undo it at any time during maintenance. You
see, all the client knows is the abstract interface, so I am free to
give it any object that satisfies that interface. In particular,
nothing the client is coupled to prevents me from giving it a real
facade object if I choose.

As you have pointed out, I might want to rename the implementation
class or its methods. That's not a problem; I snap my fingers and the
facade is back and relaying the messages to the newly renamed
methods. Have I doomed myself to tight coupling with the client or a
maintenance nightmare? Not at all; a snap of the fingers and any
worries I might have from giving the client an implementation object
vanish like smoke.

You tell me that giving the client a pointer to an implementation
object is very much worse than giving it a facade object. I naturally
push back with: How can it be very much worse when I can pop in a
facade object at any time with no effort? Suppose I have $1.25 and I
am standing in front of a pop machine. Is the change in my fingers
very much worse than a can of pop? I don't see how it could be,
because if I decide that I would rather have pop I can instantly
trade one for the other. The money is good just because it allows me
to get the pop, even if for no other reason.

This is my little realization. I'm not pushing back against your
warnings about not exposing implementation objects because I feel it
is better to expose implementation objects. All I've been trying to
make you see is that it is simply impossible for doing so to be as
horrible as you describe, at least the way I'm doing it. I freely
admit that I don't have your experience and wisdom, but I need no
special wisdom to see that when two things are freely
interchangeable, neither one is terribly worse than the other.

The key point here is that the interface to the subsystem is not
the same as the public interface of the objects that implement the
subsystem. There is only one subsystem interface that is part of a
bridge to the client. One client = one interface.

I was sure that I read in one of your posts that it was okay to split
the subsystem interface across multiple objects, but now I cannot
find it. Even so, I certainly cannot imagine how it could cause any
harm. If I must have one huge facade object for handling all messages
I could just relay the messages to it through the smaller facades.
From the client's perspective the difference is mostly
inconsequential, but I imagine it might be easier to work with
smaller, simpler objects rather than one huge object with dozens of
methods.

I just don't think it matters. Just as with the pointers to
implementation objects issue, the only thing that makes this have any
significance at all is how strongly and inexplicably you seem set on
doing it one way rather than the other even though they seem totally
interchangeable.

The job of Facade is to hide the interfaces of the objects that
implement the subsystem subject matter.

It's not important to hide those interfaces. You said yourself in a
recent post that those interfaces are likely to change in
maintenance, so whatever horrible insight the client might get will
soon be out-of-date. I know you thought that I wouldn't be able to
change those interfaces, but since I have explained how I can do it
so nicely above, I bet you're thinking differently now.

But the class definition /always/ defines responsibilities
throughout OOA/D/P.

And what if I don't want it to? What horrible consequences will I
face if I decide that I'd like to give a class definition to the
client without defining responsibilities? If it gets method names and
nothing else, how will I be punished? I see the benefit of reduced
coupling, so I'm curious about this downside that you imply must be
there. Surely people wouldn't always do it unless there were a good
reason.

There is a huge difference between displayIcon() and
createSymbol(). The createSymbol() behavior is only one activity
of potentially several that are necessary to service the
displayIcon() request _within the UI subject matter_. Each of
those activities are specific to the implementation of the UI
subject matter.

Here we have subtly shifted into the other issue. If I hadn't been
watching for it, I might not have even noticed. 'createSymbol' could
just as easily be a message sent through facade. It would be exactly
as easy, in fact, and you would still think it is just as wrong
because it is poor subsystem interface design.

So now it is time to reveal another thing that I realized about my
design while I was thinking. I think it is a clear and simple way to
explain my reasoning behind letting the client send a 'createSymbol'
message and similar messages of that sort.

The trick is that this is all about putting the tightly connected
responsibilities within the same subsystem and separating those that
are only loosely connected into different subsystems. That's what
you've been telling me to do so I'm sure you'll agree with that, but
I've got a new way of looking at it.

The UI subsystem has two simple and tightly connected
responsibilities. One is understanding the hardware and talking to it
somehow. The other is understanding the client's representation of
the symbols that must be displayed and mapping those client
representations into something that the hardware is capable of
displaying. If what the user sees is not close enough to what the
client intended, the blame goes straight to the UI subsystem. These
two responsibilities cannot be separated because they are so tightly
tied together; you have to understand the hardware to know the best
way to interpret the symbols that the client wants to display. It
might be different if there was some good universal intermediate
representations for symbols, but that is not currently the case.

So that is what the subsystem is good at. It has to be good at those
two things because that is its mission. Notice that a few things were
not listed. The UI subsystem is not good at memory management,
garbage collection, or even predicting which symbols are likely to be
displayed next. These are entirely peripheral issues to the
streamlined, tight little package that is the subsystem that is only
good at two things.

You wonder why I seem to want to expose the client to all sorts of
nasty stuff like holding onto terminal-ready symbols and managing
them in memory and data structures. Now I wonder why you would want
to expose the UI subsystem to that sort of thing. Nothing in the UI
subsystem says anything about knowing the best way to store this
terminal-ready symbols or knowing when it is alright to delete them.
Nothing in the UI subsystem's purpose says anything about dynamic
memory allocation or managing the relationship between client icon
values and terminal-ready symbol values, or even deciding when system
resources are free enough to do some relatively expensive terminal-
ready symbol calculations.

All of that stuff would be bleeding in issues that are unrelated to
the two things that the UI subsystem is ultimately responsible for:
interpreting client icons and displaying them.

I am clearly begging for the question: Why not just put that stuff
into other subsystems? Just because the UI subsystem shouldn't deal
with it doesn't mean that the client has to. I say that not only
should the UI subsystem not deal with those peripheral activities, it
should not even know how they should be dealt with. If we use another
subsystem to manage to memory and terminal-ready symbol resources of
the application, then the UI subsystem should not be forced to know
anything about it, not even its interface. To reveal that sort of
thing would be inappropriate coupling and cause the same leaking of
peripheral knowledge that I am trying to avoid.

So instead I suggest a UI subsystem interface that has only these two
fundamental messages:

createSymbol(client symbol data)
displaySymbol(x,y,terminal-ready symbol)

One message for each of the things that the UI subsystem should know
how to do. When it is finished preparing a terminal-ready symbol, it
passes it to the only other subsystem that it knows about, the
client, and then forgets it. It is as if it were saying "let someone
else deal with the messy stuff. I've got my hands full with just this
complicated job."

I'm giving the terminal ready symbol to the client, which is
unfortunate, but think of the benefits. The client gets to choose the
subsystem that handles the memory management. The client gets to
choose to use LRU or whatever miracle heuristic subsystem it can
find. If the client programmer thinks the application is hogging
memory or is sluggish, he doesn't have to throw away the entire UI
subsystem and look for something faster, he can throw away just the
memory manager and find a better one.

And the point is that with good subject matter definition and a
good, high-level, client-oriented Facade interface there would be
no need to tell the UI subsystem to create a symbol. The client
just wants to displayIcon. It doesn't know or care that creating
symbols is necessary to that.

But in fact the client does know that creating symbols is necessary.
Not even the most unusual possible implementation of the UI subsystem
could avoid creating symbols. Carving on stone tablets involves
creating symbols; you need to define the strokes of the chisel. The
same applies to paints on canvas. Smoke signals need to decide how
one should move the blanket to send the correct pattern into the air.

Even if we did not allow a 'createSymbol' message to let the client
announce the symbols that it will need in advance, it would still
know that creating symbols is part of the job of the UI subsystem.
The client can give the subsystem a mere number '2' and the subsystem
will cause pixels to glow to display the actual numeral '2'. That
sure looks like creating a symbol to me.

Notice that createSymbol does not return with the terminal-ready
symbol. The client is not telling the UI subsystem when to create it,
just that it should be created because it will be needed soon. When
the UI subsystem feels like creating the symbol, only then will it be
passed back to the client.

But for smoke signals would you really choose Terminal as a
problem space abstraction?

Sure. Why not? Is there something about Terminal that you've noticed
that makes it inappropriate for sending smoke signals?

The point of this subsystem is to allow a client to write an
application that will work no matter what the hardware situation.
That's not an actually achievable goal, but the closer the better.
Therefore, if I could make the subsystem work with smoke signals then
I'd go for it.

OK, but that just amplifies the point above. The client wants to
start displaying things or stop displaying things. The client does
not want to initialize Terminals or shutdown Terminals.

It seems like you might be reading too much into a name. Would you be
more comfortable if I changed the name of Terminal to UI?

It doesn't support smoke signals, holograms, or heliographs. Those
things need to be abstracted differently because they are
fundamentally different than computer terminals. So much so that
swapping subsystems would probably be much easier than trying to
make a single one-size-fits-all subsystem.

It would be easier, but then the application would have to be
modified to interact with the new subsystem interface. That's no good
for making the application portable to many platforms. (Even though
it is quite unlikely that I'd ever find a platform that actually uses
one of those things.)

In order to avoid having the UI modifications leak into the rest of
the application, I think I'd just have to get messy and try to figure
out a way to rework the current subsystem to do smoke signals.

I am arguing that it is not natural from the client's perspective.
The client just wants to display a bunch of icons in a 2D display.
That's WHAT the client wants to do. Where is there anything in
that that suggests terminals, conversions, terminal-ready symbols,
and all the rest? That stuff only exists if you try to figure out
HOW an icon is displayed on a computer terminal.

The client doesn't know anything about terminals. Sure, there may be
a facade object who happens to be named 'Terminal', but that's the
limit of the client's knowledge on that subject. The client doesn't
know anything about conversions. It knows is that the subsystem has
to create a terminal-ready symbol before it can be displayed, but it
has no idea how that happens or if conversions are involved. And that
is about it. There is no rest, as far as I can tell.

So the only thing I'm exposing is the existence of a terminal-ready
symbol, something which I think is common to every conceivable symbol
displaying subsystem, whether it exposes them or not. I could hide
the terminal-ready symbol, but only by horribly destroying the
cohesion of the UI subsystem by exposing it to memory management
issues, which is not a UI matter at all.

The fact that a conversion process is necessary at all is a
private matter of the UI subject matter implementation. That
is what I want to hide.

You can rest easy then, because I have decided to hide it. It will
certainly be hidden far more carefully than it would have before your
advice.
.



Relevant Pages

  • Re: Text terminal rendering design
    ... The idea is that I have the client coordinate the conversion of symbols from its view of the symbols to the terminal-ready version as an extra step in addition to actually telling the subsystem to display a symbol. ... I am referring to giving the client implementation objects rather than facade objects. ...
    (comp.object)
  • Re: Handling error/status messages by interface to C++ programs
    ... You can then use a mode attribute somewhere to determine which subsystem to access based on the environment. ... A more elegant approach is to put the code for both UI modes in the same subsystem with one subsystem interface. ... They can also run either in the console or Windows mode, and send appropriate error and status messages. ... It also requires unique design at the OOD level. ...
    (comp.object)
  • Re: Text terminal rendering design
    ... If you want to change that, then let's not pretend that it is a subsystem implementation issue. ... The Terminal generalization is abstracted from entities in the problem space that you need to resolve the subsystem requirements. ... When you expose the Terminal class to the client you are exposing the fact that that the subsystem implementation has Terminal objects and that those Terminal objects have the specific behaviors of init, shutdown, and display. ... There are several approaches to doing that, ranging from loading all terminal-ready Symbols into memory at startup to keeping and tracking ...
    (comp.object)
  • Re: Text terminal rendering design
    ... in which case the client needs to provide it with its ... requirements belongs in the subsystem. ... all the decision making to resolve nonfunctional requirements belong ... amount of client problem space information then it can become a break ...
    (comp.object)
  • Re: Text terminal rendering design
    ... Which is exactly what would happen in your case when the interface of the implementation object changes. ... But then you need a new object to own the actual responsibility within the subsystem implementation. ... I can pop in a facade in a completely painless manner without being forced to rename or kludge anything. ... Facade to avoid touching the client. ...
    (comp.object)