Thursday, 9 February, 2012

From Pro to Starter – A Bit of a Shift

So, I finally made the jump from D2010 to Delphi EX2.  I moved to the Starter edition which might seem a bit odd – but it’s not, I assure you.  Only two or three weeks ago Delphi was, primarily due to a decrease in time that I can spend with it, doomed as an active member of my list of hobbies and I was certain that Embarcadero had seen the last of my credit card.

Like most people who work with Delphi,  I was initially pretty stoked to get hold of a copy of the latest IDE to play with some of the new [amazing] features of the product but at $500US for a product I’m just playing with, and not very often at that,  just doesn’t seem appealing any more.  Fortunately, they left enough of the good stuff in the Starter edition that it remains well worth the $150 I have to pay for it, and I actually do get the chance to have a go at FireMonkey, Generics, Attributes, Enhanced RTTI, Source Code Management Integration and most of all [for me personally] ... Live Bindings.

So, what’s the point of the post .. well, I expect that now that I have a totally new perspective to play with, I might be able to come up with a post or two on how Embarcadero can improve the Starter Edition and make it a bit more appealing to the target audience and perhaps one or two on how Starter Edition users can replace [part of] what Embarcadero has hacked out with third party tools like CNWizards, TWM’s Enhanced GExperts, Model Maker Code Explorer ... and more.  Something of a self appointed advocate perhaps.  With any luck, not the only one.

Thanks for stopping by,
-- Dave


Read the whole article ...

Wednesday, 25 January, 2012

TMS Aurelius - A Quick Peek

The Latin name Aurelius means Golden.  Has TMS Software created the “Golden” Object Relational Mapping [ORM] Framework?  Like any tool, it depends on your perspective and needs ... and for me, it sure looks like they have.  Let me explain ...

I’m pretty sure that I’ve kissed every frog that exists in the land of Delphi ORM/OPF and it’s only now, that I think I’ve finally found the prince[ss].  I installed the product, opened the simple example in D2010Pro, read the Quick Start Guide, reviewed the code and ran the demo ... and knew, not only what was going on, but everything I needed to know to get a trivial Hello World application of my own up and running.  This is a new experience for me.  If I was going to design and build a ORM framework to be used with Delphi, admittedly the end result would likely be nothing even remotely close to TMS Aurelius, but it would be exactly what I have in mind and be striving to attain.

For me, probably the most significant point in evaluating an ORM framework for use would have to be “Does it persist an out of the box class?”.  Some do – even without late breaking language enhancements, but many don’t.  By taking advantage of language enhancements in the IDE, TMS Aurelius does – in spades.  I’m speaking primarily of class attributes and anonymous methods but I expect the RTTI enhancements are getting a good workout [under the hood] as well.  With some frameworks there is nothing even remotely resembling a “class” once you’re finished dressing them up.  Having said that, most were developed long before the new language enhancements came along.

TMS Aurelius, from what I’ve read, really doesn’t seem to give a damn about the name of database you use on the backend.  It seems to be designed to switch between them whenever it suits you. I also don’t think any ORM framework that I’ve seen, is as “switchable” on the back-end as this product – although with some of them, I never got close enough to this particular feature to know for certain.  The product just happens to cover all “my” database needs but then, my needs are simple.  The few utilities I have in use, all currently use MS Access for storage.  I’ve been looking at Firebird as a replacement on one and possibly SQLite for a couple of others, but being busy searching for the perfect ORM, have yet to move in that direction.  Looks like the opportunity may have finally arrived.  The product ships with Component Adapters for AnyDac, dbExpress,  dbGo (ADO), Interbase Express (IBX), NexusDB, SQL-Direct and SQLite, with the promise of more to follow – at least that’s what I’ve read.

You seem to have the power to to do pretty much anything you’ve ever wanted to do with an ORM framework.  You can get your hands as dirty as you would like or just sit back and let the framework handle things for you.   One question you see a lot is “Can I map to an existing database/structure?”.  Apparently this isn’t a problem with TMS Aurelius.  Personally, I think it’s a winner.

Maybe you should have a look.

… thanks for stopping by,
Dave


Read the whole article ...

Tuesday, 12 April, 2011

WinProcs and WinTypes – Not Dead Yet

... or so it would seem.  Discontinued with the release of D2 but still haunting unit uses clauses everywhere.

I just recently tracked down and fixed an error in a module that was compiling fine but causing the Code Completion and Code Parameters IDE features to fail once it had.  I tracked the problem down to a reference to the “WinProcs” unit in the uses clause of a utility unit/file I was including in my project’s main form uses clause.  I looked into WinProcs, found out it and its partner WinTypes were both replaced by the “Windows” unit in D2, I then ensured that the Windows unit was referenced, commented out the WinProcs reference [you never know eh :) ], tested the project, noticed that the repair had fixed everything I was having problems with and prepared to move on.  Then the questions came.

How is it possible that there are any remaining references to these two units [WinProcs and WinTypes] after them being discontinued with the release of D2 [1996]?  That’s a long, long time ago.  The logical response is backward compatibility with D1 ... hahaha, logical – I don’t think so, that just can’t be true.  Was the penetration of these units that deep?  Is there that much code still floating around from that long ago?  How many other units have been renamed/replaced in the same time span?  In what version – not likely version 1 – they are long forgotten if they were.  Not these two.

I’ve got a very popular 3rd party tool intended for D6 and up.  A search for WinProcs [and in this case WinTypes] comes up with at least 10 hits.

Why is it that you don’t find the same response to the “... can’t find unit FooBar.dcu” questions that get asked?  Why is it with WinProcs and/or WinTypes the response is not “you need to replace them with a reference to Windows”  -but-  add “WinProcs=Windows; WinTypes=Windows” to the Aliases list of your project?  Huh?

Why is it that references to these long dead version 1 units are still popping up and breaking specific IDE features?  Why is it that some units with WinProcs and/or WinTypes references will actually even compile and result in these broken IDE features?  Why is it that Embarcadero has set it up so that when you create a new project, “WinProcs=Windows; WinTypes=Windows” are automagically added to the Aliases list of your project – now that one is just plain spooky if you really think about it.  Conspiracy theories anybody ??  Hahaha.

If you mask a reference to one of these long dead units in an alias is it really fixing anything?  Have you ever seen a unit with both WinProcs/WinTypes and Windows in the uses clause ... I have ... recently.  What effect does the alias have in that condition?  It doesn’t stop the compiler ... believe me.

The usual fix to the problem; “Code Completion and Code Parameters IDE features don’t work.” is, add “WinProcs=Windows; WinTypes=Windows” to the Aliases list of your project?  Huh?

I am seriously baffled by these references and how they get treated, but that’s just the way it is I guess – perhaps there’s some interesting history that I still need to dig up … thanks for stopping by,
Dave


Read the whole article ...

Friday, 1 April, 2011

tiOPF Tip – Wiring Up A Simple Look-Up Table

I have a requirement for several simple database centric look-up tables in one of my projects.  They are no more complex than name-value pairs [EA-Each, CT-Carton, ... ] that get stored in the database and are fully editable by the users.  I needed to find a way around the tiOPF [the OPF of choice] default of using GUID OID values for any object you plan to persist because,  to my mind, there is something seriously wrong with storing “6FFC3175-37CD-4682-A599-EF6B86E1F397” over and over to represent “Each” where “EA” is more than up to performing the task.

Although the default of the tiOPF is to generate GUID OID values for objects, it is built to handle pretty much any OID value type you want to throw at it.  What I want/need to do is a bit of an edge case use where simply replacing the generated GUID OID string with one that is not only considerably shorter but makes infinitely more sense in context, will suffice quite nicely.  I had seen the process explained before but, of course, now that I want to use it, the information can’t be found.  That’s fine ... time to play.

The example I used in my test project was a Countries look-up table.  There are at least three standards used by [at least] as many organizations that provide a two, if not three character unique ID code for each country in the world.  For my purposes, any of them will do nicely.  I chose the ISO 3166-1 Country Codes which provides not only a two and a three character unique ID for each Country but a three digit numerical code as well.  For the example I’ll use the three digit character code.

The database table, appropriately named “Countries”, will have two fields “ISO” [3] and “Name” [36],  the object we define will have one new property/attribute “CountryName".

In a nutshell, all there is to the solution is;  you map the object OID to the ISO field and add a bit code such that whenever you create a new object you replace the automatically generated GUID OID with an appropriate three digit character code.  The GUID assigned to the OID property is simply a string, we plan on swapping it out with ISO which is also a string – size doesn’t get involved until we try and shove the data into the database fields we created to persist them.

So, here we have the simple TCountry object that we can use to create/store our country names and the unique key to each.  Notice that I’ve implemented an overloaded CreateNew method [constructor] from the parent object.  Notice as well, that I’m passing two parameters even though the object technically only indicates support for one – the other one is the OID which you usually don’t mess with.

TCountry = class(TtiObject)
  private
    FCountryName: string;
    procedure SetCountryName(const Value: string);
  public
    constructor CreateNew(const AISO, ACountryName: string); reintroduce; overload;
  published
    property CountryName: string read FCountryName write SetCountryName;
  end;
{ ... irrelevant code clipped ... }

The CreateNew constructor get’s coded out as follows;

constructor TCountry.CreateNew(const AISO, ACountryName: string);
begin
  inherited CreateNew;          {<< create a new default object        }
  OID.AsString := AISO;         {<< swapping out the GUID OID with AISO}
  FCountryName := ACountryName;
