synergy 2007
DESCRIPTION
SYNERGY 2007. Design Patterns and the Object Factory. Peter Bragg, Care Data Systems. Miami l Thursday, May 17th. The Company (Background). Care Data Systems Limited Based in Birmingham, UK Dataflex users since 1985 - PowerPoint PPT PresentationTRANSCRIPT
SYNERGY 2007
Design Patterns and the Object Factory
Miami Thursday, May 17th
Peter Bragg, Care Data Systems
The Company (Background)
• Care Data Systems Limited
• Based in Birmingham, UK
• Dataflex users since 1985
• Employed that handsome, young fellow
Peter Bragg on September 18th 2001
• Senior Product Developer
The Product (Background)
• Donorflex
• Comprehensive system designed to support the ‘Not for Profit’
sector
– donations management (financial control, gift aid, gifts in kind,
merchandise sales)
– fundraising and strategic development (campaign management
& control)
– relationship management (profiling, segmentation, targeting)
– operational support (acknowledgements, communications, action
plans)
– events management (charity events and third party fundraising
and sponsorship)
Typical Clients (Background)
• Healthcare
• Education
• Arts
• Community Social Services
• Overseas Aid
• Disability Services
• Political Reform
• Religious Organisations
• Service Veterans
• Sport
Design Patterns and the Object Factory
The Story Begins…
• January 17th 2007
Design Patterns & the Object Factory
Design Patterns & the Object Factory
• I answered the question so precisely
that no further comments were
required! (Erm…)
• No one is interested in Factories
• No one is using Factories
• Might make an interesting topic?
Design Patterns & the Object Factory
• I am not a Design Pattern Expert
• ..at all!
• Knowledge limited to ‘what I do’
Design Patterns & the Object Factory
Creational Design Patterns and the Object
Factory
…in donorflex
Design Patterns & the Object Factory
• Our Design Need:
One Application (donorflex)
Different backends (Dataflex, MSSQL, ?)
Intelligent code optimisation/execution
Design Patterns & the Object Factory
• Possible design solutions:
If, Else or Case Statements
Deferred creation of ‘Process’ Objects
• The need:
Call a process (e.g. a ‘search’) and
execute code appropriate for the backend.
Design Patterns & the Object Factory
View Object (i.e. Enquiry)
Process Object (i.e. Enquiry Engine)
Procedure OnProcess
If (Backend=Dataflex) …
end
else if (Backend=MSSQL)…
end etc.
End_Procedure
Design Patterns & the Object Factory
View Object (i.e. Enquiry)
Procedure DoRunEnquiry
Handle hoEnquiryEngine
If (Backend=Dataflex) Get Create U_ … to hoEnquiryEngine
else if (Backend=MSSQL) Get Create U_ …….
Send DoProcess of hoEnquiryEngine
send Destroy of hoEnquiryEngine
End_Procedure
Design Patterns & the Object Factory
View Object (i.e. Enquiry)
Factory Object (oFactory)Responsible for creating appropriate Process Object
Procedure DoRunEnquiry
Send DoProcess of (Instance(oFactory(self)))
End_Procedure
So how does it all hang together?
• Write different versions of a (business) process as classes:
cAbstractPowerSearch (defines interface)
cPowerSearch_df is a cAbstractPowerSearch
cPowerSearch_mssql is a cAbstractPowerSearch
• Classes are then registered with a “Factory”:
Design Patterns & the Object Factory
Class cPowerSearchEnq_Factory is a cFactory
Procedure Construct_Object Forward Send Construct_Object
Send RegisterType Factory_Type_Dataflex U_cPowerSearch_DF Send RegisterType Factory_Type_MSSQL U_cPowerSearch_MSSQL End_Procedure
End_Class
So how does it all hang together?
• An instance of the factory is then added to a view
Object oPowerSearchEnq Is A cPowerSearchEnq_FactoryEnd_Object
• A call is then made to an ‘Instance’ of this Factory
So instead of:
Send DoProcess of oPowerSearchEnq
We would code:
Send DoProcess of (Instance(oPowerSearchEnq(self)))
• A ‘singleton’ Factory Controller instructs the Factory which
version of the process to create
Factory Controller ClassEnum_List Define Factory_Type_Dataflex Define Factory_Type_MSSQL Define Factory_Type_Pervasive Define Factory_Type_MySQL Define All_Factory_Types //need to be last – gives number of defined typesEnd_Enum_List
Class cFactoryController is a cObject Procedure Construct_Object String sEngine Forward Send Construct_Object Property Integer piCurrentType Factory_Type_Dataflex // Default type. // Check the application object: Get psFactoryEngine of ghoApplication to sEngine //set during OnCreate of Application object Case Begin Case (sEngine = "mssql") Set piCurrentType to Factory_Type_MSSQL Case Break Case End End_Procedure // Construct_Object
Function CurrentType Returns Integer Function_Return (piCurrentType(Self)) End_FunctionEnd_Class
Factory Controller Object
Global_Variable Handle ghFactoryControllerGet Create of Desktop U_cFactoryController to ghFactoryController
The Factory ClassClass cFactory is a cObject Procedure Construct_Object integer[] iTypeMappings
Forward Send Construct_Object
// Holds the type of the current factory object: Property Integer piInstanceType (CurrentType(ghFactoryController)) // Holds the object instance that this factory object represents: Property Handle phInstance 0 // Initially zero. // Array that holds class identifiers to be constructed dependant on the required type: Property Integer[] piTypeMappings
//initialise piTypeMappings Array to avoid index out of range errors move 0 to iTypeMappings[All_Factory_Types] Set piTypeMappings to iTypeMappings End_Procedure // Construct_Object // Provides a means by which to 'register' what class should be used against a particular type of connection: Procedure RegisterType Integer iType Integer iClass integer[] iTypeMappings Get piTypeMappings to iTypeMappings move iClass to iTypeMappings[iType] Set piTypeMappings to iTypeMappings
End_Procedure
The Factory Class… // Returns the class associated by a previous call to RegisterType: Function TypeClass Integer iType Returns Integer Integer iClass integer[] iTypeMappings Get piTypeMappings to iTypeMappings move iTypeMappings[iType] to iClass // if we don't find a registered class of the type we are looking for, look for and return the native dataflex // factory type.. if (iClass = 0) move iTypeMappings[Factory_Type_Dataflex] to iClass // Support drop through the default // type. // if we still have no registered class, we must give an error if (iClass = 0) Error 4113 ("Programmatic Error - No registered type for factory "+name(self)) Function_Return iClass End_Function // Returns the current 'type' associated with this factory: Function InstanceType Returns Integer Function_Return (piInstanceType(Self)) End_Function // Allows a new type to be set against this object: Procedure Set InstanceType Integer iType Set piInstanceType to iType End_Procedure
The Factory Class… // Returns or creates the correct instance of a type for this factory: Function Instance Returns Handle Handle hObj Integer iType iNewType Get phInstance to hObj Get InstanceType to iType Get CurrentType of ghFactoryController to iNewType // If the type doesn't match what we are currently using then destroy the object and create a new one: If ((iType <> iNewType) And (hObj <> 0)) Begin Send Destroy of hObj Move 0 to hObj End
If (Not(hObj)) Begin Get Create (TypeClass(Self, iNewType)) to hObj Set phInstance to hObj Set InstanceType to iNewType End Function_Return hObj End_Function // Boolean function that tells the caller if the factory instance has been created yet: Function IsCreated Returns Boolean Function_Return (phInstance(self) <> 0) End_Function End_Class
A Simple Example
• User logs in and we look to see if they have any communications to make.• Code we execute will depend on the backend• So we will use a factory for this…
A Simple Example
Use cFactory.pkg
Define the interface:
class cAbstractLoginPrompts is a cObject Function DoFindLoginPrompts string sUser date dDate integer iDays returns integer[] end_function end_class
Or sometimes..
class cAbstractLoginPrompts is a cObject Function DoFindLoginPrompts string sUser date dDate integer iDays returns integer[] Error DFErr_Program “Method not defined for concrete class” end_function end_class
A Simple Example
Write the classes:
class cLoginPrompts_df is a cAbstractLoginPrompts Function DoFindLoginPrompts string sUser date dDate integer iDays returns integer[] integer[3] iRetVal //outstanding/today/future integer iOverdue iToday iFuture open doncomms For_All DONCOMMS BY Index.3 Constrain Doncomms.On_behalf_of EQ sUser Constrain Doncomms.Login_prompt EQ "Y" DO if (doncomms.date < dDate) increment iOverdue else if (doncomms.date = dDate) increment iToday else if (doncomms.date <= (dDate+iDays-1)) increment iFuture End_For_All move iOverdue to iRetVal[0] move iToday to iRetVal[1] move iFuture to iRetVal[2] function_return iRetVal end_function end_class
A Simple Example class cLoginPrompts_sql is a cAbstractLoginPrompts Function DoFindLoginPrompts string sUser date dDate integer iDays returns integer[] integer[3] iRetVal //outstanding/today/future integer iFetchResult iCount iData handle hSQLInstance hConn hSQL string sSQL sDate sDateFuture sBase boolean bNoMoreResultSets move (Instance(ghSQLConnectionFactory)) to hSQLInstance
move ("Select count(*) from doncomms where doncomms.On_behalf_of = '"+sUser+"' and doncomms.Entity = 'R' and doncomms.Login_prompt = 'Y' and ") to sBase
Get DateToSQL of hSQLInstance dDate to sDate move (sSQL+sBase+"doncomms.Date < '"+sDate+"';") to sSQL move (sSQL+sBase+"doncomms.Date = '"+sDate+"';") to sSQL Get DateToSQL of hSQLInstance (dDate+1) to sDate Get DateToSQL of hSQLInstance (dDate+iDays-1) to sDateFuture move (sSQL+sBase+"doncomms.date between '"+sDate+"' and '"+sDateFuture+"';") to sSQL // Open Connection Get Connection of hSQLInstance to hConn Get SQLOpen of hConn to hSQL // Send the Statement .. Send SQLExecDirect of hSQL sSQL move False to bNoMoreResultSets
A Simple Example Repeat Get SQLFetch Of hSQL To iFetchResult If (iFetchResult <> 0) begin move (trim(SQLColumnValue(hSQL,1))) to iData increment iCount move iData to iRetVal[iCount-1] end Get SQLNextResultSet of hSQL to iFetchResult if (not(iFetchResult)) move True to bNoMoreResultSets Until (bNoMoreResultSets) // Release the Statement: Send SQLClose of hSQL function_return iRetVal end_function end_class
A Simple Example
Write a factory class and register both classes:
class cLoginPrompts_Factory is a cFactory
Procedure Construct_Object Forward Send Construct_Object
// Register the default class that will be used as a drop through if other types aren't // registered: Send RegisterType Factory_Type_Dataflex U_cLoginPrompts_df Send RegisterType Factory_Type_MSSQL U_cLoginPrompts_sql End_Procedure end_Class
Create an Object instance of the Factory
Object oLoginPromptsFactory is a cLoginPrompts_FactoryEnd_object
A Simple Example
Then call the function and let the factory do the rest
Integer[] iRetVal
Get DoFindLoginPrompts of (instance(oLoginPromptsFactory(Self))) sUser dDate iDays to iRetVal
Under the hood:
• The function “Instance” asks “have I already created an object for you?”• If so, it simply returns a handle to the object it created earlier• If not:
It looks to see what the “Factory Controller” says it should be creating It creates an object of the required class It sets a property with the handle of the object It returns a handle to the object “DoFindLoginPrompts” is then executed
Another Example:
How it works…
Use dnTreeView.pkgUse cProfileFactory.pkg
class cProfileTree is a dnTreeView
Import_Class_Protocol Server_Mixin Procedure Construct_Object forward send Construct_Object Property Integer piPrivate.CurrentProfile 0 Property Integer piPrivate.HelpArray_ID 0 Property Handle phProfileTreeFactory (Create(self,U_cProfileTree_Factory)) Send Define_Server // Set tree properties: Set TreeLinesState to False Set TreeRootLinesState to False Object PopupMenu is a cProfilePopUpMenu Delegate Set PopupMenu_ID to Self End_Object end_procedure
How it works…
Design Patterns & the Object Factory
• Using this ‘Factory’ approach as a model:
“The need to know” - objects only get involved
with what they need to. Views simply make a call
to a common interface and don’t care about what
lies behind this.
Scalability – new classes can be written and
registered with a Factory very easily.
Easy to code and implement new factory
instances.
SYNERGY 2007
Thank You
Miami Thursday, May 17th