Decorating With This Access(Updated: 2006.12.05 10:40:30 AM)
|
Category 3 Star Topics
*=====================================
DEFINE CLASS Decorator AS Relation
*=====================================
oDecorated= .NULL.
FUNCTION INIT( toDecorated)
THIS.oDecorated = toDecorated
FUNCTION THIS_ACCESS( tcMember)
IF PEMSTATUS(THIS,UPPER(tcMember),5)
RETURN THIS
ELSE
RETURN THIS.oDecorated
ENDIF
ENDFUNC
FUNCTION DESTROY()
This.oDecorated = .null.
ENDDEFINE
Category VFP Bugs since this is broken in VFP 7 and VFP 8, including VFP 8 SP1. See __ Decorating With This Access. -- Steven Black
All this now works in VFP 9.
"One of the difficulties of managing a decorator is maintaining its interface in sync with the objects to be decorated." -- Steven Black
*===================================== DEFINE CLASS AbstractFrog AS CUSTOM *===================================== cBestFriend = '' cColor = '' DIMENSION aFriends[2] FUNCTION Jump(nValue) FUNCTION Eat() ENDDEFINE
*===================================== DEFINE CLASS ConcreteFrog AS AbstractFrog *===================================== FUNCTION Jump(nValue) Wait Window "Frog Jumping "+ALLTRIM(STR(nValue))+" feet." FUNCTION Eat() Wait Window "Frog Eating" ENDDEFINE
*===================================== DEFINE CLASS DecoFrog AS AbstractFrog *===================================== oRealFrog = .NULL. FUNCTION INIT( oFrog) THIS.oRealFrog = oFrog FUNCTION DESTROY() This.oRealFrog = .null. ENDDEFINE
DecoFrog class might look like this in the end.*===================================== DEFINE CLASS DecoFrog AS AbstractFrog *===================================== oRealFrog= .NULL. FUNCTION INIT( oFrog) THIS.oRealFrog = oFrog FUNCTION Jump(n) THIS.oRealFrog.Jump(n) FUNCTION Eat() THIS.oRealFrog.Eat() FUNCTION DESTROY() This.oRealFrog = .null. ENDDEFINE
_ACCESS and _ASSIGN methods in the DecoFrog class, or by implementing "get" and "set" methods in the Frog class for each property, and rigging the requisite pass-through functions in the DecoFrog class as I have done here with Jump and Eat.THIS_ACCESS.A THIS_ACCESS method is created in code within a DEFINE CLASS command, or in the New Method or Edit Properties dialog boxes for .vcx visual class libraries. A THIS_ACCESS method must always return an object reference, otherwise an error is generated. The THIS object reference is typically returned. A THIS_ACCESS method must also include a parameter to accept the name of the member of the object that is changed or queried.Given this, and an educated guess about how
THIS_ACCESS works, the following alternative decorator seems like a reasonably cheap lunch.
*===================================== DEFINE CLASS LC_DecoFrog AS CUSTOM *===================================== oRealFrog= .NULL. FUNCTION INIT( oFrog) THIS.oRealFrog = oFrog FUNCTION THIS_ACCESS( tcMember) IF PEMSTATUS(THIS,UPPER(tcMember),5) RETURN THIS ELSE RETURN THIS.oRealFrog ENDIF ENDFUNC FUNCTION DESTROY() This.oRealFrog = .null. ENDDEFINE |
Or, more generally:*===================================== DEFINE CLASS Decorator AS CUSTOM *===================================== oDecorated= .NULL. FUNCTION INIT( toDecorated) THIS.oDecorated = toDecorated FUNCTION THIS_ACCESS( tcMember) IF PEMSTATUS(THIS,UPPER(tcMember),5) RETURN THIS ELSE RETURN THIS.oDecorated ENDIF ENDFUNC FUNCTION DESTROY() This.oDecorated = .null. ENDDEFINE Thus, one can wrap pretty much anything. CUSTOM might not be the best baseclass, RELATION might be better, whatever, build to suit. - ?lc |
First, note the LC_DecoFrog is not based on AbstractFrog. But, THIS_ACCESS will pipe any requests for members not defined in LC_DecoFrog over to the wrapped object. Essentially, the THIS_ACCESS function is doing all the wrapping for us at the cost of a PEMSTATUS() each time the object is hit (thus the lunch is not quite free).
Let's extend a bit, to make this worthwhile:
*===================================== DEFINE CLASS DancingFrog AS LC_DecoFrog *===================================== FUNCTION Dance() This.Jump(+2) This.Jump(-2) This.Jump(+3) ENDDEFINE
oFrog = CREATEOBJECT("ConcreteFrog")
oDancingFrog = CREATE("DancingFrog",oFrog)
oDancingFrog.cColor = "GREEN"
oDancingFrog.Jump(5)
oDancingFrog.Eat()
oDancingFrog.aFriends[1] = "ELMO"
oDancingFrog.aFriends[2] = "GONZO"
oDancingFrog.cBestFriend = "MISS PIGGY"
oDancingFrog.Dance()
This got redirected to the textbox, etc. Once I was done fiddling, I realized that my approach was not too well thought out.Dance method above? Jump is a member of oRealFrog, but the DancingFrog class gets to refer to it via THIS.JUMP , what you can't do is have a Jump() method on the decorator and expect to get to the wrapped class's Jump() unless you get explicit with this.oRealFrog.Jump() Ahh.. maybe that's what you mean. {s} In this case, the explicit call to This.oRealFrog.Jump() would sort of take the place of a DODEFAULT() call in a regular subclass. -DEFINE CLASS RecordDecorator AS Session
DataSession = 1 && Default
oRecord = .NULL.
PROCEDURE Scatter
SCATTER NAME THIS.oRecord
ENDPROC
PROCEDURE Gather
GATHER NAME THIS.oRecord
ENDPROC
FUNCTION THIS_ACCESS( tcMember)
IF PEMSTATUS(THIS,UPPER(tcMember),5)
RETURN THIS
ELSE
RETURN THIS.oRecord
ENDIF
ENDFUNC
FUNCTION DESTROY()
This.oRecord = .null.
ENDFUNC
ENDDEFINE
New development: Apparently and object reference in an Array stores the value for "THIS" and doesn't call THIS_ACCESS when trying to dereference the object. For example, using the RecordDecorator above:
USE myTable && Assume this has a MEMO field called MemoField
oRec = CREATEOBJECT('RecordDecorator')
oRec.Scatter
WAIT WINDOW oRec.MemoField && This works fine!
DIMENSION myArr[3]
myArr[1] = CREATEOBJECT('RecordDecorator')
myArr[1].Scatter
WAIT WINDOW myArr[1].MemoField && This fails with "Property 'MemoField' is not found"
oRec2 = myArr[1]
WAIT WINDOW oRec2.MemoField && This works fine!
I just ran into this again: Very Irritating! THIS_ACCESS is not called when Dereferencing the object in the array.
*=====================================
DEFINE CLASS Decorator AS Relation
*=====================================
oDecorated[1]= .NULL.
nDeco=0
c_accessed="" && i added this just in case you need to know wich class was accessed last :)
*-
FUNCTION INIT( toDecorated)
THIS.oDecorated[1] = toDecorated
THIS.nDeco=1
ENDFUNC
*-
FUNCTION Add_Decorated( toDecorated)
THIS.nDeco=THIS.nDeco+1
DIMENSION THIS.oDecorated[ THIS.nDeco]
THIS.oDecorated[ THIS.nDeco]= toDecorated
ENDFUNC
*-
FUNCTION THIS_ACCESS( tcMember)
IF PEMSTATUS(THIS,UPPER(tcMember),5)
THIS.c_accessed = THIS.class
RETURN THIS
ELSE
LOCAL i, ox
FOR i=1 TO THIS.nDeco
ox=THIS.oDecorated[ i] && there are some isues with arrays ref
IF PEMSTATUS(ox,UPPER(tcMember),5)
THIS.c_accessed = THIS.oDecorated.class
RETURN THIS.oDecorated
ENDIF
ENDIF
ENDFUNC
*-
FUNCTION DESTROY()
LOCAL i
FOR i=1 TO THIS.nDeco
This.oDecorated[ i] = .null.
ENDFOR
ENDFUNC
ENDDEFINE
oCritter = CreateObject("Decorator",CreateObject("piggy"))
oCritter.Add_Decorated("ugly_duck")
oCritter.fly() && Hey look! Pigs can fly :)
*-
oCritter.Add_Decorated("DancingFrog")
oCritter.Dance() && i don't know how on earth can it fly and dance at the same time, but look at him ;)
Good question. Personally, I keep this in my "quick and dirty" tool box. If I identify that a decorator pattern is likely to be the best solution to a given problem, I'll often take this approach as a first pass at getting tests to pass. Then, later on, I may refactor into a more traditional approach to gain some performance and avoid some of the minor debugging hassles that come with THIS_ACCESS approaches. Mainly, it really helps cut down on the interface maintenance while development progresses. - ?lc
FUNCTION THIS_ACCESS
LPARAMETERS cMember
LOCAL loDecorated
loDecorated = THIS
DO WHILE NOT ISNULL(loDecorated)
IF PEMSTATUS(loDecorated, UPPER(m.cMember), 5)
RETURN loDecorated
ELSE
loDecorated = loDecorated.oDecorated
ENDIF
ENDDO
RETURN THIS
ENDFUNC