end;

I am using AutoMapping for these particular objects, which is perfect, as all we need do [in this demo] is load the whole table anyway.  Down in the initialization section of the unit, notice that we map the OID object attribute to the ISO database field.

initialization

  with GTIOPFManager.ClassDBMappingMgr do
    begin
      {               Object,    TableName,   AttrName,      ColName   PKInfo }
      RegisterMapping(TCountry, 'Countries', 'OID',         'ISO'  ,   [pktDB]);
      RegisterMapping(TCountry, 'Countries', 'CountryName', 'Name'            );
      { ... irrelevant code clipped ... } 
    end;

Now somewhere out in your project you create and persist a bunch of objects;

procedure AddABunchOfRecords;
var
  Country: TCountry;
  CountryList: TCountryList;

begin
  CountryList := TCountryList.Create;

  Country := TCountry.CreateNew('CAN', 'Canada');
  CountryList.Add(Country);

  Country := TCountry.CreateNew('GBR', 'United Kingdom');
  CountryList.Add(Country);

  Country := TCountry.CreateNew('USA', 'United States');
  CountryList.Add(Country);

  Country := TCountry.CreateNew('BRA', 'Brazil');
  CountryList.Add(Country);

  Country := TCountry.CreateNew('HND', 'Honduras');
  CountryList.Add(Country);

  Country := TCountry.CreateNew('IND', 'India');
  CountryList.Add(Country);

  GTIOPFManager.Save(CountryList);

end;

Although it has taken me a relatively long time to get there, it all seems pretty simple once you have seen it done.  I hope you find this helpful, if you see any errors or have any questions I’d appreciate it if you let me know.

Thanks for stopping by ...
Dave


Read the whole article ...

Tuesday, 28 December, 2010

A Simple Fix For My Broken WUBI

I updated my Wubi Ubuntu 10.04 install a couple of days ago ... and it broke.  That was really annoying. It was also, from my perspective, a bit of a kick in the bum for Ubuntu WUBI.  It breaks kinda easy it seems, and from the number of posts I’ve read, it’s not the first time.  Over the last couple of days I’ve spent a considerable amount of time trolling newsgroups trying to find a fix that suits my specific situation, without a whole lot of luck.  Until today.  Today I discovered an absolutely brain-dead fix to the whole problem that, if you’re set up for it, takes about 5 minutes to take care of.

Wubi, which stands for Windows-based Ubuntu Installer, is a convenient and easy way to try another operating system without the hassle of partitioning or other issues that sometimes occur when attempting to run more than one operating system.
Wikipedia

Wubi uses a virtual disk mounted within the host system (Windows), allowing users to access the full functionality of Ubuntu after the computer starts and the operating system has been chosen from the available menu.
Wubi Guide

Wubi was never intended as anything other than a means to try a Linux operating system and, as such, it is usually recommended to install Ubuntu to a dedicated partition on the hard-drive.
Agostino Russo (Wubi developer)

There are bits and pieces of how to fix this all over the web.  Every one is a slight, but completely understandable, variation of the last.  One guy decided to gather it all together in one thread for others to bump-up and/or reference whenever someone new come stumbling along tired from trying to figure it out themselves.  The big fix is located at the Wubi Megathread for all to follow.  Problem is, it didn’t work for me and finding the solution took a very very long time.

The gist of the “repair” is as follows ... From a terminal on a running Ubuntu OS (Applications > Accessories > Terminal), run the following commands:

  1. sudo mkdir /media/win
  2. sudo mount /dev/sda1 /media/win
  3. sudo mount -o loop /media/win/ubuntu/disks/root.disk /mnt
  4. sudo cp /mnt/boot/grub/grub.cfg /mnt/boot/grub/grub.cfg.copy
  5. sudo chmod +w /mnt/boot/grub/grub.cfg gksu gedit /mnt/boot/grub/grub.cfg
  6. Remove all lines prior to the first menuentry of /boot/grub/grub.cfg.

which, to me, reads ... [sudo] super/substitute user do:

  1. made a folder “/media/win” to mount a drive on,
  2. mount the drive “/dev/sda1” on the “/media/win” folder we just created,
  3. now that we can access it through mounting the drive ... 
    mount the image “/media/win/ubuntu/disks/root.disk” on the “/mnt “ folder,
    yea ... they skipped the create a folder “/mnt” to work on :)
  4. create a back-up copy of the “grub.cfg” file found at “/mnt/boot/grub/”,
  5. make “grub.cfg” editable and open it for editing,
  6. Remove all lines prior to the first menuentry of /boot/grub/grub.cfg.

The tough bit is in the second step and the tough part of it is figuring out which drive your WUBI is installed on.  That drive is represented by the “sda1” part of “/dev/sda1”.  You see, the “a1” part of it is actually “drive and partition”.  The drives on your machine are numbered sdaX, sdbX, sdcX ... and the partitions are sda1..?   You could be mounting “/dev/sdc4” ... who knows eh?  Well, they have an answer for that, there is a script you can run that will provide you with all the gory details and more - see Wubi Megathread for directions.  Let’s add another twist ... I’m running a RAID0That, they don’t have directions for ... ANYWHERE that I’ve managed to find in the last three days.  There must be a way to plug the appropriate partition off a raid drive into the “/dev/sdXY” structure but I’ll be damned if I can find it.  Wait though – you don’t really need to.

The GUI To The Rescue

Menu

I know I’ve browsed around on the Ubuntu file system before and I know it treats my RAID0 as a single drive.  Most of my experience though is on the WUBI and that’s what I’m trying to fix.  So, time to create a boot USB drive and do some playing around.  When the Ubuntu OS loads, it recognizes all my drives and it recognizes them, according to me, exactly the way it should.  After booting from the USB Drive, the middle [of three] menu items, the one called “Places”, is populated near the bottom with all of the drives that the OS found on the machine.  This, is where the easy fix comes from ...

Open the “Places” menu, slide the mouse down to one of the drives the OS has found and hover over it.  After a second or two a “tool-tip” pops up “Mount X” – X being whatever the drive was named.

As can be seen from the image to the right, the OS finds three “drives” and names them;
1) DelUtility
2) RECOVERY and
3) OS
I put the term “drives” in quotes simply because there are the three partitions on my twin 500GB drives that make up the single 1TB RAID0 drive.  Truth of the matter is, at this juncture I simply don’t care what it “is” as long as I can access it because I know my WUBI is in there somewhere.  Turns out it’s easy ... click on OS.  That’s all there is to it.  Yep, get your face out of the terminal box and click on an OS GUI element and, for a newbie like me, all that goofy “mount /dev/sd whatever” crap fades to black  :)

When you click on one of the drive/partition items found in the places menu the drive/partition gets mounted and [in Ubuntu 10.10 which is what I was using] immediately opens in the Nautilus file browser to the correct location.  Before you close it to get it out of the way, search through and find the “root.disk” file we need. Right click on it, select properties and copy the path to the file – you’ll need it.  It also shows up on your desktop as you can see in the image below.

To The Task At Hand

From a terminal on the running USB Ubuntu OS (Applications > Accessories > Terminal), making sure that you substitute the correct path data in the “sudo mount –o loop” command, run the following commands:

Screenshot-2

After running the second command you will notice another drive/partition show up on the desktop [this is a good thing] and [again, in Ubuntu 10.10 which is what I was using] immediately opens in the Nautilus file browser to the correct location.  You can close Nautilus, it is of no further use to us.  The file we are going to edit is owned by “root” and only by pretending to be “root” can we edit it.

The third and final command opens the existing “grub.cfg” in gedit and you have full editing power.  If you prefer to make a back-up before editing it’s easily done using gedit –but- you can do it in the terminal too if that’s what you gotta do.

OK, So I Lied ...

I had the WUBI running last night having, out of total frustration with the lack of a working procedure for my specific instance, given up and used System Restore in Windows 7.  Worked like a hot damn ... Windows 7 is the best Windows – EVER.

I even tried “every” known tool/utility that will open “root.disk” on Windows. Fun ... oh yea.

To those that would recommend I change and move to a dedicated partition … please don’t.  I like things just the way they are.  I don’t have to learn how to or care about partitioning and if I get tired of Ubuntu it’s really easy to get rid of.  The BIGGEST part though is ... Ubuntu is no longer the exclusive realm of the “elite hackers using a close to the core OS”!  Your grandmother is going to be using Ubuntu soon, you better pay more attention to the WUBI, because it’s going to be the install of choice.

Hope this all works for you ... it worked for me :)

 

Thanks for stopping by,
-- Dave


Read the whole article ...

Saturday, 28 November, 2009

The Decorator Design Pattern in Delphi

The decorator pattern, as defined in the book, with one glaring exception, is pretty easy to get a handle on and really isn’t going to take a lot of work from me in way of text here.

The Decorator Pattern ... is a design pattern that allows new/additional behaviour to be added to an existing object dynamically.
-- Wikipedia

The Decorator Pattern attaches additional responsibilities to an object dynamically.  Decorators provide a flexible alternative to subclassing for extending functionality.
-- Head First Design Patterns

The Beverage super-class I have put in it’s own unit just to provide me with the ability to also separate the Coffee and Condiment classes.  This, to me, is the logical thing to do and it also keeps the units small enough to present here.

