Re: Text terminal rendering design



Responding to Guild...

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.

The client will know the difference as soon as you need to modify
the interface of one of those objects in response to requirements
changes that only apply within the UI subject matter.


You seem to be suggesting that I might need to modify the interface to a subsystem interface class even though I don't require something new from the client. Of course that would be a horrible thing, but why would it ever happen? It seems impossible.

No, I am saying you might need to modify the interfaces to the objects that implement the subsystem subject when the requirements for that subject matter change. That becomes bad news if you have chosen to make those object the serve double duty as the subsystem's interface.

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.

Just because there happens to be a 1:1 mapping between subsystem interface messages and object method calls does not give one
license to expose those methods.


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.] This is so common it is enshrined in the VXI standard for test systems.

The reason the instrument drivers provide that level of interface is to support rack & stack systems where multiple instruments need to be coordinated. In that situation all the instruments need to be initialized before the test is run so one needs to do that separately from the other activities. A similar logic applies for fetching the results.

But what happens if the context is not rack & stack and there is only one instrument? Now the client doesn't care about those details and wants to invoke a single testIt(...) method in the interface. The interface has to decompose that single client request into three different device driver requests.

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.

I've worked with a system where a data "value" in a single interface event was really a bitmap. The client viewed it as a scalar value but to the service subsystem each bit was important and mapped into a logic state for a particular object. So that single client event needed to be broken up into up to 6048 individual events, each addressed to a different object. (The object identity for addressing was mapped by the position of the logic state bit in the bitmap.)

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.

So typically the Facade only does exotic things in two situations:
(A) to resolve syntactic mismatches in reuse situations when the
new client wants to see a different interface than the subsystem
provides and (B) to make changes transparent to the client. IOW,
the price of good decoupling is the indirection. The benefit is
realized when the indirection ceases to be 1:1.


Of course indirection is a good thing. (I was shocked to read below that you are thinking I am using absolutely no indirection.) But once you have even basic indirection, you can add more indirection within the subsystem as needed according maintenance.

The indirection that one needs in hiding the subsystem's implementation is that the client can't know what objects implement that subsystem. As soon as the client knows that SymbolFactory is an object in the subject matter, the client knows something about that implementation.

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. Conceivably one might have a coding standard that requires one to do that to ensure better readability when patterns are used.

Now that is clearly a private matter for the implementation of the UI subsystem. If you have a separate Facade interface, that renaming will be completely transparent to the client. 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.

This seems to be a very fundamental disconnect. The objects that abstract a subject matter's problem space /are/ the subsystem's implementation. As such they and their responsibilities need to be hidden just as thoroughly as the implementations of their individual responsibilities. I am not making this stuff up; it is basic application partitioning in an OO context. The GoF Facade pattern is also very clear that the Facade is a separate wrapper object that hides all the contained objects and their individual interfaces.

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."

Who is this client?


The client is a piece of software that has things to display to the user in the form of an array of symbols.


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-)

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.

You already have an abstract notion of an array of symbols in 2DArray. [Whose name captures that idea a whole lot better. B-)] The client may also have a notion of 2D display and the {x,y} coordinates provide a mapping between the client's view and the terminal's view.

[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.]

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.

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. It is those context announcements that would be mapped into the UI subsystem interface as messages and the UI would know what display mechanism activities they map into (i.e., methods of a Terminal abstraction).

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. My issue here is that the interface should be abstract enough that the client doesn't care how icons are displayed. That allows one complete freedom to swap implementations behind the interface to support computer terminals, smoke signals, heliographs or whatever.

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. So far the only thing those display mechanisms must share is some notion of two dimensional placement of individual displayable elements. [Which will require multiple fireplaces and blankets or some tricky hardware for smoke signals. B-)]

Clearly your UI software exists so that the client can be
indifferent to what particular terminal the display is on. What I
am arguing is that the client shouldn't even care if the display
is on a computer. It just wants the icon displayed.


I understand and agree with you on that point. What I don't see is why you think I am harming that sort of flexibility. Although your ideas about physical coupling suggest one rather unexpected explanation.

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.

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.

If your client directly accesses the objects that implement your subsystems then there is no isolation, there is no encapsulation,
and there is no decoupling of implementations. None. Nada. Zip.


