d1 from interfaces to solid

52
June 2015 From Interfaces to SOLID Arnaud Bouchez

Upload: arnaud-bouchez

Post on 28-Jul-2015

19 views

Category:

Software


0 download

TRANSCRIPT

June 2015

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

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

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

June 2015

From Interfaces to SOLID

Arnaud Bouchez