Re: Text terminal rendering design



H. S. Lahman wrote in Uum5i.14916$mD.10815@trndny02:">news:Uum5i.14916$mD.10815@trndny02:

Responding to Guild...

Generally when requirements change one has to modify
implementations more often than interfaces, as you imply above.
That's why we hide implementations behind interfaces. But that
applies to entire subsystem just as much as it applies to
individual objects.

You write as if you don't expect me to agree with you, but of course
I do agree. The disconnect here seems to be rather strange.

I know that you are aware that revealing an object reveals nothing
more than the public members of that object. The public members are
the interface of that object and taken together all the interfaces of
all the revealed objects becomes the interface of the subsystem.

I wonder if the disconnect comes from you thinking that I am
revealing an object that has public members that the client should
not see. I assure you that I am not. The objects that I reveal to the
client have only public members that correspond directly with
messages that I expect the client to send to the subsystem (or else
they are for giving the client access to other interface objects).

But surely there is always a 1:1 mapping between subsystem
interface messages and object method calls. How else can the
client send the subsystem messages but through method calls?

No way, Jose. For example, in the electronics test industry
individual test instruments usually provide interfaces for three
different low-level activities: Setup [the instrument]; Initiate
[the test]; and Fetch [the results.]

[snipped interesting example]

The point of your example seems to be that one message to the
subsystem can lead to several method invocations internally. However,
that does not address the intent of my question. I was not talking
about how a subsystem deals with messages internally, I was talking
simply about how the client sends messages to the subsystem. This
must surely happen by the client invoking interface methods.

How the subsystem reacts to those method calls is an entirely
separate and unrelated issue. I hope you notice that just as a facade
object is free to call whatever methods and however many methods that
it chooses to relay the message to the subsystem, so can any object,
even an implementation objects.

More to the point, an implementation object could be transformed into
a facade during maintenance with no effect on the client, just as a
facade could be transformed into an implementation object with no
effect on the client. You have mentioned that facade objects might
tend to become more complicated during maintenance as the design of
the subsystem changes. If the facade becomes complicated enough, it
might end up with serious implementation responsibilities.

If you don't like that, you can always revert back to an earlier
design. Is this not true? If it is true, then surely it is the very
definition of decoupling! The fact that I can make the radical
alteration of turning a facade object into an implementation object
or an implementation object into a facade object without the
slightest change to the client proves that they are decoupled.

I bet that has not convinced you. The best way to make something
clear is to demonstrate it, so I will attempt to form a small example
to illustrate.

Abstraction is also important. The subsystem interface should
capture the invariants of the subsystem's subject matter from the
client's perspective. In almost all client/service relationships
the client will be at a higher level of abstraction than the
service. That quite commonly requires the decomposition of client
requests.

Method invocations are naturally handled by taking a request from
some caller and turning it into a series of steps that the object
performs. Facade objects are not the only objects capable of
decomposing a request.

This is why I was pushing on the fact that your client wants to
think about displayIcon rather than createSymbolTypeX. In fact,
the call to SymbolFactory is just one of several calls that may be
necessary to service the client's request. That sequence --
including where it starts -- is a private matter for the UI
subsystem implementation.

Then it is my subsystem interface that needs to be adjusted. The fact
that I give the client implementation objects to receive the client's
messages is an entirely separate issue. If I am going to change the
interface then naturally I will have to change the object that
handles those messages. Depending on the interface that I finally
settle upon, it might be a facade object.

The indirection that one needs in hiding the subsystem's
implementation is that the client can't know what objects
implement that subsystem.

You are saying that in a misleading way. Based on what you wrote
below, this isn't really about what the client knows or doesn't know.
I think we agree that the client can never know what objects
implement the subsystem, even if the client is given a pointer to
each and every one of those objects.

This ignorance comes directly from the fact that the client has no
way of knowing if the objects it sees are implementation objects or
facade objects. Since you agree with that, perhaps it would be better
to explain your position in another way.

As soon as the client knows that SymbolFactory is an object in the
subject matter, the client knows something about that
implementation.

