Re: avoid cast to derived
- From: "H. S. Lahman" <h.lahman@xxxxxxxxxxx>
- Date: Wed, 27 Dec 2006 17:34:32 GMT
Responding to Grizlyk...
Your code has a poor design. If the userUser class can only work with
userBase objects, then define it as such. If it can work with all Base
objects, then it shouldn't need to dynamic_cast the Base object it has.
Can anybody say the way to avoid cast to derived in the case?
Yes, several depending on what the real problem is. However, your code already hard-wires several design decisions so it is difficult to determine what are problem requirements and what is design. My first suggestion would be that just because misbegotten OOPLs like C++ encourage one to write C programs with strong typing and dynamic dispatch doesn't mean one should do so. However, given what is here...
Example:
// *** library module
#define adjust_list
#define adjust_list1
#define adjust_list2
#define adjust_list3
//object
struct Base{ virtual void method()=0; };
//Most parts of user code
struct User
{
virtual void user1(Base& obj){ obj.method(); }
virtual void user2(Base& obj){ obj.method(); }
};
struct Creator
{
virtual Base* create_base()=0;
virtual User* create_user()=0;
};
// *** user module
struct userBase: public Base
{
void adjust(adjust_list){}
void method();
};
struct userUser: public User
{
//to get extended interface of derived via pointer to its base class
void user2(Base& obj)
{
userBase *const tmp=dynamic_cast<userBase*>(&obj);
if(!tmp){ obj.method(); return; }
tmp->adjust(adjust_list1);
obj.method();
tmp->adjust(adjust_list2);
obj.method();
}
};
First, note that when one passes an object reference to a behavior method one is instantiating a temporary relationship that exists only for the scope of the called method. Thus
1 R1 invokes 1 1 R2 invokes 1
[A] --------------------- [B] ----------------------- [C]
+ methodB(&C)
When [A] invokes B::methodB passing a C as an argument, [A] is instantiating the R2 relationship between B and C. That means that [A] is responsible for knowing exactly which member of [C] the B in hand should collaborate with in the current context. This is important because...
Fundamentally what we have here is:
[Client]
| 1
|
| R1
|
| invokes
| 1 1 R2 invokes 1
[User] -------------------------- [Base]
| 1 + method()
| A
| | R4
| R3 +--------+--------+
| | |
+--------------------- [userBase] ???
invokes 0..1 + adjust()
If [User] wants to invoke method() for all members of [Base] it must navigate the R2 relationship. But if it wants to invoke the adjust() specialization, it must navigate the R3 relationship. So there /must/ be two separate relationships if one wants to access all members of [Base] AND some members of [Base] from User.user2.
So the real problem is that [Client] is instantiating two relationships here when passing a Base reference, one of which is conditional (i.e., when the Base in hand is not a member of [userBAse]). IOW, there isn't enough information here for [User] to navigate R1 and R2 properly. If the client is going to pass a [Base] member, it has the responsibility of knowing which relationship is being instantiated. In particular, [Client] needs to tell [User] whether the [Base] reference happens to be a member of [userBAse]. So one needs code like:
void user2 (Base& obj, int type)
{
if (type == USER_BASE)
obj.method(); // navigate just R2
else
{
obj.adjust(adjust_list1); // navigate R3
obj.method(); // navigate R2
obj.adjust(adjust_list2); // navigate R3
obj.method(); // navigate R2
}
}
However, the real problem is that the sibling to [UserBase] is an instance of [Base]. IOW, some [Base] members are defined as NOT [userBase] members. In OOA/D there is a rule that the union of subclass members must be a complete set of superclass members, precisely to eliminate that sort of ambiguity for subsequent maintenance. So what one should have is:
[Client]
| 1
|
| R1
|
| invokes
| 1 1
[User] ---------------------+
| 1 |
| |
| R2 | R3
| |
| invokes | invokes
| 0..1 | 0..1
[userBase] [plainBase]
+ adjust() |
| |
+-----------+-------------+
| R4
_
V
[Base]
+ method()
Now [Client] must instantiate exactly one relationship and User.user2 uses the conditionality to navigate the right one:
void user2 (userBase& obj1, otherBase& obj2)
{
if (otherBase != NULL))
obj2.method(); // navigate just R2
else
{
obj1.adjust(adjust_list1); // navigate R3
obj1.method(); // navigate R2
obj1.adjust(adjust_list2); // navigate R3
obj1.method(); // navigate R2
}
}
where [Client] passes a NULL reference for the one that isn't in play.
As a practical matter one wouldn't normally instantiate the relationship by passing an object reference, especially in this situation. [User] would have referential attributes for R2 and R3 and [Client] would simply set the appropriate one prior to invoking User.user2. The reason is that it separates the concerns of relationship instantiation (who participates) from those of collaboration (when they collaborate). So any object that understands the context determining which specific [Base] to use can instantiate the relationship (and deinstantiate it when the context no longer prevails). Thus that object may not be the one that needs to collaborate with [User] by invoking User.user2 in the overall flow of control (i.e., in this example [Client] might not be the object to instantiate R2/R3).
This segues to my opening point above. The fact that object references are being passed to behavior methods suggests more fundamental problems in the design. However, without a description of the problem actually being solved I can't speculate on exactly what those might be.
[Having said all this, let me note that languages like C++ do not provide facilities for processing a stream of arbitrary objects. In that case one must use dynamic_cast to map the type of the object in hand. (OOPLs like Ada and those derived from scripting languages usually do provide such facilities in a disciplined fashion.) However, as a practical matter such situations are pretty few and far between so there is rarely any reason to use dynamic_cast.]
*************
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
.
- References:
- avoid cast to derived
- From: Grizlyk
- avoid cast to derived
- Prev by Date: Re: Databases as objects
- Next by Date: Re: what's software used in the book of 'UML Distilled'?
- Previous by thread: avoid cast to derived
- Next by thread: Re: avoid cast to derived
- Index(es):
Relevant Pages
|