That is amazing! And where did all the wonderful encapsulation that all objects naturally have disappear to?

All you have left is object-level encapsulation /within/ the subsystem implementation. The encapsulation of the /subsystem/ subject matter doesn't exist because the objects that implement it are exposed.

And that is just the coupling issues. What about the level of abstraction? How can the client focus on how an 'icon' relates to
the customer's problem if it also has to get mired in the minutia
of initializing terminals?


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. That's not my fault for allowing the client access to implementation objects; that's my fault for thinking that the client wouldn't mind sending a message of that sort to the subsystem.

You first sentence is arguing my point. That is the client's view of what is happening. It is an announcement of the client's state. The client should not have any expectations about what will happen, if anything, in response to that announcement. Mapping that into an internal responsibility to initialize some hardware is the UI subsystem's problem, not the client's.

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.

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.

Exactly -- up to the "necessary conversion procedure". That
procedure lives in the UI software alone and the client doesn't
need to know anything about it.


Except that if the client doesn't provide the conversion procedures then the subsystem has no way of knowing what to do with a value that the client has given. The value has no semantics for the subsystem unless the client explains the semantics.

Oh, my. This chasm just gets deeper and broader. B-)

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. 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. I thought that was the subsystem's mission in life; those conversion rules are what the subject matter encapsulates.

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.

If the client is some sort of WYSIWYG display designer we are
really in a different ball game. I was assuming the client just
had stuff to display in certain positions and was relying on the
UI subsystem to map that stuff into particular terminals. In that
case I would expect some set of defaults to be in place and the
client could override them.


Yes, I am afraid that it is a difficultly with the subsystem interface. The client will naturally want to have a way to display text without worrying about the font or color sometimes, while other times controlling it with fine detail.

That is a potentially nasty problem for application partitioning. One needs to define subsystem boundaries very clearly because one needs to allocate requirements to subsystems unambiguously. You have to make a decision about what the client needs to know about displaying things. Once that decision is made, then you need to stick with it.

So if you decide that the client needs to understand display at the level of fonts and color, then the client will have to provide that full view _for all requests_. IOW, the UI subsystem just displays exactly what the client says to display. At that point the UI subsystem becomes very mechanical and limited in it mission.

In effect, the client must provide a fully specified view of what to display and the UI just handles things like ASCII/unicode or binary color representations and dumbing down the full graphic view provided by the client to the more simplistic view of a character terminal.

But if the client is talking to Terminal and SymbolFactory
directly, it necessarily knows how the display is done.


This sounds immensely bizarre. I know that I said that the facade was both exposed and hidden in a previous post and that sounded strange. What you've just said above sounds just as strange. I invoke methods all the time without knowing how they do what they do.


Just knowing the classes reflects some knowledge of the
implementation.


What does "knowing the class" mean exactly? Does it mean knowing the implementation of the class? In that case what you are saying is of course true, but the client has no such knowledge. Does it mean knowing the public methods of the class? In that case the client does have that knowledge, but what you are saying is clearly false.


If the display was smoke signals then the relevant objects would
be Fireplace, Blanket, and SignalTimer instead of Terminal,
Symbol, and SymbolFactory.


Terminal and SymbolFactory are abstract classes which are meant to have various implementations. Some of those implementations could use smoke signals. Why not?

The subsystem subject matter has a problem space and one needs to abstract that problem space. The problem space for smoke signals does not have a lot of computer terminals in it.

The situation is analogous to providing a UI that is a GUI or a browser. Each has a unique paradigm: Window/Control for the GUI and Page/Section/Link for the browser. Those are incompatible views so one needs different subsystem implementations. In an application that needs to deal with both views, one would need two subsystem implementations that were substituted based on UI context. But the subsystems that were clients of the UI would use the same interface for both so the substitution would be transparent.

Note, though, that all the client sees is Compile (input, output).
It has no knowledge at all of Scanner, Parser, and whatnot.


Considering all the rest of the things you have said in that post, I am surprised that you think that anything at all is hidden from a class that has access to the Compile method.