That's true, but a little bizarre. What makes you think that the
client would know that SymbolFactory is an object in the subject
matter? Are you going to tell him yourself? I'm not going to tell
him.

For example, suppose requirements change and you decide that
instead of using the Abstract Factory pattern, you need to use the
Factory Method pattern for creating symbols. To document the
implementation so it is clear what pattern is being used, you
might choose to rename SymbolFactory to SymbolCreator.

If I want the client to tell the subsystem what symbols to create,
then what you are describing is a subsystem interface change.

Now that is clearly a private matter for the implementation of the
UI subsystem.

That is not clear at all. This subsystem is designed to serve the
client's needs in displaying symbols to the user, therefore the
client is the ultimate source for all symbols.

The subsystem doesn't tend to create symbols for itself. By nature
only the client knows what symbols should be displayed and
SymbolFactory is an attempt to use Abstract Factory to allow the
client to abstractly convey that information to the subsystem.

If SymbolFactory should change, then it can only be because we have
found a new and better way for the client to inform the subsystem
about what symbols need to be displayed. If SymbolFactory changes but
the subsystem interface does not, then what is the point?

If you have a separate Facade interface, that renaming will be
completely transparent to the client.

Despite my doubts about your example, I should point out that even if
I assume there is a good reason for changing SymbolFactory into
SymbolCreator and hiding it from the client, your example still does
not make its point because I can make the renaming transparent to the
client without having a separate Facade interface.

Of course, I have to keep SymbolFactory around since it is a part of
the subsystem interface, but I am completely free to create a class
called SymbolCreator and use it internally in place of SymbolFactory.
If I still think the client's instructions to create symbols are
worth something, I can even relay those message to SymbolCreator
through SymbolFactory.

But if you are also using SymbolFactory for the subsystem
interface, you have to modify the interface call within the client
subsystem to use the new name. That's a dependency of the client
implementation on the UI subsystem implementation.

It would be, if I actually had to do that.

Why would it need to know that icons were displayed on terminals
or that terminals needed to be initialized?

That depends on what you take the word 'terminal' to be. I take
it to be nothing more than an array of symbols. The client needs
to know that because I cannot think of any way more abstract than
that to describe it. If the client didn't even know the symbols
were being displayed in an array, how could it provide
coordinates?

If that is all it is, then why does it have behaviors like init()
and shutdown()? B-)

That's a good point. I was overstating the abstraction that I am
using. Still, init() and shutdown() are really pretty abstract. Even
if I were implementing this as a smoke signals I would want messages
like that, especially shutdown(). I would need the client to tell me
when it is safe to put the fire out.

I took it to be an abstraction of the particular hardware display
mechanism with unique properties that affected how symbols would
be processed. IOW, it would be the Terminal object that described
the differences between 25x80 character display and a 1600x1200
pixel display.

That seems about right, though I imagine the number of pixels in the
display would be encapsulated within the object and not open to
examination. On the other hand, I plan to have the width and height
of the display in characters be available to the subsystem client
upon request. It needs that to know what coordinates will be visible
to the user.

You already have an abstract notion of an array of symbols in
2DArray. [Whose name captures that idea a whole lot better. B-)]

I have my doubts about that name, though. 2DArray suggests a 2D array
of something, not necessarily symbols. And the abstraction is
naturally not exactly as I said it above, since it is also part of
the abstraction that those symbols are to be displayed to the user.
That part is certainly not captured by the name 2DArray.

[BTW, it occurs to me that the differences in terminals are
probably easily parameterized. So one probably only needs a single
flavor of Terminal with appropriate attributes whose values define
differences.]

That is highly doubtful. Perhaps some terminals can be handled that
way, but the differences between a graphical terminal and a PDCurses
terminal are far more radical.

I suppose that it doesn't really need to know that terminals need
to be initialized, but as it happens they do. The initialization
message is mostly just a courtesy to the subsystem to help it
provide an efficient implementation by not having to guess too
much about what the client will do.

