Re: Text terminal rendering design
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Wed, 23 May 2007 03:55:46 GMT
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?
If you did, then I jumped all over it because it breaks all sorts of good practices for application partitioning. B-) In fact, I recall going into a lengthy discussion of decoupling with message-based interfaces when you hinted at something like this a couple of messages ago.
Clearly the notion of 'terminal' is unique to the subject matter of rendering on display terminals. Similarly SymbolFactory is a conceptual factory devoted to creating Symbols, which only exist in your UI subject matter (i.e., they seem to be thought of as Icons in the client). So those objects /implement/ the UI display subject matter.
On pure OO grounds they must be hidden from any client external to that subject matter. Similarly, on general principles of modularity the way the UI software is implemented should be hidden from everyone outside. Even a procedural developer practicing Structured Programming would encapsulate that implementation behind an API.
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.
Exactly. For example, as soon as the client needs the header file for Terminal or SymbolFactory to compile, it becomes tightly coupled with the UI subsystem implementation.
[Technically such header file dependence is known as physical coupling. John Lakos was the first to describe it in detail in "Large Scale C++ Software Design". Since then several authors like Martin and Fowler have written entire books about managing it with dependency management refactoring.]
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.
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. In practice most subsystem interfaces in original development are straight pass-through re-dispatching. That's because the developers of both client and service are in lock step.
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.
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."
Who is this client? Why would it need to know that icons were displayed on terminals or that terminals needed to be initialized? Why would it make any difference to the client if the icons were displayed on a computer or with Navajo smoke signals?
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.
[One of the review criteria for OOA models is that the model could be unambiguously implemented in a manual system without change. In your case the problem space of the UI subsystem /is/ the computing space so that is not possible. But the client doesn't need to know that to solve the customer's problem.]
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 application becomes more maintainable if one isolates subject matters, encapsulates them, and decouples their implementations. At the subsystem or application level the primary means for doing that is by providing indirection through Facade wrappers. Those wrappers completely hide the objects that implement the subsystem.
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.
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? The UI software lives for mucking with terminals. But it is a service precisely because nobody else wants to have to deal with that stuff. The clients want to think about the customer's problem at a higher level of abstraction than unicode.
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.
Save: I thought that was what the DB was for. It was just an example of the level of abstraction anyway.
My point here is the level of abstraction. The client wants the UI subsystem to change the color of an icon in the display. But that is the extent of its request. The client shouldn't need to know how the color is changed or what object does it in the UI service. And it certainly doesn't want want a Symbol handle; it already has an Icon that in knows all about. IOW, the client is asking your subsystem to display the client's icon. The fact that your subsystem needs to muck with Symbols is not at all relevant from the client's perspective.
The CEO tells some gopher, "Here are the sales results for Topeka. Get them to Jones." Is the CEO expecting to get back a registered mail receipt or a copy of the fax the gopher sent? I don't think so. I think all the CEO wants is for the gopher to get the sales results to Jones.
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. What the client must provide is enough information about the icon itself so that the UI subsystem can render it, including conversions. But that information is only what the client knows about its own icon.
[In DbC terms, there is a contract that defines what information about the icon the client must provide to get it displayed. Presumably that information will be some subset of what the client already knows about the icon. (If the UI subsystem needs information that the client doesn't naturally have, then it is time to go notify the Systems Engineer that there is a Big Problem because the subject matter requirements have not been properly defined.)]
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.
For characters I would expect an interface message like displayCharacter(code). That works if the client "registers" with the UI and indicates it always uses ASCII or unicode for the code. If it could be either, then I would expect two messages or a boolean in the message to identify the code base.
For the vector I would expect something like displayIconVector (id, <points>). IOW, the identifier is the icon identifier in the client's space and the points are just necessary information to describe it.
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.
That's usually not a good idea a good idea. When one instantiates objects, one should initialize all the attributes in the same scope, just like referential attributes. Otherwise one has to take explicit steps in the dynamics to ensure the object doesn't get accessed prematurely.
If the client wants an icon displayed I think there are two choices here. It either provides and Icon ID that the UI can use to retrieve the data from the DB or it provides /all/ the data about the Icon that the UI needs with its request. The setters would only be used to override what was already there.
Per the point above, the client only supplies data it knows about for its icon. For print characters, that might just be the character code and the UI fills in the other properties for a graphic display with defaults.
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.
Quibble: the color should default to black when the Symbol is created for B&W terminals. If a color attribute is defined for a Symbol, it needs to contain a valid value so long as the Symbol exists.
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.
Exactly. That's why one needs 2DArray -- to provide the decoupling. The Symbols might not have identifiers are all. One just needs to map the arbitrary icon ID from the client into the relevant Symbol addresses. (I would expect that is only necessary for processing change requests and the lookup would be handled by the interface; otherwise ordinary relationship navigation would support collaborations for rendering, conversions, and whatnot after the Symbols were instantiated.)
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.
That's the beauty of displayCharacter(code). The client doesn't even have to think in terms of icons for that. All the grunt work for the particular terminal is handled by the UI subsystem.
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.
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.
But if the client is talking to Terminal and SymbolFactory directly, it necessarily knows how the display is done. Just knowing the classes reflects some knowledge of the implementation. If the display was smoke signals then the relevant objects would be Fireplace, Blanket, and SignalTimer instead of Terminal, Symbol, and SymbolFactory.
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.
Note, though, that all the client sees is Compile (input, output). It has no knowledge at all of Scanner, Parser, and whatnot.
BTW, the compile example was not a subsystem interface per se; it was, itself, a solution and was just raising the level of abstraction for the client. For subsystems it is usually not a good idea to make the Facades very complicated.
The reason is that those complications usually mean one is really applying problem space rules and policies needed to solve the problem. (In the compiler example the Facade "hard-wires" the sequencing rules for invoking compiler stages in its implementation.) But for subsystems all those rules and policies are supposed to be what subject matter is about. So, in effect, one is hiding important aspects of the subject matter in the Facade interface rather than having them out front in the subject matter implementation.
So the Facades for subsystems should be limited to interface kinds of responsibilities, like resolving syntactic mismatches (e.g., units conversions, resolving namespaces, etc.).
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.
And I am arguing that /all/ objects that abstract the subject matter problem space should be hidden by a Facade wrapper. You can subdivide Facade wrapper into smaller wrappers if you want, but the only thing visible to the subsystem clients should be the wrapper(s).
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.
As soon as the client directly calls Terminal and SymbolFactory that part of the UI subsystem implementation is out in the open for the world to see.
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?
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?
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.
Yes, objects encapsulate their implementations. They provide an interface to do that.
But a subsystem is at a different level of abstraction and logical indivisibility. The subsystem encapsulates a subject matter. To do that it needs an interface to hide the implementation of the subject matter. That interface is for the entity subsystem entity. But the implementation of a subsystem is not just some bits in a memory location (attribute) or a sequence of low level executable statements (method). The behavior of the subsystem is not a single, standalone method; it is an aggregation of all the object interactions. The subsystem implementation is a collection of objects, their relationships, and their collaborations.
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.
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?
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.
That kind of dependency has created an entire subdomain of OOP development: dependency management.
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.
Au contraire! You are more that suggesting it; you are actually doing it as soon as you include the Terminal or SymbolFactory header in client classes.
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?
You must have had a traumatic encounter with a 3GL type maven in your youth. B-) They are the only people that think like that. [BTW, OOA/D is class-based rather than type-based; the only place where types appear at all are knowledge attribute ADTs. Nor does the interface define the object. In UML an Interface model element is a separate construct from a Class definition and it is just associated with the Class.]
In OOA/D the equivalent to your second sentence is: define what the responsibilities /are/ and then worry about the implementation. The crucial difference is that defining the responsibilities is about problem space abstraction (identifying what knowledge and behavior the underlying entity has that is relevant to solving the problem in hand). IOW, it is about identifying intrinsic properties rather than access.
At the subsystem level the steps are:
(1) define the subject matter
(2) define the level of abstraction
(3) define what the subject matter needs to know and do
(4) define the interface to (3)
(5) define the objects, relationships, and collaborations for (3)
(6) implement (4) to map into (5).
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.
But there has to be something identifiable in the client that the client wants to display. Icon seems as good a placeholder for that as any. My issue here is that whatever that is in the client's space, it isn't a Symbol as we have been talking about.
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.
The paragraphs are consistent if the client is talking to SymbolFactory directly. I never even considered that baroque possibility. B-))
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.
Not quite. I assumed a natural symbol was some kind of generic representation that was compatible with the rendering paradigm but not be suitable as a source of data for some or most flavors of Terminal. A terminal-ready symbol would be one that a given Terminal flavor can eat directly. Thus a natural symbol might be a terminal-ready symbol for some terminals, but not all.
*************
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
.
- Follow-Ups:
- Re: Text terminal rendering design
- From: Brendan Guild
- Re: Text terminal rendering design
- References:
- Text terminal rendering design
- From: Brendan Guild
- Re: Text terminal rendering design
- From: H. S. Lahman
- Re: Text terminal rendering design
- From: Brendan Guild
- Re: Text terminal rendering design
- From: H. S. Lahman
- Re: Text terminal rendering design
- From: Brendan Guild
- Re: Text terminal rendering design
- From: H. S. Lahman
- Re: Text terminal rendering design
- From: Brendan Guild
- Re: Text terminal rendering design
- From: H. S. Lahman
- Re: Text terminal rendering design
- From: Brendan Guild
- Re: Text terminal rendering design
- From: H. S. Lahman
- Re: Text terminal rendering design
- From: Brendan Guild
- Re: Text terminal rendering design
- From: H. S. Lahman
- Re: Text terminal rendering design
- From: Brendan Guild
- Text terminal rendering design
- Prev by Date: Re: "biz app" scope (Re: Whose Fish?)
- Next by Date: Re: Book - Modeling Software with Finite State Machines - anybody read it?
- Previous by thread: Re: Text terminal rendering design
- Next by thread: Re: Text terminal rendering design
- Index(es):