Re: Regarding IO, OO-design and Encapsulation



Responding to Kromann-Larsen...

I've been reading up on patterns and oo-design in general for an exam
recently, but in a related work project, I'm having problems applying
all my newly found wisdom. One of the problems that really baffle me is
how to do proper input and output without breaking the encapsulation of
classes. Let me try to define the problem with a small example.

Say we're defining a data model for modelling questionaires and we have
the following classes:

Question (abstract class or public interface)
 - QuestionNumeric (derived from Question)
 - QuestionOptions (derived from Question)

Encapsulation tells me that Question should be the public interface
that clients should see when working on either of the Question-types.
Also, I should not expose the inner workings of the Question to easier
adapt to changing requirements in later development.

Not exactly. When one accesses an object through a superclass one is using inclusion polymorphism to substitute the actual subclass behavior transparently to the client. That is quite a different thing than encapsulation. Encapsulation is primarily about implementation hiding, cohesion, and logical indivisibility. IOW, hiding implementations behind interfaces via encapsulation enables behavior substitution, but it isn't the same thing.


Behavior substitution has a strong limitation: all subclasses must provide an implementation for the superclass behavior. But we have subclasses so that instances of the root superclass can be subdivided into groups with specialized properties (behaviors and knowledge unshared with other, sibling groups). Therefore one can only access properties through a superclass that are implemented by all members of all subclasses (i.e., generalized properties).

Thus even though a client is accessing a generalized property, the instance in hand has specializations. Those specializations are just hidden from the client when the access is at the superclass level. This is relevant to your problem below because it is really about creating or exporting instances that are members of specific subclasses....


In regard to input and output, say I would like to load the various Question-types from an XML file and be able to output these to an XML file also. However, I do not wish for my Question classes to be responsible for doing this, since I might have different XML formats that correspond to the same model, also it'd give my classes weaker cohesion and be more prone to change upon getting new input/output formats. Thus I'd make external objects (eg. an Abstract Factory or the likes) that handles input and likewise for output.

My question is this: How do I do this without exposing all the inner
variables in the various Question and thus breaking the encapsulation
of the Question classes?

The short answer is to encapsulate the rules and policies of XML formatting in some other object. (This represents the isolation, separation of concerns, and cohesion aspects of encapsulation.) Note that the various GoF factory patterns could provide exactly the sort of decoupling you want when creating Question instances from XML input. The factory object parses the XML specification and invokes the QuestionX constructor with the correct arguments gleaned from the XML specification. One can even use factory subclassing to substitute the particular XML formats dynamically (e.g., think: concreteFactory <=> XML format).


One can do a similar thing on the output side. Essentially the formatter is a factory object in reverse. It accesses the QuestionX attributes and encodes them in XML strings per the format de jour. Then the only thing exposed in QuestionX are the attribute getters.


One solution could be that QuestionNumeric and QuestionOptions expose these accessor methods since client objects will only be using the Question interface and will not be accessing the subclasses directly, thus only getting the interface without the accessor functions. The Factory will be rather tightly coupled to the subclasses it has to create anyways.

Unfortunately accessing Question's interface doesn't work for create/export. The problem is that one needs to know the specific subclass the instance must belong to in order to create it. That is, when creating an instance in a subclassing relation, one must fully specify it, including its specializations. [Some OOPLs allow one to create superclass instances without specifying the subclass but that is a VERY BAD practice. Those that do so will eventually get burned in proportion.]


Similarly, when writing out a Question instance one must provide enough information to allow a correct subclass member to be instantiated from that XML specification (assuming one eventually reads them back into this application). So whenever one is creating or exporting object instances, those instances should be fully defined. That means one must create/export at the leaf subclass level rather than the superclass level where only generalizations are visible. That, in turn, means that direct relationships with the individual subclasses are required.

However, relationships do not indicate tight coupling per se. The nature of the coupling will depend on how the relationships are implemented, instantiated, and navigated. For example, the worst form of coupling is to pass an object reference as a method argument. There are a number of reasons why this is bad during collaboration, but the one relevant here is that the client knows too much. For example,

[Client]
    | 1
    |
 R1 |
    | invokes service of
    | 1
[Service]
    | 1
    |
 R2 |
    | talks to
    | 1
[Bystander]

If the Client passes the Bystander instance to Service when Client invokes the Service's behavior, Client is essentially instantiating the R2 relationship temporarily. The fact the [Client] has a relationship with [Bystander] is really none of [Client's] business; it is a personal matter between [Service] and [Bystander]. In order for Client to pass the correct Bystander reference, Client must understand the context of participation of the R2 relationship. The rules and policies that govern that participation are unlikely to have anything to do with the rules and policies that dictate when [Client] needs to collaborate with [Service]. Therefore one is creating an unnecessary dependency in the [Client] implementation on how [Service] and [Bystander] collaborate.

Client can navigate the R1 relationship with much less coupling if it just worries about its collaboration with Service. For that to be possible somebody else must instantiate the R2 relationship by providing the correct Bystander to Service to instantiate the relationship. (Passing a reference is OK if all one is doing is instantiating a relationship.) Now Service responds to Client and, in the course of responding, sends a message to Bystander without Client knowing anything about that. This decoupling is achieved by separating the concerns of relationship participation from those of collaboration. So even though [Client] may be a leaf subclass in some subclassing tree, the coupling has been greatly reduced compared to, say, passing a Bystander reference to a Client that is not subclassed at all. IOW, leaf subclass relationships do not imply a high degree of coupling.

(BTW, if you respond to this, it will be awhile before I reply; I am going on vacation for a week or so.)

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

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



.



Relevant Pages

  • Re: LSP and subtype
    ... These "poorly formed" OOPLS almost invariably provide means to disallow the instantiation of a superclass without specifying a subclass. ... Suppose a Client invoking Base.foois happy with either implementation so all is well. ... I don't see how this backs your assertion that one should not be able to instantiate Base classes. ...
    (comp.object)
  • Re: Text terminal rendering design
    ... The client wants to later change the color and provides ... If the client is doing a multiple part conversion then the client ... On separating concerns of instantiation and data acquisition: ... A SymbolFactory always produces a Symbol of the same subclass. ...
    (comp.object)
  • Re: LSP and subtype
    ... The problem arises with libraries that provide classes that can be used out-of-the box and as base classes. ... Why would the client ever care about what implementation he gets? ... client code will run just fine with *any* subclass object of Base, ... doesn't/shouldn't really tell its prey to respond to its attack. ...
    (comp.object)
  • Re: LSP and subtype
    ... whole tree_ to access it properly. ... If Sub1.foodoes so then the client will never notice a difference. ... client code will run just fine with *any* subclass object of Base, ... doesn't/shouldn't really tell its prey to respond to its attack. ...
    (comp.object)
  • Re: inheritance question
    ... Whoever originally had the client access ... aggravated when there are overrides of implementation inheritance. ... >>the availability of a response. ... > bad motive for implementing a subclass relationship... ...
    (comp.object)