Part of the DbC contract with the UI subsystem may well be some
sort of mapping for when startup and shutdown activities occur
that is driven by the practical requirements around display
mechanisms. But my point here is that the client doesn't need to
know the details.

What details are you supposing I am asking the client to know about?
It is an init() method, just like that. It takes no arguments and it
is nothing but an announcement that the client is about to need to
display some symbols. What did I say to suggest otherwise?

There just needs to be something in the client space that
naturally maps into triggering those activities. I am guessing the
the client knows when it needs to use the display and when it is
done.

You have guessed correctly. The sole purpose of the init() method is
allowing the client to announce one of those things. It is strange
that we agree on so much and yet you write as if we did not.

Why would it make any difference to the client if the icons were
displayed on a computer or with Navajo smoke signals?

I rather think that it wouldn't make any difference at all. In
fact, that is the point of all this abstraction that I am
introducing and the object-oriented design. If I wanted the
client to be forced to use one particular implementation I would
just have it use PDCurses.

Yes, but then there is no Terminal object. Instead we have to make
due with Fireplace and Blanket abstractions.

You are the one suggesting thie unusual implementation. Can you blame
me for choosing slightly inappropriate names for objects when they
are going to be implemented in radically unexpected ways? It may be
the case that Terminal is a strange name for an object that organizes
smoke signals, but that does not mean that it cannot do it.

I am sure that the appropriate subclass of Terminal would gladly make
use of Fireplace and Blanket to send out smoke signals, although the
concept of coordinates might be a little awkward. Terminal
encapsulates anything that anyone might use to figure out whether it
is smoke signals or a machine doing the displaying.

My issue here is that the interface should be abstract enough that
the client doesn't care how icons are displayed.

If you think that it is not abstract enough as it is, then please
suggest ways in which I could make it more abstract. You don't need
to tell me that an abstract interface is a good thing; it is
currently as abstract as I can manage.

My problem is that you seem to think that the object that implements
the interface determines how abstract the interface is, while
actually the abstractness of the interface depends only upon the
interface itself.

Think of the subsystem Facade wrapper as a polymorphic dispatch
mechanism that allows one to swap display paradigms (e.g., DLLs
for entirely different subsystems) without the client knowing
anything about it.

You are talking as if my current design does not already allow that.
That was the fundamental goal of this project from the very
beginning. I don't know why you seem to think my current design fails
to allow that sort of swap, but I am seriously attempting to discover
it.

It's because SymbolFactory and Terminal are specific to a
particular design solution for the UI subsystem. The notion of
Symbol itself is a specific abstraction that fits into a
particular vision of the solution. However natural all that stuff
might seem to you as the UI subsystem designer, it isn't the only
way to solve the problem.

That stuff seemed natural to me when I put myself in the place of the
client. You are talking about my vision of the client's subsystem
interface, not the internal subsystem design. I get the impression
that my subsystem interface seems incredibly foolish and restrictive
to you and that is why I am so eager to find a better one.

However, it is not easy. I designed the best interface that I could
imagine, so seeing better interfaces can be difficult, especially
when all I have are the hints and pointers that I naturally get from
helpful usenet people. Forming the incomplete ideas posted here into
an actual interface that is above and beyond what I could imagine on
my own is a difficult job, but not impossible.

So you need a subsystem interface that is independent a particular
design vision. For that you need an interface that is at a higher
level of abstraction consistent with the client's view of the
world rather than the UI's.

So, if you were the client programmer, knowing what you know about
the purpose of this subsystem, what interface would you suggest? I
mean including colors and symbols that can be drawn using arbitrary
client drawing procedures. It would also be nice to support ASCII,
Unicode, various font attributes such as bold and underline, and also
line-drawing symbols such as horizontal lines and vertical lines and
diagonal slashes and corners.

There really isn't much minutia, just a single message with no
data that tells the subsystem that symbols will be arriving for
display soon.

You first sentence is arguing my point.

It is also describing the current state of my design. Your point does
not seem to be as radical a departure from my current design as you
think it is.

