Advertisement
Advertisement

More Related Content

Advertisement
Advertisement

D1 from interfaces to solid

  1. Software Architecture & Design  Architecture  From n-Tier to SOA  From SOAP to REST  Technical Debt  Design  From SQL to ORM, NoSQL and ODM  From RAD to MVC  SOLID principles  Domain Driven Design (DDD) Applying patterns on Delphi code using mORMot Software Architecture & Design
  2. From Interfaces to SOLID
  3. From Interfaces to SOLID  Delphi and interfaces  SOLID design principles  Dependency Injection, stubs and mocks  Using mORMot features  Delphi and Weak pointers From Interfaces to SOLID
  4. 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
  5. 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;
  6. 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;
  7. 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;
  8. 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;
  9. 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;
  10. 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;
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. SOLID design principles  Single Responsibility  Splitting classes  Imagine a TBarCodeScanner class to handle a serial bar-code scanner  Later on, we want to implement USB support  First idea may be to inherit From Interfaces to SOLID
  17. SOLID design principles  Single Responsibility  Splitting classes  Imagine a TBarCodeScanner class to handle a serial bar-code scanner  Later on, we want to implement USB support  But we would rather split the class hierarchy From Interfaces to SOLID
  18. SOLID design principles  Single Responsibility  Splitting classes  Later on, the manufacturer updates its protocol To add new features, e.g. 3D scanning or coffee making  How do we implement this?  We have two “axis of change” so we would define two classes From Interfaces to SOLID
  19. SOLID design principles  Single Responsibility  Splitting classes  Later on, the manufacturer updates its protocol To add new features, e.g. 3D scanning or coffee making  We defined two classes, which will be joined/composed in actual barcode scanner classes From Interfaces to SOLID
  20. SOLID design principles  Single Responsibility  Splitting classes TAbstractBarcodeScanner = class(TComponent) protected fProtocol: TAbstractBarcodeProtocol; fConnection: AbstractBarcodeConnection; ... constructor TSerialBarCodeScanner.Create( const aComPort: string; aBitRate: integer); begin fConnection := TSerialBarcodeConnection(aComPort,aBitRate); fProtocol := TBCP1BarcodeProtocol.Create(fConnection); end; From Interfaces to SOLID
  21. SOLID design principles  Single Responsibility  Another example from SynDB class definitions:  TSQLDBConnectionProperties  TSQLDBConnection  TSQLDBStatement  Each class has its own purpose, and axis of change  And could be implemented via ODBC, OleDB, direct client access… From Interfaces to SOLID
  22. SOLID design principles  Single Responsibility  Do not mix GUI and logic  Do not mix logic and database  Do not couple your code to an OS  Check your uses statement  There should be no reference to the UI (e.g. Dialogs) in your business class  No dependency to DB libraries (this is a hard one)  No reference to WinAPI.Windows From Interfaces to SOLID
  23. SOLID design principles  Single Responsibility  Code smells  When abusing of: FreeAndNil() {$ifdef} … {$endif} uses unit1, unit2, unit3, … unit1000;  When you can’t find the right method in a class  (mORMot may need some refactoring here)   When unitary tests are hard to write From Interfaces to SOLID
  24. SOLID design principles  Open / Close principle  When you define a class or an interface  it shall be open for extension  but closed for modification From Interfaces to SOLID
  25. 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 stinks!  Closed for modification  E.g. via explicitly protected or private members  RTTI is dangerous: it may open the closed door From Interfaces to SOLID
  26. SOLID design principles  Open / Closed principle  In practice  Define meaningful interface types  Following the Design By Contract principle  Define Value objects (DTOs) to transfer the data  With almost no methods, but for initialization  With public members, to access the value  Define Process objects to modify the data  With public methods, abstracted in the interface  With mostly private members From Interfaces to SOLID
  27. SOLID design principles  Open / Close principle type TAbstractBarcodeScanner = class(TComponent) protected fProtocol: TAbstractBarcodeProtocol; fConnection: AbstractBarcodeConnection; public property Protocol: TAbstractBarcodeProtocol read fProtocol; property Connection: AbstractBarcodeConnection read fConnection; ...  Protocol and Connection are read/only  They are injected by the overridden class constructor  So it will be Open for extension From Interfaces to SOLID
  28. SOLID design principles  Open / Close principle type TAbstractBarcodeScanner = class(TComponent) protected fProtocol: TAbstractBarcodeProtocol; fConnection: AbstractBarcodeConnection; public property Protocol: TAbstractBarcodeProtocol read fProtocol; property Connection: AbstractBarcodeConnection read fConnection; ...  Protocol and Connection are read/only  You could not change the behavior of a class  It is Closed for modification From Interfaces to SOLID
  29. SOLID design principles  Liskov substitution principle From Interfaces to SOLID
  30. SOLID design principles  Liskov substitution principle From Interfaces to SOLID
  31. 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
  32. SOLID design principles  Liskov substitution principle  Tied to the Open / Closed principle  If you define a child, you should not modify the parent  Code-reusability of the parent implementation  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 From Interfaces to SOLID
  33. SOLID design principles  Liskov substitution patterns  Design by contract  Meyer's (Eiffel language) 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”  Define assertions at method level:  What does it expect, guarantee, and maintain?  About input/output values or types, side effects, invariants, errors/exceptions, performance… From Interfaces to SOLID
  34. SOLID design principles  Liskov substitution patterns  Write your code using abstract variables  Rely on parents methods and properties  Use interface variables instead of class implementation  Uncouple dependencies via class composition From Interfaces to SOLID
  35. SOLID design principles  Liskov substitution patterns  Factory pattern  In strongly typed languages (Java, C#, Delphi), the factory is the class/object type and its constructor  Repository pattern  Allow persistence agnosticism (e.g. via ORM)  Service locator pattern  Get a class instance implementing a given interface  According to the given context (no global/singleton) From Interfaces to SOLID
  36. SOLID design principles  Liskov substitution patterns  Practical, not dogmatic LSP  “Parent” and “Child” are not absolute  Depending on the context, you may define a given level as the “highest” LSP abstract class  e.g. if you work on server side, you may need some server-only properties and methods  LSP idea is to be consistent, once the abstraction level is defined From Interfaces to SOLID
  37. SOLID design principles  Liskov substitution patterns  Practical, not dogmatic LSP  “Parent” and “Child” are not absolute  Inheritance is often used as code sharing among classes, not as an abstraction contract  In this context, LSP may not apply at class level: be pragmatic, and write efficient code  But LSP may apply at interface level, for a set of methods implemented by the class From Interfaces to SOLID
  38. 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
  39. 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
  40. SOLID design principles  Interface segregation principle  Smaller dedicated classes should be preferred  Single Responsibility Principle  Favor composition over inheritance  Smaller dedicated interfaces  Gather operations/methods per context  Meaningful naming and conventions  So that the contract would be easier to understand From Interfaces to SOLID
  41. SOLID design principles  Interface segregation principle  Perfectly fits the SOA uncoupling pattern  Stateless MicroServices for horizontal scaling  Allows to release memory and resources ASAP  Smaller objects have more bounded dependencies  Ease unit testing  Less coverage  Less coupling From Interfaces to SOLID
  42. SOLID design principles  Interface segregation principle  Excludes RAD  Logic implemented in TForm TDataModule where methods are procedural code in disguise  Put your logic behind interfaces and call them from your UI (over VCL and FMX)  Favors DDD  Segregation avoid domain leaking  Dedicated interfaces, e.g. for third party consumption From Interfaces to SOLID
  43. SOLID design principles  Dependency Inversion  High-level modules should not depend on low-level modules  Both should depend on abstractions  Following other SOLI principles  Abstractions should not depend upon details  Details should depend upon abstractions  Business Logic should not depend on database  Application Layer should not depend on client UI techno From Interfaces to SOLID
  44. SOLID design principles  Dependency Inversion  In most conventional programming style:  You write low-level components (e.g. DB tables)  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
  45. SOLID design principles  Dependency Inversion may be implemented  Via a plug-in system  e.g. external libraries (for embedded applications)  Using a service locator  e.g. SOA catalog (SaaS/cloud)  class resolution from an interface type From Interfaces to SOLID
  46. SOLID design principles  Dependency Inversion may be implemented  Via Dependency Injection  Concretions are injected to the objects using them  Only abstractions are known at design/coding time  Uncoupled implementation injected at runtime  Modular coding  Perfect for bigger/complex projects, with a set of teams  Ease testing, via stub/mock dependencies injection  e.g. a fake database, defined as a Repository service  Scaling abilities From Interfaces to SOLID
  47. SOLID design principles  Dependency Inversion may be implemented  Via Dependency Injection  A class will define its dependencies as read-only interface members  Implementation will be injected at constructor level  via explicit constructor parameters  via automated resolution via RTTI  via service locator/resolver From Interfaces to SOLID
  48. 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
  49. 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
  50. 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;
  51. 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;
  52. 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;
  53. 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;
  54. 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) From Interfaces to SOLID
  55. DI, Stubs and Mocks  "The Art of Unit Testing" (Osherove, Roy - 2009)  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
  56. DI, Stubs and Mocks From Interfaces to SOLID
  57. DI, Stubs and Mocks  Expect – Run – Verify pattern From Interfaces to SOLID procedure TMyTest.ForgotMyPassword; var SmsSender: ISmsSender; UserRepository: IUserRepository; begin TInterfaceStub.Create(ISmsSender,SmsSender). Returns('Send',[true]); TInterfaceMock.Create(IUserRepository,UserRepository,self). ExpectsCount('Save',qoEqualTo,1); with TLoginController.Create(UserRepository,SmsSender) do try ForgotMyPassword('toto'); finally Free; end; end;
  58. 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(ISmsSender,SmsSender). Returns('Send',[true]); TInterfaceMock.Create(IUserRepository,UserRepository,self). ExpectsCount('Save',qoEqualTo,1);
  59. 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;
  60. DI, Stubs and Mocks  Expect – Run – Verify pattern From Interfaces to SOLID procedure TMyTest.ForgotMyPassword; var SmsSender: ISmsSender; UserRepository: IUserRepository; begin TInterfaceStub.Create(ISmsSender,SmsSender). Returns('Send',[true]); TInterfaceMock.Create(IUserRepository,UserRepository,self). ExpectsCount('Save',qoEqualTo,1); with TLoginController.Create(UserRepository,SmsSender) do try ForgotMyPassword('toto'); finally Free; end; end;
  61. 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(ISmsSender,SmsSender). Returns('Send',[true]); Spy := TInterfaceMockSpy.Create(IUserRepository,UserRepository,self); with TLoginController.Create(UserRepository,SmsSender) do try ForgotMyPassword('toto'); finally Free; end; Spy.Verify('Save'); end;
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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;
  71. 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;
  72. 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;
  73. 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 and a lot of existing code  Only UTF-16 string type  Direct 8 bit string type disabled   UTF8String RawByteString back in 10.1 Berlin  From Interfaces to SOLID
  74. From Interfaces to SOLID
Advertisement