Re: Text terminal rendering design



Responding to Guild...

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.


That is an interesting idea, but it has some issues that are concerning me. 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.

All that says is that the multiplicity is 1 on both sides of the Terminal/2DArray association. In effect 2DArray /is/ the collection class between Terminal and Symbol. It is just "smart" because it also manages coordinates.

I don't know anything about PDCurses, so I can't really comment specifically on that problem. But I do have a megathinker view. As I understand it you are just using PDCurses to save coding for particular text terminals. In general I don't think it is a good idea to let your implementation technologies drive your design. There are a lot of pig-slow applications around that are a nightmare to maintain because they tried to use CRUD/USER layered model infrastructures to save coding when solving problems outside the CRUD/USER realm.

The fact is that to solve the problem you need a 2DArray for the terminals where you can't use PDCurses. In fact, 2DArray is needed to solve the problem for /all/ the terminals at the OOA/D level where one knows nothing about PDCurses. IOW, PDCurses is just an OOP implementation for the OOA/D that has 2DArray in it. The problem you are encountering is that the PDCurses implementation does not implement 2DArray in a way that is convenient for other terminals (i.e., your access is limited or indirect). So I think you have some hard choices...

(A) Find a way to isolate PDCurses in a service subsystem. At some point you have to talk to the terminal hardware. You may be able to treat PDCurses as a sort of device driver that your solution talks to when it is time to actually render something to particular text terminals. There will probably be some inefficiency due to redundancy since PDCurses will be internally duplicating 2DArray.

(B) Find a way to use a subset of PDCurses functionality that doesn't involve the 2DArray. IOW, use PDCurses as a simpleton put(...) for display writes.

(C) Look for a better tool that is more compatible with the overall design you need to provide for the full problem. That will probably be more akin to a low level terminal device driver that just abstracts register reads and writes, which may not save a lot.

(D) Encode the solution without PDCurses. Many of the operations you are talking about are already pretty low level (e.g., ASCII/unicode conversions), so it may be that all PDCurses is really providing is the implementation of 2DArray for certain character terminals. You are going to need 2DArray anyway for other terminals, so you may not loose all that much.

Having said this, I discovered below that we may have a disconnect one what 2DArray really is (see below).

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


In cases where the array is out of my hands, to do what you are suggesting I would have to create a redundant array and use it to modify the array supplied by the low-level library.

OK, but that is an implementation issue. I am arguing that you need to solve the customer's problem at the OOA/D level first. If that maps conveniently into a PDCurses implementation, fine. But if it doesn't, I would be very reluctant to change the design to make it fit.

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 do not understand what you intend to be the difference between symbol ID and Symbol reference. In my current design, a Symbol object holds whatever is needed to render the Symbol: in some cases a int that can be given to PDCurses and in some cases a reference to a Drawer object that knows how to render the symbol to some graphics library.

Your client software is external so it can't possibly know about Symbol references. 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.

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

So once we have a Symbol object, what could we need an additional symbol ID value for? I cannot clearly see what you intend its use or purpose to be in the array.

If the client wants you to do anything at all with a particular symbol once it is instantiated in memory it will have to identify that symbol to your software in its request. Since it can't know the address, you will need to find the reference as a table lookup on the symbol identifier the client provides.

[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. That just moves the problem of associating memory addresses with symbol identifiers back into the client. That will get messy if the it takes awhile to instantiate a Symbol or the client communications are asynchronous.]

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


I am unclear on the difference between these two messages. In my current design, creating a symbol is done by sending messages to a SymbolFactory which offers a variety of methods for translating the desire of the client into something that can be rendered. These include more complex and varied messages than those two, such as translating an arbitrary symbol generated by a different means into the closest symbol that can be generated by this factory.

Isn't 'extract a symbol from a database' just one of the many more explicit versions of the general 'create a new symbol' message?

All I am emphasizing here is that instantiating a Symbol in memory requires different processing if the source of initialization values is a database or the <external> client. Basically one has:

parse method arguments to attributes
invoke constructor.

versus

construct DB query
submit query
parse returned dataset to attributes
invoke constructor

