d1 from interfaces to solid
TRANSCRIPT
June 2015
From Interfaces to SOLID
Delphi and interfaces
SOLID design principles
Weak pointers
Dependency Injection, stubs and mocks
From Interfaces to SOLID
June 2015
Delphi and interfaces
In Delphi OOP model
An interface defines a type
that comprises abstract virtual methods
It is a declaration of functionality
without an implementation of that functionality
It defines "what" is available,
not "how" it is made available
From Interfaces to SOLID
June 2015
Delphi and interfaces
Declaring an interface
Naming convention: ICalculator
No visibility attribute: all published
No fields, just methods and properties
Unique identifier by GUID (Ctrl Shift G)
From Interfaces to SOLID
type ICalculator = interface(IInvokable) ['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}'] /// add two signed 32 bit integers function Add(n1,n2: integer): integer; end;
June 2015
Delphi and interfaces
Implementing an interface
ICalculator added to the “inheritance chain”
TCalculator implements a behavior
From Interfaces to SOLID
type TServiceCalculator = class(TInterfacedObject, ICalculator) protected fBulk: string; public function Add(n1,n2: integer): integer; procedure SetBulk(const aValue: string); end;
function TServiceCalculator.Add(n1, n2: integer): integer; begin result := n1+n2; end;
procedure TServiceCalculator.SetBulk(const aValue: string); begin fBulk := aValue; end;
June 2015
Delphi and interfaces
Using an interface
Strong typing
The variable defines a behavior (contract)
Path to abstraction
From Interfaces to SOLID
function MyAdd(a,b: integer): integer; var Calculator: ICalculator; begin Calculator := TServiceCalculator.Create; result := Calculator.Add(a,b); end;
June 2015
Delphi and interfaces
Using an interface
Strong typing
The variable defines a behavior (contract)
Path to abstraction
Automatic try..finally
From Interfaces to SOLID
function MyAdd(a,b: integer): integer; var Calculator: TServiceCalculator; begin Calculator := TServiceCalculator.Create; try result := Calculator.Add(a,b); finally Calculator.Free; end; end;
function MyAdd(a,b: integer): integer; var Calculator: ICalculator; begin Calculator := TServiceCalculator.Create; result := Calculator.Add(a,b); end;
June 2015
Delphi and interfaces
Automatic try..finally
Compiler generates
some hidden code…
Behavior inherited from TInterfacedObject Similar to COM / ActiveX
From Interfaces to SOLID
function MyAdd(a,b: integer): integer; var Calculator: ICalculator; begin Calculator := TServiceCalculator.Create; result := Calculator.Add(a,b); end;
function MyAdd(a,b: integer): integer; var Calculator: TServiceCalculator; begin Calculator := nil; Calculator := TServiceCalculator.Create; try Calculator.FRefCount := 1; result := Calculator.Add(a,b); finally dec(Calculator.FRefCount); if Calculator.FRefCount=0 then Calculator.Free; end; end;
June 2015
Delphi and interfaces
Interfaces are orthogonal to implementation
There is more than one way to do it
From Interfaces to SOLID
type TOtherServiceCalculator = class(TInterfacedObject, ICalculator) protected function Add(n1,n2: integer): integer; end;
function TOtherServiceCalculator.Add(n1, n2: integer): integer; begin result := n2+n1; end;
function MyOtherAdd(a,b: integer): integer; var Calculator: ICalculator; begin ICalculator := TOtherServiceCalculator.Create; result := Calculator.Add(a,b); end;
June 2015
SOLID design principles
Single responsibility principle
Open/closed principle
Liskov substitution principle (design by contract)
Interface segregation principle
Dependency inversion principle
From Interfaces to SOLID
June 2015
SOLID design principles
Single responsibility Object should have only a single responsibility
Open/closed Entities should be open for extension,
but closed for modification
Liskov substitution (design by contract) Objects should be replaceable with instances of their
subtypes without altering the correctness of that program
Interface segregation Many specific interfaces are better than one
Dependency inversion Depend upon abstractions, not depend upon concretions
From Interfaces to SOLID
June 2015
SOLID design principles
Help to fight well-known weaknesses
Rigidity
Hard to change something because every change
affects too many other parts of the system
Fragility
When you make a change, unexpected parts of the
system break
Immobility
Hard to reuse in another application because it cannot
be disentangled from the current application
From Interfaces to SOLID
June 2015
SOLID design principles
Single Responsibility
When you define a class, it shall be designed to
implement only one feature
The so-called feature can be seen as
an "axis of change" or a "a reason for change"
From Interfaces to SOLID
June 2015
SOLID design principles
Single Responsibility
One class shall have only one reason
that justifies changing its implementation
Classes shall have few dependencies
on other classes
Classes shall be abstracted
from the particular layer they are running
From Interfaces to SOLID
June 2015
SOLID design principles
Single Responsibility
Do not mix GUI and logic in classes
Do not mix logic and database
For instance, in SynDB, we defined:
TSQLDBConnectionProperties
TSQLDBConnection
TSQLDBStatement
From Interfaces to SOLID
June 2015
SOLID design principles
Open / Close principle
When you define a class
it shall be open for extension
but closed for modification
From Interfaces to SOLID
June 2015
SOLID design principles
Open / Closed principle
Open for extension
Abstract class is overridden by implementations
No singleton nor global variable – ever
Rely on abstraction
if aObject is aClass then … it smells!
Closed for modification
E.g. via explicitly protected or private members
RTTI is dangerous: it may open the closed door
From Interfaces to SOLID
June 2015
SOLID design principles
Liskov substitution principle
If TChild is a subtype of TParent
then objects of type TParent
may be replaced with objects of type TChild
without altering any of the desirable properties
of that program (correctness, task performed, etc.)
From Interfaces to SOLID
June 2015
SOLID design principles
Liskov substitution principle
You will be able
to stub or mock an interface or a class
Allow correct testing of a whole system:
even if all single unit tests did pass,
real system may not work if this principle was broken
Tied to the Open / Closed principle
If you define a child, you should not modify the parent
Code-reusability of the parent implementation
From Interfaces to SOLID
June 2015
SOLID design principles
Liskov substitution code smells
if aObject is aClass then …
case aObject.EnumeratedType of …
function … abstract; without further override;
unit parent; uses child1,child2,child3;
From Interfaces to SOLID
June 2015
SOLID design principles
Liskov substitution patterns
Write your test using abstract variables
Design by contract
Meyer's rule: "when redefining a routine [in a
derivative], you may only replace its precondition by a
weaker one, and its postcondition by a stronger one“
Factory pattern
Repository pattern
Service locator pattern
From Interfaces to SOLID
June 2015
SOLID design principles
Interface segregation principle
Once an interface has become too 'fat'
it shall be split into smaller
and more specific interfaces
so that any clients of the interface will only know
about the methods that pertain to them
In a nutshell, no client should be forced
to depend on methods it does not use
From Interfaces to SOLID
June 2015
SOLID design principles
Interface segregation principle
Smaller dedicated classes should be preferred
Excludes RAD with every logic method
implemented in the TForm
Interface should host the process methods
Perfectly fits the SOA uncoupling pattern
Allows to release memory and resources ASAP
From Interfaces to SOLID
June 2015
SOLID design principles
Dependency Inversion
High-level modules
should not depend on low-level modules
Both should depend on abstractions
Abstractions should not depend upon details
Details should depend upon abstractions
From Interfaces to SOLID
June 2015
SOLID design principles
Dependency Inversion
In most conventional programming style:
You write low-level components
Then you integrate them with high-level components
But this limits the re-use of high-level code
In fact, it breaks the Liskov substitution principle
It reduces the testing abilities (e.g. need of a real DB)
From Interfaces to SOLID
June 2015
SOLID design principles
Dependency Inversion may be implemented
Via a plug-in system
e.g. external libraries
Using a service locator
e.g. SOA catalog
Via Dependency Injection
A class will define its dependencies as protected
interface members
Implementation will be injected at constructor level
as explicit parameters, or via factories
From Interfaces to SOLID
June 2015
Weak references
Delphi type reference model
class
as weak references (plain pointer) and explicit Free
with TComponent ownership for the VCL/FMX
integer Int64 currency double record widestring variant with explicit copy
string or any dynamic array
via copy-on-write (COW) with reference counting
interface as strong reference with reference counting
From Interfaces to SOLID
June 2015
Weak references
Strong reference-counted types (OLE/ COM)
Will increase the count at assignment
And decrease the count at owner’s release
When the count reaches 0, release the instance
Issue comes when there are
Circular references
External list(s) of references
From Interfaces to SOLID
June 2015
Weak references
Managed languages (C# or Java)
Will let the Garbage Collector handle
interface variable life time
This is complex and resource consuming
But easy to work with
Unmanaged languages (Delphi or ObjectiveC)
Need explicit weak reference behavior
mORMot features zeroing weak pointers
Like Apple’s ARC model
From Interfaces to SOLID
June 2015
Weak references
Zeroing weak pointers
From Interfaces to SOLID
IParent = interface procedure SetChild(const Value: IChild); function GetChild: IChild; function HasChild: boolean; property Child: IChild read GetChild write SetChild; end;
IChild = interface procedure SetParent(const Value: IParent); function GetParent: IParent; property Parent: IParent read GetParent write SetParent; end;
June 2015
Weak references
Zeroing weak pointers
This code will leak memory
From Interfaces to SOLID
IParent = interface procedure SetChild(const Value: IChild); function GetChild: IChild; function HasChild: boolean; property Child: IChild read GetChild write SetChild; end;
IChild = interface procedure SetParent(const Value: IParent); function GetParent: IParent; property Parent: IParent read GetParent write SetParent; end;
procedure TParent.SetChild(const Value: IChild); begin FChild := Value; end;
procedure TChild.SetParent(const Value: IParent); begin FParent := Value; end;
June 2015
Weak references
Zeroing weak pointers
This code won’t leak memory
FChild and FParent will be set to nil
when the stored instance will be freed
From Interfaces to SOLID
IParent = interface procedure SetChild(const Value: IChild); function GetChild: IChild; function HasChild: boolean; property Child: IChild read GetChild write SetChild; end;
IChild = interface procedure SetParent(const Value: IParent); function GetParent: IParent; property Parent: IParent read GetParent write SetParent; end;
procedure TParent.SetChild(const Value: IChild); begin SetWeakZero(self,@FChild,Value); end;
procedure TChild.SetParent(const Value: IParent); begin SetWeakZero(self,@FParent,Value); end;
June 2015
Weak references
Delphi NextGen memory model
Uses ARC for every TObject instance
This is transparent for TComponent / FMX
No try … finally Free block needed
But breaks the proven weak reference model
One immutable UTF-16 string type
Efficient COW pattern is lost
Direct 8 bit string type disabled (PITA for UTF-8)
From Interfaces to SOLID
June 2015
DI, Stubs and Mocks
Thanks to SOLID design principles
All your code logic will now be abstracted
to the implementation underneath
But you need to inject the implementation
This is Dependency Injection purpose
You can also create fake instances to implement
a given interface, and enhance testing
Introducing Stubs and Mocks
From Interfaces to SOLID
June 2015
DI, Stubs and Mocks
Dependency Injection
Define external dependencies as interface
as (private / protected) read-only members
To set the implementation instance:
Either inject the interfaces as constructor parameters
Or use a Factory / Service locator
From Interfaces to SOLID
June 2015
DI, Stubs and Mocks
Dependency Injection
Purpose is to test the following class:
From Interfaces to SOLID
TLoginController = class(TInterfacedObject,ILoginController) protected fUserRepository: IUserRepository; fSmsSender: ISmsSender; public constructor Create(const aUserRepository: IUserRepository; const aSmsSender: ISmsSender); procedure ForgotMyPassword(const UserName: RawUTF8); end;
constructor TLoginController.Create(const aUserRepository: IUserRepository; const aSmsSender: ISmsSender); begin fUserRepository := aUserRepository; fSmsSender := aSmsSender; end;
June 2015
DI, Stubs and Mocks
Dependency Injection
Dependencies are defined as
Two small, uncoupled, SOLID task-specific interfaces
From Interfaces to SOLID
IUserRepository = interface(IInvokable) ['{B21E5B21-28F4-4874-8446-BD0B06DAA07F}'] function GetUserByName(const Name: RawUTF8): TUser; procedure Save(const User: TUser); end; ISmsSender = interface(IInvokable) ['{8F87CB56-5E2F-437E-B2E6-B3020835DC61}'] function Send(const Text, Number: RawUTF8): boolean; end;
June 2015
DI, Stubs and Mocks
Dependency Injection
Using a dedicated Data Transfer Object (DTO)
No dependency against storage, nor other classes
From Interfaces to SOLID
IUserRepository = interface(IInvokable) ['{B21E5B21-28F4-4874-8446-BD0B06DAA07F}'] function GetUserByName(const Name: RawUTF8): TUser; procedure Save(const User: TUser);
end;
TUser = record Name: RawUTF8; Password: RawUTF8; MobilePhoneNumber: RawUTF8; ID: Integer; end;
June 2015
DI, Stubs and Mocks
Dependency Injection
The high-level method to be tested:
Open/Closed, Liskov and mainly Dependency
Inversion principles are followed
Will we need a full database and to send a SMS?
From Interfaces to SOLID
procedure TLoginController.ForgotMyPassword(const UserName: RawUTF8); var U: TUser; begin U := fUserRepository.GetUserByName(UserName); U.Password := Int32ToUtf8(Random(MaxInt)); if fSmsSender.Send('Your new password is '+U.Password,U.MobilePhoneNumber) then fUserRepository.Save(U); end;
June 2015
DI, Stubs and Mocks
"The Art of Unit Testing" (Osherove, Roy - 2009)
Stubs are fake objects implementing a given contract and returning pre-arranged responses
They just let the test pass
They “emulate” some behavior (e.g. a database)
Mocks are fake objects like stubs which will verify if an interaction occurred or not
They help decide if a test failed or passed
There should be only one mock per test
From Interfaces to SOLID
June 2015
DI, Stubs and Mocks
Expect – Run – Verify pattern
From Interfaces to SOLID
procedure TMyTest.ForgotMyPassword; var SmsSender: ISmsSender; UserRepository: IUserRepository; begin TInterfaceStub.Create(TypeInfo(ISmsSender),SmsSender). Returns('Send',[true]); TInterfaceMock.Create(TypeInfo(IUserRepository),UserRepository,self). ExpectsCount('Save',qoEqualTo,1); with TLoginController.Create(UserRepository,SmsSender) do try ForgotMyPassword('toto'); finally Free; end; end;
June 2015
DI, Stubs and Mocks
Expect – Run – Verify pattern
TInterfaceStub / TInterfaceMock constructors
are in fact Factories for any interface
Clear distinction between stub and mock
Mock is linked to its test case (self: TMyTest)
From Interfaces to SOLID
procedure TMyTest.ForgotMyPassword; var SmsSender: ISmsSender; UserRepository: IUserRepository; begin TInterfaceStub.Create(TypeInfo(ISmsSender),SmsSender). Returns('Send',[true]); TInterfaceMock.Create(TypeInfo(IUserRepository),UserRepository,self). ExpectsCount('Save',qoEqualTo,1);
June 2015
DI, Stubs and Mocks
Expect – Run – Verify pattern
Execution code itself sounds like real-life code
But all dependencies have been injected
Stubs will emulate real behavior
Mock will verify that all expectations are fulfilled
From Interfaces to SOLID
with TLoginController.Create(UserRepository,SmsSender) do try ForgotMyPassword('toto'); finally Free; end; end;
June 2015
DI, Stubs and Mocks
Expect – Run – Verify pattern
From Interfaces to SOLID
procedure TMyTest.ForgotMyPassword; var SmsSender: ISmsSender; UserRepository: IUserRepository; begin TInterfaceStub.Create(TypeInfo(ISmsSender),SmsSender). Returns('Send',[true]); TInterfaceMock.Create(TypeInfo(IUserRepository),UserRepository,self). ExpectsCount('Save',qoEqualTo,1); with TLoginController.Create(UserRepository,SmsSender) do try ForgotMyPassword('toto'); finally Free; end; end;
June 2015
DI, Stubs and Mocks
Run – Verify (aka “Test spy”) pattern
From Interfaces to SOLID
procedure TMyTest.ForgotMyPassword; var SmsSender: ISmsSender; UserRepository: IUserRepository; Spy: TInterfaceMockSpy; begin TInterfaceStub.Create(TypeInfo(ISmsSender),SmsSender). Returns('Send',[true]); Spy := TInterfaceMockSpy.Create(TypeInfo(IUserRepository),UserRepository,self); with TLoginController.Create(UserRepository,SmsSender) do try ForgotMyPassword('toto'); finally Free; end; Spy.Verify('Save'); end;
June 2015
DI, Stubs and Mocks
Another features:
Return complex values (e.g. a DTO)
Use a delegate to create a stub/mock
Using named or indexed variant parameters
Using JSON array of values
Access the test case when mocking
Trace and verify the calls
With a fluent interface
Log all calls (as JSON)
From Interfaces to SOLID
June 2015
DI, Stubs and Mocks
Dependency Injection
Inheriting from TInjectableObject type TServiceToBeTested = class(TInjectableObject,IServiceToBeTested) protected fService: IInjectedService; published property Service: IInjectedService read fService; end;
Will auto-inject interface published properties
At instance creation
Handled by TSQLRest.Services
From Interfaces to SOLID
June 2015
DI, Stubs and Mocks
Dependency Injection
Inheriting from TInjectableObject var Test: IServiceToBeTested; begin Test := TServiceToBeTested.CreateInjected( [ICalculator], [TInterfaceMock.Create(IPersistence,self). ExpectsCount('SaveItem',qoEqualTo,1), RestInstance.Services], [AnyInterfacedObject]); ...
From Interfaces to SOLID
June 2015
DI, Stubs and Mocks
Dependency Injection
Inheriting from TInjectableObject procedure TServiceToBeTested.AnyProcessMethod; var Service: IInjectedService; begin
Resolve(IInjectedService,Service); Service.DoSomething; end;
From Interfaces to SOLID
June 2015
DI, Stubs and Mocks
Dependency Injection
Inheriting from TInjectableAutoCreateFields type TServiceToBeTested = class(TInjectableObjectAutoCreateFields,
IServiceToBeTested) protected fService: IInjectedService; fNestedObject: TSynPersistentValue; published property Service: IInjectedService read fService; property NestedObject: TSynPersistentValue read fNestedObject; end;
Will auto-define published properties Resolve interface services
Create TPersistent TSynPersistent TAutoCreateField
From Interfaces to SOLID