 |
BorlandTalk.com Borland discussion newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
BMitchell Guest
|
Posted: Sat Jun 10, 2006 5:41 am Post subject: What now? |
|
|
I know this may be over engineering here but I am trying to learn how to
do something. Basically take two derived classes that could be used in
the same object but not at the same time.
Lets say I have an object called TContact. This object can be used for
any number of entities (person or company). I need the phone number to
work with different countries. But TContact only needs one object for
the phone number but only based on the current country. How do I do
this? OOP seems to be escaping me right now. I have been doing some
reading but can not get the grasp of it so I am trying to put objects
together and see what happens.
Thank you for your help.
TPhone = class(TObjectList)
private
FNumber: string;
FExtension: string;
FPhoneType: TPhoneType;
public
property PhoneNumber: string read FNumber write FNumber;
property PhoneExtension: string read FExtension write FExtension;
property PhoneType: TPhoneType read FPhoneType write FPhoneType;
end;
TUSPhone = class(TPhone)
public
function GetNumber: string; //Just returns the phone number in
the correct format for US
procedure SetNumber(pNumber: string); //checks the phone number
for correct number of digits
end;
TEnglandPhone = class(TPhone)
public
function GetNumber: string; //Just returns the phone number in
the correct format for England
procedure SetNumber(pNumber: string); //checks the phone number
for correct number of digits
end;
TUSAddress = class(TObjectList)
private
FAdressType: TAddressType;
FNumber: string;
FStreetName: string;
FAptNumber: string;
FState: string;
FCity: string;
FCountry: string;
FCounty_Prov: string;
public
//Add getters and setters for address
end;
TContactInfo = class(TObject)
private
FPhone: ?; //This could be either TUSPhone or TEnglandPhone; I
have seen examples where I would just declare it as TPhone, but I don't
understand how that helps me.
FUSAddress: TUSAddress;
public
Q: What procedures and properties would I need here to complete the
contact list?
end; |
|
| Back to top |
|
 |
Yannis Guest
|
Posted: Sat Jun 10, 2006 6:29 am Post subject: Re: What now? |
|
|
BMitchell wrote:
| Quote: | I know this may be over engineering here but I am trying to learn how
to do something. Basically take two derived classes that could be
used in the same object but not at the same time.
Lets say I have an object called TContact. This object can be used
for any number of entities (person or company). I need the phone
number to work with different countries. But TContact only needs one
object for the phone number but only based on the current country.
How do I do this? OOP seems to be escaping me right now. I have been
doing some reading but can not get the grasp of it so I am trying to
put objects together and see what happens.
Thank you for your help.
TPhone = class(TObjectList)
private
FNumber: string;
FExtension: string;
FPhoneType: TPhoneType;
public
property PhoneNumber: string read FNumber write FNumber;
property PhoneExtension: string read FExtension write
FExtension; property PhoneType: TPhoneType read FPhoneType
write FPhoneType; end;
TUSPhone = class(TPhone)
public
function GetNumber: string; //Just returns the phone number in
the correct format for US procedure SetNumber(pNumber: string);
//checks the phone number for correct number of digits end;
TEnglandPhone = class(TPhone)
public
function GetNumber: string; //Just returns the phone number in
the correct format for England procedure SetNumber(pNumber:
string); //checks the phone number for correct number of digits
end;
TUSAddress = class(TObjectList)
private
FAdressType: TAddressType;
FNumber: string;
FStreetName: string;
FAptNumber: string;
FState: string;
FCity: string;
FCountry: string;
FCounty_Prov: string;
public
//Add getters and setters for address
end;
TContactInfo = class(TObject)
private
FPhone: ?; //This could be either TUSPhone or TEnglandPhone; I
have seen examples where I would just declare it as TPhone, but I
don't understand how that helps me. FUSAddress: TUSAddress;
public Q: What procedures and properties would I need here to
complete the contact list?
end;
|
In order to understand how it is helpfull to declare it as TPhone lets
change a litle bit your TPhone declaration.
TPhone = class(TObjectList)
private
FNumber: string;
FExtension: string;
FPhoneType: TPhoneType;
public
property PhoneNumber: string read FNumber write FNumber;
property PhoneExtension: string read FExtension write FExtension;
property PhoneType: TPhoneType read FPhoneType write FPhoneType;
function GetNumber: string; virtual; abstract;
procedure SetNumber(pNumber: string); virtual; abstract;
end;
Now this class has to more methods which are virtuall which meen that
the classies that inherite from this can override this methods to
change the behavior of the class. They are also declared as abstract
which in sort meens that in this class (TPhone) there is no
implementation of this methods. If you try to call them an exception
will be raised.
Lets move on to the two other class that inherit from this one
TUSPhone = class(TPhone)
public
function GetNumber: string; override;
procedure SetNumber(pNumber: string); override;
end;
TEnglandPhone = class(TPhone)
public
function GetNumber: string; override;
procedure SetNumber(pNumber:string);override;
end;
What we have done here is to instruct the compiler that this two
methods will be overriden by the new declarations.
A variable of type TPhone can hold with out a problem any of the three
objects TPhone, TUSPhone, TEnglandPhone;
EG.
Var
Phone1, Phone2, Phone3 : TPhone;
begin
Phone1 := TEnglandPhone.Create(nil);//or whatever the create requires.
Phone2 := TUSPhone.Create(nil);
Phone3 := TPhone.Create(nil);
end;
The above code will compile and execute as needed. The trick part is
what happens when you call the GetNumber and SetNumber methods.
Phone1.Getnumber will actually call the TEnglandPhone.GetNumber method
because in this class you override the method with new code.
Phone2.Getnumber will actually call the TUSPhone.GetNumber because this
is the class used to create the object in the TPhone variable and you
have override the code.
Phone3.GetNumber will raise an exception because TPhone.GetNumber is an
abstract method which meens that it has no code.
As for the rest of your question I will let the experts answer it.
regards
Yannis. |
|
| Back to top |
|
 |