The uBeverage Unit:

unit uBeverage;

interface

type
  TBeverage = class abstract(TObject)
  private
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; virtual; abstract;
    function GetDescription: string; virtual; abstract;
  public
    constructor Create; overload; virtual; abstract;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

  TCondimentDecorator = class abstract(TBeverage)
  public
    constructor Create(const ABeverage: TBeverage); overload; virtual; abstract;
  end;

implementation

end.

The uCoffee Unit:

unit uCoffees;

interface

uses
  uBeverage;

type
  TEspresso = class(TBeverage)
  private
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; override;
    function GetDescription: string; override;
  public
    constructor Create; override;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

  THouseBlend = class(TBeverage)
  private
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; override;
    function GetDescription: string; override;
  public
    constructor Create; override;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

  TDarkRoast = class(TBeverage)
  private
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; override;
    function GetDescription: string; override;
  public
    constructor Create; override;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

  TDecaf = class(TBeverage)
  private
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; override;
    function GetDescription: string; override;
  public
    constructor Create; override;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

implementation

uses
  SysUtils;

resourcestring
  csCoffee = 'A %s has been poured for you.';

  { TEspresso }

constructor TEspresso.Create;
begin
  inherited;
  Description := 'Espresso';
  Cost := 2.99;
  Writeln(Format(csCoffee, [Description]));
end;

function TEspresso.GetCost: single;
begin
  Result := FCost;
end;

function TEspresso.GetDescription: string;
begin
  Result := FDescription;
end;

constructor THouseBlend.Create;
begin
  inherited;
  FDescription := 'House Blend Coffee';
  FCost := 1.89;
  Writeln(Format(csCoffee, [Description]));
end;

{ THouseBlend }

function THouseBlend.GetCost: single;
begin
  Result := FCost;
end;

function THouseBlend.GetDescription: string;
begin
  Result := FDescription;
end;

constructor TDarkRoast.Create;
begin
  inherited;
  FDescription := 'Dark Roast Coffee';
  FCost := 3.99;
  Writeln(Format(csCoffee, [Description]));
end;

{ TDarkRoast }

function TDarkRoast.GetCost: single;
begin
  Result := FCost;
end;

function TDarkRoast.GetDescription: string;
begin
  Result := FDescription;
end;

{ Decaf }
constructor TDecaf.Create;
begin
  inherited;
  FDescription := 'Decafinated Coffee';
  FCost := 2.05;
  Writeln(Format(csCoffee, [Description]));
end;

function TDecaf.GetCost: single;
begin
  Result := FCost;
end;

function TDecaf.GetDescription: string;
begin
  Result := FDescription;
end;

end.
The uCondiments Unit:
unit uCondiments;

interface

uses
  uBeverage;

type
  TMocha = class(TCondimentDecorator)
  private
    FBeverage: TBeverage;
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; override;
    function GetDescription: string; override;
    property Beverage: TBeverage read FBeverage write FBeverage;
  public
    destructor Destroy; override;
    constructor Create(const ABeverage: TBeverage); override;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

  TMilk = class(TCondimentDecorator)
  private
    FBeverage: TBeverage;
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; override;
    function GetDescription: string; override;
    property Beverage: TBeverage read FBeverage write FBeverage;
  public
    destructor Destroy; override;
    constructor Create(const ABeverage: TBeverage); override;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

  TSoy = class(TCondimentDecorator)
  private
    FBeverage: TBeverage;
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; override;
    function GetDescription: string; override;
    property Beverage: TBeverage read FBeverage write FBeverage;
  public
    destructor Destroy; override;
    constructor Create(const ABeverage: TBeverage); override;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

  TWhip = class(TCondimentDecorator)
  private
    FBeverage: TBeverage;
    FCost: single;
    FDescription: string;
  protected
    function GetCost: single; override;
    function GetDescription: string; override;
    property Beverage: TBeverage read FBeverage write FBeverage;
  public
    destructor Destroy; override;
    constructor Create(const ABeverage: TBeverage); override;
    property Cost: single read GetCost write FCost;
    property Description: string read GetDescription write FDescription;
  end;

implementation

uses
  SysUtils;

resourcestring
  csCondiment = '%s has been added to your Coffee.';

constructor TMocha.Create(const ABeverage: TBeverage);
begin
  inherited;
  Beverage := ABeverage;
  Writeln(Format(csCondiment, ['Mocha']));
end;

destructor TMocha.Destroy;
begin
  Beverage.Free;
  inherited;
end;

function TMocha.GetCost: single;
begin
  Result := 0.80 + Beverage.Cost;
end;

function TMocha.GetDescription: string;
begin
  Result := Beverage.Description + ', Mocha';
end;

{ TMilk }

constructor TMilk.Create(const ABeverage: TBeverage);
begin
  inherited;
  Beverage := ABeverage;
  Writeln(Format(csCondiment, ['Milk']));
end;

destructor TMilk.Destroy;
begin
  Beverage.Free;
  inherited;
end;

function TMilk.GetCost: single;
begin
  Result := 0.50 + Beverage.Cost;
end;

function TMilk.GetDescription: string;
begin
  Result := Beverage.Description + ', Steamed Milk';
end;

{ TSoy }

constructor TSoy.Create(const ABeverage: TBeverage);
begin
  inherited;
  Beverage := ABeverage;
  Writeln(Format(csCondiment, ['Soy']));
end;

destructor TSoy.Destroy;
begin
  Beverage.Free;
  inherited;
end;

function TSoy.GetCost: single;
begin
  Result := 0.95 + Beverage.Cost;
end;

function TSoy.GetDescription: string;
begin
  Result := Beverage.Description + ', Soy';
end;

{ TWhip }

constructor TWhip.Create(const ABeverage: TBeverage);
begin
  inherited;
  Beverage := ABeverage;
  Writeln(Format(csCondiment, ['Whip']));
end;

destructor TWhip.Destroy;
begin
  Beverage.Free;
  inherited;
end;

function TWhip.GetCost: single;
begin
  Result := 1.10 + Beverage.Cost;
end;

function TWhip.GetDescription: string;
begin
  Result := Beverage.Description + ', Whip';
end;

end.

Now we just need a program front end to tie it all together, see if our code works and provide a dynamic environment to prove that the decorators are decorating properly.  First I would like to explain a bit of the code you will see in the console application that I will use.  Console applications are very rudimentary in Delphi so I’ve added a “Console unit” developed by Rudy Velthuis to assist with some simple console functionality.  I have taken some liberty with the unit provided by Rudy and changed the name of the unit for clarity to “Console_Ext” before I compiled it ... the code does simply just extend and not replace the console.  You do not need to download and install the unit to work with my code as I have commented out the {$DEFINE USE_CONSOLE_EXT} line to leave you with the scrolling off to oblivion console app native to Delphi.  I do recommend you get and use the code from Rudy if you do very much console testing at all.  The only part I needed from the unit is ClrScr.

The StarbuzzCoffee Unit:

program StarbuzzCoffee;
{$APPTYPE CONSOLE}
//{$DEFINE USE_CONSOLE_EXT}

uses
  SysUtils,
{$IFDEF USE_CONSOLE_EXT} Console_Ext, {$ENDIF}
  uCondiments in 'uCondiments.pas',
  uBeverage in 'uBeverage.pas',
  uCoffees in 'uCoffees.pas';

var {global and visible to all Methods.}
  Coffee: TBeverage;
  Quit: string;

procedure ClearScreen;
begin
{$IFDEF USE_CONSOLE_EXT}
  ClrScr;
{$ELSE}
  Writeln;
{$ENDIF}
end;

function CoffeeSelected: Boolean;
var
  strInput: string;
  intValue: Integer;

begin
  Result := False;
  Writeln('Welcome to Starbuzz.');
  Writeln('1) House Blend');
  Writeln('2) Dark Roast');
  Writeln('3) Decaf');
  Writeln('4) Espresso');
  Write('Select a Coffee and press Enter: ');
  ReadLn(strInput);
  if TryStrToInt(strInput, intValue) then
    begin
      ClearScreen;
      case intValue of
        1: Coffee := THouseBlend.Create;
        2: Coffee := TDarkRoast.Create;
        3: Coffee := TDecaf.Create;
        4: Coffee := TEspresso.Create;
      end;
      if Assigned(Coffee) then
        Result := True;
    end;
end;

function CondimentsAdded: Boolean;
var
  strInput: string;
  intValue: Integer;
  bolFinished: Boolean;
begin
  bolFinished := False;
  repeat
    Writeln('Make a Condiment Selection and press Enter:');
    Writeln('0) Nothing more thank-you.');
    Writeln('1) Milk');
    Writeln('2) Mocha');
    Writeln('3) Soy');
    Writeln('4) Whip');
    Writeln('[Enter anything else to quit.]');
    Write('Select a Condiment: ');
    ReadLn(strInput);
    if TryStrToInt(strInput, intValue) then
      begin
        ClearScreen;
        Result := True;
        case intValue of
          0: bolFinished := True;
          1: Coffee := TMilk.Create(Coffee);
          2: Coffee := TMocha.Create(Coffee);
          3: Coffee := TSoy.Create(Coffee);
          4: Coffee := TWhip.Create(Coffee);
        end;
      end
    else
      Result := False;
  until bolFinished or (Result = False);
end;

