Author: Vanners
Welcome to the last episode in the “Learn to Program with Starter” series. In the first four episodes we have covered “Introduction and Installation” of the free Delphi Starter edition, then we moved to IDE basics in “Building in Debugging”. In “Architecture and Layers of Code” the key concepts of proper app structure were discussed and in the last episode we have started “Designing User Interfaces”.
There is also “Learn to Program in C++” series written by C++Builder Product Manager David Millington, who already has finished the series with the final blog post covering C++ operators and final application.
Coding in C++ and Object Pascal is different. Initially I was tempted to implement the calculator here in my “own way”, but I have realised that for some developers it could be interesting to be able to compare how the two languages can be used to implement the very same UI and code design. Consequently I have slightly modified the calculator UI that I have created in the previous episode so it matches David’s.
The final source code of the “Delphi Super Calculator” project can be downloaded from my Amazon S3.
Application Structure
Here is the screen shot from Project Manager and you can see that the calculator project contains one main form and five units with code.
In the previous episode we have created the calculator project with the main form with 20 buttons and a label that acts as a “display”. It is very important to separate user interface code from the application logic. The best way to achieve this separation is by using interfaces. Trying to match David’s design I have added to my project a new “uInterfaces” unit and defined two interfaces there. One “ICalculator” that my “TCalculator” class implements and one “ICalculatorDisplay” that is implemented by the main form. These two interfaces are the only way that the UI communicates with the application logic.
unit uInterfaces; interface type ICalculatorDisplay = interface procedure UpdateUI(strText: string); end; TOperatorEnum = (opNull, opAdd, opSubtract, opMultiply, opDivide); ICalculator = interface procedure AddDigit(digit: integer); procedure AddDecimalSeparator; procedure SetOperator(op: TOperatorEnum); procedure Equals; end; implementation end.
You can remove the sample code from the “TCalculator” class that was added earlier for demo purposes and we can start implementing the calculator for real.
unit uCalculator; interface uses uInterfaces; type TCalculator = class(TInterfacedObject, ICalculator) private FDisplay: ICalculatorDisplay; // ... public constructor Create(ADisplay: ICalculatorDisplay); destructor Destroy; override; // ICalculator procedure AddDigit(digit: integer); procedure AddDecimalSeparator; procedure SetOperator(op: TOperatorEnum); procedure Equals; end; implementation constructor TCalculator.Create(ADisplay: ICalculatorDisplay); begin FDisplay := ADisplay;
// ... end; // ...
Our “TCalculator” class implements an interface and that is why it cannot be inherited directly from “TObject”, but it needs to be derived from “TInterfacedObject” which is the base class for all Delphi classes that implements interfaces. If you do not specify the base class for your object, you will be inheriting from “TObject”. Similarly all Delphi interfaces derives from “IInterface” and the “TInterfacedObject” class just implements these inherited methods. The constructor of our “TCalculator” class takes a reference to “ICalculatorDisplay” and stores it in the private field, so any time calculator needs to display something, it can just call “UpdateUI” method and pass the string to be displayed. This is good design. The calculator class knows about the UI as little as possible. It just needs to be able to pass a string for display. This could be a FireMonkey, VCL or maybe even a console app.
uses uInterfaces, // ... type TFormCalc = class(TForm, ICalculatorDisplay) //.. private FCalculator: ICalculator; public // ICalculatorDisplay procedure UpdateUI(strText: string); end; var FormCalc: TFormCalc; implementation {$R *.fmx} uses uCalculator; procedure TFormCalc.FormCreate(Sender: TObject); begin FCalculator := TCalculator.Create(self); end; procedure TFormCalc.UpdateUI(strText: string); begin LabelDisplay.Text := strText; end; // ..
The main form also contains the reference to the “ICalculator” class as a private field. The actual “uCalculator” unit reference is in the “implementation” uses clause of the form, so this is as clean as possible. The code in the main form only needs to know about methods of the “ICalculator” interface to communicate with the application logic.
So far this roughly covers the C++ code that David discusses in “Learn to program with C++Builder: #3, Design, Architecture, and UIs” blog post.
The next two episodes go into the inner workings of C++ and Object Pascal just do things differently. My goal was to mimic in Delphi the code from C++. There is no concept of “smart pointers” in Object Pascal. There are constructors and destructors that you need to call to instantiate the object and to free it.
There is also no “boost” library in Delphi and no optional types that provide nullable semantics for both simple and object types. Object Pascal treats built-in types differently than C++. The Object Pascal “nullable” types are planned for the future – according to RAD Studio official roadmap – but still not there. In order to model to some extent “boost optional” type there is “uOptionals” unit in the project in Delphi project that is using a generic “TOptional<T>” type to model optional integers and optional doubles that C++ “TCalculator” class uses.
unit uOptionals; interface type TOptional<T> = record Value: T; Exists: boolean; procedure Reset; end; TOptionalInt = TOptional<integer>; TOptionalDouble = TOptional<double>; implementation procedure TOptional.Reset; begin Exists := False; end; end.
It it very primitive, but to some extent mimics C++ boost “optional” semantics.
Another interesting type in the implementation is the polymorphic “operator” class that performs an operation on two double values and return result as double. It also has the “Name” function that is used to display the symbol of a given operation.
unit uOperator; interface uses uInterfaces; type TOperator = class abstract function Calc(A, B: double): double; virtual; abstract; function Name: string; virtual; abstract; end; TOperatorAdd = class(TOperator) function Calc(A, B: double): double; override; function Name: string; override; end; TOperatorSubtract = class(TOperator) function Calc(A, B: double): double; override; function Name: string; override; end; TOperatorMult = class(TOperator) function Calc(A, B: double): double; override; function Name: string; override; end; TOperatorDiv = class(TOperator) function Calc(A, B: double): double; override; function Name: string; override; end; function CreateOperator(op: TOperatorEnum): TOperator; implementation function CreateOperator(op: TOperatorEnum): TOperator; begin case op of opAdd: Result := TOperatorAdd.Create; opSubtract: Result := TOperatorSubtract.Create; opMultiply: Result := TOperatorMult.Create; opDivide: Result := TOperatorDiv.Create; else Result := nil; end; end; function TOperatorAdd.Calc(A, B: double): double; begin Result := A + B; end; function TOperatorAdd.Name: string; begin Result := '+'; end; function TOperatorSubtract.Calc(A, B: double): double; begin Result := A - B; end; function TOperatorSubtract.Name: string; begin Result := '-'; end; function TOperatorMult.Calc(A, B: double): double; begin Result := A * B; end; function TOperatorMult.Name: string; begin Result := '*'; end; function TOperatorDiv.Calc(A, B: double): double; begin Result := A / B; end; function TOperatorDiv.Name: string; begin Result := '÷'; end; end.
In Object Pascal we can just define a class with virtual abstract methods that acts as a blueprint for specialized classes that provide implementations for inherited abstract methods. There is also a global factory function that returns correct “TOperator” compatible instance type.
The rest of the project code is almost identical as the C++ simple calculator version available from GitHub. The final source code of the “Delphi Super Calculator” project can be downloaded from my Amazon S3
That’s it! I hope that you have you enjoyed the “Learn to Program with Delphi” series as much as I did.
It is time for new challenges. In coming days I will be doing two global webinars that you are all invited for. You can register below:
- Dec 14th: Appx Development for Windows 10 Store
- Dec 20th: Migrating to RAD Server
Design. Code. Compile. Deploy.
Start Free Trial Upgrade Today
Free Delphi Community Edition Free C++Builder Community Edition