As promised, we’ll continue our review of scoping in Visual FoxPro with a look at how scope affects object properties (and methods). I find it makes things easier to think of properties as “variables that are scoped to an object”. In other words, as long as an object exists, so do its properties. As soon as the object is released, its properties (and their values) are gone too. In this sense, at least, the analogy to memory variables and their scoping (Public, Private and Local) holds up. However, despite first appearances, all properties are not created equal.
VFP is a little unusual among object oriented languages in that all of the properties, events and methods (PEMs) of its classes are created as PUBLIC by default. It is important to realize that, in the context of an object, the word ‘public’ refers to the visibility of a property (or method) to other objects. In fact, properly speaking, the set of PEMs that an object exposes to the outside world is its “Public Interface”. In many object oriented languages native properties, events and methods are not exposed in the public interface and it is up to the developer to make provision for those elements that need to be exposed. VFP takes the exact opposite approach. Unless you, the developer, specifically decide otherwise, all PEMs whether native or custom, are exposed as part of the public interface of an object and so are said to be PUBLIC in scope.
This, of course, is generally a good thing in an application development tool like Visual FoxPro. Imagine what a pain it would be if every time you wanted to refer to the property of an object like a textbox (e.g. Value, Background Color, Height, Width, Font) from some other object you had either to have specifically defined that property as “public” first or had to write the required code into a special method on the object in question. This is, however, exactly how things work in many other object oriented languages. Look at properties defined in C# (or in a COM interface) and you will find that they have two methods associated with them – a “PUT” method to assign a value, and a “GET” method to return the value. Contrast that with the ease of creating and working with properties in VFP!
However, there are times when you may not want a specific property (or method) to be visible to objects outside. Why? Either because the property is essential to the inner workings of your object and you do not want it changed except under very specific and controlled circumstances or because it is a method that should only ever be called as part of, or on completion of, some other operation. For this reason VFP implements two additional key words that can be used to define the scope of PEMs.
· PROTECTED Protected PEMs are only accessible to objects that are instantiated from a class that actually defines the PEM, or from a class that inherits from the class that defined it. Since Visual FoxPro does not implement multiple inheritances, we can shorten the definition to: “visible only within the originating class and its sub-classes”.
· HIDDEN Hidden PEMs are only accessible to objects that are instantiated directly from the class that actually defines the PEM. In other words they are: “visible only within the originating class”
You may be wondering, at this point, what happens if you try and access a PEM that is either protected or hidden. The answer is simple, you get an error. Paste the following into a PRG and run it:
oTest = CREATEOBJECT( 'xObj' )
CLEAR
?
? "The next line will cause an Error - hit ignore"
? "Access Property directly to see the value: " + oTest.cProperty
?
? "Call Get Method and see the value: " + oTest.GetProperty()
DEFINE CLASS xObj AS Session
cProperty = 'This is not visible'
PROTECTED cProperty
FUNCTION GetProperty()
RETURN This.cProperty
ENDFUNC
ENDDEFINE
As you can see, by declaring the property protected we get an “Property <name> does not exist” error when we try to access it directly from outside. However, when we call the GetProperty() method – which is itself public remember - there is no problem. The method is defined in the class that defines the property and so it can see the property, and return its value to the caller even though the caller cannot actually see the property. Of course the method could do much more than simply return the value. Common uses for a protected property, with an exposed Get() method, like this include handling serial (sequential values), or accumulator (running total) values. Each time the method is called, it generates the next number (or adds some value) to the property and returns the new value. The point is that the ONLY way to change the value from outside of the object is to call the method and that allows us to check that the value only gets changed when it is supposed to be changed. (Contrast this with the ThisForm.Text1.Value = “xxxxx” approach).
The example I gave above shows how a custom property can be defined as PROTECTED (or HIDDEN) when creating the class definition in code. First we declare the property name, and initialize it, then define its scope. (This must be done within the class definition, but outside of any Procedure or Function (method) definitions.
To protect, or hide, a native property which is already declared by default in the class, we only need to include it in the PROTECTED, or HIDDEN, list, like this:
DEFINE CLASS xObj AS session
cProperty = ‘This is not visible’
*** Now protect our custom, and a native, property
PROTECTED cProperty, datasessionid
Methods, too, can be protected or hidden by simply including the appropriate key word as part of the declaration. The following examples define a hidden, and a protected, method in a class:
HIDDEN FUNCTION SetValue( tuInVal )
<code here>
ENDFUNC
PROTECTED PROCEDURE AnotherSet
LPARAMETERS tuInVal
<code here>
ENDPROC
It doesn’t really matter whether you define your methods as functions (although this is my personal preference since methods always return a value and so really are “functions”) or procedures. Nor does it matter if you use implicit parameter definition (again, my personal preference is to do it this way in classes defined in code because the method signature is also the calling prototype) or explicit parameter definition (either local or private).
Note: If you are working with visual classes, then you can define (and change) the scope of PEMs in the “Edit Property/Method” dialog which is accessed from the “Class” pad of the main system menu.
So, as we have seen, defining a PEM as either PROTECTED, or HIDDEN, prevents it from being accessed from outside of the object, so in that sense it doesn’t matter which you use. Similarly if an object is instantiated directly from the class that defines the PEM, then all PEMs are visible to any method in the object whether they are Public, Protected or Hidden. However, as soon as we begin to work with sub classes, the difference becomes significant.
The following code defines a class (“propobj”) with three custom properties, one Public, one Protected and one Hidden. A public “SelfTest()” method simply displays the values of each of the three properties. In addition it has two pairs of methods (a Get and a Set) related to each of the restricted properties.
*** Class that defines some properties
DEFINE CLASS propobj AS Session
cPub = "This is a Public Property"
cPro = "This is a Protected Property"
cHid = "This is a Hidden Property"
PROTECTED cPro
HIDDEN cHid
*** Function To test self
FUNCTION SelfTest
ACTIVATE SCREEN
? This.Name
? This.cPub
? This.cPro
? This.cHid
?
RETURN "Completed Self-Test"
ENDFUNC
*** Get method to return the protected value
FUNCTION GetcPro
RETURN This.cPro
ENDFUNC
*** Set Method to update the protected value
*** Notice that this restricts values to Character Strings
*** Any other data type is simply ignored
FUNCTION SetcPro( tcValue AS String )
IF VARTYPE( tcValue ) = "C"
This.cPro = tcValue
ENDIF
ENDFUNC
*** Get method to return the Hidden value
FUNCTION GetcHid
RETURN This.cHid
ENDFUNC
*** Set Method to update the Hidden value
FUNCTION SetcHid( tcValue AS String )
IF VARTYPE( tcValue ) = "C"
This.cHid = tcValue
ENDIF
ENDFUNC
ENDDEFINE
If you save this code and then instantiate an object based on this class, and call it’s SelfTest() method you will see the name of the object, the contents of the three properties and the return message displayed on screen:
SET PROCEDURE TO demo
oTest = CREATEOBJECT( 'propobj' )
? oTest.SelfTest()
Displays:
This next class defines a sub-class of propobj (named “SonOfProp”)
DEFINE CLASS SonOfProp AS propobj
*** Function To test self
FUNCTION TestSelf
ACTIVATE SCREEN
? This.Name
? This.cPub
? This.cPro
? This.cHid
?
RETURN "Completed TestSelf"
ENDFUNC
ENDDEFINE
If we repeat the above test, substituting the SonOfProp class for PropObj, there is no apparent difference. The sub-class returns exactly the same result as the parent (apart, of course, from the object name)
This should not be too surprising, because the code that is executing actually is that which is defined in the parent class. However, if now call the “TestSelf” method, which is only defined in the sub class (although it has exactly the same code as in the parent class) we do see an immediate difference! In fact, we get an error as soon as we try to access the hidden property (because the code that is executing is not defined in the same class that defined the property) and we are, therefore, missing a line in the result.
If we override the SelfTest method in the sub class (i.e. just re-write the identical code in the sub-class) the result would be the same, an error! However, overriding the method in the subclass with different code, and using a DODEFAULT() works fine because, once again, the code which is to be executed is defined in the same class as the hidden property.
Notice that the GetcHid() and SetcHid() methods work just fine in the sub class. These public methods are defined in the parent class and so can read from, and write to, the hidden property no matter where they are called from (also notice that they really prevent the property from being set to anything but a character string).
The final class in this little demonstration (“norelation”) is not related to the propobj class at all. Instead it defines its own public custom property (“oRef”) and, in its Init() method creates an instance of the “PropObj” class and assigns it to that property. It too has a self-test method that, when called, attempts to access the three properties of our original class. But now we get two errors! The only property that can be accessed directly is the one which was defined as Public:
DEFINE CLASS NoRelation AS Session
oRef = NULL && Public property to hold a reference to the Property Definition Object
FUNCTION Init
This.oRef = CREATEOBJECT( "propobj" )
ENDFUNC
*** Function To test self
FUNCTION SelfTest
ACTIVATE SCREEN
? This.Name
? This.oRef.cPub
? This.oRef.cPro
? This.oRef.cHid
?
RETURN "Completed Self-Test"
ENDFUNC
ENDDEFINE
The result is now missing two lines.
This is, of course, because the class on which the object is based does not inherit from PropObj and so it cannot even access properties that were defined as “Protected”, let alone anything that was actually “Hidden”. However, notice that it can still get access to these properties by calling the appropriate methods. This.oRef.GetcHid() and This.oRef.SetcHid() work just fine, as do the equivalent methods for the protected property.
All very interesting, but is it really useful?
You may, by now, be wondering this. The short answer is that it is extremely useful. I have already mentioned two reasons for protecting properties (sequences, and creating “strongly typed” properties). For methods, one of the biggest benefits I see is that it dramatically reduces the amount of code I need to write. Here’s what I mean:
How often do you include code in your methods to check that a parameter was correctly passed? If you are a defensive programmer like me, the answer is “pretty much every time – and especially when the parameter is saved and re-used elsewhere”. By protecting methods that do not need to be called externally you remove the necessity for checking parameters. After all, if the only place the method can be called from is inside your own code, then you can ensure (in any calling method) that the values to be passed are correct and accept them without checking again inside the method.
Published Friday, May 13, 2005 4:40 PM by andykr
No comments:
Post a Comment