Jump to content
bravesofts

Ensuring Consistent Base Interface Implementation in Delphi Class Hierarchy

Recommended Posts

i have base interface:
 

type

  iBaseFoo = interface
    BaseMethod1;
    BaseMethod2;
    BaseMethod3;
  end;

  iTestFoo = interface(ibaseFoo)
    TestFooMethod;
  end;

--

  TBaseFoo = class(TInterfacedObject, iBaseFoo)
  private
  // some code here
  protected
    BaseMethod1;
    BaseMethod2;
    BaseMethod3;
  end;

TTestFoo = class(TBaseFoo, ITestFoo) 
  // how to Ensures TTestFoo inherits IBaseFoo methods from TBaseFoo, avoiding re-implementation.
  procedure TestFooMethod;
end;

--

my question is:

How can I ensure that TTestFoo (or any descendant of TBaseFoo) uses the IBaseFoo method implementations provided by TBaseFoo, without re-implementing these methods in every descendant class?

Share this post


Link to post

The TTestFoo class inherits all of the implemented methods of the TBaseFoo class.  This is literally how class inheritance works.  I don't understand the question.

 

However, interface inheritance works a little different. Sometimes you have to make sure that a derived class explicitly declares all of the interfaces it supports - even the base ones - and not rely on inheritance alone to do that for you, eg:

TTestFoo = class(TBaseFoo, IBaseFoo, ITestFoo) 
  // automatically inherits the methods of TBaseFoo...
  procedure TestFooMethod;
end;

 

Edited by Remy Lebeau
  • Like 2

Share this post


Link to post
8 minutes ago, Remy Lebeau said:

I don't understand the question.

i search a workaround or a pure solution that let me avoid re-implementation of iBaseFoo methods since TBaseFoo Already there

 

Share this post


Link to post
Just now, bravesofts said:

i search a workaround or a pure solution that let me avoid re-implementation of iBaseFoo methods since TBaseFoo Already there

Any class that derives from TBaseFoo inherits the methods that TBaseFoo implements.  Why do you think any workaround is needed?  What is the actual PROBLEM you are having with your code in the first place?

Share this post


Link to post
Just now, Remy Lebeau said:

What is the actual PROBLEM you are having with your code in the first place?

I am looking for a good software design solution that helps me avoid repeatedly implementing methods from TBaseFoo. Logically, by adding iTestFoo as an ancestor to TTestFoo, I am always forced to implement the methods of iBaseFoo, which come from iTestFoo. I want to avoid re-implementation of these methods. I tried marking the methods of TBaseFoo as virtual, but it didn’t help.

Share this post


Link to post

Please show the code that actually exhibits the problem. The code you posted (if we ignore the fact that you are missing procedure keyword in your method declarations) works the way you want it to work. 

  • Like 2

Share this post


Link to post
unit Model.DB.Exceptions;

interface
uses
  System.SysUtils;

type
  EDatabaseError = class(Exception);
  EInvalidParameterError = class(EDatabaseError);
  EConnectionError = class(EDatabaseError);

implementation

end.

----

Unit Model.Firedac.ParamsTypes;

interface
uses
  System.Classes,
  System.Generics.Collections;

type
{$REGION '  Firedac Connection Params .. '}
{$REGION '  DriverName Param & DataType .. '}
  TDriverFDTypeEnum = (dtAccessDB, dtSqlite, dtPostgreSql, dtMySQL, dtFirebird, dtInterbase, dtInterbaseLite);
  TDriverFDTypeEnumHelper = record helper for TDriverFDTypeEnum
  public
    function ToString: string;
    function GetDriverName: string;
  end;

  TBaseFiredacDriver = class
  protected
    class function DBName(const aValue: TDriverFDTypeEnum): string; static;
    class function DriverName(const aValue: TDriverFDTypeEnum): string; static;
  end;

  TMSAccessDriver = class(TBaseFiredacDriver) end;
  TSQLiteDriver = class(TBaseFiredacDriver) end;
  TPostgreSqlDriver = class(TBaseFiredacDriver) end;
  TMySQLDriver = class(TBaseFiredacDriver) end;
  TFirebirdDriver = class(TBaseFiredacDriver) end;
  TInterbaseDriver = class(TBaseFiredacDriver) end;
  TInterbaseLiteDriver = class(TBaseFiredacDriver) end;
{$ENDREGION}

{$REGION '  SQLite Params .. '}
  TSQLiteLockingMode = (mLockingExclusive, mLockingNormal);
  TSQLiteLockingModeHelper = record helper for TSQLiteLockingMode
  public
    function ToString: string;
  end;

  TSQLiteEncryptMode = (EncryptNone, AES128, AES192, AES256, AES_CTR128, AES_CTR192, AES_CTR256, AES_ECB128, AES_ECB192, AES_ECB256);
  TSQLiteEncryptModeHelper = record helper for TSQLiteEncryptMode
  public
    function ToString: string;
    class function FromString(const aValue: string): TSQLiteEncryptMode; static;
  end;
{$ENDREGION}

  TFDRemoteBy = (FlatFile, Remote, Custom);
    TFDRemoteByHelper = record helper for TFDRemoteBy
  public
    function ToString: string;
  end;
{$ENDREGION}