That is the client's view of what is happening. It is an
announcement of the client's state.

Agreed.

The client should not have any expectations about what will
happen, if anything, in response to that announcement.

Why would it?

Mapping that into an internal responsibility to initialize some
hardware is the UI subsystem's problem, not the client's.

That is exactly what my current design is doing.

It is completely unexpected how you suddenly assume that just
because a few implementation objects are exposed that the entire
implementation is suddenly exposed to the client, and worse, that
the client is forced to deal with it. On the contrary, none of
the implementation is exposed to the client.

Those objects /are/ the implementation.

Not exactly. Those objects encapsulate the implementation. There is a
big difference. In one case the client has a hold of the
implementation, and in the other the client only has a hold of
objects that are deliberately hiding the implementation.

It seems to me that those conversion procedures depend solely on
the particular flavor of terminal in hand, which the UI subsystem
is supposed to be making transparent to the client.

The UI subsystem is certainly supposed to be making that transparent,
I completely agree with that. However, the conversion procedures are
not at all solely dependent upon the particular flavor of terminal.
(If they were, things would be much easier.)

The conversion procedure needed depends in equal parts upon the
flavor of the terminal and upon the client's representation of the
symbol. Just like any conversion procedure, it depends upon the
encoding of the input just as much as the encoding of the output.

So you see that the conversion procedure depends upon the client and
therefore cannot be completely hidden from the client.

If the UI knows what flavor of terminal is active and it knows what
it needs to display, it /must/ know how to convert it.

The conversion procedure could also be called a decoding procedure.
The client has some sort of encoded symbol representation and the
subsystem needs to figure out what it means before it can know what
it needs to display. Unfortunately, by their very nature, there is no
universal standard way to express a symbol, so all representation
schemes can be viewed as being in some sort of special code.

I could demand that the client conform to one particular encoding and
make the conversion process trivial, but that is impractical because
it would require a almost universal encoding scheme, which I think we
agree does not exist.

Whatever encoding I ask the client to use for inputting the symbols
might be perfect for a certain implementation, such as ASCII for a
PDCurses terminal, but it could be terrible for another, such as
smoke signals.

Suppose that the client for some reason is internally representing
its output as smoke signals and we are running on a platform that has
a fire and blanket all ready for giving smoke signals to the user. If
my subsystem interface demands that the smoke signals be converted to
ASCII by the client before being given to my subsystem, and then my
subsystem somehow converts the ASCII codes back into smoke signals,
then I do not think the user will be pleased with the results.

I know there is no perfect solution, but I would like a good
solution. I have a combinatorial problem of converting from client
encoding schemes into terminal encoding schemes and I would like to
make a fair attempt at a design the supports handling that as
elegantly as possible.

I thought that was the subsystem's mission in life; those
conversion rules are what the subject matter encapsulates.

Yes, that seems about right.

I really had no idea that you would suppose I would give such a
header file to the client. I'm talking about encapsulation and
how I haven't really exposed anything and you are thinking that
I've pulled down the pants of all my classes like this.

Then I am confused. You said that the client directly invokes
methods of Terminal and SymbolFactory because they are part of the
subsystem Facade. I don't know what language your are using, but
to compile the caller the compiler needs to understand the class
structure.

That is true if by "class structure" you mean the set of messages
that the objects of the class accept. Of course, the client is always
forced to understand a set of messages no matter what subsystem it is
using.

I did not mean to suggest that I wasn't giving any header file at all
to the client. I only though it funny that you would suggest I give a
header file that shows my class's private members to the client. I
may not be as experienced at object-oriented design as you are, but
even I could spot that sort of hole in my encapsulation.

The header file contains a class definition, of course, but it
has no private members. In fact, it doesn't really have any
public members, at least not directly. The members are denoted as
being pure virtual, which means to C++ folk that they are not
implemented here.
[...]
All you are doing here is using polymorphic dispatch through an
abstract base superclass.