Joanna Carter [TeamB] Guest
|
Posted: Sat Jun 10, 2006 8:11 am Post subject: Re: What now? |
|
|
"BMitchell" <billy.dean.mitchell (AT) gmail (DOT) com> a écrit dans le message de news:
448a1517 (AT) newsgroups (DOT) borland.com...
Yannis has given you the idea of using virtual methods to allow you to treat
Phone numbers correctly.
| TPhone = class(TObjectList)
You would never derive a singular noun like Phone from a list class. You
should always have a separate typesafe list class for every type for which
you need to hold a list.
| TUSAddress = class(TObjectList)
Once again, don't confuse single entities with lists.
Now to look at a couple of other issues.
First, I cannot see why you would need a TPhoneType and then You don't have
to repeat the "Phone" in the property names, otherwise you get code that
looks like :
begin
aPhone.PhoneNumber
...
You know that it is a phone so just use "Number" for the property name.
So your Phone class would look like this :
TPhone = class(TObject)
private
fNumber: string;
fExtension: string;
protected
function GetNumber: string; virtual;
procedure SetNumber(const value: string); virtual;
procedure SetExtension(const value: string); virtual;
public
property Number: string
read GetNumber
write SetNumber;
property Extension: string
read fExtension
write SetExtension;
end;
Then you will need a "metaclass" for our "factory" method
TPhoneClass = class of TPhone;
Then you need a typesafe list class to hold lists of Phone numbers. You
should never derive from TObjectList as this then allows users of your list
class to add anything, not just Phone objects to your list.
TPhoneList = class(TObject)
private
fItems: TObjectList;
public
constructor Create;
procedure Add(item: TPhone);
property Item[idx: Integer]: TPhone
read GetItem; default;
...
end;
You don't need a default property value unless you intend to use these
classes in the IDE designers, but then you would have to derive from
TComponent and I wouldn't recommend having business classes in the IDE
designer, they should be totally UI neutral. Instead of using the default
modifier, which doesn't set the property value outside of the designers, you
should initialise the field in the constructor.
Then you can add the list of numbers to the Contact class, but you really
should add a "Factory" method to create an appropriate Phone for the country
of the Contact :
TContact = class(TObject)
private
fCountry: TCountryCode;
fPhones: TPhoneList;
procedure SetCountry(country: TCountryCode);
function GetPhone(idx: Integer): TPhone;
public
constructor Create;
property Country: TCountryCode
read fCountry
write SetCountry;
property Phone[idx: Integer]: TPhone
read GetPhone;
function AddPhone: TPhone;
procedure RemovePhone(phone: TPhone);
end;
implementation
function TContact.GetPhone(idx: Integer): TPhone;
begin
if (fPhones = nil) then
raise Exception.Create('No phones stored')
if (idx < 0) or (idx >= fPhones.Count) then
raise Exception.Create('Index out of bounds');
result := fPhones[idx];
end;
constructor TContact.Create;
begin
fCountry := ccUS;
end;
function TContact.AddPhone: TPhone;
var
phoneClass: TPhoneClass
begin
case fCountry of
ccUK:
phoneClass := TUKPhone;
ccFrance:
phoneClass := TFrancePhone;
else
phoneClass := TUSPhone;
end;
result := phoneClass.Create;
if fPhones = nil then
fPhones := TPhoneList.Create;
fPhones.Add(result);
end;
procedure TContact.RemovePhone(phone: TPhone);
begin
if fPhones != nil then
begin
fPhones.Remove(phone);
if fPhones.Count = 0 then
begin
fPhones.Free;
fPhones := nil;
end;
end;
end;
Joanna
--
Joanna Carter [TeamB]
Consultant Software Engineer |
|
| Back to top |
|
 |