begin
  repeat
    try
      ClearScreen;
      if CoffeeSelected and CondimentsAdded then
        begin
          Writeln;
          Writeln('Your order is as follows:');
          Writeln(Coffee.Description + '. Please pay: $' + FormatCurr('0.00', Coffee.Cost));
        end;
    finally
      FreeAndNil(Coffee);
    end;
    Writeln;
    Write('Would you like to Quit [Y/N]?');
    ReadLn(Quit);
  until UpperCase(Quit) = 'Y';

end.

And there you have it ... a Delphi Decorator.

Comments ...

I won’t even bother getting into my thoughts on the metaphor used, as it has been more than adequately covered in this article on stackoverflow.  I must however comment that it broke my heart to copy and paste that much code, that many times, just to provide a different price and description for a coffee.  Finally, although trivial in retrospect, the big thing [the exception mentioned way above] about this pattern is  “attaching additional responsibilities to an object dynamically” ... well, what’s with the hard coded example then?  Once I did find an example that gave at least an idea of how to implement the pattern dynamically, in Delphi FWIW, it was smooth going.

The logic of the implementation of this pattern really makes you think.  I mean who, is exactly wrapped up in who?  One point I want to make clear, the results of the demonstration really don’t make much sense unless you follow exactly what is happening.  The example Description call, digs down to the core and creates the description on it’s way back out.  To understand this, you really need to play with the code a bit ... experimenting until you fully understand the recursive nature of the final object.

Using Classes instead of Interfaces

There was a bit of discussion with the Strategy Pattern post that I did on my use of Interfaces.  Now, because a pattern is a pattern is a pattern and should be applicable whatever code structure you decide to use, I am using and will stick to classes alone for as long as it is possible.  My only concern with this decision is that what I am producing is not a direct translation of the Java code, which is still my sole intent but if you can live with my choice ... so can I.

In Closing ...

The above is summary and [my rendition of] the Delphi code taken from the third chapter of the book, Head First Design Patterns from O’Reilly.

As I mentioned in the first post in this series, I’m studying Design Patterns using the Head First Design Patterns book from O’Reilly.   As part of this learning process I’m working through the existing examples written in Java and recreating them in Delphi.  This is the forth post in the series and the third that actually deals with one of the design patterns.  The first post in the series also provides a list of additional resources on Design Patterns in Delphi that I’ve managed to track down.  I must mention again that I don’t plan on teaching you design patterns, as that would be quite presumptuous of me considering that I’m just a student of them myself.  I do intend to provide simply an overview of what I’ve learned and the resulting code I produced in the process.

Thanks for stopping by ...
Dave


Read the whole article ...

Sunday, 8 November, 2009

The Observer Design Pattern in Delphi – Pull

As mentioned in the previous article The Observer Design Pattern in Delphi – Push I intended to do a “Pull” article to show, what I think is, a significant “feature” of this pattern and one that I thought was underplayed in the Head First Design Patterns book that I’m learning from.

I wrote code for this article a couple of times and in the end simply refactored the code from the previous article for simplicity and consistency.  The primary reason I was looking at different code is that I find the Weather Station metaphor a bit weak, as it doesn’t show the significant difference between Push and Pull to advantage.  Push, Pull who cares ... it’s a handful of simple data types and an observer pattern is just an observer pattern.  Sorry, not quite my perspective on it.  As it is, with just the refactored code, observers are being notified who don’t need to be but I’ll leave that concept to you to develop on your own.

So, to the code.  I’ve actually changed quite a bit of it although the actual concept change is not complicated and is, in fact, quite easy to understand.  What we had was an Information Provider that shoved all of it’s data at the consumers in one massive hunk and did that every time any one specific item changed.  It was left to the consumer to decide what was of use, and discard the remainder.  Now we have modified the Provider to send a flag telling the consumers, not only that there has been a change, but what has changed.  Now it is up to the consumer, using a stored reference to the provider, to fetch any information it might be interested in.  The consumers still use the same methods to let the provider know that it is, or is not, interested in receiving information.  For the sake of the provided code, the extent to which we have broken down the tasks is sufficient.  As I’ve mentioned, telling only the consumers that need to know about a specific change in data, would be the next step in improving what we have.

The uObservable Unit

I’ve combined the two original primary object class files into one.  Personal Lesson Learned: Don’t be so damn anal about having every class/concept squirreled away in it’s own unit.  The Observable and Observer root classes are very closely related, they don’t really have much in the way of secrets from each other, and they fit into a single unit quite nicely.  Circular reference ... be gone!  The specific changes in this unit are as follows:

  • added an enumerator to work as a flag specific to what has been changed,
  • modified the TObservable root class method NotifyObservers to include a flag indicating what to notify Observers of,
  • modified the TBaseObserver root class method UpDate removing all the data it was sending and reducing it to a flag indicating what changed.
unit uObservable;

interface

type
  TUpdateType = (utTemperature, utHumidity, utPressure);

  TBaseObserver = class;
  TDisplayObserver = class;

  TObservable = class(TObject)
    procedure NotifyObservers(UpdateType: TUpdateType); virtual; abstract;
    procedure RegisterObserver(Observer: TBaseObserver); virtual; abstract;
    procedure RemoveObserver(Observer: TBaseObserver); virtual; abstract;
  end;

  TBaseObserver = class(TObject)
  public
    procedure UpDate(UpdateType: TUpdateType); virtual; abstract;
  end;

  TDisplayObserver = class(TBaseObserver)
  public
    procedure Display; virtual; abstract;
  end;

implementation

end.

The WeatherStation Unit

This unit has changed quite a bit but most of the new code is in initializing and monitoring the measurement data.  The base concept has been changed from pushing information at the observers to leaving it up to them to pull what they need.  Now it just sends out a notice to each observer that a piece of data has changed, so the observers can retrieve the changed data if it wants it.  The specific changes in this unit are as follows:

  • added getters to the three measured data properties – all getters and setters are private,
  • refactored the SetMeasurements method to InitialiseMeasurements and added a call to the object’s Create event,
  • nuked the MeasurementsChanged method [it did very little] and replaced it with CheckCurConditions to simulate continuous monitoring,
  • coded the new measured data [Temperature, Pressure and Humidity] value setters to store data received and start the notification process,
  • wrapped all user input code in a big Try-Except to keep the code simple and not check each input value.
unit WeatherStation;

interface

uses
  SysUtils,
  Classes,
  uObservable;

type
  TWeatherStation = class(TObservable)
  private
    FHumidity: Single;
    FPressure: Single;
    FTemperature: Single;
    Observers: TList;
    function GetHumidity: Single;
    function GetPressure: Single;
    function GetTemperature: Single;
    procedure NotifyObservers(UpdateType: TUpdateType); override;
    procedure SetHumidity(const Value: Single);
    procedure SetPressure(const Value: Single);
    procedure SetTemperature(const Value: Single);
  public
    constructor Create;
    destructor Destroy; override;
    procedure CheckCurConditions;
    procedure InitialiseMeasurements;
    procedure RegisterObserver(Observer: TBaseObserver); override;
    procedure RemoveObserver(Observer: TBaseObserver); override;
    property Humidity: Single read GetHumidity write SetHumidity;
    property Pressure: Single read GetPressure write SetPressure;
    property Temperature: Single read GetTemperature write SetTemperature;
  end;

implementation

constructor TWeatherStation.Create;
begin
  inherited;
  Observers := TList.Create;
  Writeln('The Weather Station is up and running.');
  Writeln;
  InitialiseMeasurements;
end;

destructor TWeatherStation.Destroy;
begin
  Observers.Free;
  inherited;
end;

procedure TWeatherStation.CheckCurConditions;
var
  Finished: Boolean;
  MonitorUnit: string;
  MonitorVal: Single;
begin
  {CheckCurConditions loops through all the monitoring units
   and checks for changes. User input is used in this case.}
  Finished := False;
  repeat
    MonitorUnit := '';
    MonitorVal := 0;
    Writeln;
    write('Enter [T]emperature, [P]ressure, [H]umdity or [Q]uit. ');
    Readln(MonitorUnit);
    MonitorUnit := lowercase(Trim(MonitorUnit));
    try
      if MonitorUnit = 't' then
        begin
          write('Enter the Current Temperature   [F]: ');
          Readln(MonitorVal);
          Temperature := MonitorVal;
        end
      else if MonitorUnit = 'p' then
        begin
          write('Enter the Current Pressure:  [" Hg]: ');
          Readln(MonitorVal);
          Pressure := MonitorVal;
        end
      else if MonitorUnit = 'h' then
        begin
          write('Enter the current Humidity      [%]: ');
          Readln(MonitorVal);
          Humidity := MonitorVal;
        end
      else if MonitorUnit = 'q' then
        Finished := True
      else
        Writeln('That input is not valid. Please try again.');
    except
      begin
        Writeln;
        Writeln('That didn'' work out so well ... please try again.');
      end;
    end;
  until Finished;
end;

function TWeatherStation.GetHumidity: Single;
begin
  Result := FHumidity;
end;

function TWeatherStation.GetPressure: Single;
begin
  Result := FPressure;
end;

function TWeatherStation.GetTemperature: Single;
begin
  Result := FTemperature;
end;