Think about the caller of Compile(...). To make be compiled with that call, all it needs is the header file for the Compiler class. As far as it is concerned Scanner, Parser, et al do not even exist. More important the caller of Compile(...) has no knowledge of the sequence in which those other objects are called.

The facade always hides the implementation objects, even if the Facade is one of those implementation objects.

[...]

Please take a look at that first sentence again. B-) It's an
oxymoron. If the Facade is one of the implementation objects, how
can it hide itself?


I suppose I was speaking figuratively, but I stand by it. The client knows that the object exists, but the client cannot know what the object does. In that sense, the object is hidden. It is opaque, a black box.

Just like Compile(...) in the GoF example, the caller of the interface should never know what objects actually implement the subsystem. The whole purpose of the Facade is to decouple the caller from even knowing that those objects exist.

If there is no Facade to decouple the client from those objects, then the implementation of the subsystem is exposed to the client. Conversely, if the objects that implement the subsystem are accessed directly, then there is no Facade.

More to the point, during maintenance any one of those objects, relationships, and collaborations can change without changing the clients' interactions with that subsystem. You can delete objects,
add objects, change the names of objects, modify message data
packets, and a host of other things within the subsystem so long
as the DbC contracts with the clients are still satisfied. But you
can't do any of that if the objects that you are modifying are
exposed to the client because that will break the client
interaction.


That just means that the changes have to happen beneath the level of the subsystem interface. The exposed objects can change, so long as they change in an interface that is not exposed to the client. Objects that are not exposed the the client can always change freely. Nothing at all about exposing objects to the client prevents me from adding new objects that the client cannot see.

My point was that any subsystem abstraction that you also use as the subsystem interface cannot be modified in the same manner as other objects that abstract the subject matter. But since they all resolve the same requirements there is no reason why a requirements change will affect the objects used for the interface any less often than the objects that aren't used in the interface. IOW, all the objects that implement the subsystem are peers so there is no reason to believe some will be less affected by change than others.

The purpose of the subsystem interface is to shield the client from /any/ changes to the implementation of the subsystem. That includes changes to the objects you want to use in the interface.

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?

To compile the client the header file needs to be available if
Terminal is invoked directly. That header exposes the entire
implementation of the Terminal class, down to offsets to attribute
values and the jump table entries. In C++ just adding a private
variable to Terminal would require recompilation of the client.
Any change to the public interface that client invoked would
require code changes in client.


At last we come to what I think is probably the real issue. This was horribly shocking for me. Thank you; your posts are almost always a joy to read.

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. So it needs the Terminal and SymbolFactory header files (or equivalent) to generate the code for the caller. That is true whether polymorphic dispatch is used or not...

Maybe I'm not putting in as much indirection as you would recommend, but that hardly means that I am ignoring even the most basic forms of indirection. Let me describe what I think is a more likely picture of the header files that I give to the client.

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. (If not for your assumption to the contrary, I would have assumed this was normal for defining classes that interface directly with the client!)

All you are doing here is using polymorphic dispatch through an abstract base superclass. That allows you to substitute implementations of the subclasses. But it doesn't allow you to change the names, invoke different methods, dispatch to members of different classes, change int arguments to doubles, and a bunch of other stuff that you might routinely do when maintaining the subsystem implementation.

What you have not changed is that the client now knows: (A) a SymbolFactory abstraction is part of the subsystem implementation; (B) the SymbolFactory has a particular set of properties useful for the subsystem implementation; (C) the responsibility being accessed is the way the subsystem /currently/ kicks off processing of the client's request; and (D) /all/ of the defined responsibilities of SymbolFactory are accessible to the client.

You can argue that the client doesn't /know/ SymbolFactory is an implementation object; from its perspective it is just an interface. The counter to that argument is that it doesn't matter; the client's knowledge is manifested a coupling. Whatever goes wrong during maintenance and causes the client to change will be because it is an implementation object rather than a facade object. The client changes because it is coupled to that change since it is actually talking directly to an implementation object.


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

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.



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: Text terminal rendering design
    ... because the client invokes a method on a particular factory does ... Terminal and SymbolFactory are part of the interface Facade?!? ... API) is a Facade pattern object. ... the subsystem /interface/, not the subsystem itself. ...
    (comp.object)