BMitchell Guest
|
Posted: Sat Jun 10, 2006 8:11 am Post subject: Re: What now? |
|
|
So, the TContact list would just need a new property for the country,
and a constructor that would check which one is selected and set the
field to the correct version. Example:
type
TCountryCode = (ccUS, ccEngland); //Short list
TPhone = class(TObjectList)
private
FNumber: string;
FExtension: string;
FPhoneType: TPhoneType;
public
property PhoneNumber: string read FNumber write FNumber;
property PhoneExtension: string read FExtension write FExtension;
property PhoneType: TPhoneType read FPhoneType write FPhoneType;
function GetNumber: string; virtual; abstract;
procedure SetNumber(pNumber: string); virtual; abstract;
end;
TUSPhone = class(TPhone)
public
function GetNumber: string; override;
procedure SetNumber(pNumber: string); override;
end;
TEnglandPhone = class(TPhone)
public
function GetNumber: string; override;
procedure SetNumber(pNumber:string);override;
end;
TContact = class(TObject)
FCountry: TCountryCode;
FPhone: TPhone;
procedure SetCountryCode(pCountry: TCountryCode);
public
property Country: TCountryCode read FCountry write SetCountryCode
default ccUS;
constructor Create; override;
end;
implementation
constructor TContact.Create;
begin
inherited;
SetCountryCode(pCountry);
end;
procedure TContact.SetCountryCode(pCountry: TCountryCode);
begin
case pCountry of
ccUS: FPhone := TUSPhone.Create;
ccEngland: FPhone := TEnglandPhone.Create;
end; //Case
end; |
|
| Back to top |
|
 |
Jim Cooper Guest
|
Posted: Sat Jun 10, 2006 3:28 pm Post subject: Re: What now? |
|
|
| Quote: | In order to understand how it is helpfull to declare it as TPhone lets
change a litle bit your TPhone declaration.
|
I think we need to change yours a little more too The getters and setters
should not be public like that. Also, if you make FNumber and FExtension
private, you can only refer to them in subclasses if those are declared in the
same unit as this one.
TPhone = class(TObject) // <----- Note change of base class
private
FNumber : string;
FExtension : string;
protected
function GetNumber : string; abstract; virtual;
procedure SetNumber(Value : string); abstract; virtual;
public
property Number : string read GetNumber write SetNumber;
property Extension : string read FExtension write FExtension;
end;
Now inherit from this to create different types of numbers, overriding GetNumber
and SetNumber as appropriate. If there is some default behaviour for most
countries, make them only virtual in the base class, and implement that
behaviour there. I would do that myself so that I could declare descendant phone
classes in other units.
Like Joanna, I had problems with the base class for TPhone, and also the names
of properties. The above example shows normal Delphi naming conventions
Cheers,
Jim Cooper
_____________________________________________
Jim Cooper jcooper (AT) tabdee (DOT) ltd.uk
Skype : jim.cooper
Tabdee Ltd http://www.tabdee.ltd.uk
TurboSync - Connecting Delphi to your Palm
_____________________________________________ |
|
| Back to top |
|
 |