procedure TWeatherStation.InitialiseMeasurements;
begin
  try
    Writeln('Enter the values as prompted and press Enter after each one.');
    write('Current Temperature   [F]: ');
    Readln(FTemperature);
    write('Current Humidity      [%]: ');
    Readln(FHumidity);
    write('Current Pressure:  [" Hg]: ');
    Readln(FPressure);
    Writeln;
  except
    begin
      Writeln;
      Writeln('That didn'' work out so well ... I''ve done it for you.');
      FTemperature := 70.5;
      FHumidity := 68.25;
      FPressure := 29.65;
      Writeln('Tenperature is 70.5, Humidity is 68.25% and Pressure is 29.65');
    end;
  end;
end;

procedure TWeatherStation.NotifyObservers(UpdateType: TUpdateType);
var
  i: Integer;
begin
  if Assigned(Observers) then
    begin
      for i := 0 to Observers.Count - 1 do
        TBaseObserver(Observers[i]).Update(UpdateType);
    end;
end;

procedure TWeatherStation.RegisterObserver(Observer: TBaseObserver);
begin
  if Observer is TBaseObserver then
    Observers.Add(Observer);
end;

procedure TWeatherStation.RemoveObserver(Observer: TBaseObserver);
begin
  if (Observer is TBaseObserver) and (Observers.IndexOf(Observer) <> -1) then
    Observers.Remove(Observer);
end;

procedure TWeatherStation.SetHumidity(const Value: Single);
begin
  FHumidity := Value;
  NotifyObservers(utHumidity);
end;

procedure TWeatherStation.SetPressure(const Value: Single);
begin
  FPressure := Value;
  NotifyObservers(utPressure);
end;

procedure TWeatherStation.SetTemperature(const Value: Single);
begin
  FTemperature := Value;
  NotifyObservers(utTemperature);
end;

end.

The Display Observers

All the display device units, having given them some work to do, received pretty much exactly the same refactoring.

  • added a strict private reference to the Information Source which gets set during construction,
  • the constructor has quite a bit of new code to initialize the display with current data from the Weather Station when it is created,
  • the UpDate method has quite a bit of new code with the transfer of the responsibility of retrieving any required data from the Weather Station to the device,
  • the constructor and UpDate method code, in all Display units, is each wrapped in one big Try-Except providing a bit of a safety net for the local provider reference,
  • added a destructor to unregister the display when it gets freed,
  • a minor change moving the Display method to the private section.

Current Conditions Display

unit CurCondDisplay;

interface

uses
  SysUtils,
  uObservable,
  WeatherStation;

type
  TCurrentConditions = class(TDisplayObserver)
  strict private
    FDataSource: TWeatherStation;
  private
    Temperature: single;
    Humidity: single;
    Pressure: single;
    destructor Destroy; override;
    procedure Display; override;
  public
    constructor Create(Source: TWeatherStation);
    procedure Update(UpdateType: TUpdateType); override;
  end;

implementation

constructor TCurrentConditions.Create(Source: TWeatherStation);
begin
  inherited Create;
  try
    FDataSource := Source;
    FDataSource.RegisterObserver(Self);
    Temperature := FDataSource.Temperature;
    Pressure := FDataSource.Pressure;
    Humidity := FDataSource.Humidity;
    Writeln('Current Conditions Display is initialized, up and running.');
    Display;
  except
    Writeln('Something went wrong in the Current Conditions Display Creation.');
  end;
end;

destructor TCurrentConditions.Destroy;
begin
  FDataSource.RemoveObserver(Self);
  inherited;
end;

procedure TCurrentConditions.Display;
begin
  Writeln;
  Writeln('***** Current Weather Conditions *****');
  Writeln('The Temperature is ' + Format('%1.5g', [Temperature]) + ' Degrees F,');
  Writeln('Humidity is at ' + Format('%1.5g', [Humidity]) + '% and');
  Writeln('the Pressure is currently ' + Format('%1.5g', [Pressure]) + '" Hg.');
  Writeln('*********** End of Report ************');
  Writeln;
end;

procedure TCurrentConditions.Update(UpdateType: TUpdateType);
var
  OutNotice, ValStr: string;
begin
  OutNotice := 'Current Conditions Display has received new %s notification.';
  try
    case UpdateType of
      utTemperature:
        begin
          Temperature := FDataSource.Temperature;
          ValStr := 'Temperature';
        end;
      utHumidity:
        begin
          Humidity := FDataSource.Humidity;
          ValStr := 'Humidity';
        end;
      utPressure:
        begin
          Pressure := FDataSource.Pressure;
          ValStr := 'Pressure';
        end;
    end;
  except
    Writeln('Something went wrong in the Current Conditions Display UpDate.');
  end;

  Writeln;
  Writeln(Format(OutNotice, [ValStr]));

  case UpdateType of
    utTemperature, utHumidity, utPressure: Display;
  end;
end;

end.

Forecast Display

unit ForecastDisplay;

interface

uses
  SysUtils,
  uObservable,
  WeatherStation;

type
  TForecastConditions = class(TDisplayObserver)
  strict private
    FDataSource: TWeatherStation;
  private
    CurPressure: single;
    LastPressure: single;
    destructor Destroy; override;
    procedure Display; override;
  public
    constructor Create(Source: TWeatherStation);
    procedure Update(UpdateType: TUpdateType); override;
  end;

implementation

constructor TForecastConditions.Create(Source: TWeatherStation);
begin
  try
    inherited Create;
    FDataSource := Source;
    FDataSource.RegisterObserver(Self);
    CurPressure := 29.92;
    LastPressure := CurPressure;
    Writeln('Forecast Display is initialized, up and running.');
    Display;
  except
    Writeln('Something went wrong in the Current Conditions Display Creation.');
  end;
end;

destructor TForecastConditions.Destroy;
begin
  FDataSource.RemoveObserver(Self);
  inherited;
end;

procedure TForecastConditions.Display;
begin
  Writeln;
  Writeln('***** Current Weather Forecast *****');
  if CurPressure = LastPressure then
    Writeln('More of the same.')
  else if CurPressure > LastPressure then
    Writeln('Improving weather on the way!')
  else
    Writeln('Watch out for cooler, rainy weather.');
  Writeln('*********** End of Report ************');
  Writeln;
end;

procedure TForecastConditions.Update(UpdateType: TUpdateType);
var
  OutNotice, ValStr: string;
begin
  OutNotice := 'Forecast Display has received new %s notification.';
  try
    case UpdateType of
      utTemperature, utHumidity: ValStr := 'irrelevant';
      utPressure:
        begin
          CurPressure := FDataSource.Pressure;
          ValStr := 'Pressure';
        end;
    end;
  except
    Writeln('Something went wrong in the Forecast Display UpDate.');
  end;

  Writeln;
  Writeln(Format(OutNotice, [ValStr]));

  case UpdateType of
    utPressure: Display;
  end;
end;

end.

Heat Index Display

unit HeatIndexDisplay;

interface

uses
  SysUtils,
  uObservable,
  WeatherStation;

type
  THeatIndex = class(TDisplayObserver)
  strict private
    FDataSource: TWeatherStation;
    destructor Destroy; override;
  private
    Temp: single;
    RelHum: single;
    procedure Display; override;
  public
    constructor Create(Source: TWeatherStation);
    procedure Update(UpdateType: TUpdateType); override;
  end;

implementation

constructor THeatIndex.Create(Source: TWeatherStation);
begin
  try
    inherited Create;
    FDataSource := Source;
    FDataSource.RegisterObserver(Self);
    Temp := FDataSource.Temperature;
    RelHum := FDataSource.Humidity;
    Writeln('Heat Index Display is initialized, up and running.');
    Display;
  except
    Writeln('Something went wrong in the Current Conditions Display Creation.');
  end;
end;

destructor THeatIndex.Destroy;
begin
  FDataSource.RemoveObserver(Self);
  inherited;
end;

procedure THeatIndex.Display;
var
  HeatIndex: single;
begin
  if Temp >= 70 then
    HeatIndex := 16.922999999999998 + 0.185212 * Temp + 5.37941 * RelHum -
      (0.100254 * Temp * RelHum) + 0.00941695 * Temp * Temp + 0.00728898 *
      RelHum * RelHum + 0.000345372 * Temp * Temp * RelHum -
      (0.000814971 * Temp * RelHum * RelHum)
      + 1.02102E-005 * Temp * Temp * RelHum * RelHum -
      (3.8646E-005 * Temp * Temp * Temp) + 2.91583E-005 * RelHum * RelHum *
      RelHum + 1.42721E-006 * Temp * Temp * Temp * RelHum + 1.97483E-007 *
      Temp * RelHum * RelHum * RelHum -
      (2.18429E-008 * Temp * Temp * Temp * RelHum * RelHum)
      + 8.43296E-010 * Temp * Temp * RelHum * RelHum * RelHum -
      (4.81975E-011 * Temp * Temp * Temp * RelHum * RelHum * RelHum)
  else
    HeatIndex := 0;

  Writeln;
  Writeln('********* Current Heat Index *********');

  if HeatIndex > 0 then
    Writeln('The Heat Index is ' + Format('%f', [HeatIndex]) + ' Degrees F.')
  else
    Writeln('Temp is less than 70F, the Heat Index is irrelevant.');

  Writeln('*********** End of Report ************');
  Writeln;
end;

procedure THeatIndex.Update(UpdateType: TUpdateType);
var
  OutNotice, ValStr: string;
