Re: Text terminal rendering design



Responding to Guild...

1 R9 displays *
[Terminal]--------------------[Symbol]
+put() +ascii
+init() +unicode
+shutdown() +background
* | +foreground
| | *
R10 | | makes
| uses symbols |
| from |
1 | 1 made by R11 |
[SymbolFactory]---------------------+

H. S. Lahman wrote in
X%H2i.23729$b67.22235@trnddc06:">news:X%H2i.23729$b67.22235@trnddc06:

Who is the client here and how is it modifying the array?


The client is an application with need of a UI that works by
displaying a 2D array of symbols to the user. The client knows
what symbols it needs to display and the coordinates at which each symbol should be displayed.

Then I think the 2D array is an important conceptual entity in the UI problem space; it is a fundamental element of the UI design. The management of that array's content by the client is quite a different concern than actually rendering it in the display de jour. So I would be inclined to make 2D Array a peer object of Terminal, Symbol, etc.

The client would talk to 2D Array to define its content, but it would talk to somebody else to trigger the rendering once the 2D Array is properly initialized. That somebody else would manage the rendering by "walking" the 2D Array (e.g., getNextSymbol).

The client calls methods of a SymbolFactory object to create a Symbol and then it calls a method to write the symbol to the Terminal's array at some coordinates. That method is as simple as put(x,y,Symbol).

This sounds like 2D Array has actually four dimensions: {symbol ID, x coord, y coord, Symbol reference}. The client provides a symbol ID and the coordinates while somebody else (SymbolFactory) provides the Symbol reference.

I assume the client provides additional information to create one indirectly, or simply provides an identifier to extract it from a database. In any case, there has to be a mapping between the symbol ID the client provides and the actual Symbol.

I mention this because the client is outside the software being designed, so it really doesn't know anything about how the symbols are rendered. All one has is a bunch of interface messages from the client that one needs to map into the software object of the UI. So far I see the following interface messages that must be supported:

- extract a symbol from a database (via symbol ID)
- create a new symbol
- nest symbols within a larger symbol (BigSymbol)
- define coordinates for a symbol's display
- identify a list of symbols (2D Array)
- trigger rendering of a single symbol or a list of symbols.

I have two points here. The first is that the client really isn't telling anyone what to do. Your software interface dispatches a client message to whatever object is best suited to respond. The Do This approach to design is a very procedural view and it can get one in trouble. One should design the software first and then worry about where to dispatch the interface messages.

For example, the message the client sends that triggers the rendering may actually have the semantics of "I'm done defining symbols" in the client's context. Whether that message is dispatched to Terminal, 2D Array, Symbol, or some other object one creates specifically for rendering will depend on how one solves the problem. I can conceive of solutions where any of those dispatches might be valid.

My second point is that these messages map to quite different responses that involve quite different business rules and policies. That is further complicated by the fact that the rendering environment itself is complex and there may be alternatives for rendering a given Symbol. So even if the responses to the first three ("creation") messages were simple enough to put in a single SymbolFactory object, one might not want to do that because of the need to map into flavors of terminals. IOW, I am still pushing on the theme of separation of concerns by arguing that I would bet there are more objects needed.

If it is just assigning a method to a Symbol, then I would be
inclined to manage that separately from the other responsibilities
[Terminal] seems to have. That's because [Terminal] already seems
to have a lot to do. In particular, that assignment seems to
depend strongly on which Symbol one has in hand.


In the case of most text terminals, a symbol is represented by a single integer with sufficient bits to hold all the information needed to display it. PDCurses represents symbols in this way, and in my current design a PDCurses-based Terminal can expect Symbol objects which directly contain that integer, so the Terminal has almost nothing to do but pass that integer on to PDCurses.

Terminal also has init() and shutdown() responsibilities. Those seem to be at a higher level of abstraction than rendering individual Symbols. It is also managing the list of symbols to be displayed.

