Re: Interface complexity problem in game



Responding to Guild...

The real key to decoupling, though, is that only by-value data is passed. The data packet may be implemented as an object (this is
what DAO is about) but it exists only for the communication. That
is, one does not pass objects across the subsystem boundary that are
part of the subsystem implementation because that exposes the
subsystem implementation.


I'm a little uncertain about this part. I am passing objects through the facade, but they may or may not be considered part of the subsystem implementation. I have a world filled with active entities, game creatures, and I let them interact through a facade so that I don't have to reveal how the entire world is implemented, but I do let them pass objects to the facade that will go to other creatures and play an active roll inside them.

Any object the message sender sends through the Facade (other than a data packet object) has to exist already in the sender subsystem implementation. (If the sender got it from another subsystem that just begs the point by moving the source back through a subsystem daisy-chain.) IOW, for the object to be available, it must have been created for some purpose within the sender implementation as part of its responsibilities. However, in this context I think the real problem lies in the distinction between a problem space entity and its abstractions in the software solution...



I'm only revealing to each creature that there are other creatures on the other side of the facade that are implemented the same way.

Therein lies the problem. B-) When one abstracts the same problem space entity in two different subsystems they should not have the same implementation. [There are exceptions but they are all pure data holders that provide computing space definitions, such as ComplexNumber or String. IOW, they are attribute ADTs.] The abstractions should differ because one tailors abstractions to the subsystem subject matter.


Since subsystem subject matters necessarily represent different functionality, they implement different functional requirements. If they implement different functional requirements, they should have different behavior responsibilities. So one should /never/ abstract the same behavior responsibility for an entity and implement it in the same way in two different subsystems.

[OTOH, it is possible for entirely different operations to be performed on a given set of data. So it is not uncommon for object abstractions in different subsystems to have the same same knowledge responsibilities. However, in that case one needs to recognize that and ensure synchronization. (Fortunately since it is only data being shared, there are trivial mechanisms for ensuring synchronization.)]

Bottom line: even when the same problem space entity is being abstracted, the abstractions themselves should be different across subsystems, at least in terms of behavior. If they aren't, then one is bleeding cohesion between the subsystems since they are both addressing the same functional requirements. IOW, the underlying entities are the same but their abstractions are apples & oranges.

There is another reason for not passing object references -- it is the worst form of coupling. The degrees of coupling can be roughly described as:

empty message (no data packet). This is the most benign because there is not much one can screw up except when the message is sent.

message w/ by-value data. Almost as benign because whatever the receiver does with the values can't directly affect the sender of the message. In addition, the receiver can't do anything except manipulate the values in its own context and that will be explicitly understood by the receiver.

message w/ data values by reference. This is nastier because the receiver can now change the data values in unexpected ways that will screw up the message sender (i.e., change the state of the sender in unexpected ways -- the classic global data problem). It may even be possible to delete the values out from under the sender.

message w/ behavior (e.g., aplet). The problem here is that receiver has no control over what the behavior does because that is unspecified. For example, one way this problem is manifested is that the phrase 'internet security' is an oxymoron.

message w/ object reference. This is orders of magnitude worse than the others because it opens a Pandora's Box in both directions. The receiver can now access and modify /any/ knowledge that the sender could access, not just what the sender might expect to be modified. The receiver can can also access any behavior responsibility. That can screw up the receiver because the behavior is unknown. It can also screw up the sender if the sender does not expect it to be invoked. Finally, often the receiver can delete the object out from under the sender.

Note that one way of viewing these problems is that we want to restrict the receiver to a need-to-know basis. The receiver should only have access to what it needs to perform its service and nothing more. Whenever we expose more properties to the receiver than it actually needs we are asking for trouble because some maintainer is probably going to access them later. That is, we want to force the maintainer to think about the problem through proactively modifying the interface to access more properties.

Bottom line: one should never pass object references across subsystem boundaries, regardless of what some interoperability infrastructures allow one to do. (An exception is generic handles that the receiver will return later with asynchronous responses, but then the receiver doesn't have the object definition to access the reference's properties.) And within a subsystem one should only pass object references to instantiate relationships (i.e., to the constructor or as a setter for a pointer attribute).

[There are exceedingly rare situations where performance optimization requires references to be passed during OOP as a form of relationship implementation. But at the OOA/D level one conceptually never does that. When one performs that sort of OOP optimization one must be aware that one is sacrificing robustness, maintainability, and may be reducing reliability.

An interesting and apropos question: Why do we think of pointers as knowledge attributes with getters/setters when they implement inherently dynamic relationships? Because to manage referential integrity we need to ensure that that are updated in a timely fashion. In OOA/D conceptually we assume all knowledge access is synchronous (immediate) because that's the only way we can deal with data integrity issues without our minds turning to mush. However, conceptually behavior access is asynchronous in OOA/D because that is the only way that we can ensure that the OOA/D specification can be unambiguously implemented in an asynchronous and/or concurrent environment during OOP if that is necessary. So we reuse the same infrastructure by implementing relationships with pointer <knowledge> attributes. That ensures that in the OOP implementation changes in referential integrity will be managed within a convenient scope to ensure consistency. It's a tangled web we weave but it all hangs together. B-)]


************* 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: Neutral Format as a Coupling reduction idea that doesnt work.
    ... There are potentially unlimited and unexpected side effects for both sender and receiver. ... That definition defines the semantics of the just the data packet. ... That represents a solution level coupling but at least it is very narrowly focused on the message definition rather than what each side does with the information. ...
    (comp.object)
  • Re: PostMessage and unprocessed messages
    ... The sender has no way to know when to delete the message, so making it the owner has no ... Since the receiver would have no way to know the message is deleted, ... collection, note that as long as something is in the queue, it is owned by the queue ... so adding memory management complexity to the ...
    (microsoft.public.vc.mfc)
  • Re: PostMessage and unprocessed messages
    ... No need to keep a linked list, because the PostMessage queue is already ... The sender has no way to know when to delete the message, ... Since the receiver would have no way to know the message is deleted, ... so adding memory management complexity to the ...
    (microsoft.public.vc.mfc)
  • Re: PostMessage and unprocessed messages
    ... No need to keep a linked list, because the PostMessage queue is already ... The sender has no way to know when to delete the message, ... Since the receiver would have no way to know the message is deleted, ... so adding memory management complexity to the ...
    (microsoft.public.vc.mfc)
  • Re: Domain Driven Design and SaveAll()
    ... Passing object references is the worst form of coupling because the receiver can do virtually anything with the object and some of those things may be quite unexpected from the sender's viewpoint. ... passing a problem space object effectively trashes implementation hiding and bleeds cohesion across subsystem or layer boundaries because such an object exposes the implementation of the sender. ... SaveStep should, in addition to persisting data, trigger a specific ...
    (comp.object)