DMarlon Studio

Lesson 20 · 20 of 20

Project: a small bank · Pascal

Bring classes, inheritance, generics and exceptions together in a small Object Pascal bank that refuses impossible withdrawals and frees all its memory.

Pascal · Project: a small bank in DMarlon Studio: the example's code and its output in the terminal

The final project packs the essentials of the course into a tiny bank. `TBank = class(specialize TFPGMap<String, Currency>)` is class, inheritance and generic all at once: a map from account holder to balance that inherits everything in TFPGMap (IndexOf, KeyData, Keys, Data, Count) and adds the bank's business rule, the Withdraw method.

Withdraw validates before touching anything and, if something is wrong, raises `EBankError`, a custom exception that inherits from Exception: first it checks the account exists with IndexOf, then that the balance is enough. Only when all is well does it take the money out and report it. `Currency` is Pascal's money type: fixed-point decimal, without Double's rounding errors.

The program opens two accounts with bank['Elena'] := 300, which adds the key or changes its value if it already exists, and processes three 200 EUR requests. Each one has its own try ... except, so a refusal does not stop the others: Elena withdraws, Pablo does not have enough and Marta has no account.

Finally the balances are listed with Keys[i] and Data[i] (without Sorted, the map keeps the order in which accounts were opened) and bank.Free releases the map with everything in it. The classes in DMarlon Studio's offline Odoo demo work exactly the same way: put a Lazarus window in front of this code and you have a desktop app, and with the Console target it runs on F9.

Code · main.pas

program main;uses SysUtils, fgl;type  EBankError = class(Exception);  TBank = class(specialize TFPGMap<String, Currency>)  // owner -> balance, plus rules    procedure Withdraw(const owner: String; amount: Currency);  end;procedure TBank.Withdraw(const owner: String; amount: Currency);begin  if IndexOf(owner) < 0 then    raise EBankError.CreateFmt('there is no account for %s', [owner]);  if amount > KeyData[owner] then    raise EBankError.CreateFmt('%s has only %.2f', [owner, Double(KeyData[owner])]);  KeyData[owner] := KeyData[owner] - amount;  WriteLn(owner, ' withdrew ', amount:0:2);end;const Requests: array[0..2] of String = ('Elena', 'Pablo', 'Marta');var  bank: TBank;  i: Integer;begin  bank := TBank.Create;  bank['Elena'] := 300;  // adds the key, or changes it if it exists  bank['Pablo'] := 120;  for i := 0 to High(Requests) do    try      bank.Withdraw(Requests[i], 200);    except      on E: EBankError do WriteLn('Refused: ', E.Message);    end;  for i := 0 to bank.Count - 1 do WriteLn(bank.Keys[i]:6, bank.Data[i]:8:2);  bank.Free;end.

Output · fpc main.pas && ./main

Elena withdrew 200.00
Refused: Pablo has only 120.00
Refused: there is no account for Marta
 Elena  100.00
 Pablo  120.00

Key points

  • A class can inherit from an already specialised generic.
  • Validate first and raise a custom exception if something is wrong.
  • One try ... except per operation keeps each error isolated.
  • Currency is the right type for money.
  • What you Create, you Free.

Exercise

Add a Transfer(src, dst: String; amount: Currency) method to TBank that uses Withdraw and raises EBankError if the destination account does not exist, and try moving 50 EUR from Elena to Pablo.