Worse, if the terminal is not a text terminal or it is a different flavor of text terminal, those rendering behaviors are not so simple and substitution is required. Even for a simple text terminal you have ASCII vs. unicode issues. All things considered, [Terminal] looks like it has plenty of potential of being a god object that hulks in the middle of the application and tells everyone else what to do. It could end up with half the code of the entire application in that one class (and subclasses).

My main issue here is that there seem to be quite different concerns here that could be delegated to make [Terminal] simpler:

- high level display control (init() and shutdown())
- managing the list of symbols to display
- managing coordinates (acquiring and mapping to Symbols)
- format conversions (ASCII/unicode -> terminal)
- Symbol instantiation
- Symbol rendering
- managing terminal configurations
- managing symbol nesting

You have delegated the mechanical details of Symbol instantiation to SymbolFactory, but all the decision making is still in Terminal. You have also delegated the last two but I am concerned that the problems you are encountering in doing that are related to not delegating some of the other tasks.

I see the following megathinker view of the tasks that need to be done to solve the problem:

(1) Get a list of symbols to display with their coordinates (2D Array).

(1A) Identify any nesting of symbols (a Composite-like structure of relationships).

(2) Instantiate those symbols in memory in their "natural" form (SymbolFactory).

(2A) Map them into the the data structures in (1).

(3) If necessary, reformat symbols to a form suitable to the terminal (ConvertSymbol?). [This may be more than just ASCII/unicode. For graphics environments there may a preferred format for other data in a particular terminal environment (e.g., 16-bit vs. 32-bit color).]

(3A) Replace the symbol mapping (2A) into the data structures in (1).

(4) render the symbols in the terminal display.

The only one of those I see logically related to the notion of 'terminal' is the 4th. Everything else is just preparing for rendering on a particular terminal. Even then, if one includes symbol nesting I think one needs to separate "walking" data structures from the actual rendering of individual symbols.

If those tasks are delegated via patterns like Strategy, I think things will be a lot easier to manage even though you will have a lot more subclasses lying around. That's because the combinatorial problem of matching Symbol formats to terminal mechanics will be reduced to table lookups (e.g., terminal type X vs. Symbol type D) that assign relationships dynamically. Then the actual tasks get done by polymorphically accessing the task in a given collaboration as one "walks" the data structure (2D Array or some variant of Composite) describing what needs to be displayed.



* displays R1 1
[Symbol] -------------------- [DisplayStrategy]
+ displaySymbol
A
| R2
+---------+-----...
| |
... ...

IOW, whoever the client is that is making the assignments for your
2D array would instantiate R1 for the right flavor of display
strategy. The [Terminal]'s job would be simplified to just sending
a message to the strategy at the end of the relationship path when
it is time to display a symbol.


That makes sense as a design, but it seems to have the drawback of forcing the client to be aware of the mechanism by which the symbol is being displayed. If I want to change from using PDCurses to a graphical library then the strategy for each Symbol would have to be changed accordingly.

The [DisplayStrategy] is fixed; those objects presumably get instantiated are startup. The R1 relationship is assigned by whoever instantiates [Symbol] because the object will have to know what flavor of [Symbol] it is creating. (It can use a simple table lookup to instantiate the relationship with a pointer to the DisplayStrategy object.)

The "client" (message sender) doesn't need to know anything about that. It just addresses the message to whatever object is at the end of the R1 relationship. The subclass substitution when displaySymbol() is invoked is transparent to the client because of the polymorphic dispatch.

************ (context start for discussion below) I think you are reading to much into the paragraph below. My concern is just the first sentence.

The SymbolFactory seems reasonable but I have a problem with it returning a Symbol object. The key characteristic of this problem
seems to be that there are a whole flock of complex dynamic
relationships. In that kind of situation it is usually best to
focus factories on instantiating relationships, such as my R1
above. Let it create a ConvertedSymbol or whatever and instantiate
relationships between it an other objects (e.g., Terminal) that
are used later in collaborations.


You mean to have SymbolFactory methods that take coordinates as well as symbol details so that when the symbol is created it will be directly and automatically given to the corresponding Terminal for display. The main reason that I have no such method in SymbolFactory is that the methods of SymbolFactory are expected to be relatively slow, complicated operations involving object construction and data conversion.

