Download as pdf or txt
Download as pdf or txt
You are on page 1of 52

From Interfaces to SOLID

Arnaud Bouchez
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
type
ICalculator = interface(IInvokable)
['{9A60C8ED-CEB2-4E09-87D4-4A16F496E5FE}']
/// add two signed 32 bit integers
function Add(n1,n2: integer): integer;
end;

 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


June 2015
Delphi and interfaces

 Implementing an interface
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;

 ICalculator added to the “inheritance chain”


 TCalculator implements a behavior

From Interfaces to SOLID


June 2015
Delphi and interfaces

 Using an interface
function MyAdd(a,b: integer): integer;
var Calculator: ICalculator;
begin
Calculator := TServiceCalculator.Create;
result := Calculator.Add(a,b);
end;

 Strong typing
 The variable defines a behavior (contract)
 Path to abstraction

From Interfaces to SOLID


June 2015
Delphi and interfaces

 Using an interface
function MyAdd(a,b: integer): integer;
function MyAdd(a,b: integer): integer; var Calculator: TServiceCalculator;
var Calculator: ICalculator; begin
begin Calculator := TServiceCalculator.Create;
Calculator := TServiceCalculator.Create; try
result := Calculator.Add(a,b); result := Calculator.Add(a,b);
end; finally
Calculator.Free;
end;
end;
 Strong typing
 The variable defines a behavior (contract)
 Path to abstraction
 Automatic try..finally

From Interfaces to SOLID


June 2015
Delphi and interfaces

 Automatic try..finally
 Compiler generates function MyAdd(a,b: integer): integer;
var Calculator: TServiceCalculator;
some hidden code… begin
Calculator := nil;
Calculator := TServiceCalculator.Create;
function MyAdd(a,b: integer): integer; try
var Calculator: ICalculator; Calculator.FRefCount := 1;
begin result := Calculator.Add(a,b);
Calculator := TServiceCalculator.Create; finally
result := Calculator.Add(a,b); dec(Calculator.FRefCount);
end; if Calculator.FRefCount=0 then
Calculator.Free;
end;
end;

 Behavior inherited from TInterfacedObject


 Similar to COM / ActiveX

From Interfaces to SOLID


June 2015
Delphi and interfaces

 Interfaces are orthogonal to implementation


 There is more than one way to do it
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;

From Interfaces to SOLID


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
 IfTChild 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
 Writeyour 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:
 Youwrite 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


 Circularreferences
 External list(s) of references

From Interfaces to SOLID


June 2015
Weak references
 Managed languages (C# or Java)
 Willlet 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
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;

From Interfaces to SOLID


June 2015
Weak references
 Zeroing weak pointers

This code will leak memory IParent = interface


procedure SetChild(const Value: IChild);
procedure TParent.SetChild(const Value: IChild); function GetChild: IChild;
function HasChild: boolean;
begin property Child: IChild read GetChild write SetChild;
FChild := Value; end;
end;
IChild = interface
procedure SetParent(const Value: IParent);
procedure TChild.SetParent(const Value: IParent); function GetParent: IParent;
begin property Parent: IParent read GetParent write SetParent;
FParent := Value; end;
end;

From Interfaces to SOLID


June 2015
Weak references
 Zeroing weak pointers

This code won’t leak memory IParent = interface


procedure SetChild(const Value: IChild);
function GetChild: IChild;
procedure TParent.SetChild(const Value: IChild);
function HasChild: boolean;
begin property Child: IChild read GetChild write SetChild;
SetWeakZero(self,@FChild,Value); end;
end;
IChild = interface
procedure SetParent(const Value: IParent);
procedure TChild.SetParent(const Value: IParent); function GetParent: IParent;
begin property Parent: IParent read GetParent write SetParent;
SetWeakZero(self,@FParent,Value); end;
end;

FChild and FParent will be set to nil


when the stored instance will be freed

From Interfaces to SOLID


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


 EfficientCOW 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:
 Eitherinject 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:
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;

From Interfaces to SOLID


June 2015
DI, Stubs and Mocks
 Dependency Injection
 Dependencies are defined as
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;

 Two small, uncoupled, SOLID task-specific interfaces

From Interfaces to SOLID


June 2015
DI, Stubs and Mocks
 Dependency Injection
 Using a dedicated Data Transfer Object (DTO)
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;

 No dependency against storage, nor other classes

From Interfaces to SOLID


June 2015
DI, Stubs and Mocks
 Dependency Injection
 The high-level method to be tested:
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;

 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


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

From Interfaces to SOLID


June 2015
DI, Stubs and Mocks
 Expect – Run – Verify pattern
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;

From Interfaces to SOLID


June 2015
DI, Stubs and Mocks
 Expect – Run – Verify pattern
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);

 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


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

with TLoginController.Create(UserRepository,SmsSender) do
try
ForgotMyPassword('toto');
finally
Free;
end;
end;

From Interfaces to SOLID


June 2015
DI, Stubs and Mocks
 Expect – Run – Verify pattern
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;

From Interfaces to SOLID


June 2015
DI, Stubs and Mocks
 Run – Verify (aka “Test spy”) pattern
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;

From Interfaces to SOLID


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


 Atinstance 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


June 2015
From Interfaces to SOLID
Arnaud Bouchez
June 2015

You might also like