That is a technically correct description of what I am doing, but it
neglects the spirit of what I am doing. The header file could be seen
as an abstract class declaration by a compiler, but a human looking
at it sees a declaration of the messages that my subsystem will
accept. Surely you would have such a thing in the subsystems that you
design and it would look much the same.

That allows you to substitute implementations of the subclasses.

That is the swapping that you seemed to be suggesting I could not do
earlier in the post.

But it doesn't allow you to change the names,

I can change the name of the class, of course. Each subclass
necessarily has its own name and I can change that freely to almost
anything I like.

I cannot change the names of the methods without breaking the
subsystem interface, but why would I want to do that? I'm not giving
the client this object lightly; I am giving the client this object
because these methods are the methods that the client would want to
invoke to interact with my subsystem. If I change the names of the
methods, that would confuse the client. (Unless I change the names to
something better, which I would be glad to do given any suggestions.)

If I am also calling those methods internally and I don't like the
look of the names, then I can change the names internally by either
relaying the client's messages to an equivalent object with different
method names, or by relaying my internal calls through an object with
different method names to this object. However, that seems unlikely,
since the method names should have been chosen with good reason and
it is unusual for the subsystem to be calling the methods that are
intended for the client, since the subsystem is rarely in a position
to pretend to be the client.

invoke different methods,

I am not invoking methods; the client is invoking methods. That is
unless you mean the methods that get invoked in response to the
client's messages. In that case I am free to invoke whatever I want.

dispatch to members of different classes,

If I want to dispatch to another object then this object can act as a
facade and relay the message. Surely you are not arguing against
facades.

change int arguments to doubles,

If the client supplies ints then I have ints. This is the sort of
thing that I must decide when I am designing the interface. There is
nothing I can do about it in maintenance, even if I were using a
facade.

What you have not changed is that the client now knows: (A) a
SymbolFactory abstraction is part of the subsystem implementation;

But you said yourself that the client does not know that. You said it
below because you knew that I would point that out.

(B) the SymbolFactory has a particular set of properties useful
for the subsystem implementation;

It knows that it has a particular set of methods that are useful for
sending messages to the subsystem. It doesn't know about any other
properties.

(C) the responsibility being accessed is the way the subsystem
/currently/ kicks off processing of the client's request;

Every client request is kicked off by sending the subsystem a
message. The client is always going to know that. Remember that all
the header file reveals is a list of messages that the client can
send to the subsystem.

(D) /all/ of the defined responsibilities of SymbolFactory are
accessible to the client.

I wouldn't reveal SymbolFactory to the client if I didn't think it
needed to give me the messages that SymbolFactory accepts. That
includes all of them.

You can argue that the client doesn't /know/ SymbolFactory is an
implementation object; from its perspective it is just an
interface.

Indeed I would argue that, so I thank you for conceding the point and
reducing the disconnect between us.

The counter to that argument is that it doesn't matter;

Just to be clear, when we say that it doesn't matter we are not
saying that it is false. Even if it doesn't matter, the client still
does not know that SymbolFactory is an implementation object. I hope
we agree on that.
.



Relevant Pages

  • 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)
  • Re: Text terminal rendering design
    ... subsystem subject matter so we need an interface to encapsulate them. ... The public members are the interface of that object and taken together all the interfaces of all the revealed objects becomes the interface of the subsystem. ... Your client is invoking specific ...
    (comp.object)
  • Re: What doesnt lend itself to OO?
    ... The whole idea that a subsystem is just ... > If the clock service has identity then the client looks like... ... The first line exists in the server. ... external interface is the traditional input interface whose ...
    (comp.object)
  • Re: Text terminal rendering design
    ... free to give it any object that satisfies that interface. ... giving it a real facade object if I choose. ... Facade to avoid touching the client. ... completely incompatible with this subsystem interface. ...
    (comp.object)
  • Re: What doesnt lend itself to OO?
    ... The whole idea that a subsystem is just ... > The first line exists in the server. ... objects between client and server i.e. as far as the client code is ... > external interface is the traditional input interface whose ...
    (comp.object)