Re: Text terminal rendering design



Responding to Guild...

Besides, it is evidently creating new ones or requesting existing
ones to be loaded from a database. To do that there must be some
sort of identifier to indicate which one the client wants. That
identifier also needs to map 1:1 to whatever the database uses for
an identifier.


It is true that in some cases we need an identifier value for a symbol in addition to the actual Symbol object, but that is only seems to be only an implementation detail of one special case of how symbols can be acquired. If we are not getting the symbols from a database then we might not need identifiers. A symbol could be specified by construction, such as by overlapping existing symbols.

If you have external interface, identity is always a problem and a mapping is required. You can't store heap addresses in the database. Since the client is external, the interface for your software provides encapsulation and implementation decoupling. That encapsulation hides the implementation of your software from the client. So proper decoupling at the subsystem level /demands/ that the client should not know what objects implement your software, much less where they are located in memory. (If you pass back a handle, the semantics should be such that the client doesn't know it is an object address, even if the client is in the same address space.)

So you need some abstract way of passing identity across a message-based decoupling interface. There are three ways to do that:

- the message identifier. The message identifier itself can be used to identify the content. This only works on very fine-grained interfaces; usually the the message identifier identifies the type of request.

- positionally. The relative position of elements in the data packet provides identity for individual elements. This is common for managing attribute identity but not object identity.

- embedded identifier. The data packet contains an explicit identifier whose values are recognizable on both sides. This is normal for objects and components.

Presumably one has objects instantiated on both sides of the interface that one needs to identify for dispatching messages in the interface. The common way that is managed is through lookup tables that map an object identifier embedded in the message to the address of the object in memory. This is so common in subsystem interfaces that it essentially qualifies as a design pattern. So...

In effect, 2DArray is a mapping table between the identifier the
client understands and the object in memory, whose identity for
relationship navigation is its address on the heap. (Obviously it
would be convenient if the identifiers were consecutive integers.)


I had thought that the concept of the 2DArray was fairly clear, but now I am very doubtful. You mentioned that the 2DArray was actually four-
dimensional in an earlier post and I had to struggle to find some meaning in that. I must apologize for failing to understand your advice in this case.

2DArray is serving double duty. Here it is providing a lookup table for the interface to map the client's symbol identity into the memory address of a Symbol object...

Are you proposing that 2DArray represent a set of 4-tuples (id,x,y,Symbol), where each such tuple means that a coordinates (x,y) on the screen there is a symbol represented to the client by id and additionally represented by Symbol?

....and here it is also associating coordinates with a Symbol. IOW, it is really a 4DArray with accessors like getSymbol(id), getX(id), getY(id). Depending on the needs of the rest of the solution, one might also provide <overloaded> interfaces for getID(symbol), getX(symbol) getY(symbol) where 'symbol' is a Symbol address. (The second view just makes 4DArray more complicated internally as one addresses performance.)

Wouldn't it be better to let 2DArray be just an actual 2DArray and then have the mapping table be a separate data structure? In fact, I still wonder why we need to bother with symbol IDs at all when we have references to Symbol objects. I know you have tried to explain that to me and I will continue to try to understand, but it seems so wasteful and redundant.

I'll deal with the handle issue in a moment. For this context assume the client has its own explicit identifier for a symbol.

You need a mapping to a Symbol on the heap. Normally that 2D lookup table would be provided in the implementation of a Facade subsystem interface if the instance just needed to dispatch the message to that object. In this case, though, I think there are reasons why an explicit object is needed in the solution.

One is that one needs to associate coordinates with a symbol. Presumably the client tells you that when asking for a symbol to be displayed. IT would be tempting to put the coordinates in [Symbol] as attributes. But the instantiation of a Symbol is a complicated process as the software has to make decisions about what flavor, where to get the data, read a database, and whatnot. So one might need to temporarily hold onto the coordinates while one does all that.

Things get more complicated when Symbols need to be converted. Logically the coordinates should only go with the converted Symbol because that it the one that is actually rendered on the terminal so it is the only one that needs coordinates. That segues to the next reason.

Because we may instantiate multiple [Symbol] objects representing the same thing that the client thinks of as a 'symbol', the coordinates are really associated with the client's /selection/ rather than a specific [Symbol] object in your software. IOW, your [Symbol] objects do not map 1:1 to what the client wants; they are surrogates for the client's notion of 'symbol' that happen to be convenient in the UI implementation.

