Search code examples
delphiinheritanceencapsulation

How to Inherit from a base datamodule in Delphi7


I created a base datamodule as such:

unit dmi_Generic;
...
type
  TDMGenericClass = class of TdmiGeneric;

  TdmiGeneric = class(TDataModule)
private
  ReferencedForms: FormIDTypes;
public
  class procedure CloseDM(FormName: string; dm: TdmiGeneric);
  class function OpenDM(FormName: string; dm: TdmiGeneric; const dmClass:
                        TDMGenericClass): TdmiGeneric;
end;
...
class function TdmiGeneric.OpenDM(FormName: string; dm: TdmiGeneric; const
                dmClass: TDMGenericClass):
                TdmiGeneric;
var
    FormID: Integer;
begin
    FormID := GetFormID(FormName);
    if dm = nil then
        dm := dmClass.Create(nil);   //trouble is probably born here
    with dm do
        if not (FormID in ReferencedForms) then
            ReferencedForms := ReferencedForms + [FormID];
    Result := dm;
end;
...

I intended to inherit other datamodules from dmi_generic, so, in another unit...

Edit1

uses
    dmi_Generic;
...
type
  TdmUserSecurity = class(TdmiGeneric)
... 
var
  dmUserSecurity: TdmUserSecurity;

and in my main form...

uses
  UserSecurityDM;
  ...
procedure TfmMain.actUserManagerExecute(Sender: TObject);
begin
  dmUserSecurity := TdmUserSecurity.OpenDM(Self.Name,dmUserSecurity,TdmUserSecurity);
  //some code
  TdmUserSecurity.CloseDM(Self.Name,dmUserSecurity);
end;

Edit2: ...which gives compile error "Incompatible type. 'TdmUserSecurity' and 'TdmiGeneric'"

How do I fix this, properly? Do I typecast the return value at the main form, or is there something I should change at the dmi_generic unit? TIA


Solution

  • OpenDM() returns a TdmiGeneric pointer. But dmUserSecurity is not declared as TdmiGeneric, so you will get an error on this assignment:

    dmUserSecurity := TdmUserSecurity.OpenDM(...);
    

    You would need a type-cast to fix that, either:

    dmUserSecurity := TdmUserSecurity(TdmUserSecurity.OpenDM(...));
    

    Or:

    dmUserSecurity := TdmUserSecurity.OpenDM(...) as TdmUserSecurity;
    

    BTW, why does OpenDM() have a dm parameter at all? It is useless in the code you showed. You should either:

    1. pass it by var and get rid of Result

      unit dmi_Generic;
      ...
      type
        TdmiGeneric = class;
        TDMGenericClass = class of TdmiGeneric;
      
        TdmiGeneric = class(TDataModule)
        private
          ...
        public
          ...
          class procedure CloseDM(FormName: string; var dm: TdmiGeneric);
          class procedure OpenDM(FormName: string; const dmClass: TDMGenericClass; var dm: TDMGeneric);
        end;
      ...
      class procedure TdmiGeneric.OpenDM(FormName: string;  const dmClass: TDMGenericClass; var dm: TdmiGeneric);
      var
        ...
      begin
        ...
        if dm = nil then
          dm := dmClass.Create(nil);
        with dm do
          ...
      end;
      
      class procedure TdmiGeneric.CloseDM(FormName: string; var dm: TdmiGeneric);
      begin
        FreeAndNil(dm);
      end;
      

      uses
        UserSecurityDM;
      ...
      procedure TfmMain.actUserManagerExecute(Sender: TObject);
      begin
        TdmUserSecurity.OpenDM(Self.Name, TdmUserSecurity, TdmiGeneric(dmUserSecurity));
        //some code
        TdmUserSecurity.CloseDM(Self.Name, TdmiGeneric(dmUserSecurity));
      end;
      
    2. get rid of dm as a parameter and declare it as a local variable instead:

      unit dmi_Generic;
      ...
      type
        TdmiGeneric = class;
        TDMGenericClass = class of TdmiGeneric;
      
        TdmiGeneric = class(TDataModule)
        private
          ...
        public
          ...
          class procedure CloseDM(FormName: string; var dm: TDMGeneric);
          class function OpenDM(FormName: string; const dmClass: TDMGenericClass): TDMGeneric;
        end;
      ...
      class function TdmiGeneric.OpenDM(FormName: string; const dmClass: TDMGenericClass): TdmiGeneric;
      var
        dm: TDMGeneric;
        ...
      begin
        ...
        dm := dmClass.Create(nil);
        with dm do
          ...
        Result := dm;
      end;
      
      class procedure TdmiGeneric.CloseDM(FormName: string; var dm: TDMGeneric);
      begin
        FreeAndNil(dm);
      end;
      

      uses
        UserSecurityDM;
      ...
      procedure TfmMain.actUserManagerExecute(Sender: TObject);
      begin
        dmUserSecurity := TdmUserSecurity.OpenDM(Self.Name, TdmUserSecurity) as TdmUserSecurity;
        //some code
        TdmUserSecurity.CloseDM(Self.Name, TdmiGeneric(dmUserSecurity));
      end;
      

    Either way, since you are calling OpenDM() on the TdmUserSecurity class and not on the TdmGeneric class, you might consider using the Self class type inside of OpenDM(), then you can remove the dmClass parameter:

    unit dmi_Generic;
    ...
    type
      TdmiGeneric = class;
      TDMGenericClass = class of TdmiGeneric;
    
      TdmiGeneric = class(TDataModule)
      private
        ...
      public
        ...
        class procedure OpenDM(FormName: string; var dm: TdmiGeneric);
      end;
    ...
    class procedure TdmiGeneric.OpenDM(FormName: string; var dm: TdmiGeneric);
    var
      ...
    begin
      ...
      if dm = nil then
      begin
        if Self = TdmiGeneric then
          raise Exception.Create('Must call OpenDM() on a descendant class type').
        dm := TDMGenericClass(Self).Create(nil);
      end;
      with dm do
        ...
    end;
    

    uses
      UserSecurityDM;
    ...
    procedure TfmMain.actUserManagerExecute(Sender: TObject);
    begin
      TdmUserSecurity.OpenDM(Self.Name, TdmiGeneric(dmUserSecurity));
      ...
    end;
    

    Or:

    unit dmi_Generic;
    ...
    type
      TdmiGeneric = class;
      TDMGenericClass = class of TdmiGeneric;
    
      TdmiGeneric = class(TDataModule)
      private
        ...
      public
        ...
        class function OpenDM(FormName: string): TDMGeneric;
      end;
    ...
    class function TdmiGeneric.OpenDM(FormName: string): TdmiGeneric;
    var
      dm: TDMGeneric;
      ...
    begin
      ...
      if Self = TdmiGeneric then
        raise Exception.Create('Must call OpenDM() on a descendant class type').
      dm := TDMGenericClass(Self).Create(nil);
      with dm do
          ...
      Result := dm;
    end;
    

    uses
      UserSecurityDM;
    ...
    procedure TfmMain.actUserManagerExecute(Sender: TObject);
    begin
      dmUserSecurity := TdmUserSecurity.OpenDM(Self.Name) as TdmUserSecurity;
      ...
    end;