Copy link to clipboard
Copied
I just finished reading a book on introducing Object Oriented Programming (OOP) in ColdFusion. I found the book to be very enlightening on many levels however I was left with some questions and there didn't seem to be anywhere to go after the book to learn more advanced topics or to seek clarification or more information.
I may be creating a few questions on this forum over the next few days/weeks with OOP related subjects and ColdFusion that I would love your feedback and opinions on - especially from people that have experience working with OOP.
My questions pertain to the use of bean objects when working with a single record of data. For sake of these questions lets work with a simple "User" object that contains information about a person like firstName, lastName, emailAddress, and a passwordHash.
Question 1: What is the purpose of using the <cfproperty> tag? Is it only for introspection purposes when you run <cfdump> on the component instance or does it actually have an impact on the component and the data within it? In fact, the only time you can see the properties when you dump the component is if it has a matching getter method.
Question 2: Why is it so important to create public getters/setters within a bean object when it would be easier to just access the properties of the bean directly?
In order to clarify what I mean, I created two components below, user.cfc and user2.cfc. User.cfc is a bean object that has getters/setters for each <cfproperty> (note: I left some getters/setters out to keep this example brief and please excuse the indentation as I can't seem to get the forum editor to display my code very well):
<cfcomponent
displayname="user"
hint="I am the user class"
><cfproperty name="firstName" type="string" default="" />
<cfproperty name="lastName" type="string" default="" />
<cfproperty name="emailAddress" type="string" default="" />
<cfproperty name="passwordHash" type="string" default="" />
<cfset variables.instance = structNew() />
<!--- Constructor --->
<cffunction name="init"
access="public"
hint="I am the constructor method"
><cfargument name="firstName" required="false" type="string" hint="I'm the first name of the user" />
<cfargument name="lastName" required="false" type="string" hint="I'm the last name of the user" />
<cfargument name="emailAddress" required="false" type="string" hint="I'm the email of the user" />
<cfargument name="passwordHash" required="false" type="string" hint="I'm the hashed password for the user" />
<cfscript>
variables.instance.firstName = arguments.firstName;
variables.instance.lastName = arguments.lastName;
variables.instance.emailAddress = arguments.emailAddress;
variables.instance.passwordHash = arguments.passwordHash;
</cfscript>
<cfreturn this />
</cffunction>
<!--- SETTER: FirstName --->
<cffunction name="setFirstName"><cfargument name="firstName" required="true" type="string">
<cfset variables.instance.firstName = arguments.firstName />
</cffunction>
<!--- GETTER: FirstName --->
<cffunction name="getFirstName"><cfreturn variables.instance.firstName />
</cffunction>
<!--- Memento returns the instance data --->
<cffunction name="getMemento"><cfreturn variables.instance />
</cffunction>
</cfcomponent>
The following code instantiates the component, dumps it, and then updates the firstName of the user object. Finally it dumps the data in the object.
<!--- instantiate our component --->
<cfset user = createObject("component", "cfc.user").init(
firstName = "John",
lastName = "Smith",
emailAddress = "test@test.com",
passwordHash = "xxxx"
)/>
<!--- Dump the component for introspection --->
<cfdump var="#user#" /><!--- dump our instance --->
<cfdump var="#user.getMemento()#" label="default instance" /><!--- change the firstName --->
<cfset user.setFirstName("Jane") /><!--- dump our instance again --->
<cfdump var="#user.getMemento()#" label="updated instance" /><!--- dump the first name --->
<cfdump var="#user.getFirstName()#" />
This gives us the following output:
In the second example, users2.cfc, I'm creating a much simpler CFC where the properties are stored in the this scope to allow direct access to the variables without the need for getter/setter methods.
<cfcomponent
displayname="user"
hint="I am the user class"
><cfproperty name="firstName" type="string" default="" />
<cfproperty name="lastName" type="string" default="" />
<cfproperty name="emailAddress" type="string" default="" />
<cfproperty name="passwordHash" type="string" default="" />
<cfset this.instance = structNew() />
<!--- Constructor --->
<cffunction name="init"
access="public"
hint="I am the constructor method"
><cfargument name="firstName" required="false" type="string" hint="I'm the first name of the user" />
<cfargument name="lastName" required="false" type="string" hint="I'm the last name of the user" />
<cfargument name="emailAddress" required="false" type="string" hint="I'm the email of the user" />
<cfargument name="passwordHash" required="false" type="string" hint="I'm the hashed password for the user" />
<cfscript>
this.instance.firstName = arguments.firstName;
this.instance.lastName = arguments.lastName;
this.instance.emailAddress = arguments.emailAddress;
this.instance.passwordHash = arguments.passwordHash;
</cfscript>
<cfreturn this />
</cffunction>
<!--- Memento returns the instance data
<cffunction name="getMemento">
<cfreturn variables.instance />
</cffunction>--->
</cfcomponent>
I then instantiate the component just like the first example however I can then update the properties of the user object by directly getting and setting them without any special methods:
<!--- instantiate our component --->
<cfset user2 = createObject("component", "cfc.user2").init(
firstName = "John",
lastName = "Smith",
emailAddress = "test@test.com",
passwordHash = "xxxx"
)/>
<!--- dump the component for introspection --->
<cfdump var="#user2#" /><!--- dump our instance --->
<cfdump var="#user2.instance#" label="default instance" /><!--- change the firstName --->
<cfset user2.instance.firstName = "Jane" /><!--- dump our instance again --->
<cfdump var="#user2.instance#" label="updated instance" /><!--- dump the first name --->
<cfdump var="#user2.instance.firstName#" />
This produces the following output:
So why do we need to go to all the extra effort of creating getter/setter methods in bean objects when we can access the properties directly as if it were a normal structure? Other than for introspection reasons I can't figure out why beans need to be built this way?
Copy link to clipboard
Copied
ColdFusion can create default getters and setters for you using the accessors="true" property of the component tag and/or the getter="true" setter="true" properties of the individual property tags.
I believe the getter/setter layer is available so you can override them for data massage or formatting or other business logic purposes. Consider the following updated setter from your first example:
<cffunction name="setFirstName">
<cfargument name="firstName" required="true" type="string">
<cfif NOT isDefined("variables.instance.firstName") or compare(variables.instance.firstName,arguments.firstName)>
<cfset variables.instance.firstName = arguments.firstName />
<cfset variables.instance.dirty = true />
</cfif>
</cffunction>
Now you can base logic on whether or not changes have been made to a bean.
Copy link to clipboard
Copied
Thanks for the reply Steve.
That is true I could use the <cfcomponent> "accessors" property and set it to true. However something feels unsettling to me by referencing values from my bean using method calls instead of variables.
<cfoutput>#myBean.value#</cfoutput>
vs
<cfoutput>#myBean.getValue()#</cfoutput>
Perhaps my wariness comes from lack of experience using bean objects and it is just something I need to get used to.
I really like your idea of having a property for "dirty" which would basically detect whether a bean has been modified from the original constructor. Of course this means all of my setters would need to be hard coded into the bean which would require a ton of methods for data with many columns.
Copy link to clipboard
Copied
RE: value vs. getValue()
I have the same concerns but really I'm fairly new at incorporating beans in my code so I have not experimented enough to know if accessing getValue() and setValue() is a requirement, or just the way the samples I found did it. My experience with other languages is that you could simply access value and the getters and setters would automatically be called accordingly.
Copy link to clipboard
Copied
Cfproperty is not intended for general use in a CFC. It is designed for use in a CFC that has Object-Relational-Mapping or Web-service functionality. In fact, Adobe's cfproperty documentation says, "If a component is not used as a web service, <cfproperty> only provides metadata information of the property. It does not define variables or set values that you can use in your component. However, it creates implicit setters and getters for the property in the CFC depending on whether getter/setter attributes are enabled".
Question 1: What is the purpose of using the <cfproperty> tag? Is it only for introspection purposes when you run <cfdump> on the component instance or does it actually have an impact on the component and the data within it? In fact, the only time you can see the properties when you dump the component is if it has a matching getter method.
There is, in this example, little purpose to use cfproperty. Their only use here is when you set accessors to true. In so doing, you instruct ColdFusion to automatically generate the getters and setters for you.
Question 2: Why is it so important to create public getters/setters within a bean object when it would be easier to just access the properties of the bean directly?
In good object-oriented design, each property of the bean must be private. The public getter and setter offer the only possibility to the caller of obtaining or modifying the value of a property.