Also note that these activities have nothing to do with converting an old Symbol into a new Symbol or rendering. Those are independent and disparate activities. Cohesion demands that a "factory" object focus on instantiation, preferably one flavor of instantiation. At a minimum I would subclass SymbolFactory. If conversion is needed, I would probably factor out accessing the initialization data to a Strategy pattern:

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

Before createSymbol() can be invoked, one always needs to understand the context for where the source data is, so that context can instantiate R1 to the right flavor of [DataAcquisitionStrategy]. Since the same values are always needed regardless of the source, the [DataAcquisitionStrategy] just puts them in superclass attributes for SymbolFactory to read.

- nest symbols within a larger symbol (BigSymbol)


I'll have to put worrying about more advanced features on hold for a while until I figure out the design of the simpler features.

Good plan. B-)

- define coordinates for a symbol's display


This doesn't seem to work as a separate message. We aren't allowing the client to handle references to Symbols, so it has to specify the coordinates within the same message where it is asking for the Symbol to be created.

OK. 2DArray works fine either way. This way one can have [SymbolFactory] update 2DArray with everything at once. Since it is a create message, the interface could dispatch it directly to a SymbolFactory.

However, I'm guessing that figuring out which [SymbolFactory] to use (or which [DataAcquisitionStrategy]) may be too complicated or too important to the solution to hide in the interface (Facade pattern) implementation. Similarly, if a conversion is necessary, one needs to create the "native" Symbol first and then create the new one via conversion. So I would start looking for an object like [ClientRequest] or [CreateRequest] that would have the smarts to coordinate those activities. (Example below)

- identify a list of symbols (2D Array)


I am not sure what you mean by 'identify' here. A 2D Array of symbols is innate in any terminal implementation and it does not need the client to identify it. For example, it will usually not be reasonable to allow the client to switch back and forth between several 2D Arrays.

But as I understood it the external client is requesting a specific set of symbols for display. That set is what 2DArray would be managing. If this is just the set of all available symbols for rendering on a given terminal (e.g., the complete ASCII set), then I am missing something somewhere.

