Re: Pattern suggestion for processing similar image types

From: H. S. Lahman (h.lahman_at_verizon.net)
Date: 11/26/04


Date: Fri, 26 Nov 2004 18:33:55 GMT

Responding to de Mello Zunino...

>> FWIW, it is generally a good idea to keep class constructors simple.
>> For a variety of reasons they tend to be fragile so one doesn't want
>> to complicate them, especially with error-prone complexities like
>> reading streams. So...
>
>
> Keeping constructors simple and away from complex or intricate
> processing is a concept that I indeed take into account. However, on
> this particular problem, I was faced by a paradox motivated by another
> concept, which states that a constructor's purpose is to create a valid
> and well-defined instance. To my interpretation, that meant that when I
> defined an instance of a PGM image, I would expect that the instance
> would be *ready* to be used, i.e. be queried for its attributes and
> pixel data. This approach would constrast with a trivial constructor
> which would zero-initialize the instance's members and rely upon another
> member funtion to do the acquisition of data. As your message evidences,
> I reckon the source of my trouble was in trying to concentrate
> responsibilities of different natures in a single entity.

The first question I would push back with is: What constitutes a "valid
and well-defined" instance? I would argue that simply means that (a)
the storage must be implemented correctly (the compiler's job) and (b)
the knowledge attributes must be assigned consistent values (the
explicit constructor's job). It is not the constructor's job to
/obtain/ consistent values; it just assigns them to heap or stack storage.

The second question I would push back with is: What does it mean that
the object be "ready to use"? I would argue that just means that (a)
consistent values of knowledge attributes have been stored for the
object identity and (b) referential integrity has been enforced (i.e.,
all necessary relationships have been instantiated). The first is
handled by passing the constructor the correct arguments (which may
include referential attributes that the instance may use for
relationship navigation). The second is an issue of context for using
the instance, which the object itself should not know about. Thus the
responsibility for both belongs to whoever instantiates the object
rather than the object's constructor.

[In fact, the developer opens a Pandora's Box of potential foot-shooting
if referential integrity is not resolved within the scope of the method
that invokes the constructor. The developer must then explicitly ensure
that there will be no attempt to access the instances until the scope
that does instantiate the relevant relationships has been invoked. That
can be a major mess in concurrent applications if one cannot rely on a
single method's scope as the integrity boundary.]

Therefore I think your concerns are really those of the "factory" object
that invokes the constructor. The constructor itself can be quite simple.

>
>> I would suggest something more like:
>>
>> [Reader]
>> | 1
>> | gets data from
>> |
>> | R1
>> |
>> |1
>> [PGMFactory]
>> | 1
>> |
>> | R2
>> |
>> | creates
>> | *
>> [PGM]
>> + initializeImage(DataPacket*)
>> A
>> | R3
>> +--------------------+
>> | |
>> [Binary] [ASCII]
>>
>> Now [Reader] isolates the stream processing. (I am assuming the
>> stream is complex; it needs to be set up, has "statements" that need
>> to be parsed, etc..) The [PGMFactory] extracts data from the [Reader]
>> as it is needed. It uses an interface to [Reader] that is oriented
>> around the PGM semantics rather than stream semantics; [Reader]
>> provides the mapping between views. That interface may have something
>> like getBinaryLine() and getASSCIILine().
>
>
> Following your suggestion of having an interface which is PGM-oriented
> on the reader, I guess I could even go a step further and make it more
> specialized, i.e. have a /PGMReader/. Then I could provide
> domain-specific methods such as /getMagicNumber()/, /getWidth()/, etc.
> Does that sound right?

It could be. However, that would depend upon the way the stream was
organized. If the stream used to instantiate the PGM is unique the the
PGM, then such subclassing of [Reader] could be justified.

OTOH, if the stream is shared and can be abstracted so that operations
on it are independent of the data semantics, that would not be
necessary. For example, suppose the stream is always laid out in a
buffer in the same way:

location 1: type of thing to be defined.
location 1: parameter 1
location 2: parameter 2
...
location N: data chunk length
location N+1: 1st element of data in 1st chunk
...
location N+legnth: 1st element of data in 2nd chunk
...

Now location 1 determines one is defining a PGM. So someone reads that,
instantiates R1, and tells PGMFactory to do its thing. Location 2 might
the the magic number in the PGM context but something entirely different
for some other context. For a PGM, one might ignore locations 2 to N-1.
  The Reader does not have to understand the semantics of the data
(e.g., whether the data is ASCII or binary); it simply provides the
offset in the buffer. Thus the interface to [Reader] might look like:

getType()
getParam1()
getParam2()
...
getDataChunkLength()
getDataChunk(n)

Now you can change the stream mechanics (e.g., switch to XML or A-V
pairs) without affecting the factory clients that read the stream so
long as the basic (chunk-oriented) data organization remains the same.
getType() is used to instantiate the R1 relationship to the right
factory and then the factory internally parses the stream context
according to its own semantics (e.g., Param1 is the "magic number").

IOW, one designs the streams around a general structural invariant and
then employs [Reader] to abstract that invariant in its interface to the
various factory clients.

>
>> [PGMFactory] understands the sequencing of header-before-image so it
>> reads the header first, gets the "magic number", and chooses the
>> correct [Reader] interface for accessing the image data. It also uses
>> the "magic number" to invoke the appropriate [PGM] subclass
>> constructor. Finally, it invokes the initializeImage behavior of [PGM]
>> to process the current bundle of image data in hand. The
>> implementation of initializeImage will know how to parse the data in
>> the packet properly.
>
>
> Right. So the /PGMFactory/, as its name implies, is the entity which
> will drive the construction of the PGM image objects. It will do so by
> querying and processing the information which is read by the
> /PGMReader/. The "magic number" will allow it to decide wheter to build
> a /RawPGM/ or a /PlainPGM/ and also whether to invoke
> /getBinaryImageData()/ or /getASCIIImageData()/ on the /PGMReader/.
> Then, with all the information available, the chosen image object may be
> constructed and returned. Am I getting it right or have I gone offtrack?

That is pretty much it. This would be the scheme if the stream were a
pure "push" (i.e., the stream input determined what to build and that
was in <at least somewhat> arbitrary order).

OTOH, if the stream order is known (e.g., all PGMs are defined and then
all the ABCs and then all the XYZs), one could access it on a "pull"
basis where R1 is initialized and them PGMFactory does a series of
getXxxx as above. When the last one is processed, control would be
returned to whoever understands that ordering and they instantiate a new
R1 to a different factory.

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

H. S. Lahman
hsl@pathfindermda.com
Pathfinder Solutions -- Put MDA to Work
http://www.pathfindermda.com
blog (under constr): http://pathfinderpeople.blogs.com/hslahman
(888)-OOA-PATH



Relevant Pages

  • Re: throwing exception from constructor
    ... This would create a three-state stream - closed, ... appropriate arguments to the FileStream constructor. ... one calls the static method rather than using the constructor, ... extra complexity, ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Creating an object that is read from an input stream.
    ... >> Should the Box class have a default constructor? ... The stream code is almost the same. ... for a graphics library of mine I wrote two rectangle classes. ... All drawable objects are defined in virtual ...
    (comp.lang.cpp)
  • Re: Creating an object that is read from an input stream.
    ... >> You can throw an exception if that happens. ... The default constructor can create a Box that's valid. ... If you have a default constructor the stream code ... > I don't know what Rectangle class you are referring to. ...
    (comp.lang.cpp)
  • How ObjectInputStream.readObject Works
    ... readObject seems to have magical properties of being ... public no-arg constructor; otherwise, if the class is Serializable, it ... Serializable Object will not be invoked, but the inner core, e.g. the ... before any of the stream data is read. ...
    (comp.lang.java.programmer)
  • Re: put data in InputStream
    ... <SNIP> ... > to the inputstream? ... I suspect another reason an input stream is ... linked / associated with a data source via a constructor is because it is ...
    (comp.lang.java.help)