Re: Downcasting - whats the problem?
From: Robert C. Martin (unclebob_at_objectmentor.com)
Date: 05/22/04
- Next message: Matthias Hofmann: "Re: Aggregation vs composition"
- Previous message: Hugo Kornelis: "Re: Nearest Common Ancestor Report (XDb1's $1000 Challenge)"
- In reply to: Daniel T.: "Re: Downcasting - whats the problem?"
- Next in thread: Daniel T.: "Re: Downcasting - whats the problem?"
- Reply: Daniel T.: "Re: Downcasting - whats the problem?"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Date: Sat, 22 May 2004 10:08:03 -0500
On Sat, 22 May 2004 03:43:01 GMT, "Daniel T."
<postmaster@eathlink.net> wrote:
>Personally I'm surprised that RCM is getting away with it without
>everyone jumping all over his case. :-)
The issue is that there are certain structures in object oriented
designs that cause us to lose static type information through
up-casts. In order to regain that static type information later, we
have to use downcasts.
One case that you and I have argued about before is the Payroll
example to be found in Chapter 19 of "Agile Software Development:
Principles, Patterns, and Practices" (www.objectmentor.com/PPP)
In Figure 19-8 and listing 19-15 we see the canonical case of a
downcast being used to resolve type information that was lost in a
dual-inheritance hierarchy.
|Employee|------>|PaymentClassification|
A
|
+-------+-------+
| | *
|SalariedClassification| |HourlyClassification|<#>->|TimeCard|
The employee holds an object that describes it's pay classification.
One represents a salaried payment, the other an Hourly payment. The
HourlyClassification holds on to a set of time cards.
However, the Employee does not know which of these two it possesses.
It has lost this information due to an upcast. This is good because
we want to be able to pay the employees through a simple algorithm
that looks like this:
void PaydayTransaction::Execute()
{
list<int> empIds;
GpayrollDatabase.GetAllEmployeeIds(empIds);
list<int>::iterator i = empIds.begin();
for (; i != empIds.end(); i++) {
int empId = *i;
if (Employee* e = GpayrollDatabase.GetEmployee(empId)) {
if (e->IsPayDate(itsPayDate)) {
Paycheck* pc =
new Paycheck(e->GetPayPeriodStartDate(itsPayDate),
itsPayDate);
itsPaychecks[empId] = pc;
e->Payday(*pc);
}
}
}
}
void Employee::Payday(Paycheck& pc)
{
double grossPay = itsClassification->CalculatePay(pc);
double deductions = itsAffiliation->CalculateDeductions(pc);
double netPay = grossPay - deductions;
pc.SetGrossPay(grossPay);
pc.SetDeductions(deductions);
pc.SetNetPay(netPay);
itsPaymentMethod->Pay(pc);
}
This is nice because the Employee object does not depend upon the type
of payment classification. It simply invokes a general algorithm to
pay employees, and lets polymorphism do all the interesting selection
work.
However, we have a problem. There are transactions that need to add
time cards to employees via their id numbers. The database returns
employees, but we need to be sure that they are Hourly. These
transactions form another hierarchy which has similarity to the
PayClassification hierarchy. The AddTimeCardTransaction class
pertains specifically and only to Employees who happen to be holding
the HourlyClassification derivative of PaymentClassification.
|Transaction|------>|Employee|
A
|
|AddTimeCard|------>|HourlyClassification|
What this means is that the Execute method of TimeCardTransaction must
downcast the PaymentClassification it finds in the Employee to an
HourlyClassification.
void TimeCardTransaction::Execute()
{
Employee* e = GpayrollDatabase.GetEmployee(itsEmpid);
if (e){
PaymentClassification* pc = e->GetClassification();
if (HourlyClassification* hc =
dynamic_cast<HourlyClassification*>(pc)) {
hc->AddTimeCard(new TimeCard(itsDate, itsHours));
} else
throw("Tried to add timecard to non-hourly employee");
} else
throw("No such employee.");
}
How would you eliminate this downcast without adding other undesirable
dependencies to the system?
-----
Robert C. Martin (Uncle Bob)
Object Mentor Inc.
unclebob @ objectmentor . com
800-338-6716
"The aim of science is not to open the door to infinite wisdom,
but to set a limit to infinite error."
-- Bertolt Brecht, Life of Galileo
- Next message: Matthias Hofmann: "Re: Aggregation vs composition"
- Previous message: Hugo Kornelis: "Re: Nearest Common Ancestor Report (XDb1's $1000 Challenge)"
- In reply to: Daniel T.: "Re: Downcasting - whats the problem?"
- Next in thread: Daniel T.: "Re: Downcasting - whats the problem?"
- Reply: Daniel T.: "Re: Downcasting - whats the problem?"
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Relevant Pages
|