My issue here is with returning a Symbol reference, not with what SymbolFactory does to create an instance or what data it needs to do that. When SymbolFactory returns a Symbol reference to the caller it is implementing a temporary relationship between the Symbol and the Client:

0..1 is related to
[Client] -------------------------+
| * |
| |
| R1 | R2
| |
| invokes |
| 1 | 0..1
[SymbolFactory] --------------- [Symbol]
1 R3 creates *

That is, [SymbolFactory] is instantiating the R2 relationship for the [Client] over the scope of the caller method. (That limited scope requires it to be conditional on both ends.) That is usually a Bad Idea because it can lead to subtle referential integrity problems during maintenance when somebody isn't aware that the reference is being passed around.

The normal way to implement relationships with with pointer referential attributes. So if a Client /should/ be related to a Symbol as soon as it is created, SymbolFactory should initialize that pointer attribute in the Client. Then it will be there whenever the client needs collaborate with Symbol. Much more important, the rules for instantiation are encapsulated in one place (i.e., Client isn't also passing it to some other object that really does the collaboration). That is a much more disciplined way to ensure referential integrity.

The SymbolFactory interface is designed to take symbol details in many forms, hopefully including whatever form the client has available to supply. The role of the SymbolFactory is then to perform potentially complex conversion operations to put the symbol into a form that the Terminal is expecting so that the Terminal can quickly and easy display the symbol.

The more complex those conversion operations are, the more I would lobby for a different object to deal with conversions. B-) Converting one symbol into another is a quite different thing than instantiating one. In effect, whoever understands the conversion rules could become a client of SymbolFactory; it extracts data from the old symbol, converts it, and passes it to SymbolFactory for instantiation as a new Symbol.

So the desired behaviour of the client is to call a factory method to create a Symbol once and then instantiate and destroy the Terminal's relationship with that Symbol as many times as needed. Would you suggest that the SymbolFactory use memoization to mimick that efficiency?

Actually, I am suggesting that SymbolFactory instantiate the relationship once via a referential attribute in Client. If anything, that will be more efficient if the Client needs to access the Symbol multiple times.

The problem with returning a Symbol is the implied assumption that
whoever invoked the factory also needs to collaborate with the
Symbol.


You are correct that the client is not expected to collaborate with the Symbol. In my current design the client is only responsible for storing the Symbols that it wants to use and then giving them to the Terminal when each symbol needs to be displayed.

Then I would cut out the middleman and let SymbolFactory instantiate the relationship directly between Symbol and Terminal.

Invoking a factory is primarily about what to create and
when to create it while collaborating with an object is about what
to do with it and when to do it in the overall solution flow of
control. Those quite often represent different business rules and
policies so it might be necessary to separate them (either now or
during later maintenance).


The actual display of the symbols requires some sort of concrete implementation, which I have called Terminal. In order to display a symbol every implementation needs two clearly distinct pieces of information: The appearance of the symbol and the position on the screen.

The client has to supply both pieces of information. The position on the screen can be safely and universally represented as a pair of ints, so I just demand the client supply them as method arguments. However, I very much do not want the client to know the specific format of the appearance of the symbol, so I give it a factory that it can use to request that symbols be constructed. This allows the client to work with the symbol's appearance as a black box, even organizing it into data structures or passing it around, all without needing to know the format that the implementation uses.

The client is even given methods to examine the factory and ask it questions about what sort of symbols it supports. For example, some SymbolFactory objects may be able to create Symbols that contain a representation of a color, while others will always produce colorless symbols. This allows the client adjust the symbols that it requests so that the UI will be meaningful in black & white.

So while the SymbolFactory seems to fit into the GoF Abstract Factory pattern, it is perhaps not what you would expect. Each SymbolFactory object represents an encoding scheme for the appearance of a symbol.

Since the marker above for context, I have only been talking about why it is not a good idea for factories to pass references back to their callers. That has nothing to do with what the factory does. My only issue in this context is how one should instantiate relationships, _given that there is a relationship to instantiate.