begin
  OutNotice := 'Heat Index Display has received new %s notification.';
  try
    case UpdateType of
      utTemperature:
        begin
          Temp := FDataSource.Temperature;
          ValStr := 'Temperature';
        end;
      utHumidity:
        begin
          RelHum := FDataSource.Humidity;
          ValStr := 'RelHum';
        end;
      utPressure: ValStr := 'irrelevant';
    end;
  except
    Writeln('Something went wrong in the Heat Index Display UpDate.');
  end;

  Writeln;
  Writeln(Format(OutNotice, [ValStr]));

  case UpdateType of
    utTemperature, utHumidity: Display;
  end;
end;

end.

The PullWeather Test Unit

program PullWeather;
{$APPTYPE CONSOLE}

uses
  SysUtils,
  uObservable in 'uObservable.pas',
  WeatherStation in 'WeatherStation.pas',
  CurCondDisplay in 'CurCondDisplay.pas',
  ForecastDisplay in 'ForecastDisplay.pas',
  HeatIndexDisplay in 'HeatIndexDisplay.pas';

var
  MyStation: TWeatherStation;
  CurCondDisp: TCurrentConditions;
  ForecastDisp: TForecastConditions;
  HeatIndexDisp: THeatIndex;

begin
  ReportMemoryLeaksOnShutdown := DebugHook <> 0;

  try
    // Create and initialize the station.
    MyStation := TWeatherStation.Create;
    // Create the consumers.
    CurCondDisp := TCurrentConditions.Create(MyStation);
    ForecastDisp := TForecastConditions.Create(MyStation);
    HeatIndexDisp := THeatIndex.Create(MyStation);
    //Run the Station Conditions Monitoring code.
    MyStation.CheckCurConditions;
  finally
    HeatIndexDisp.Free;
    ForecastDisp.Free;
    CurCondDisp.Free;
    MyStation.Free;
  end;

end.

JD-GUI - Java Browsing Tool

The freeware JD-GUI a standalone graphical utility that displays Java source codes of “.class” files has been a great help to me for having a formatted peek into the book related Java files that are available from the HFDP site at Head First Labs.  If you are looking for something along this line, I highly recommend it.

Using Classes instead of Interfaces

There was a bit of discussion with the Strategy Pattern post that I did on my use of Interfaces.  Now, because a pattern is a pattern is a pattern and should be applicable whatever code structure you decide to use, I am using and will stick to classes alone for as long as it is possible.  My only concern with this decision is that what I am producing is not a direct translation of the Java code, which is still my sole intent but if you can live with my choice ... so can I.

In Closing ...

The above is summary and [my rendition of] the Delphi code taken from the second chapter of the book, Head First Design Patterns from O’Reilly.

As I mentioned in the first post in this series, I’m studying Design Patterns using the Head First Design Patterns book from O’Reilly.   As part of this learning process I’m working through the existing examples written in Java and recreating them in Delphi.  This is the forth post in the series and the third that actually deals with one of the design patterns.  The first post in the series also provides a list of additional resources on Design Patterns in Delphi that I’ve managed to track down.  I must mention again that I don’t plan on teaching you design patterns, as that would be quite presumptuous of me considering that I’m just a student of them myself.  I do intend to provide simply an overview of what I’ve learned and the resulting code I produced in the process.

Thanks for stopping by ...
Dave


Read the whole article ...

Monday, 26 October, 2009

The “Death of Delphi” Rumour Proven Untrue

Hot News ...

jamiei, a Delphi Developer, Web Developer and General Geek, has come forward and provided the community with irrefutable proof  [it’s on the internet, it must be true] clearly dispelling all rumoured answers to the questions,

  1. is Delphi Dead or even
  2. is Delphi Dying ?
    [Simply click ether link and the truth will be revealed]

Read the full story here on his Time to bury this “Is Delphi Dying” nonsense blog post.

 

Influence The Product

With this comforting news in mind, you are urged to spend a bit of your time to complete “The Delphi Survey” providing Embarcadero with valuable input on where you would like the product to go in the future.  The survey does take a bit of time, but it is well worth the effort, specifically in that it is your chance to be heard and have an influence on what the [alive and doing quite well] Delphi may look like tomorrow.  This is important, please take the time to do this survey.  It is kind of like voting ... you have no right to complain about the government if you can’t be bothered to take the time to vote.  Yea, that does specifically mean that if you hang around on Forums » Delphi » Non-Technical that the survey is non-optional.

 

Design Patterns in Delphi

If you were expecting the next instalment of my series on Design Patterns in Delphi, my apologies.  I’ve been waylaid by some other must-do tasks and running a bit off my intended schedule.  It’s not that far off.  I’m hoping to get to is soon.

 

Gee, this is kinda like one of my favourite reads  Random Thoughts by Nick Hodges ... OK, not even close I know :)

 

Thanks for stopping by,
Dave


Read the whole article ...

Sunday, 11 October, 2009

The Observer Design Pattern in Delphi – Push

A summary and [my rendition of] the Delphi code taken from the second chapter of the book, Head First Design Patterns from O’Reilly.

As I mentioned in the first post in this series, I’m studying Design Patterns using the Head First Design Patterns book from O’Reilly.   As part of this learning process I’m working through the existing examples written in Java and recreating them in Delphi.  This is the third post in the series and the second that actually deals with one of the design patterns.  The first post in the series also provides a list of additional resources on Design Patterns in Delphi that I’ve managed to track down.  I must mention again that I don’t plan on teaching you design patterns, as that would be quite presumptuous of me considering that I’m just a student of them myself.  I do intend to provide simply an overview of what I’ve learned and the resulting code I produced in the process.

The Observer Pattern ... (a subset of the asynchronous publish/subscribe pattern) is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.  It is mainly used to implement distributed event handling systems.
-- Wikipedia

The Observer Pattern ... defines a one to many dependency between objects so that when one object changes state, all of it’s dependants are notified and updated automatically.
-- Head First Design Patterns

There was a bit of discussion with the Strategy Pattern post that I did last time on my use of Interfaces.  This time, because a pattern is a pattern is a pattern and should be applicable whatever code structure you decide to use, I decided to stick to classes alone.  In this instance, and I expect in more of them to come should I make this a rule, it pushed me into areas of writing code that I had yet to experience, which was [some] fun.  My only concern with it is that this is not a direct translation of the Java code, which is still my sole intent.

This one was fun to work with.  It’s brain-dead simple and really easy to understand. Putting it into practise, depending on your personal coding standards/methods can present some challenges.  Me, I’m an everything in it’s own unit kinda guy and the infamous “circular reference” warning and I got to be good pals ... yet again.  There was more than once I wanted to shove it all in one unit and get it over with, but I eventually hammered out a solution that works.

There are a few things that I want to clarify before I start shoving code at you:

  • the title of this post is “Push” and that’s the style of Observer that is coded this time. I plan on doing a “Pull” article before I move on to the next pattern. I think the significant difference between push/pull is not emphasised in the book as much as it should be,
  • in the book, they start using the “built in Java Observer” units on page 64, I stopped there,
  • I, initially, didn’t understand why they called the Observable super-class “Subject”, but having read more on the pattern have come to understand the intent.  I had already created my classes at that point and taken license by using the term “Observable” and will stay with that decision.  I hope it’s not confusing.
  • the book is explicit in explaining how Observers register/subscribe or unregister/unsubscribe to the Observable/Subject when they want but, the code provided registers the Observers when they are created and the code required for an Observer to subscribe/unsubscribe at will is missing.  The Pull example I do next will have this feature.  Having said all this ... I want to ensure that I also make it clear that patterns are flexible and there aren’t only two answers to the question.  It’s the principle that’s important.

My rendition of the Java classes [with appropriate notations] are as follows:

The Observable [Subject] Class

unit uObservable;

interface

uses uObserver;

type
  TObservable = class(TObject)
    procedure NotifyObservers; virtual; abstract;
    procedure RegisterObserver(Observer: TBaseObserver); virtual; abstract;
    procedure RemoveObserver(Observer: TBaseObserver); virtual; abstract;
  end;

implementation

end.

The Observer Class

Next I built the abstract Observer class, TDisplayObserver, which is derived from TBaseObserver. 

unit uObserver;

interface

type
  TBaseObserver = class(TObject)
  public
    procedure UpDate(Temp: Single; Humid: Single; Press: Single); virtual; abstract;
  end;

  TDisplayObserver = class(TBaseObserver)
  public
    procedure Display; virtual; abstract;
  end;

implementation

end.

Note that the TDisplayObserver class is not only an abstract class but a compound abstract class. Any descendant class has to implement both the Display and Update methods.  We’ve done this to get around the lack of multiple inheritance in Delphi but required to closely follow the Java code supplied.  The other way around compound classes is a class that implements multiple Interfaces.  In addition, the Observable/Subject object don’t need to know any more about Observers than the have a UpDate method whereas the Observers all have to have both an UpDate method and a Display method.

The WeatherStation Class

unit WeatherStation;

interface

uses
  SysUtils, Classes, uObservable, uObserver;

type
  TWeatherStation = class(TObservable)
  private
    Observers: TList;
    FTemperature: Single;
    FHumidity: Single;
    FPressure: Single;
    function GetTemperature: Single;
    function GetHumidity: Single;
    function GetPressure: Single;
    procedure MeasurementsChanged;
  public
    property Humidity: Single 
      read GetHumidity 
      write FHumidity;
    property Pressure: Single
      read GetPressure
      write FPressure;
    property Temperature: Single
      read GetTemperature
      write FTemperature;
    constructor Create;
    destructor Destroy; override;
    procedure NotifyObservers; override;
    procedure RegisterObserver(Observer: TBaseObserver); override;
    procedure RemoveObserver(Observer: TBaseObserver); override;
    procedure SetMeasurements;
  end;