implementation
uses
  System.SysUtils,
  Model.DB.Exceptions;

{$REGION '  Driver FireDac Type Helper.. '}
function TDriverFDTypeEnumHelper.ToString: string;
const
  cDBTypeNames: array [TDriverFDTypeEnum] of string = (
    'MSAccess', 'SQLite', 'PostgreSQL', 'MySQL', 'Firebird', 'Interbase', 'Interbase Lite'
  );
begin
  Result := cDBTypeNames[Self];
end;

function TDriverFDTypeEnumHelper.GetDriverName: string;
const
  cDBDriverNames: array [TDriverFDTypeEnum] of string = (
    'MSAcc', 'SQLite', 'PG', 'MySQL', 'FB', 'IB', 'IBLite'
  );
begin
  Result := cDBDriverNames[Self];
end;
{$ENDREGION}

{$REGION '  SQLite Locking Mode Helper .. '}
function TSQLiteLockingModeHelper.ToString: string;
const
  cLockingModeNames: array [TSQLiteLockingMode] of string = (
    'Exclusive','Normal'
  );
begin
  Result := cLockingModeNames[Self];
end;
{$ENDREGION}

{$REGION '  SQLite Encrypt Mode Helper .. '}
function TSQLiteEncryptModeHelper.ToString: string;
const
  cEncryptModeNames: array [TSQLiteEncryptMode] of string = (
    'No', 'aes-128', 'aes-192', 'aes-256', 'aes-ctr-128',
    'aes-ctr-192', 'aes-ctr-256', 'aes-ecb-128', 'aes-ecb-192', 'aes-ecb-256'
  );
begin
  Result := cEncryptModeNames[Self];
end;

class function TSQLiteEncryptModeHelper.FromString(const aValue: string): TSQLiteEncryptMode;
var
  LEncryptMode: TSQLiteEncryptMode;
begin
  for LEncryptMode := Low(TSQLiteEncryptMode) to High(TSQLiteEncryptMode) do
    if SameText(aValue, LEncryptMode.ToString) then
      Exit(LEncryptMode);

  raise EInvalidParameterError.CreateFmt('Invalid encryption mode: %s', [aValue]);
end;
{$ENDREGION}

{$REGION '  FireDac RemoteBy Helper .. '}
function TFDRemoteByHelper.ToString: string;
const
  cFDRemoteByNames: array [TFDRemoteBy] of string = (
    'FlateFile', 'Remote', 'Custom'
  );
begin
  Result := cFDRemoteByNames[Self];
end;
{$ENDREGION}

{ TBaseFiredacDriver }

class function TBaseFiredacDriver.DBName(
  const aValue: TDriverFDTypeEnum): string;
begin
  Result := aValue.ToString;
end;

class function TBaseFiredacDriver.DriverName(
  const aValue: TDriverFDTypeEnum): string;
begin
  Result := aValue.GetDriverName;
end;

end.
----
unit Model.FiredacParams.BaseInterface;

interface
uses
  Model.Firedac.ParamsTypes;

type
  iBaseFDParams<TDBType: TBaseFiredacDriver> = interface ['{B5A4A031-EFA0-4424-902D-2529FC4F1B48}']

    function Pooled(aValue: Boolean): iBaseFDParams<TDBType>; overload;
    function Database(aValue: string): iBaseFDParams<TDBType>; overload;
    function Username(aValue: string): iBaseFDParams<TDBType>; overload;
    function Password(aValue: string): iBaseFDParams<TDBType>; overload;
//    function MonitorBy(aValue: TFDRemoteBy): iBaseFDParams<TDriverFDType>; overload;

    function Pooled: Boolean; overload;
    function Database: string; overload;
    function UserName: string; overload;
    function Password: string; overload;
//    function MonitorBy: TFDRemoteBy; overload;

    function Params: iBaseFDParams<TDBType>;
  end;

implementation

end.
----

The code provided above contains necessary units used by my main unit below (Model.Firedac.ConnectionParams), where the actual issue occurs.

unit Model.Firedac.ConnectionParams;

interface
uses
  System.Classes,
  System.SysUtils,
  System.Generics.Collections,
//
  Model.Firedac.ParamsTypes,
  Model.FiredacParams.BaseInterface,
  Model.DB.Exceptions;