This last point is the real issue around 2DArray/4DArray. A central part of the problem is keeping track of what the client has /selected/ for display. That selection is conceptually different than the individual [Symbol] objects that you need to implement the display. Shadows on the wall of Plato's cave and all that.

So having 2DArray as an important abstraction to keep track of the selection itself makes sense. From there associating the coordinates with the selection (as 4DArray) rather than the [Symbol] objects, that may or may not actually render it, seems consistent. Finally, making that distinction between the client's selection and the role [Symbol] objects play in the rendering allows one to record the client's desires before launching into the complex processing necessary to actually get to a terminal image. If that complexity then gets spread over multiple objects now or during maintenance, it won't cause any problems like passing coordinates through long chains of method calls because the client's wishes are already saved for later reference when needed.

Having said all that, I agree with you that it is actually a pretty close call. One can make a pretty good case for putting the coordinates in a [TerminalReadySymbol] subclass that may or may not be directly created. That's especially true if one consolidates things like conversion and database access activities into a single [SymbolFactory] method that is invoked directly from the interface. My problem is that I don't like that consolidation idea because it makes the responsibility complicated and less cohesive.

Your only alternative is to provide the address to the client as
a handle when a symbol is instantiated that the client uses in all
subsequent communications.


This is what I am doing in my current design. The SymbolFactory returns a reference to the constructed Symbol to the client and the client then uses that as a handle to instantiate the relationship between that Symbol and the Terminal as necessary.


That just moves the problem of associating memory addresses with
symbol identifiers back into the client.


That seem clear enough, though I hadn't thought of it as a problem. I expect that the client will often need to work with Symbols as abstract atomic values. Usually when a client object A is put into a relationship with a symbol, it will not be expected to perform any sort of analysis of the symbol; the relationship only exists so that the symbol will be used as a UI element to stand for something that the object A needs to represent to the user.

For that sort of thing, an address handle works just as well as any other sort of symbol identifier.

While handles are convenient for certain situations like dealing with an OS Window Manager, they are really kind of a kludge that bleeds cohesion across subsystem boundaries no matter how abstract they are. The client receiving the handle still needs to keep track of it and associate it properly with the right context. For a window handle that is usually pretty simple because a GUI subsystem is going to have Window objects and the handle just becomes a secondary identity.

That works well because there is a 1:1 mapping between OS window handles and Window objects. Equally important the creation of windows is in lock step between the GUI subsystem and the OS Window Manager. As a practical matter one always requests a window from the OS Window Manager exactly when one instantiates a Window object on the GUI side.

However, that "practical matter" is actually driven by the way the OS Window Manager's interface is designed. If one waits between instantiating the Window and making the OS Window Manager request, there is a risk of getting out of synch and running into rude referential integrity problems. So even though the window handle is semantically quite abstract on the GUI side, the GUI-side solution is being driven to some extent by it because of the practical constraints of synchronization. IOW, the <service> tail is wagging the <client> dog.

Bottom line: using handles is really only suited to very constrained and specialized situations. Which segues to...

That will get messy if the it takes awhile to instantiate a Symbol
or the client communications are asynchronous.


Can you explain in more detail how that makes it messy? I have an idea, but I would like to confirm it.

The client has some client class Icon of objects that are in a 1:* relationship with a symbol, i.e., Icon objects must always have an associated symbol. If that symbol is represented according the the client's view of the symbol, it is immediately available when each Icon is being constructed. However, if the client uses an address handle it must wait for the SymbolFactory to finish instantiation and that time could leave an Icon in an inconsistent state where an asynchronous process could access it.

That 1:* relationship is an example of where that mapping can get messy. Why does the client need to know that there might be two different [Symbol] objects needed to process the display request? That is purely a problem of the way that your UI software needs to deal with particular terminals. At its level of abstraction all the client wants to do is display a single symbol, Icon.

As I indicated in the GUI example above, a crucial feature of /useful/ handle mapping is that the mapping is 1:1 so there is no ambiguity. To use the 1:* handle relationship your client must understand the semantic differences between the symbols to use them correctly in the interface. But those differences are only relevant to the UI subject matter and are none of the client's business. So using 1:* handles is exposing the UI implementation to the client.