implementation

constructor TWeatherStation.Create;
begin
  inherited;
  Observers := TList.Create;
  Writeln('The Weather Station is up and running.');
end;

destructor TWeatherStation.Destroy;
begin
  Observers.Free;
  inherited;
end;

function TWeatherStation.GetHumidity: Single;
begin
  Result := FHumidity;
end;

function TWeatherStation.GetPressure: Single;
begin
  Result := FPressure;
end;

function TWeatherStation.GetTemperature: Single;
begin
  Result := FTemperature;
end;

procedure TWeatherStation.MeasurementsChanged;
begin
  NotifyObservers;
end;

procedure TWeatherStation.NotifyObservers;
var
  i: Integer;
begin
  if Assigned(Observers) then
    begin
      for i := 0 to Observers.Count - 1 do
        TBaseObserver(Observers[i]).Update(FTemperature, FHumidity, FPressure);
    end;
end;

procedure TWeatherStation.RegisterObserver(Observer: TBaseObserver);
begin
  if Observer is TBaseObserver then
    Observers.Add(Observer);
end;

procedure TWeatherStation.RemoveObserver(Observer: TBaseObserver);
begin
  if (Observer is TBaseObserver) and (Observers.IndexOf(Observer) <> -1) then
    Observers.Remove(Observer);
end;

procedure TWeatherStation.SetMeasurements;
begin
  Writeln('Enter the values as prompted and press Enter after each one.');
  Write('Current Temperature   [F]: ');
  Readln(FTemperature);
  Write('Current Humidity      [%]: ');
  Readln(FHumidity);
  Write('Current Pressure:  [" Hg]: ');
  Readln(FPressure);
  MeasurementsChanged;
end;

end.

Current Conditions Display

unit CurCondDisplay;

interface

uses SysUtils, uObserver, WeatherStation;

type
  TCurrentConditions = class(TDisplayObserver)
  private
    Temperature: single;
    Humidity: single;
    Pressure: single;
  public
    constructor Create(Source:TWeatherStation);
    procedure Update(Temp: Single; Humid: Single; Press: Single); override;
    procedure Display; override;
  end;

implementation

constructor TCurrentConditions.Create(Source:TWeatherStation);
begin
  inherited Create;
  Source.RegisterObserver(Self);
end;

procedure TCurrentConditions.Display;
begin
  WriteLn;
  Writeln('***** Current Weather Conditions *****');
  Writeln('The Temperature is ' + Format('%1.5g', [Temperature]) + ' Degrees F,');
  Writeln('Humidity is at ' + Format('%1.5g', [Humidity]) + '% and');
  Writeln('the Pressure is currently ' + Format('%1.5g', [Pressure]) + '" Hg.');
  Writeln('*********** End of Report ************');
  WriteLn;
end;

procedure TCurrentConditions.Update(Temp: Single; Humid: Single; Press: Single);
begin
  Temperature := Temp;
  Humidity := Humid;
  Pressure := Press;
  Display;
end;

end.

Forecast Display

unit ForecastDisplay;

interface

uses SysUtils, uObserver, WeatherStation;

type
  TForecastConditions = class(TDisplayObserver)
  private
    CurPressure: single;
    LastPressure: single;
  public
    constructor Create(Source:TWeatherStation);
    procedure Update(Temp: Single; Humid: Single; Press: Single); override;
    procedure Display; override;
  end;

implementation

constructor TForecastConditions.Create(Source:TWeatherStation);
begin
  inherited Create;
  Source.RegisterObserver(Self);
  CurPressure := 29.92;
end;

procedure TForecastConditions.Display;
begin
  WriteLn;
  Writeln('***** Current Weather Forecast *****');
  if CurPressure = LastPressure then
    Writeln('More of the same.')
  else if CurPressure > LastPressure then
    Writeln('Improving weather on the way!')
  else
    Writeln('Watch out for cooler, rainy weather.');
  Writeln('*********** End of Report ************');
  WriteLn;
end;

procedure TForecastConditions.Update(Temp, Humid, Press: Single);
begin
  LastPressure := CurPressure;
  CurPressure := Press;
  Display;
end;

end.

Statistics Display

I skipped this one. Accurately displaying float values is impossible for a project of such miniscule scope – I looked into it, extensively, and chucked it in as a hopeless muddle.  Actually it’s quite frustrating not being able to store 12.3 and then be able to simply display it as 12.3 using FloatToStr() instead of the 12.2999995499 gibberish you get, without calling in the ace coder to design a special module for it.  Math/Statistics with this mess ... not even going to go there.  I’ll play some more with the available tools and see where it takes me ...
Edit: Thanks to a comment left by “msohn” I see where I went wrong with using Format() and could, at some point, come back and add this Display.  Sorry, but Format() just isn’t something I’ve had a need to use up to recently.  I did fix the project code.

Heat Index Display

unit HeatIndexDisplay;

interface

uses SysUtils, uObserver, WeatherStation;

type
  THeatIndex = class(TDisplayObserver)
  private
    t: single;
    rh: single;
  public
    constructor Create(Source:TWeatherStation);
    procedure Update(Temp: Single; Humid: Single; Press: Single); override;
    procedure Display; override;
  end;

implementation

constructor THeatIndex.Create(Source:TWeatherStation);
begin
  inherited Create;
  Source.RegisterObserver(Self);
end;

procedure THeatIndex.Display;
var
  hi: single;
begin
  if t >= 70 then
    hi := 16.922999999999998 + 0.185212 * t +
      5.37941 * rh -
      (0.100254 * t * rh) +
      0.00941695 * t * t +
      0.00728898 * rh * rh +
      0.000345372 * t * t * rh -
      (0.000814971 * t * rh * rh) +
      1.02102E-005 * t * t * rh * rh -
      (3.8646E-005 * t * t * t) +
      2.91583E-005 * rh * rh * rh +
      1.42721E-006 * t * t * t * rh +
      1.97483E-007 * t * rh * rh * rh -
      (2.18429E-008 * t * t * t * rh * rh) +
      8.43296E-010 * t * t * rh * rh * rh -
      (4.81975E-011 * t * t * t * rh * rh * rh)
  else
    hi := 0;
  WriteLn;
  Writeln('********* Current Heat Index *********');

  if hi > 0 then
    Writeln('The Heat Index is ' + Format('%f', [hi]) + ' Degrees F.')
  else
    Writeln('Temp is less than 70F, the Heat Index is irrelevant.');

  Writeln('*********** End of Report ************');
  WriteLn;
end;

procedure THeatIndex.Update(Temp, Humid, Press: Single);
begin
  t := Temp;
  rh := Humid;
  Display;
end;

end.

Finally, pull it all together with ...

The Push Weather Program

program PushWeather;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  uObserver in '..\Common\uObserver.pas',
  uObservable in '..\Common\uObservable.pas',
  WeatherStation in 'WeatherStation.pas',
  CurCondDisplay in 'CurCondDisplay.pas',
  ForecastDisplay in 'ForecastDisplay.pas',
  HeatIndexDisplay in 'HeatIndexDisplay.pas';

var
  MyStation: TWeatherStation;
  CurCondDisp: TCurrentConditions;
  ForecastDisp: TForecastConditions;
  HeatIndexDisp: THeatIndex;

begin
  //ReportMemoryLeaksOnShutdown := DebugHook <> 0;

  try
    MyStation := TWeatherStation.Create;

    CurCondDisp := TCurrentConditions.Create(MyStation);
    ForecastDisp := TForecastConditions.Create(MyStation);
    HeatIndexDisp := THeatIndex.Create(MyStation);

    MyStation.SetMeasurements;

    Writeln('Let''s boot the Forecast Display and try again.');
    MyStation.RemoveObserver(ForecastDisp);

    MyStation.NotifyObservers;

    WriteLn('Press Enter to Exit.');
    Readln;

  finally
    HeatIndexDisp.Free;
    ForecastDisp.Free;
    CurCondDisp.Free;
    MyStation.Free;
  end;

end.

and, we’re done :)

Any comments you may have on either the code I’ve written as my interpretation of the Java code supplied in the book or the information I’ve provided would be most appreciated and well accepted.

Thanks for stopping by [and here’s hoping I got it close to right] ...
Dave


Read the whole article ...

Friday, 18 September, 2009

The Strategy Design Pattern in Delphi

A summary and [my rendition of] the Delphi code taken from the first chapter of the book, Head First Design Patterns from O’Reilly.

As I mentioned in a previous post, I’m studying Design Patterns using the Head First Design Patterns book from O’Reilly.   As part of this learning process I’m working through the existing examples written in Java and recreating them in Delphi.  This is the second post in my series and the first that actually deals with one of the design patterns.  This first post in the series provides a list of additional resources on Design Patterns in Delphi that I’ve managed to track down.  I must mention again that I don’t plan on teaching you design patterns, as that would be quite presumptuous of me considering that I’m just a student of them myself.  I do intend to provide simply an overview of what I’ve learned and the resulting code I produced in the process.