<aside>
At the risk of getting further off on a tangent, the factory should always instantiate any relationships that the created object should have when it is created. IOW, one should not pass the reference back to the caller to let the caller instantiate its relationships. The reason is that referential integrity is much easier to deal with during OOP if all the instantiation is done in a single method's scope.

That probably isn't a big deal for your application but it can be a Major Headache if the application is implemented concurrently. So in the OOA/D the good practice is to always instantiate relationships in the factory and to do it with referential attributes. In the <extremely> rare situation that there is a performance issue around that, one can always optimize with a passed reference at OOP time. But then the risk to referential integrity is an informed decision.
</aside>

I am also not keen of SymbolFactory modifying existing Symbols. If
the Symbol already exists and one needs to do something like
changing its color, then that should be done directly through a
Symbol setter rather than through a middleman like SymbolFactory.
Let the factory encapsulate only rules and policies for
instantiation and leave dynamic context to collaborations.


I see your point. Perhaps my reason for allowing SymbolFactory to modify as well as create is not a good one, but I'll explain it even so.

Certain encodings are more suited to certain symbols than other symbols, and for any encoding there are always symbols that cannot be represented at all. However, one can almost always find a best match in the encoding you are using for the symbol you want to represent. For example, there might be a client that feels it needs to represent symbols in unicode, but unfortunately the only terminal available on a certain platform is ASCII.

In order to operate in such a situation, I added a method to SymbolFactory called adapt(Symbol). All it does is construct a Symbol in the appropriate encoding by finding a best match for the given symbol. Once I had that, it was obvious that I would want a version of that method for constructing the appropriate encoding but with a given color.

OK, the rules are complex enough that the client shouldn't need to know about them and so it can't do the modification directly. IOW, the client knows When it needs to be converted, but not How.

OTOH, that complexity argues strongly for encapsulating it in some other class than [SymbolFactory]. Conversion != Instantiation.

This also argues for my point above that conversion is a unique processing step. One has a Symbol in "native" format (e.g., what the <external> client wants displayed) and needs a Symbol in "terminal" format (i.e., that is tailored to the terminal in hand). The processing may be optional, but when it is done the conversion represents a unique context.

I am not keen on Symbol owning the conversion operations. The
conversion is how SymbolFactory creates a new Symbol, so it is
logical for it to understand the conversion rules. Conversely, it
doesn't seem reasonable that a given Symbol should know how to
convert to some other Symbol. If you need different conversion
implementations, then subclass SymbolFactory and instantiate a
relationship to the right source Symbol.


Symbol doesn't contain conversion operations for converting to arbitrary other formats, only two standard formats: ASCII and Unicode. I chose those two this because ASCII is what I expect to be using and Unicode is general enough to represent almost any symbol. The reason I felt that every Symbol had to have standard conversion methods was so that every SymbolFactory object would always be able to construct an appropriate Symbol, even if it is given a Symbol using an encoding that had not been invented when the SymbolFactory was created.

You can get away with that because getASCIICharacterCode() and getUnicodeCharacterCode() can be different getters for the same basic characterCode knowledge attribute. In that case the conversion is intrinsic to the knowledge itself. But... B-)

So I can create a new subclass of SymbolFactory with my own special representation scheme for symbols that no one but I knows, and then I can give the Symbols that I create to the factory for any terminal and it will make a good effort to display what I intended, by first converting my symbols to ASCII or Unicode and then converting again to whatever the terminal needs.

Converting between ASCII and unicode is one thing, but converting a given code to what can be represented on a specific terminal is quite another. That's what I thought you meant so I was complaining that it doesn't belong in [Symbol]. It belongs is someone whose mission in life is to understand the mapping between standard character codes and the specific terminal representations.

So if the character is Kanji and the terminal has no explicit support for Kanji, you will have to provide a representation the terminal does support. I would expect that representation to be different for a text terminal (a predefined character code) and a graphics terminal (a special icon). I don't see those sorts of decisions as intrinsic to Symbol.

Per my previous comments above, I also think such conversions are different enough to warrant encapsulation elsewhere than SymbolFactory.

