You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Delphi中正确实现依赖倒置原则——基于不可修改父类的多接口实现方案

Fixing Delphi Interface Implementation Errors & Applying Dependency Inversion Principle with Unmodifiable Parent Classes

Great question! Let's break this down step by step—first resolving those frustrating compiler errors, then diving into how to apply the Dependency Inversion Principle (DIP) effectively, especially in multi-interface scenarios.


Why You're Seeing Those E2291 Errors

Let's start with the root cause. In Delphi, all custom interfaces implicitly inherit from IInterface, which defines three non-negotiable COM-style methods: QueryInterface, _AddRef, and _Release.

Your unmodifiable Parent class doesn't implement IInterface (it's just a bare class declaration), so when you make Child implement INiceInterface, the compiler expects you to provide these three methods yourself—they don't get inherited from Parent.


Solution: Manually Implement IInterface Methods in Child

Since you can't modify Parent, you need to add the required IInterface implementations directly to Child. Here's a robust, thread-safe way to do it:

unit ChildImplementation;

interface

type
  Parent = class; // Unmodifiable library class

  INiceInterface = interface
    ['{YOUR-UNIQUE-GUID-HERE}'] // Always add a GUID (use Tools > Create GUID)
    procedure HelloWorld;
  end;

  Child = class(Parent, INiceInterface)
  private
    FRefCount: Integer;
    // IInterface required methods
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    constructor Create;
    procedure HelloWorld;
  end;

implementation

uses
  SysUtils, Windows;

constructor Child.Create;
begin
  inherited;
  FRefCount := 1; // Initialize ref count to 1 for object creation
end;

// IInterface implementation
function Child.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

function Child._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount); // Thread-safe increment
end;

function Child._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount); // Thread-safe decrement
  if Result = 0 then
    Destroy; // Clean up when ref count hits 0
end;

// INiceInterface implementation
procedure Child.HelloWorld;
begin
  Writeln('Hello from Child, implementing INiceInterface!');
end;

end.

Key Notes:

  • GUIDs are mandatory: Delphi uses GUIDs to identify interfaces, so always generate one for your custom interfaces.
  • Thread safety: Use InterlockedIncrement/InterlockedDecrement to avoid race conditions in multi-threaded code.
  • Memory management: Stick to interface references (var Intf: INiceInterface; Intf := Child.Create;) instead of direct object references to let the ref count handle cleanup automatically.

Applying Dependency Inversion Principle (DIP) for Multi-Interface Scenarios

Now that your interface works, let's focus on DIP—core rule: Depend on abstractions, not concretions. With your unmodifiable Parent class, interfaces are your best tool to decouple code from concrete implementations. Here's how to apply it:

1. Define Single-Responsibility Interfaces

Follow the Interface Segregation Principle (ISP) alongside DIP: split behaviors into small, focused interfaces instead of one bloated interface:

INiceInterface = interface
  ['{GUID-1}']
  procedure HelloWorld;
end;

IWorkable = interface
  ['{GUID-2}']
  procedure DoWork;
end;

IConfigurable = interface
  ['{GUID-3}']
  procedure SetConfig(Value: String);
end;

2. Have Child Implement Relevant Interfaces

Your Child class can inherit from Parent while implementing multiple interfaces—Delphi supports this natively:

Child = class(Parent, INiceInterface, IWorkable, IConfigurable)
  // ... existing IInterface methods ...
  procedure HelloWorld;
  procedure DoWork;
  procedure SetConfig(Value: String);
end;

3. Program to Interfaces, Not Concrete Classes

In your application code, never depend directly on Child or Parent. Always use interfaces to interact with objects:

procedure GreetUser(Greeter: INiceInterface);
begin
  Greeter.HelloWorld;
end;

procedure RunWorker(Worker: IWorkable);
begin
  Worker.DoWork;
end;

// Usage
var
  MyChild: INiceInterface;
  Worker: IWorkable;
begin
  MyChild := Child.Create;
  GreetUser(MyChild);

  // Query for other interfaces if needed
  if MyChild.QueryInterface(IWorkable, Worker) = S_OK then
    RunWorker(Worker);
end;

4. Optional: Decouple Entirely from Parent with an Adapter

If you want to avoid tying your code to Parent at all, use an adapter pattern. Create a separate class that implements your interfaces and wraps a Parent instance:

ParentAdapter = class(TInterfacedObject, INiceInterface, IWorkable)
private
  FParent: Parent;
public
  constructor Create(AParent: Parent);
  destructor Destroy; override;
  procedure HelloWorld;
  procedure DoWork;
end;

// Implementation
constructor ParentAdapter.Create(AParent: Parent);
begin
  inherited Create;
  FParent := AParent;
end;

destructor ParentAdapter.Destroy;
begin
  FParent.Free;
  inherited;
end;

procedure ParentAdapter.HelloWorld;
begin
  // Delegate to Parent's logic or implement custom behavior
  Writeln('Hello from ParentAdapter!');
end;

This way, your application code never knows Parent exists—it only interacts with your interfaces.


内容的提问来源于stack exchange,提问作者Adrian Maire

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 22:04:05