If your UI returns a single handle for Icon, that opens another Pandora's Box when a conversion is needed: which handle to return? One way or another that gets messy due to synchronization problems (e.g., the client may need to replace the "natural" handle you just gave it with a new "terminal" handle).

<aside>
While I am not a big fan of handle-based infrastructures for the reasons I gave in the GUI example above, they happen to be crucial for asynchronous subsystem interfaces. However, the handles are usually managed completely within the Facade implementations of the subsystem interfaces so that they are not visible to the implementation of the subsystem. I don't want to get any further side tracked than we already are, so I won't go there. But I think I talk about it in the Bridge Model posting of the Application Partitioning category of my blog.
</aside>

* R1 gets data 1
[SymbolFactory] ------------------ [DataAcquistionStrategy]
+ createSymbol() + getInitializationData()
+ dataElement1
+ dataElement2
...
A
| R2
+--------------------+----------------+
| | |
[FromClientRequest] [FromOtherSymbol] [FromDB]

Now for all situations createSymbol() just looks like:

navigate R1
invoke constructor (dataElement1, ...)


Then I don't understand why we even have SymbolFactory if its job is so trivial. Why does DataAcquisitionStrategy bother breaking up the data into elements and offering each element individually instead of directly constructing the Symbol and offering that? Clearly DataAcquistionStrategy knows the nature of the symbol encoding it is acquiring data for, if that data is going to be useful.

That's because I was focused on the delegation of acquiring data. The reason is for what I didn't show: instantiating relationships. Your problem is ideally suited to patterns like Strategy and dynamic instantiation of relationships. The factory encapsulates two things: the instantiation of the right flavor of object and the instantiation of its relationships to the right flavors of other objects (e.g., [DisplayStrategy]). In your problem the rules of participation in those relationships are a critical part of the solution.

There is also another concern not shown. [SymbolFactory] will probably be subclassed due to the need to support different sorts of terminals. That subclassing is orthogonal to the data acquisition. So even if all one does is invoke a constructor, it may be a different constructor in each situation. Instantiating a simple relationship to a subclass is a lot cleaner than putting a bunch of cascaded IF statements in each location where a symbol may be instantiated.

I would agree with you if it turned out to be that trivial and my knowledge of the problem space suggested that being trivial was an invariant of the problem space. However, I would also argue that in the OOA/D one should initially assume more complexity than less. So I litter the model with factories until the solution demonstrates that they really are trivial. Eliminating a factory whose method just calls a constructor is easy to do later. Refactoring construction logic out of an object to put it in a factory may not be so easy.


Unless SymbolFactory does something more than construct a wrapper for the data in DataAcquisitionStrategy, isn't SymbolFactory just a useless middleman?

Let's assume all that is left after delegating [DataAcquisitionFactory] is a constructor call and we don't need subclassing. We still have the fact that SymbolFactory gets called in two contexts: creating a "terminal" symbol and, sometimes, creating a "natural" symbol. I argue that the solution will be more flexible if we isolate that instantiation in a single method called from different contexts. It then makes sense to encapsulate it in an owner object because context collaborations are peer-to-peer at the object level. (See the discussion of the Sequence Diagram redux below.)

I am aware that the various means of creating symbols have
equally various implementations, but that is not enough
explanation for why the factory might be better as several
objects rather than one. In the current design a factory is
nothing but a stateless collection of methods that presented to
the client as means of building symbols for later rendering.
Splitting the one object into several means delivering that
collection of to the client methods as a collection of objects
rather than one object.

In a phrase: to avoid god objects. As another phrase: divide-and-conquer. In more PC OO terms: to ensure high cohesion.

I assert that if behaviors are broken up and spread over multiple objects, it will be easier later to connect the dots of flow of
control with messages.


I want to be clear about this. Are you advising me that my SymbolFactory should not be an instance of the GoF Abstract Factory pattern? The Abstract Factory pattern is about an interface for creating families of related objects by providing various methods within a single factory object.

As a matter of fact, I think it is quite likely that your [SymbolFactory] will be one of the GoF construction patterns. The problem I foresee is the opposite: the GoF patterns may be inadequate.

I want to delegate data acquisition responsibilities because they are really orthogonal to the terminal-based criteria that determine what sort of [Symbol] it must instantiate. You have 3 ways to get the initialization data and N different terminal contexts for which you instantiate a Symbol. If you subclass [SymbolFactory] for all those combinations you have 3N subclasses. If you delegate data acquisition you reduce that to N + 3 total subclasses. You just have Abstract Factory AND Strategy rather than just Abstract Factory.