The Strategy Pattern ... is useful for situations where it is necessary to dynamically swap the algorithms used in an application.  The strategy pattern is intended to provide a means to define a family of algorithms, encapsulate each one as an object, and make them interchangeable.  The strategy pattern lets the algorithms vary independently from clients that use them.
-- Wikipedia

Chapter Summary

The Strategy Pattern we eventually get to, is demonstrated using “a highly successful duck pond simulation game, SimUDuck” that proves difficult to evolve due to the use of basic OO design principles where all Duck types are inherited from a single super-class.

The difficulty in evolving the software became apparent when a new “fly” method was added to the super-class which was deemed inappropriate when applied to some of the derived objects.  For the sake of this demonstration we’ll just ignore that fact that anyone that has given a small child a bath well knows that not only do rubber ducks fly, but nine times out of ten they will hit you right on the head in the process.  Moving on ...

The Design Principle:Identify the aspects of your application that vary and separate them from what stays the same” is introduced to move us toward the pattern to be used as a solution. They simplify the principle with – “take the parts that vary and encapsulate them, so that later you can alter or extend the parts that vary without affecting those that don’t”.  They also mention, which I took to be significant, that this principle “forms the basis for almost every design pattern in providing a way to let some part of a system vary independently of all other parts”.  With this in mind it is decided that both the fly() and quack() methods are to be refactored out of the Duck classes and each built into a separate set of classes of it’s own.

Along with the process of refactoring the application to move the selected behaviours into their own set of classes, in the Designing the Duck Behaviours section the [easily overlooked but significant] new requirement to be able to change these behaviours at runtime is also introduced.  Another Design Principle: Program to an interface, not an implementation is introduced as this point as well nudging us in the required direction when declaring these new objects in the Duck super-class.

They move right into the code for the two behaviour classes next ... and I was forced to jump right out of my comfort zone.  They had already gone through redefining these new classes with an TObject based [sorta like] super-class and rejected the idea.  It’s all fairly well explained but it still took me a bit to make the leap to using classes based on an TInterfacedObject super-class. Memories of threads and threads of the perils of interfaces and their over-use kept running through my head ... but I somehow shoved it all aside and used them anyway.  The perils of not actually freeing an object you intentionally created is still giving me the willies –but- in this specific case, it sure makes the whole process a lot less complex as we’ll see.

My rendition of the DuckFly behavior class is as follows:

unit DuckFly;

interface

type
  IFlyBehaviour = interface
    procedure Fly;
  end;

  TFlyWithWings = class(TInterfacedObject, IFlyBehaviour)
  public
    procedure Fly;
  end;

  TFlyWithNoWings = class(TInterfacedObject, IFlyBehaviour)
  public
    procedure Fly;
  end;

  TFlyNoWay = class(TInterfacedObject, IFlyBehaviour)
  public
    procedure Fly;
  end;

implementation

{ TFlyWithWings }
procedure TFlyWithWings.Fly;
begin
  WriteLn('');
  Writeln('FlyWithWings behaviour implemented:');
  Writeln('"Hey, look at me ... flapping my wings and flying."');
end;

{ TFlyNoWay }
procedure TFlyNoWay.Fly;
begin
  WriteLn('');
  Writeln('FlyNoWay behaviour implemented:');
  Writeln('Don''t be silly ... Decoy Ducks can''t fly.');
end;

{ TFlyWithNoWings }
procedure TFlyWithNoWings.Fly;
begin
  WriteLn('');
  Writeln('FlyWithNoWings behaviour implemented:');
  Writeln('"Weeeeee ... look at me - I''m a rocket."');
end;

end.

and the DuckQuack behavior class is:


unit DuckQuack;

interface
type
  IQuackBehaviour = interface
    procedure Quack;
  end;

  TQuack = class(TInterfacedObject, IQuackBehaviour)
  public
    procedure Quack;
  end;

  TSqueak = class(TInterfacedObject, IQuackBehaviour)
  public
    procedure Quack;
  end;

  TMuteQuack = class(TInterfacedObject, IQuackBehaviour)
  public
    procedure Quack;
  end;

implementation

{ TQuack }
procedure TQuack.Quack;
begin
  WriteLn('');
  Writeln('Quack behaviour implemented:');
  Writeln('"Quack quack quack."');
end;

{ TSqueak }
procedure TSqueak.Quack;
begin
  WriteLn('');
  Writeln('Squeak behaviour implemented:');
  Writeln('"Squeak squeak squeak."');
end;

{ TMuteQuack }
procedure TMuteQuack.Quack;
begin
  WriteLn('');
  Writeln('Silent behaviour implemented:');
  Writeln('<< silence >>');
end;

end.

In the next section the Duck class is re-coded.  I opted to take a bit of licence with a some of the instruction but only because I disagreed with what they wanted me to do.  I didn’t bother to refactor the Fly and Quack methods to performFly and performQuack respectively.  If you want ADuck to Fly would you use ADuck.performFly ... no, I didn’t think so.

I’ve coded the two behaviour classes to Properties in the Duck class.  Based on my translation of the Java example it seems to be what they are doing as well.  Using Properties allows me to meet the “set the behaviours at runtime” requirement that was introduced.

unit DuckModel;

interface

uses DuckFly, DuckQuack;

type
  TDuck = class(TObject, IFlyBehaviour, IQuackBehaviour)
  private
    FFlyBehaviour: IFlyBehaviour;
    FQuackBehaviour: IQuackBehaviour;
    procedure SetFlyBehaviour(const Value: IFlyBehaviour);
    procedure SetQuackBehaviour(const Value: IQuackBehaviour);
  public
    constructor Create;
    procedure Display; virtual; abstract;
    procedure Fly;
    procedure Quack;
    procedure Swim;
    property FlyBehaviour: IFlyBehaviour
      read FFlyBehaviour
      write SetFlyBehaviour
      implements IFlyBehaviour;
    property QuackBehaviour: IQuackBehaviour
      read FQuackBehaviour
      write SetQuackBehaviour
      implements IQuackBehaviour;
  end;

  TMallardDuck = class(TDuck)
  public
    constructor Create;
    procedure Display; override;
  end;

  TDecoyDuck = class(TDuck)
  public
    constructor Create;
    procedure Display; override;
  end;

implementation

{TDuck}
constructor TDuck.Create;
begin
  inherited;
  //Set the default behaviours for all ducks.
end;

procedure TDuck.Fly;
begin
  FlyBehaviour.Fly
end;

procedure TDuck.Quack;
begin
  QuackBehaviour.Quack
end;

procedure TDuck.SetFlyBehaviour(const Value: IFlyBehaviour);
begin
  FFlyBehaviour := Value;
end;

procedure TDuck.SetQuackBehaviour(const Value: IQuackBehaviour);
begin
  FQuackBehaviour := Value;
end;

procedure TDuck.Swim;
begin
  WriteLn('');
  Writeln('The duck is swimming.');
end;

{ TMallardDuck }
constructor TMallardDuck.Create;
begin
  inherited;
  FFlyBehaviour := TFlyWithWings.Create;
  FQuackBehaviour := TQuack.Create;
end;

procedure TMallardDuck.Display;
begin
  WriteLn('');
  Writeln('The Mallard duck is displayed.');
end;

{ TDecoyDuck }
constructor TDecoyDuck.Create;
begin
  inherited;
  FFlyBehaviour := TFlyNoWay.Create;
  FQuackBehaviour := TMuteQuack.Create;
end;

procedure TDecoyDuck.Display;
begin
  WriteLn('');
  Writeln('The Decoy duck is displayed.');
end;

end.

The quickest and easiest method to test this type of code I’ve found is simply to use a console application and this is what I’ve done here.

program SimUDuck;

{$APPTYPE CONSOLE}

uses
  DuckModel in 'DuckModel.pas',
  DuckFly in 'DuckFly.pas',
  DuckQuack in 'DuckQuack.pas';

var
  ADuck: TDuck;

begin
  //ReportMemoryLeaksOnShutdown := DebugHook <> 0;
  WriteLn('Duck Display Program run ...');
  Writeln('Starting with the Mallard Duck.');
  try
    ADuck := TMallardDuck.Create;
    ADuck.Display;
    ADuck.Swim;
    ADuck.Fly;
    ADuck.Quack;
  finally
    ADuck.Free;
  end;
  WriteLn('');
  Writeln('Changed to the Decoy Duck.');
  try
    ADuck := TDecoyDuck.Create;
    ADuck.Display;
    ADuck.Swim;
    ADuck.Fly;
    ADuck.Quack;
    WriteLn('');
    WriteLn('[Setting Behaviour at Run Time Demo]');
    WriteLn('Toss the Decoy across the pond.');
    ADuck.FlyBehaviour := TFlyWithNoWings.Create;
    ADuck.Fly;
  finally
    ADuck.Free;
  end;
  // More Ducks and Actions here:
  WriteLn('');
  WriteLn('Duck Display Program end ... Press [Enter] to Exit');
  Readln;
end.

There you have it.  The selected Duck Behaviours have been delegated off to separate classes.  We have knowingly broken the rule of “Program to an interface, not an implementation” in the Create events of the Duck sub-classes but apparently we’ll learn how to fix that later.

Any comments you may have on either the code I’ve written as my interpretation of the Java code supplied in the book or the information I’ve provided would be most appreciated and well accepted.

Thanks for stopping by [and here’s hoping I got it close to right] ...
Dave


Read the whole article ...