[Apropos of the PDCurses issue above, if this array is just the available symbols, then it may be easier to map into PDCurses. That's because it is a different set of symbols than the one I was talking about. (Though all client-requested symbols would have the same Symbol reference in both tables.)]

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.


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. [There is a quite rigorous, albeit tedious, approach to defining messages in OO applications based on DbC. But that technique only works of behaviors are self-contained and highly cohesive. Breaking up objects through delegation helps to ensure that. Polymorphic dispatch and dynamic relationship instantiation also support it.]

But the main reason is maintainability. When the solution is formed by connecting dots of encapsulated, logically indivisible, and independent behaviors it becomes much easier to modify when requirements change. Surprisingly often one can substantially change the solution by simply reorganizing where messages are generated without touching the method code at all. When the methods do need to change, the changes will usually be nicely isolated and can be implemented without surprising side effects.

In addition, providing more objects with fewer, simpler, more focused responsibilities has two other important advantages. One is that it allows one to capture business rules and policies in static structure. That is manifested in things like instantiating relationships. That tends to reduce the executable code around collaborations substantially. That, in turn, improves reliability because there are fewer executable statements in the collaborations where defects might be inserted.

The second advantage is that static structure in the form of relationships that are instantiated at the object level are the OO paradigm's primary mechanism for limiting access to state variables. The more narrowly participation in individual relationships is defined, the less opportunity there is for accessing state variables unexpectedly. (As a bonus there is a performance benefit because searches are severely limited compared to the RDB query/join paradigm.)

So I am not belaboring breaking up objects like [Terminal] because I think it a crucial to the solution to this particular problem. I am advocating it on general OO principles because I think it will /probably/ make the overall solution easier and because I /know/ it will make the overall solution more maintainable.

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.


I have also delegated format conversion into SymbolFactory, together with Symbol instantiation. I am having trouble imagining it anywhere else, though I am aware that you consider them to be separate concerns.

All I see is that the client has one representation of a symbol and the the terminal has another. The job of creating a Symbol object is exactly the job of converting from the client's symbol representation to the terminal's representation. After all, you cannot construct a Symbol without knowing what it should contain, and if you know what a Symbol should contain there is no way to give that knowledge to any other procedure but by constructing the Symbol and passing it.

I am arguing that creating a symbol in memory is about invoking a constructor with a set of attribute values. That's all.

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.

For example, to create a new symbol by converting an old one you need the old Symbol in memory. So it needs to be created by a SymbolFactory, just like the new Symbol. That's a three step process: create old, convert old attributes, and create new. Having the SymbolFactory for the new Symbol own the creation of the old Symbol seems unnecessarily complicated -- especially if the new and old Symbol only differ in a particular value of the same character code (i.e., one invokes exactly the same constructor with a different value).

Similarly, providing different behaviors for different source contexts complicates the factory. Since getting the right data is distinct from the details of instantiation, that makes a good candidate for delegation.

But if one separates those activities as I did above with the [DataAcquisitionStrategy], things are much simpler and easier to modify. 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.

Why is this better? Because it doesn't matter who makes the decision about converting the symbol. Maybe the Client does. Maybe Terminal does. Maybe some other processing is required first so somebody else does. But the only thing that changes is who instantiates R1 and who invoked SymbolFactory. 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).

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.

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

[...]

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


I am fundamentally confused by what you are suggesting in tasks 2 and 3. What do you mean by "natural" form? It seems to be some sort of intermediate form between the client's view of the symbol and the terminal's view, but wouldn't that require a universal symbol representation that could serve as an intermediate between any client and any terminal?

Any form that is independent of the terminal. That could be the external client's view for brand new symbols or the database view for stored symbols.

There must be some such form or conversion would not be an issue. You already mentioned ASCII/unicode and color as examples. You also indicated the conversion rules were "complex". IOW, whatever you need to convert from the "natural" form and whatever you need to convert it to is the "terminal" 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.


I am uncertain about that last step, the passing of the data from the converter to SymbolFactory. How do you suggest I pass the symbol data if not in a Symbol object? Do I need another class, SymbolData, that represents symbol data just like Symbol does but is subtly different? Why is the conversion object allowed to instantiate SymbolData but not Symbol?

SymbolFactory::createSymbol(...)

where the ellipses represent the data values used to instantiate the Symbol object. Exactly the same way you would do it if it was a brand new Symbol the client was defining from scratch.

<aside>
Unfortunately we are getting side tracked with specific examples. In fact, it would usually be the factory object's job to go get the data it needs to initialize the object. That is because an important part of instantiation is getting the /right/ data. That idea underlies my use of [DataAcquisitionStrategy] above.

As a fairly general rule it is usually a bad idea to pass data in collaboration messages in the OOA/D. Usually the only time that is done is if the requirements demand something like a "snapshot" of something like sensor data that must be processed from the same time slice. In the OOA/D the method needing the data usually accesses it directly from the owning objects.

The reason one does not pass data in messages is that it makes managing data integrity much easier during OOP. If the OOA/D is implemented in a distributed or concurrent environment there may be a substantial delay between when a message is generated and when it is consumed. So there is risk that the passed data is out of date or inconsistent by the time the receiving method processes it. If the method always accesses the data it needs synchronously, one can manage scope for distributed processing and threads much more easily.

[Of course such synchronous access can be inefficient when the caller owns the data. So one may optimize by passing it as method arguments during OOP if there is a demonstrable performance problem. But the OOP developer is then responsible for ensuring data integrity in all contexts when making such a decision.]
</aside>


It seems unlikely that I correctly understand this, but let me describe what seems to be the design you are advocating. The SymbolFactory of my design is hidden from the client and the client is given a SymbolConverter object. The client uses the SymbolConverter the client calls a method of SymbolConverter that looks something like this:

converter.convert(x, y, someSymbolRepresentation)

The SymbolConverter uses that call as a sign to convert the given representation to the required representation. SymbolConverter has a reference to a SymbolFactory object and once SymbolConverter has the correct symbol encoding, it calls a method of SymbolFactory that looks like this:

factory.instantiate(x, y, goodSymbolRepresentation)

Then SymbolFactory does nothing much but be a middle-man and use the reference to 2DArray that it has to call this method:

array.putSymbolAt(x, y, goodSymbolRepresentation)

I feel that I am missing the separation of concerns that you are trying to suggest. You seem to be saying that the work can somehow be divided between a converter and a factory, but all I can imagine is the above which leave the factory with nothing at all to do. It looks like one atomic concern to me, impossible to separate.

In my example above, [FromOtherSymbol] is more what I had in mind. It (alone) would have a relationship instantiated to the Symbol to be converted. It would navigate that relationship to get the attributes to be converted. It would convert them and put the results in its own dataElementN attributes.

This is an interesting example of handshaking messages in the right sequence to solve the problem. Assuming an object like ClientRequest was managing all this, the flow of control would look like:

ClientRequest SymbolFactory DataAcquistionStrategy 2DArray
| | | |
| | getData | |
|--------------------------------------->| |
| | | |
| done | | |
|<---------------------------------------| |
| | | |
| create | | |
|---------------->| | |
| | | |
| done | | |
|<----------------| | |
| | | |
| | getData | |
|--------------------------------------->| |
| | | |
| done | | |
|<---------------------------------------| |
| | | |
| create | | |
|---------------->| | |
| | | |
| done | | |
|<----------------| | |
| | | |
| | | add |
|---------------------------------------------------------->|
| | | |

The first getData is invoking [FromDB] and ClientRequest would instantiate R1 for that because it knows the request is for a database Symbol. (ClientRequest also instantiates a relationship between it and the right flavor of [SymbolFactory].) ClientRequest then invokes getData by sending a message via R1. When that task is finished ClientRequest invokes SymbolFactory to actually create the "natural" Symbol from the DB in memory. SymbolFactory navigates the same R1 relationship to get the initialization data.

Now Client Request figures out that a conversion is needed. So it reinstantiates R1 to the [FromOtherSymbol] flavor of [DataAcquistionStrategy]. It also instantiates the relationship between FromOtherSymbol and the "natural" Symbol. (It may also need to reinstantiate the relationship to a different flavor of [SymbolFactory] if the conversion produces a different type of [Symbol] than the original.) It now repeats the same getData/create sequence.

Finally, ClientRequest can instantiate the 2DArray by passing it the reference for the new symbol. (Presumably ClientRequest has attributes for symbolID and the coordinates that it got from the interface request.)

Apropos of the points I made about changing the decision making, I think it should be fairly clear that ClientRequest doesn't need to directly manage this. For example, Once the "natural" Symbol is in place it might send a message to Terminal. Terminal then checks attributes in Symbol and discovers a conversion is necessary. Terminal would then issue the second getData/create sequence. One could also have SymbolFactory invoke add for 2DArray every time so long as 2DArray is smart enough to check if an entry for the symbol identifier is already present.

Also, there is nothing to prevent [SymbolFactory] from invoking getData. In fact, that might be preferable to my version. That's because ClientRequest's job is more narrowly defined to instantiating relationships without needing to know the specific sequence of operations in the actual collaborations (which is now defined by daisy-chaining messages).

The point is that once the responsibilities are broken up, SymbolFactory, DataAcquisitionStrategy, and 2DArray all do the same things no matter where the messages come from. So one can reallocate the processing for decisions like whether conversion is needed quite easily. One can also insert a pretty arbitrary amount of processing in between any of these steps without changing any of them if the problem gets more complicated.

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.


But then the Symbol needs to be converted and constructed once for every occurrance on the screen. The client is not collaborating with Symbol, it is just passing Symbol on to the 2D Array, but that doesn't mean that the client is useless in the process! Only the client knows exactly where and when the Symbol needs to appear.

What I had I mind was:

[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. A Symbol is only added to the R1 collection when it is created (at the Client's request) and the Symbol always needs to be instantiated in memory if it is to be displayed. [My assumption.]

If we cut out the middleman in this case, we need the factory to know how to instantiate the relansionships between the 2D Array and the Symbol. Giving that information to the factory would be wildly impractical, because we are not just talking about a pair of coordinates. The Symbol might need to appear in many places. That could be represented by a bitmap over the position in the array, but even that is not enough because the relationship between the array and the symbols varies over time. It's possible to do, but are you really suggesting that I try a scheme like that?

All [SymbolFactory] needs to know is that there is a unconditional relationship between Terminal and /every/ Symbol that it instantiates for the Client. Presumably we don't get any create requests for Symbols that are not to be displayed.

However, it occurs to me there is another way to interpret the problem. If there is a limited and fixed set of symbols that are available for display, one might create then all at startup. (This might be what PDCurses is doing.) Then the external Client would merely selects from those available.

Even in that case, though, the selection would still be instantiated with the R1 collection; it would just contain a subset of the instantiated Symbols. However, I have trouble reconciling that startup instantiation view with the client requesting symbols to be created unless I have badly misunderstood your description.

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.


When you say that the factory should instantiate the relationships that should exist when the product is created, you are strongly suggesting that the factory is not responsible for instantiating relationships at other times. Since the relationships between the 2D Array and Symbols are frequently being instantiated and destroyed over the execution of the application, who should be responsible for making those instantiations at other times?

To be sure we are on the same page, let me digress a bit about conditionality in OOA/D...

[A]
| 1
|
| R1
|
1 0..1 | 1 R3
[B] ------------ [C] ------------------- [D]
R2 | 1 0..1
| 0..1
|
| R4
|
| 0..1
[E]

We need to instantiate a C. In this model it must be associated with some A and some B throughout its life in the application. So any time we instantiate the C but wait to instantiate R1 or R2 until after the method with the constructor returns, we have a potential referential integrity problem because somebody may try to navigate those relationships before we get around to instantiating them. So the developer must explicitly ensure that can't happen _in the model_.

In contrast, if we instantiate those relationships in the same method as invoking the constructor, we don't have to worry about it in the OOA/D. That's because routine OOP techniques exist for managing referential integrity for distributed/concurrent implementations _at the method scope_. And in a single threaded, single task implementation it comes for free because only one method can execute at a time.

As it happens we have a more subtle problem for A. Each A must have exactly one C related to it at all times and each C is related to exactly one A. Together those constraints mean that if we create a C, we must also create an A and instantiate a relationship to it. For the same reasons as above, we should do that in a single factory method scope if we want to take advantage of routine OOP facilities and techniques.

We can also have a problem with R3 and R4. Even though the model says we don't need a D of E at all times, that does not mean that the problem requirements don't require it when a C is created. IOW, we may be free to remove a participant later in the solution but we may need one when the C is created. Whether one needs to worry about that will depend on the problem in hand. But if one does need to worry about it, one has the same scope issues to referential integrity as one has for R1 and R2.

So my issue was mainly about referential integrity at the OOA/D level and how it maps into OOP techniques _for unconditional relationships_. Instantiating relationships in the same method scope as invoking the constructor makes life much easier for OOP in concurrent, asynchronous, and/or distributed environments. Since a good OOA/D should be unambiguously implementable in any computing environment without change, the practices one applies during OOA/D tend to be more rigorous than many apply in OOP.

Your issue here, though, is the converse. Any time one has a conditional relationship there is at least the potential for deferring instantiation. In fact, since participation in a conditional relationship is inherently dynamic (i.e., dependent on the specific solution context), they are almost never instantiated in a factory.

Though more rare, there are situations where participants in unconditional relationships are swapped based on solution context. Conceptually, that removes the old participant and reinstantiates the relationship for the new participant. For the same sorts of referential integrity reasons, it is a very good idea to do the entire swap in the same method scope. However, since the swap is dynamic, one usually does that outside a factory.

Finally, there is the issue of * multiplicity. The OOA/D multiplicity only specifies "at least one". So one can add participants routinely after the 1-side object is instantiated. In fact, that is quite the norm.

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.

Bottom line: the factory's main job is instantiating objects. But the practicalities of referential integrity demand that the factory also instantiate any relationships that must be in place when the object is born.

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


What exactly do you suppose Terminal will do once it has navigated R1 to get to the Symbol? I can imagine that there would be a
symbol.draw() method that would cause the symbol to draw itself, or else a relationship to a DisplayStrategy that has a similar method, but what should the arguments of that method be?

Yech. That is the Alan Holub School of OOA/D. 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.

Here Symbol certainly has knowledge responsibilities but we would need some heavy duty anthropomorphization to give it any responsibilities to do something. So I would expect others to access its knowledge to do things like drawing.

You originally attributed rendering of a symbol as a responsibility of Terminal. If so, I would expect the drawSymbol behavior to be there. 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.

In a PDCurses implementation, all it would need would be coordinates but in other implementations greater context information could be required. When I let Terminal hold ultimate responsibility for drawing, it is free to draw directly or call a method of its choosing with all the needed information supplied as arguments, but when I design in a more clever and detailed way and specify in advance the control path that drawing must take I am forcing the drawing to happen blindly.

Imagine a draw(x,y) method that must be used for both graphical and PDCurses terminal implementations. For the terminal implementation the draw(x,y) method would be overridden to call the 'mvaddch()' PDCurses library function.

This gets back to my opening issue in this message around PDCurses. I could envision something like:

* R1 utilizes 1
[Terminal] -------------------- [DisplayStrategy]
+ draw()
A
| R2
+----------------+---...
| |
[PDCurses] [GUIIcon]

This might allow you to completely encapsulate the PDCurses implementation for a particular terminal/symbol combination. Alas, I am not sanguine because such infrastructures tend to provide a monolithic environment. That is, one can't isolate part of the implementation and that implementation assumes you will do other activities like selecting symbols in that environment. IOW, you need to do everything their way or nothing. Which segues to...


For the graphical implementation it would have to be responsible for actually drawing the symbol. Unfortunately, there I get stuck because just (x,y) does not seem like enough information. Does (x,y) represent a pixel on the screen, or the position of a symbol in the array? How wide and tall should the symbol be drawn? The graphical library probably gives me a new drawing context each time I need to redraw the screen, but how does the draw(x,y) method get that? This is why I say that I am drawing blindly.

Is it possible that we are causing problems by being too object oriented?

I think it is bit more complicated than that. 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.

If all you needed was the niche PDCurses serves, it might well be overkill to try an OO solution. The situation is very similar to RAD IDEs. If one has a CRUD/USER problem to solve, the automation provided by RAD IDEs is very good and trying to do OO just obfuscates the solution. Conversely, if one has a problem outside the CRUD/USER realm, the P/R paradigms of RAD IDEs just get in the way.


Plus, in your diagram for the DisplayStrategy, you showed it being many Symbols for each DisplayStrategy. That means that when the strategy wants to draw it has to be passed the current Symbol, and that means that it will have to know the subclass of Symbol to draw it correctly. That is exactly what you said we were trying to avoid in Terminal.

One always has that problem with any * collection where one needs to access individuals. However, that was a good catch in this case. For this problem context only one Symbol is displayed at a time so the relationship could be 1:1 even though the same DisplayStrategy can be reused for multiple Symbols.

[There may be some value in also providing the * relationship at OOP time. Let's say we have conveniently provided Symbol IDs that are consecutive integers. Then the collection is just an array and the search to instantiate the right Symbol in the 1:1 "current" relationship is just a table lookup. Of course one could do the same thing with a static Find method on the [Symbol] class. The advantage is that the relationship collection may be a library class that handles all the drudge work so you don't have to bootstrap it in the [Symbol] constructors.]

--

*************
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
    ... 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)
  • Re: Text terminal rendering design
    ... The client is an application with need of a UI that works by ... The management of that array's content by the client is quite a different concern than actually rendering it in the display de jour. ... 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. ... So even if the responses to the first three 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. ...
    (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)
  • Text terminal rendering design
    ... PDCurses at the lowest level of implementation, ... intended line and draw it, after testing that the factory of the ... The idea is that not all terminals have to be implemented by an ... into a pair called a GraphicSymbol. ...
    (comp.object)

Quantcast