views:

596

answers:

3

I'm writing some software that targets two versions of very similar hardware which, until I use the API to initialize the hardware I'm not able to know which type I'll be getting back.

Because the hardware is very similar I planned to have a parent class (TParent) that has some abstract methods (for where the hardware differs) and then two child classes (TChildA, TChildB) which implement those methods in a hardware dependent manner.

So I would first instantiate an object of TParent check what kind it is then cast it to the correct child.

However when I do this and call one of the abstract methods fully implemented in the child class I get an EAbstractError.

e.g:

myHardware:=TParent.Create();

if myHardware.TypeA then
   myHardware:=TChildA(myHardware)
else
   myHardware:=TChildB(myHardware);

myHardware.SomeMehtod();

I'm assuming that I can't cast a Parent Class to a child class, and also that there's probably a better way of doing this. Any pointers?

+5  A: 

You need a factory method to return you the correct class depending on the Type of hardware you are using...

function CreateHardware(isTypeA: Boolean): TParent;
begin
  if IsTypeA then Result := TChildA.Create
  else Result := TChildB.Create;
end;
...

var
  myHardware: TParent;
begin
  myHardware := CreateHardware(True);
  myHardwarde.SomeMethod;
end;

... or you could use the State pattern.

Common in either approach is that your TParent class does not has the knowledge to determine the type of hardware.That knowlegde is transfered into the factory method, caller of the factory method, factory itself or state class.

Lieven
+1. Your answer is pitched better to the asker, and is a more general case than mine. Nice one
Binary Worrier
+1. Initializing the hardware to determine its type is something I want to do as little as possible (it's mechanically stressful) but I'd still like to do encapsulate the initialization within the class whereas this way I need to initialize the hardware first, if I understand correctly?
RobS
@RobS - At some point you "have" to know which Child should get created. If initializing the actual hardware is a requirement to determine the type, I don't see any other way than initializing it (besides using mock objects). Initializing should only be done once though.
Lieven
... and the initialization of the hardwarde can be postponed until you actually call the factory method.
Lieven
This seems to be the wrong solution, as the information about the specific class to create is not available until at least some initialization has been performed. The pImpl idiom looks like a better fit.
mghie
@mghie - as the OP implemented it now, it's identical to the state pattern. I don't see any difference with the suggested factory method either besides hiding the implementation in another class.
Lieven
@Lieven: Where is the value of isTypeA coming from if it's only available during initialization? This is kind of a chicken - egg problem, isn't it? And IMHO this is strategy pattern, not state pattern.
mghie
BTW - Wikipedia has the line "This pattern is invisible in languages with first-class functions." for both state and strategy. Depending on the differences between your two hardware versions you could of course also get by with procedure / method pointers that are set depending on hardware type.
mghie
@mghie - Where is it coming from in the OP's InitializeHardware? Imagine the CreateHardware method to be in the THardware class and imagine in the constructor of THardware the assignment to a TParent like HardwareSpecificMethods := CreateHardware. Besides the encapsulation within a class, they look
Lieven
the same to me. I won't argue about state vs strategy. Never understood their difference very well.
Lieven
+3  A: 

You're right, you can't and shouldn't cast from base class to derived class.

I'm assuming you don't want to have the Child object re-run the Parent constructor?

If so . . .

Remove the Parent/Child relationship as it stands, you will have only one Hardware class. For the specific ChildA and ChildB functionality, create a new inheritance pattern, so that you have an ISpecificHardwareTasks interface or base class, and two derived classes (SpecificA & SpecificB).

When Hardware is constructing it's self, and it gets to the point where it knows what type of hardware it's working with, it then creates an instance of SpecificA or SpecificB). This instance is private to Hardware.

Hardware exposes methods which wrap the ISpecificHardWareTasks methods (it can even implement that interface if that makes sense).

The Specific classes can take a reference to the Hardware class, if that's necessary (though I don't know if you have access to the this pointer in a constructor, my Delphi is getting rusty)

Hope these ramblings helped somewhat.

Binary Worrier
+1. Your second sentence hits the nail on the head. When I understand your "ramblings" (your words not mine ;) ) correctly I may well implement what you suggest.
RobS
+1 for the answer, even though it is a little less clear than it could be. The important point is that inheritance is the wrong tool here. The OP should implement a generic hardware class, and use the pImpl idiom for the specifics.
mghie
@mghie. Thank you for pointing me to its name, I'd +1 your comment if I could.
RobS
+4  A: 

Thanks to Binary Worrier and Mghie for pointing me in the right direction in this instance. The answer given by Lieven would be the easier way in cases where minimising the initialization of the hardware wasn't an issue.

The pImpl idiom is discussed elsewhere on SO

Here is how I understand the implementation in pseudo-delphi-code (note I've not bothered with public/private distinctions for this):

class TParent 
  procedure SomeMethod(); abstract;
end;

class TChildA (TParent)
  procedure SomeMethod(); override;
end;

class TChildB (TParent)
  procedure SomeMethod(); override;
end;

class THardware
 HardwareSpecficMethods: TParent;
 procedure SomeMethod;
 constructor Create();

contrsuctor THardware.Create();
begin
 InitializeHardware();
 If Hardware.TypeA then
  HardwareSpecificMethods:=TChildA.Create()
 else
  HardwareSpecificMethods:=TChildB.Create();
end;

procedure THardware.SomeMethod();
begin
  HardwareSpecificMethods.SomeMethod();
end;
end; {class THardware}
RobS
@RobS - You've just used your first State Pattern implementation. Congrats :)
Lieven
@Lieven - Thank you. :)
RobS