[That's not quite true because the attributes needed may be different for different Symbols, which would lead to a combinatorial number of strategies for the data acquisition. I am assuming there are only M Symbol types with different attributes where M < N. IOW, I am assuming the same symbol type can be eaten by more than one terminal type.]

Getting the initialization data from the client, the database, or
as a conversion from another Symbol are different activities with
different business rules and policies. In this case those rules
and policies seem to be pretty crucial to the problem in hand. I
would like to see those isolated and encapsulated so one can deal
with each context independently. While getting the right
initialization data is the factory's responsibility, when that
becomes complicated it is time for delegation.


But I do not see how that makes it any less complicated. Surely the factory will be less complicated because we are moving the complicated part to another object, but the complicated part is still just as complicated in its new home.

Statically it is more complicated because there are more classes and relationships. But dynamically it becomes simpler and easier to manage. That's because we have eliminated any possibility of combinatorial interactions. We are using encapsulation and implementation hiding to separate the concerns. We are using dynamic relationship instantiation and polymorphic dispatch to substitute behaviors among different contexts at a much lower level of abstraction (i.e., context is evaluated and each concern is managed independently). IOW, we have much better focus. We can deal with each concern in complete isolation from the others.

But the real key is maintainability. If you don't factor out data acquisition, you have 3N subclasses, each dealing with both acquiring data and building the right Symbol. What happens if requirements change and you find that you need to add some common processing /between/ acquiring the data and actually instantiating the symbol? Now you have to perform surgery on every one of those 3N subclasses. But if data acquisition is delegated, one puts processing in one place and just reorganizes the messages.

As it happens, I have a hard time coming up with a plausible example of what such processing might be for your problem. But that really doesn't matter. OO developers are not in the business of planning for future requirements changes. What we do is build the software so that it will be easy to modify for /any/ requirements change. To do that we employ generic techniques like separating concerns through delegation on general principles.

When the client requests creation of a Symbol from the DB,
we initialize R1 to acquire the data from the DB and we invoke
DataAcquistionStrategy to get it. Then we invoke SymbolFactory to
create a Symbol using that data. Now we have a "native" Symbol in
memory. At that point somebody can check the terminal type and
realize a new "terminal" Symbol must created from it. So that
somebody instantiates R1 again for the conversion flavor and
repeats the process.


I am not sure what you are suggesting this as an alternative to. My current design does almost exactly this, but instead of having DataAcquisitionStrategy get data, it gets a Symbol object and we skip the SymbolFactory step. The rest is entirely the same, including allowing someone or anyone to check the terminal type and invoke a converter.

Then I missed something in your explanation. As I understood it, if you did not need a conversion you invoked a different factory method -- either in the same [SymbolFactory] subclass or in a different [SymbolFactory] subclass -- to deal with a situation like sourcing from the DB or getting data directly from the client.

If a different method is used for direct, DB, or symbol sourcing, then the caller has to understand the context of where the initialization data comes from. If combinatorial subclassing is used, then instantiating the relationship requires applying the rules for terminal type and data acquisition at the same time. In addition you need to encode a unique method for every combination of data sourcing and terminal type.

Why is this better? Because it doesn't matter who makes the
decision about converting the symbol.


So I do not understand your meaning here when you are trying to explain why it is better.


The code in SymbolFactory and DataAcquistionStrategy is exactly the
same not matter how many times the requirements change for who
needs to make that decision (or the criteria for how it is made).


I notice that you often talk about "SymbolFactory and DataAcquisitionStrategy" together. That alone suggests that they are rather tightly tied together.

They could be. As I pointed out in the Sequence Diagram discussion, SymbolFactory could invoke DataAcquisitionStrategy directly. The point is that they don't need to be. Once data acquisition is delegated, one can insert an arbitrary amount of processing between those two activities. (See the Sequence Diagram redux discussion below.)

They are tied together because DataAcquisitionStrategy needs to know what data the corresponding factory needs. If you find a new source of symbol data you not only need a new DataAcquisitionStrategy, but also likely a new SymbolFactory to construct a new Symbol subclass to store the new form of data that has been acquired.

Actually, I think [DataAcquisitionStrategy] is tied to the type of symbol that must be produced. Values for that symbol's attributes are what the strategy must provide.

Let's say I need to build a Symbol of SymbolX type. I will need a strategy to provide the SymbolX attributes from each of the three relevant sources. If a new source of initialization data appears on the scene, I will need a new strategy for extracting that source for SymbolX. But the terminal still eats the same SymbolX attributes, so it would not need to change.

In addition, it might be possible to use SymbolX for multiple terminal types. That is, multiple subclasses of [Terminal] might be able to eat the SymbolX format. That sort of situation could arise for lower level conversions like ASCII/unicode that just change values.

Similarly, if you want a new encoding for symbols you need a new subclass of Symbol and therefore a new subclass of SymbolFactory, and so long as SymbolFactory is forbidden from doing anything interesting, you need an entirely new strategy class to take the place of DataAcquisitionStrategy and supply the correct data for the encoding.

This triggers a thought about something that we haven't talked about. That is related to the basic notion of having two versions of [Symbol], one derived from the other. I think that notion might be useful in a much broader architectural sense, rather than regarding conversions as a special case.

You are getting data about symbols from the client or from a database. Those represent views that may or may not conform to the terminal view. In fact, we would expect them to be independent of the terminal, which implies they might not have all the rendering detail needed for some terminals. OTOH, we know that there are certain concrete views of 'symbol' that the various terminals need.

This suggests that architecturally one might want to think about the solution as /normally/ involving a conversion of [Symbol] views from abstract to concrete. Then the situation where one doesn't need a conversion becomes the special case.

The reason I am bringing this up is that it allows one to think about the client/DB view in terms of symbol invariants that are common to all symbols. That may lead to one or a very few flavors "source" or "natural" symbols.

The problem then becomes a sequence of conversions. The first is to construct the generic abstract view from whatever symbol description one has. The second is to convert the generic abstract view into a concrete view for the terminal de jour. That may make the combinations more manageable.

[I have no idea if this mindset would help the design. That will depend very much on the specific types of symbols you need to display.]

That also gets back to my assertion about the flow of control
being easier. Because the behaviors are isolated/separated AND one
has captured business rules in relationships, one can connect the
flow of control dots of the overall solution any way one wants
after objects have all been defined.


It is asking for a large amount, but it would be very helpful if you could give a small example of how that could be used, especially something that could not be done if SymbolFactory and DataAcquisitionStrategy were not separate objects.

Note that I asserted that it would be easier, not that one could do something your approach can't.

Assume we subclass [SymbolFactory] with one subclass for each type of [Symbol] that we might need to produce. Each subclass has a create() method that invokes the right constructor and instantiates the right relationships for the created symbol based on its type. I assume you will agree I can write the code for each method without knowing anything about the rest of the solution.

Assume we subclass DataAcquisitionStrategy for each combination of [Symbol] subclass we need to create and the possible sources of data for that symbol format. Each subclass has a single getData() method. Again, I assume you will agree I can write the code for each method knowing only the attributes needed and where the values come from. That is, any conversion rules can be expressed purely in terms of the target attributes and the source.

Assume 2DArray internally is an array of {symbol ID, x, y, symbol address} and it has getters/setters for those attributes. [I deliberately defer deciding the specific getters/setters until later in the discussion because they will depend on when the information is added. But that is mainly a cosmetic adjustment in the interface.]

Assume ClientRequest is an object that manages the preparation of a Symbols. I am vague about what it actually does because that will depend one how we decide to manage flow of control.

Consider my Sequence Diagram without any messages:

ClientRequest SymbolFactory DataAcquisitionStrategy 2DArray
| | | |
| | | |
| | | |

Now assume a single use case that involves creating a Symbol where the only way to do that is by reading the DB, instantiating the DB view of the Symbol, and then creating a new Symbol by converting/augmenting its data. The problem is to fill in the messages on the Sequence Diagram _after all the behaviors have been encoded_.

Basically we have the following tasks to do:

(1) record the client's request to display a symbol and its location
(2) read the DB
(3) instantiate a Symbol from the data in (2)
(4) convert the attributes in (3)
(5) use the converted values from (4) to instantiate a Symbol
(6) update the 2DArray mapping function with the symbol from (5)

Note that steps (2) through (6) are constrained to be done in a specific order. Step (1), OTOH, could be done anytime so long as 2DArray gets updated at or before Step (6). The question of the hour is: How do we define that sequence in terms of messages?

One way is my original proposal where ClientRequest ran the whole show. It generated all the triggering messages for all the activities. It would wait for a handshaking acknowledgment from each activity before triggering the next. Since it was running everything I made an arbitrary decision that it would update 2DArray via addSymbol (id, x, y, reference) at the end of its processing.

Now let's look at an equally viable solution for the flow of control:

ClientRequest SymbolFactory DataAcquisitionStrategy 2DArray Terminal
| | | | |
| | | add(id,x,y) | |
|------------------------------------------------->| |
| | | | |
| create | | | |
|------------>| | | |
| | getData | | |
| |------------------>| | |
| | | | |
| | done | | |
| |<------------------| | |
| | | | done |
| |---------------------------------------------->|
| | | | |
| | create | | |
| |<----------------------------------------------|
| | | | |
| | getData | | |
| |------------------>| | |
| | | | |
| | done | | |
| |<------------------| | |
| | | | done |
| |---------------------------------------------->|
| | | | |
| | | |<---------|
| | | | add(id, |
| | | | ref) |
| | | | |

And yet a third solution:

SymbolFactory DataAcquisitionStrategy 2DArray Terminal
| | | |
| | getData | |
| |<------------------------------------------|
| | | |
| | | done |
| |------------------------------------------>|
| create | | |
|<---------------------------------------------------------|
| | | |
| | | done |
|--------------------------------------------------------->|
| | | |
| | getData | |
| |<------------------------------------------|
| | | |
| | | done |
| |------------------------------------------>|
| create | | |
|<---------------------------------------------------------|
| | | |
| | | done |
|--------------------------------------------------------->|
| | | |
| | |<---------------|
| | | add (id,x,y, |
| | | ref) |
| | | |
| | | |

The implementation of DataAcquisitionStrategy, SymbolFactory, and 2DArray is exactly the same in all three solutions. But from the perspective of the overall solution flow of control, these represent three entirely different solutions. In addition, the responsibilities of ClientRequest and Terminal will be quite different. (In fact, they only both appear in one solution.) And that segues to the real point here: How does one choose between these solutions?

That choice will be made based on what is going on in the overall solution /outside/ of SymbolFactory, DataAcquisitionStrategy, and 2DArray. That is, the choice will be driven by how one defines objects and responsibilities in the rest of the solution. That will be in response to other requirements than the mechanics of creating Symbols and acquiring data.

That is manifested in the fact that SymbolFactory and DataAcquisitionStrategy are accessed by different objects in each solution. More to the point, it is only in the second solution that DataAcquisitionStrategy is accessed by SymbolFactory. And that is the only solution where one could combine the two as in your solution.

So as soon as you combine them, you immediately preclude the other two solutions for flow of control. That inflexibility may make it more difficult to make these objects play together with other objects. It will also be a problem if the requirements change in a manner that can only be satisfied by one of the other solutions.



[Terminal]
| 1
| displayed for Client on
|
| R1
|
| *
[Symbol]

The R1 relationship contains only those Symbols that the external
Client has indicated it wants displayed. The collection class for
that * relationship becomes 2DArray when the Client also specifies
the position in the display for each of those desired elements.


That seems to be how things are in my current design.


A Symbol is only added to the R1 collection when it is created


However, that is certainly wrong. A Symbol is only added to the R1 collection when it needs to be displayed according to the client's UI needs. Therefore, a Symbol can be created at any time before it needs to be displayed and it can be added to the R1 collection many and various times after being created.

That confirms my suspicions of a disconnect over the semantics of 2DArray. Why would the UI create a symbol if it wasn't going to be displayed immediately?

That only makes sense if one wants to create all the /available/ symbols displayable by a terminal at startup. I would argue that is semantically a separate collection of [Symbol] objects. (I agree that if this is the case, then the Symbol references in 2DArray can be provided from the existing stock rather than being created.)

[BTW, I didn't even think of this possibility because it is really an optimization in response to nonfunctional requirements on performance. As a translationist, I only think in terms of functional requirements in an OOA model because dealing with nonfunctional requirements is a job for a different union (transformation engine developers). B-)]

So to answer your question, there are lots of ways to instantiate relationships outside of factories. But those are related to
dynamic contexts. When an object has unconditional relationships,
at least one participant must be provided to ensure referential
integrity and, in doing so, it is best to do it in a single
method's scope.


I could be wrong, but it seems things have gotten confused somehow. The relationship between the terminal and a symbol has always been described as being highly dynamic, hasn't it? Symbols appear and disappear and move around on the screen in complex patters again and again over the course of an application. Surely it is a perfect example of a dynamic relationship.

I have discovered my mistake. I keep flailing around with my UML diagrams. My first one had this:
0..* displays 1
[Terminal] ------------------>[Symbol]
Which is obviously horribly wrong, since a terminal will display more than one symbol. Unfortunately, I tried to correct it by turning it into the following in my next post:
1 R1 displays *
[Terminal]--------------------[Symbol]
Which is a lot closer to correct, but now I see that it is subtly wrong. In fact it is equally ridiculous because it implies that every symbol is continuously displayed. It should have been:
0..1 R1 displays *
[Terminal]----------------------[Symbol]
To allow for Symbols that are not displayed. If that is the source of our misunderstanding then I strongly apologize.

That will be true if you instantiate Symbols at startup (or for other reasons than display). As I indicated above, that is a disconnect we had.

As a thick-skinned veteran of too many design reviews to count, though, I can say it is not something one needs to apologize about. B-) If natural language was unambiguous customers would always get what they really wanted.

The notion of drawing something is related to a display subject
matter. That is quite different than the intrinsic properties
associated with the notion of 'symbol'. A symbol exists in a problem
space quite independently of whether it is displayed on a computer.
Displaying things is a responsibility of objects that abstract the
display paradigm itself.


That is why my current design makes Terminal responsible for drawing. You seemed to be suggesting that was bad.

Not exactly. I have no problem with rendering being Terminal's responsibility. What I have a problem with is terminal controlling or being heavily involved with keeping track of what symbols the client wants displayed (an embedded 2DArray) or controlling how Symbols are created. So I have been pushing for getting everything /except/ rendering out of Terminal

At some point somebody needs to make some decisions about what flavor of [Symbol] to create and how to instantiate relationships. Those decisions will very likely based on some of Terminal's knowledge responsibilities (e.g., what type of terminal it is). But I think somebody else should own the decision making.


Unfortunately, this necessarily requires that Terminal know the subclass of each Symbol object that it is given, but I can certainly see no way around that. At some point someone is going to have to cast from Symbol to a subclass, no matter who is given the responsibility of drawing. (Unless it is the Symbol itself, but you've made it clear that is bad for other reasons.)

This was one of our original disagreements. I don't think [Terminal] needs to know anything about Symbols. And there certainly shouldn't be an casts. Getting the right data from a symbol only requires that the right flavor of [Symbol] is provided at the end of the relationship. If [Terminal] needs specialized properties that only some subclasses of [Symbol] can provide, then the relationship needs to be instantiated at the subclass level so a Terminal can be 100% confident that the Symbol on the other end has exactly what it needs.

[Symbol]
A
|
+-----------+---...
| |
[TextA] [GraphicsZ]
| * | *
| accesses | accesses
| |
| R2 | R3
| |
| 1 | 1
[Text1] [Graphics5]
| |
+-----------+---...
|
_
V
[Terminal]

This is straight forward so long as a terminal type and the symbol format it eats maps 1:1. The implementation of each [Terminal] subclass would "hard-wire" the single [Symbol] subclass type that was appropriate. For example, in C++ we might have:

class Text1 : Terminal
{
private:
TextA* mySymbol;
....
}

class Graphics5 : Terminal
{
private:
GraphicsZ* mySymbol;
....
}

My understanding, though, was that a given [Terminal] subclass could access a subset of the [Symbol] subclasses and a given [Symbol] subclass could be fed to a subset of [Terminal] subclasses. I thought that you were introducing [Drawer] to reify that problem (as opposed to using the Visitor pattern for a full combinatorial mapping).

You originally attributed rendering of a symbol as a
responsibility of Terminal. If so, I would expect the drawSymbol
behavior to be there.


Then I am out of ideas for what you could have meant by this (from a earlier post):

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


If you expect Terminal to draw Symbol, how can it get away without knowing even the flavor of Symbol?

If it accesses polymorphically at the [Symbol] superclass level, then every subclass of [Symbol] will provide a viable implementation of the attributes Terminal needs. Then the substitution will be completely transparent to Terminal.

However, since we are talking about data, it seems much more likely that a given flavor of [Terminal] will need different attributes from the Symbol it eats than other flavors of [Terminal] need. In that case the relationship is instantiated at the subclass level (or an intermediate superclass).

OTOH, if there is a fixed suite of things to draw, the idea of a
DisplayStrategy or RenderingStrategy owning a generic draw() behavior
is quite appealing.


That does sound appealing, but I cannot imagine how it could work. What sort of fixed suite are you talking about? I think the problem may be that the drawing process is too simple for a strategy pattern. After all the conversion work has been done to carefully arrange for a symbol in just the form that Terminal wants it, there is really only one way to draw that symbol for any given Terminal.

Let's say for a given terminal type, say TypeB, there are three viable flavors of [Symbol] that can be rendered on that terminal but each flavor requires accessing different data or uses different algorithms for the rendering. If we have:

[Terminal]
A
| R1
....-----+---------+----...
| |
[TypeA] [TypeB]
| *
| displays symbol for
|
| R2
|
| 1
[RenderingStrategy]
+draw()
A
| R3
+-------------+----------+
| | |
[Flavor1] [Flavor2] [Flavor3]
| 0..1 | 0..1 | 0..1
| | |
| R4 | R5 | R6
| | |
| eats | eats | eats
| 0..1 | 0..1 | 0..1
[Flavor1] [Flavor2] [Flavor3]
| | |
+-------------+----------+
| R7
_
V
[Symbol]

This assumes one of R4, R5, and R6 is instantiated for the current Symbol being displayed.

So long as R2 is instantiated properly for the symbol in hand, Terminal just invokes RenderingStrategy::draw() and everything Just Works. Note that Terminal has no clue which Symbol is being eaten, or even that a Symbol is being eaten. The implementations of the [RenderingStrategy] subclasses will be hard-wired to read a particular flavor of [Symbol].

Note that assigning R2 and R4/R5/R6 is deterministic for a given terminal type when one creates a [Symbol] because one knows exactly what type it is. (If [Symbol] is created at startup, one needs a type attribute to know what it is when the client selects it for display.)

PDCurses is providing all the infrastructure for a /full/ solution.
The problem is that the solution is only appropriate for part of your
problem. The techniques you need to manage /your/ solution extend
PDCurses in ways it was not designed to support.


I am not exactly trying to wrap my solution around PDCurses. I am trying to create a general text terminal subsystem which has the flexibility to be used on many different physical terminals. I am doing this by supporting multiple symbol encodings and encoding conversions and I am creating a subclass of Terminal for each type of physical terminal I might encounter.

PDCurses only comes into it because if my design is at all effective then I should be able to implement a Terminal subclass and associated classes to make my subsystem smoothly work with PDCurses. After all, PDCurses is a library designed to work with text terminals, so the job should be trivial.

Not necessarily. I suspect the situation may be analogous to LISP and Smalltalk being incapable of coexisting with code from other languages for the '70s and most of the '80s. Each had an IDE that wanted to run everything so other code had to be subordinate to that model. PDCurses may have its own way of doing things that requires any extensions to be built around it.


*************
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
    ... because the client invokes a method on a particular factory does ... Terminal and SymbolFactory are part of the interface Facade?!? ... Clearly the notion of 'terminal' is unique to the subject matter of rendering on display terminals. ... the subsystem /interface/, not the subsystem itself. ...
    (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?
    ... >>server is a pure data transfer interface. ... essentially exposing the client or service implementation. ... >>paradigms can be abstracted just like any other problem space in an OO ...
    (comp.object)
  • Re: Abstract public member variales?
    ... Entity has no encapsulation and no real methods, but the great thing about it is that its public interface will never have to change during maintenance. ... Assuming there is at most only one Property instance for each property type, then the R1 collection class would own the smarts for finding the right one. ... However, I would point out that the client of the getter is someone who needs to collaborate with a specific Property, not the Entity itself. ... The second line generates a message to the Property for the collaboration. ...
    (comp.object)
  • Re: Text terminal rendering design
    ... Which is exactly what would happen in your case when the interface of the implementation object changes. ... But then you need a new object to own the actual responsibility within the subsystem implementation. ... I can pop in a facade in a completely painless manner without being forced to rename or kludge anything. ... Facade to avoid touching the client. ...
    (comp.object)