type
  iMSAccesseParams  = iBaseFDParams<TMSAccessDriver>;

  iSQLiteParams     = interface (iBaseFDParams<TSQLiteDriver>)
    function LockingMode(aValue: TSQLiteLockingMode): iBaseFDParams<TSQLiteDriver>; overload;
    function Encrypt(aValue: TSQLiteEncryptMode): iBaseFDParams<TSQLiteDriver>; overload;

    function LockingMode: TSQLiteLockingMode; overload;
    function Encrypt: TSQLiteEncryptMode; overload;
  end;

  iPostgreSqlParams = iBaseFDParams<TPostgreSqlDriver>;
  iMySQLParams      = iBaseFDParams<TMySQLDriver>;
  iFirebirdParams   = iBaseFDParams<TFirebirdDriver>;
  iInterbaseParams  = iBaseFDParams<TInterbaseDriver>;
  iInterbaseLParams = iBaseFDParams<TInterbaseLiteDriver>;

  function GetDefault_SqliteParams(const aDatabase: string;
       const aUsername: string = ''; const aPassword: string = '';
       const aLockingMode: TSQLiteLockingMode = mLockingExclusive; aEncrypt: TSQLiteEncryptMode = EncryptNone): iSQLiteParams;
implementation

type
  iBaseFiredacParams = iBaseFDParams<TBaseFiredacDriver>;

  TBaseConnectionParams = class(TInterfacedObject, iBaseFiredacParams)
  strict private

  protected
    function Pooled(aValue: Boolean): iBaseFiredacParams; overload; virtual;
    function Database(aValue: string): iBaseFiredacParams; overload; virtual;
    function Username(aValue: string): iBaseFiredacParams; overload; virtual;
    function Password(aValue: string): iBaseFiredacParams; overload; virtual;

    function Pooled: Boolean; overload; virtual;
    function Database: string; overload; virtual;
    function UserName: string; overload; virtual;
    function Password: string; overload; virtual;

    function Params: iBaseFiredacParams; virtual;

     procedure ValidateParams; virtual; abstract;
  end;

  TSqliteParams = class(TBaseConnectionParams, iSQLiteParams)
  public
    constructor Create
      (const aDatabase: string;
       const aUsername: string = ''; const aPassword: string = '';
       const aLockingMode: TSQLiteLockingMode = mLockingExclusive; aEncrypt: TSQLiteEncryptMode = EncryptNone);
    procedure ValidateParams; override;

    function LockingMode(aValue: TSQLiteLockingMode): iBaseFDParams<TSQLiteDriver>; overload; virtual;
    function Encrypt(aValue: TSQLiteEncryptMode): iBaseFDParams<TSQLiteDriver>; overload; virtual;

    function LockingMode: TSQLiteLockingMode; overload; virtual;
    function Encrypt: TSQLiteEncryptMode; overload; virtual;

  end;
function GetDefault_SqliteParams(const aDatabase: string;
       const aUsername: string = ''; const aPassword: string = '';
       const aLockingMode: TSQLiteLockingMode = mLockingExclusive; aEncrypt: TSQLiteEncryptMode = EncryptNone): iSQLiteParams;
begin
  Result := TSqliteParams.Create(aDatabase, aUsername,aPassword,aLockingMode, aEncrypt) as iSQLiteParams;
end;

end.

TSqliteParams act as TTestFoo and TBaseConnectionParams as TBaseFoo

Edited by bravesofts

Share this post


Link to post
1 hour ago, bravesofts said:

The code provided above contains necessary units used by my main unit below (Model.Firedac.ConnectionParams), where the actual issue occurs.

Can you trim that down to a MINIMAL example that actually exhibits the problem? Most of that code is irrelevant to the issue.  Are you getting a compiler error?  If  so, what is it?  What EXACTLY are you trying to implement that you think is missing?

  • Like 1

Share this post


Link to post
1 minute ago, Remy Lebeau said:

Can you trim that down to a MINIMAL example that actually exhibits the problem?

I understand the code above is a bit long, but I’d like to keep it to ensure the example can be compiled fully. It provides the necessary context for the issue. If you don’t mind, I’d prefer to leave it here.

Share this post


Link to post

I'm sorry, but the code you have provided is full of errors, I can't even attempt to compile it as-is, and I'm not going to waste my time trying to fix it all.  When you can provide an example that can be COPY/PASTED as-is (or better, provide the actual files as attachments), and it can compile and produce a specific compiler error that you are trying to fix, then I will be happy to look at it again.

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post
2 hours ago, bravesofts said:

I hope this example is easy to understand.

That is not what I would call a MINIMAL example, but at least it compiles this time.  There is still a lot of IRRELEVANT code that has nothing at all to do with demonstrating the issue at hand.  But so be it.

 

Your TBaseConnectionParams and TSqliteParams classes are duplicating similar code because THEY ARE RETURNING DIFFERENT THINGS, so how to do you expect one to just inherit the behavior of the other?  They can't.  If you really want to make use of class inheritance then you need to simplify the design of the common elements so they are coherent and overridable across descendants.  You are not even override'ing the base methods, you are reintroduce'ing them.  That is not how inheritance works.

  • Haha 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×