Peter Morris [Droopy eyes Guest
|
Posted: Sat Jun 10, 2006 4:43 pm Post subject: Re: What now? |
|
|
| Quote: | I need the phone number to work with different countries.
|
Okay, I understand this bit.
| Quote: | But TContact only needs one object for the phone number but only based on
the current country.
|
So a contact can only have 1 phone number or more?
--
Pete
====
Audio compression components, DIB graphics controls, ECO extensions,
FastStrings
http://www.droopyeyes.com
My blog
http://blogs.slcdug.org/petermorris/ |
|
| Back to top |
|
 |
Peter Morris [Droopy eyes Guest
|
Posted: Sat Jun 10, 2006 6:57 pm Post subject: Re: What now? |
|
|
| Quote: | Initial implementation might look something like this:
TPhone
private
FNumber : string;
FLocation: integer;
procedure SetNumber(const aNumber : string);
function GetFormattedNumber(const aLocation : integer) : string;
function GetValid: boolean;
public
property Number : string read FNumber write SetNumber;
property FormatedNumber : string read GetFormattedNumber;
property Location : integer read F write F. //todo
property Valid : boolean read GetValid;
end;
|
I was going to suggest something that I have used previously (but for bank
identification numbers instead). You allow a country to record one or more
regex expressions indicating the formats allowed (one for mobile, one for
landline, etc).
--
Pete
====
Audio compression components, DIB graphics controls, ECO extensions,
FastStrings
http://www.droopyeyes.com
My blog
http://blogs.slcdug.org/petermorris/ |
|
| Back to top |
|
 |
Bob Dawson Guest
|
Posted: Sat Jun 10, 2006 7:38 pm Post subject: Re: What now? |
|
|
Having things like TEnglandPhone or TUSPhone is a bad bad idea.
Combinatorial explosion.
Look up the strategy pattern. Basically we're going to remove all
localisation issues from TPhone and move them into a Localization class.
Initial implementation might look something like this:
TPhone
private
FNumber : string;
FLocation: integer;
procedure SetNumber(const aNumber : string);
function GetFormattedNumber(const aLocation : integer) : string;
function GetValid: boolean;
public
property Number : string read FNumber write SetNumber;
property FormatedNumber : string read GetFormattedNumber;
property Location : integer read F write F. //todo
property Valid : boolean read GetValid;
end;
function TPhone.GetFormattedNumber(const aLocation : integer): string;
var
localizer : TPhoneLocalizer;
begin
localizer := TPhoneLocalizer.Create(FLocation);
try
Result := localizer.Format(FNumber);
finally
localizer.Free;
end;
end;
procedure TPhone.SetNumber(const aNumber : string);
var
localizer : TPhoneLocalizer;
begin
localizer := TPhoneLocalizer.Create(FLocation);
try
FNumber := localizer.StripFormattingIfPresent(FNumber);
finally
formatter.Free;
end;
end;
function GetValid : boolean;
var
localizer : TPhoneLocalizer;
begin
localizer := TPhoneLocalizer.Create(FLocation);
try
Result := localizer.IsValid(FNumber);
finally
formatter.Free;
end;
end;
bobD |
|
| Back to top |
|
 |
Joanna Carter [TeamB] Guest
|
Posted: Sat Jun 10, 2006 8:21 pm Post subject: Re: What now? |
|
|
"Bob Dawson" <RBDawson (AT) prodigy (DOT) net> a écrit dans le message de news:
448acb40$1 (AT) newsgroups (DOT) borland.com...
| Having things like TEnglandPhone or TUSPhone is a bad bad idea.
| Combinatorial explosion.
|
| Look up the strategy pattern. Basically we're going to remove all
| localisation issues from TPhone and move them into a Localization class.
| Initial implementation might look something like this:
I would totally agree with you Bob, my simple example was only to further
demonstrate the principle of polymorphism to the OP; sufficient for a small,
limited test program. Your use of the Strategy pattern has to be the only
real way to go for a full blown commercial app.
Joanna
--
Joanna Carter [TeamB]
Consultant Software Engineer |
|
| Back to top |
|
 |
Yannis Guest
|
Posted: Sun Jun 11, 2006 7:00 am Post subject: Re: What now? |
|
|
Jim Cooper wrote:
| Quote: |
In order to understand how it is helpfull to declare it as TPhone
lets change a litle bit your TPhone declaration.
I think we need to change yours a little more too The getters and
setters should not be public like that. Also, if you make FNumber and
FExtension private, you can only refer to them in subclasses if those
are declared in the same unit as this one.
TPhone = class(TObject) // <----- Note change of base class
private
FNumber : string;
FExtension : string;
protected
function GetNumber : string; abstract; virtual;
procedure SetNumber(Value : string); abstract; virtual;
public
property Number : string read GetNumber write SetNumber;
property Extension : string read FExtension write FExtension;
end;
Now inherit from this to create different types of numbers,
overriding GetNumber and SetNumber as appropriate. If there is some
default behaviour for most countries, make them only virtual in the
base class, and implement that behaviour there. I would do that
myself so that I could declare descendant phone classes in other
units.
Like Joanna, I had problems with the base class for TPhone, and also
the names of properties. The above example shows normal Delphi naming
conventions
Cheers,
Jim Cooper
|
If you take a closer look you will see that I haven't used this methods
as getters or setters for any properties. I have tried to keep it as
simple as possible in order to show why one would want to declare a
variable as a base class and not the actuall class used.
As far as I remember your example will have problems compiling
something about not be able to use virtuall methods for getters and
setters if I recall correctly (haven't done any object programming for
the past two years so I might be wrong). This I have solved with an
extra layer (changeValue and readValue ) which are virtual and are
called from the get, set methods of the object.
I am not an expert on the field so everything you read here is based on
trial and error experience
Regards
Yannis.
-- |
|
| Back to top |
|
 |
Peter Morris [Droopy eyes Guest
|
Posted: Sun Jun 11, 2006 8:11 am Post subject: Re: What now? |
|
|
| Quote: | Your use of the Strategy pattern has to be the only
real way to go for a full blown commercial app.
|
Not just for validating phone numbers. My RegEx approach also worked very
well in a real application. I think a strategy for something this simple is
overkill and inflexible, you'd have to update the application every time new
phone number formats came out. |
|
| Back to top |
|
 |
Jim Cooper Guest
|
Posted: Sun Jun 11, 2006 2:30 pm Post subject: Re: What now? |
|
|
| Quote: | If you take a closer look you will see that I haven't used this methods
as getters or setters for any properties.
|
I did see that, and it was one of the changes I deliberately made. At the very
least your approach was violating naming conventions.
| Quote: | As far as I remember your example will have problems compiling
something about not be able to use virtuall methods for getters and
setters if I recall correctly
|
There's no problem using virtual methods as I did.
Cheers,
Jim Cooper
_____________________________________________
Jim Cooper jcooper (AT) tabdee (DOT) ltd.uk
Skype : jim.cooper
Tabdee Ltd http://www.tabdee.ltd.uk
TurboSync - Connecting Delphi to your Palm
_____________________________________________ |
|
| Back to top |
|
 |
CheGueVerra Guest
|
Posted: Sun Jun 11, 2006 7:16 pm Post subject: Re: What now? |
|
|
| Quote: | Not just for validating phone numbers. My RegEx approach also worked very
well in a real application. I think a strategy for something this simple is
overkill and inflexible, you'd have to update the application every time new
phone number formats came out.
|
Wouldn't you have to put out a new application if a new Localisation
arrives?
From this code:
[snip Bob dawson's reply]
function TPhone.GetFormattedNumber(const aLocation : integer): string;
var
localizer : TPhoneLocalizer;
begin
localizer := TPhoneLocalizer.Create(FLocation);
try
Result := localizer.Format(FNumber);
finally
localizer.Free;
end;
end;
[/snip]
If a new element is added to the TPhoneLocalizer class, don't you have
to update your clients with a new something? If the architect has put
this in a dll, then the new dll s rewuired, but if they haven't (like
our current appication 8-< ) then a new exe will be required for every
client....
CheGueVerra |
|
| Back to top |
|
 |
Peter Morris [Droopy eyes Guest
|
Posted: Sun Jun 11, 2006 11:04 pm Post subject: Re: What now? |
|
|
| Quote: | Wouldn't you have to put out a new application if a new Localisation
arrives?
|
Not for my approach, mine was data-driven.
Country *---* PhoneFormat
PhoneFormat:
Name: string;
RegExPattern: string;
The users can add as many new "valid" patterns as they like
Mobile phone / (pattern)
Land line / (pattern)
Pager / (pattern)
and so on. If the number scheme changes in any way they can just add a new
one.
--
Pete
====
Audio compression components, DIB graphics controls, ECO extensions,
FastStrings
http://www.droopyeyes.com
My blog
http://blogs.slcdug.org/petermorris/ |
|
| Back to top |
|
 |
Bob Dawson Guest
|
Posted: Mon Jun 12, 2006 8:11 am Post subject: Re: What now? |
|
|
"Peter Morris wrote
| Quote: |
[...] My RegEx approach
|
.... is perfectly compatible with the strategy approach. There's no reason
why the phone localzer class shouldn't work using regex or other persistent
transform templates, or that addng a new localizer instance shouldn't be
just a matter of adding a row to a table.
Our difference, FWIW, is that I think it a mistake to add phone localization
to the country class. That road leads to a large country class with little
cohesion--just an array of miscellaneous services: postal code formatting,
address formatting, currency conversion, local holiday lists, line voltages
and frequency, etc., etc.
bobD |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|