3

I have a base object type : TBServiceBookings and then I've derived another object type from that : TBServiceQuotes

So when my form is created I decide which object to use. In this instance I've created the derived object.

if fScreenType = ST_NewAppointment then
  fBookingObject :=   TBServiceBookings.CreateServiceBookings(nil,botSingle)
else
  fBookingObject  := TBServiceQuotes.CreateServiceQuotes(nil,botSingle);

At some stage I want to call a method of the base class only. So I cast the derived object to the base type and call it's method, but it keeps on going to the derived method - which I don't want.

I've tried:

1)  fBookingObject := TBServiceBookings(fBookingObject);
    fBookingObject.SetupNewAppointmentScreen;

2)
    TBServiceBookings(fBookingObject).SetupNewAppointmentScreen;

3)
    (fBookingObject as TBServiceBookings).SetupNewAppointmentScreen;

What am I missing? Why does the derived method get called each time even though I've downcasted specifically to call the base method?

The only option I have left is to create a new variable of the base type and then carry on with that. But I already have a form variable which is my object, I just want to call a specific base class method.

Any help appreciated please!

Frank Pedro
  • 198
  • 2
  • 10
  • Your code is incomplete so we cannot see if `SetupNewAppointmentScreen` is properly marked as `virtual` in the base class and `override` in the child class – Stefan Glienke Sep 22 '15 at 08:33
  • 1
    I assume that the method you call is virtual (or dynamic). Then it always calls the method of the *actual* type, not of the *declared* type. Casting does not change this. The fact you want to call base class behaviour means that you should probably reconsider your design. – Rudy Velthuis Sep 22 '15 at 08:33
  • @Stefan: then it would actually behave as he wants, or have the behaviour of the most derived class that did this properly. If it is virtual and properly marked as override, it does what he describes.But the need to call base class behaviour is, IMO, a code smell. – Rudy Velthuis Sep 22 '15 at 08:36
  • StefanGlienke, yes it's declared virtual and and the derived class as override. – Frank Pedro Sep 22 '15 at 08:37
  • @FrankPedro: and that is exactly why it behaves as it does. Virtual methods are called on the actual class type, not on the declared (or cast) one. – Rudy Velthuis Sep 22 '15 at 08:38
  • Rudy Velthuis, Ok, so there is no way to do it like this then? I have a form with the base object. This form is customized based on the type of object created. But I need to also go back to the base form behaviour. So in the beginning I created the derived object. Later on, I must be able to go back to the base behaviour and so this is why I tried to downcast the object to get the right behaviour... – Frank Pedro Sep 22 '15 at 08:41
  • Yes, I read it wrong. If you want the base functionality then don't make it virtual, simple as that. I also agree with Rudy, this looks like a code smell. – Stefan Glienke Sep 22 '15 at 08:42
  • 4
    Actually, if you want the base functionality by calling that method, then something is wrong with your design. That base functionality should probably be a separate (public) method which is then called by the base version of the virtual method, but which is callable separately too. – Rudy Velthuis Sep 22 '15 at 08:44
  • the method SetupNewAppointmentScreen should customize my form. So this is why I have derived types in order to tweak the form based on the object created. This was my idea though... – Frank Pedro Sep 22 '15 at 08:47
  • Then create a method (non-virtual) (let's call it `SetupBasicAppointment`) that customizes your form in the base way, and use that in the virtual `SetupNewAppointment` methods. – Rudy Velthuis Sep 22 '15 at 08:49
  • Ok Rudy, thank you for the advice. – Frank Pedro Sep 22 '15 at 08:59

2 Answers2

3

Besides the question that lies behind your question "where do you need this unusual and somewhat suspicious construct for?", there are some possibilities to access an ancestor virtual method.

  1. Really ugly: change the type of your component:

    var
      SaveType: TClass;
    begin
      SaveType := Self.ClassType;
      PClass(Self)^ := TAncestor;
      try
        Self.AncestorMethod;
      finally
        PClass(Self)^ := SaveType
      end;
    end;
    
  2. Cast the method instead of the class:

    type
      TInstanceMethod = procedure(Instance: TObject);
    
    begin
      TInstanceMethod(@TAncestor.AncestorMethod)(Self);
    end;
    
  3. Employ a class helper:

    type
      TAncestorHelper = class helper for TAncestor
        procedure AncestorMethodViaHelper;
      end;
    
    procedure TAncestorHelper.AncestorMethodViaHelper;
    begin
      inherited AncestorMethod;
    end;
    
    begin
      Self.AncestorMethodViaHelper;
    end;
    

When in need, I myself always use the second solution. But that is only when dealing with ancestors I cannot change, e.g. the VCL. Within your own framework, you would never need these hacks because you can just redesign.

NGLN
  • 43,011
  • 8
  • 105
  • 200
1

Well if the class is yours, you have full control, so just don't override the base method you want to call. Like :

fBaseObject.ThisMethodBase; { calls the original }
fDerivedObject.ThisMethod;  { calls the new one }

Seems like the simplest way to do it. Also remember you can simply call the Inherited method from your overriden method. So if you want to get creative you can pass a boolean that indicates if you want the base functionality. Like :

type
TX1=class
  function ThisMethod(whatever:Integer;callOldOne:Boolean=false):integer;virtual;
end;

TX2=class(TX1)
  function ThisMethod(whatever:Integer;callOldOne:Boolean=false):integer;override;
end;

function TX1.ThisMethod(whatever:Integer;callOldOne:Boolean=false):integer;
begin
   result:=1;
end;

function TX2.ThisMethod(whatever:Integer;callOldOne:Boolean=false):integer;
begin
   if callOldOne then result:=inherited ThisMethod(whatever) else Result:=2;
end;

Object oriented programming is fun.

Vaneik
  • 2,963
  • 1
  • 12
  • 9
  • Thanx Kung Lao. This seemed to defeat the purpose of what I was trying to do. I wanted the object to know what to do based on it's type. I seperated logic into the base and derived class and I wanted the object to handle it accordingly. – Frank Pedro Sep 22 '15 at 11:29