I am also not keen on Symbol keeping track of what factory created
it. If the client needs to to process different flavors of Symbol differently, then it needs some other mechanism for doing the
right thing than trying to determine its subclass, however
indirectly that may be.


That is probably the biggest weakness of my design.


However, in this case I think the problem is easily resolved by
the application of Strategy I suggested above. When a Symbol is instantiated, SymbolFactory can instantiate the R2 relationship
because if already needs to know the particular flavor of Symbol
to create it. Then one always gets the right display package when
it is time to display.


That does seem to be a good idea for display, but as things are the Terminal always knows the specific subclass of Symbol that it will be dealing with because it can only deal with one. By having the Symbol store the SymbolFactory that created it, Terminal can ensure that the Symbol is using the right encoding and if not it can use the factory to automatically convert before it starts the actual display procedure.

I am arguing that Terminal should not need to know what flavor of Symbol it is accessing. It just navigates a relationship

1 R1 *
[Terminal] --------------- [Symbol]

to whatever is on the other end of R1. It is somebody else's job to make sure the right flavor of [Symbol] is there (i.e., has the responsibilities and implementations needed for the collaboration). In your case there are different flavors of both [Terminal] and [Symbol] and there are rules about which flavors can talk to each other. In that case the relationships are instantiated at the subclass level. But somebody else still understands those rules and ensures that Terminal always gets the right Symbol when navigating the relationship.

The real problem is in SymbolFactory which has a strong habit of determining a specific subclass out of a list of options. It could always convert to ASCII or Unicode without a dynamic cast, but often it can create a better encoding by using special knowledge about other encoding schemes. The most obvious example is that the factory might be given a symbol that is already in the target encoding, in which case converting to ASCII and then back might be disastrous.

The main idea I have been pushing here is that this problem is primarily about relationship instantiation. If one instantiates relationships properly as Terminals and Symbols are created, then those relationships define the structure of the solution. Once they are in place, things like symbol conversion and rendering become relatively trivial because the right participants are already in place for every collaboration. All the solution needs to do is "walk" those relationships through the structure using polymorphic dispatch to access the "right" behaviors.

FWIW, I think that on the whole you are already heading in the right direction with delegation to [SymbolFactory] and [Drawer]. You are also focused on the right problem: the combinatorial correspondence between [Terminal] and [Symbol] subclasses. I just think that things will become more clear if (A) you break up [Terminal] and [SymbolFactory] with further delegation and (B) think of the rendering as just "walking" a structure that is defined by instantiating relationships. That trivializes the rendering and focuses the solution on preparing the structure itself.


*************
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
    ... Now I am how giving a reference to a Symbol to the client forces me ... deal with particular terminals. ... the client wants to do is display a single symbol, ... Conversion from one format to another is by ...
    (comp.object)
  • Re: Text terminal rendering design
    ... The management of that array's content by the client is quite a ... The array and the terminal are more tightly tied together than having them as separate objects suggests: there is exactly one array for every terminal and in some cases modifying the array causes the UI to be immediately modified. ... This is rather beyond my control since I am not implementing the array in some cases, such as with PDCurses where the array is internal to that library. ... The fact is that to solve the problem you need a 2DArray for the terminals where you can't use PDCurses. ...
    (comp.object)
  • Re: Text terminal rendering design
    ... The client is an application with need of a UI that works by ... what symbols it needs to display and the coordinates at which each ... In the case of most text terminals, a symbol is represented by a single ... complex conversion operations to put the symbol into a form that the ...
    (comp.object)
  • Re: Text terminal rendering design
    ... The client software knows about Symbol ... Why does DataAcquisitionStrategy bother breaking up the data ... the position in the display for each of those desired elements. ... flexibility to be used on many different physical terminals. ...
    (comp.object)
  • Re: Text terminal rendering design
    ... it is a fundamental element of the UI design. ... The management of that array's content by the client is quite a ... The array and the terminal are more tightly tied ... to do that because of the need to map into flavors of terminals. ...
    (comp.object)

Quantcast