如何在Delphi中正确实现依赖倒置原则——基于不可修改父类的多接口实现方案
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/InterlockedDecrementto 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

