可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am reorganizing my ColdFusion directory structures and am curious about how experienced CF developers are organizing libraries of smaller cffunctions.
I am not as curious about elaborate components (objects) as I am about the dozens of little utility functions we all build up over time.
- Do you use a large single file with cffunctions and cfinclude it?
- Do you use a large single file as a cfcomponent and call creatobject/cfinvoke?
- Do you put each utility cffunction in its own cfc and call createobject/cfinvoke?
- Do you use the cfimport taglib syntax?
- Do you use the CustomTags or cfmodule?
- Do you have a better way?
Since I don't like verbose syntax I have been just cfincluding a lib.cfm that has a bunch of common cffunctions in it. I may refactor them to grouped cfcs I can createobject on just to have better isolation on variable scopes.
Is there a better way to do this?
回答1:
This is a reprint of a blog post I did back on June 13, 2007. I've been using this method for quite sometime and it works great! YMMV.
Who doesn't like user-defined functions (UDFs)? If you have done any programming, chances are that you have used them extensively. The biggest problem that people have with them is how to include and organize them in your application.
What I've found that most people do is create a Utils.cfc or UDFs.cfc and cut and paste their UDFs that they want to use into the component as demonstrated below:
<!--- UDFs.cfc --->
<cfcomponent output="false">
<cffunction name="init" access="public” returntype="Any" output="false">
<cfreturn this>
</cffunction>
<cffunction name="myUDF1" access="public" returntype="Any" output="false">
</cffunction>
<cffunction name="myUDF2" access="public" returntype="Any" output="false">
</cffunction>
</cfcomponent>
Once you have all the UDFs that your application will be using pasted into your component, you will need to make the UDFs available to your application. Almost everyone I've seen does this loading by the component into the application scope. The following line is placed into the onApplicationStart()
if you're using Application.cfc or by just adding it into the Application.cfm if you're using that:
<cfset application.functions = CreateObject("component", "udfs").init()>
Whichever one you're using, Application.cfc or Application.cfm, the results are the same; all your UDFs are available to your application and you can use them freely throughout. The only difference is what variable name you use. I use application.functions, some use application.utils or application.udfs; doesn’t matter, again, the results are the same.
There is one problem that I have with this approach though, it's cumbersome and the UDFs component will get huge. The problem with having such a huge component file is editing it becomes a nightmare since scrolling through thousand of lines of code isn't very fun and also I've noticed that CFEclipse bogs down on huge files. Sure code collapse does provide some relief but there has to be a better way.
What I wanted was to just have one file for each UDF I was using and a way for my application to load them automatically. The reason behind this was so that if I needed to edit myUDF1
, I could just open the file myUDF1.cfm
and edit what I needed. I also wanted to be able to grab UDFs from CFLib.org and just drop them into my application without having to edit anything. If I ever needed to remove a UDF from my application, it would be as easy as deleting the UDF file and reinitializing my application.
To accomplish what I wanted, I modified my UDFs.cfc to 11 lines of code:
<!--- UDFs.cfc --->
<cfcomponent output="false">
<cfset variables.udfdir = GetDirectoryFromPath(GetCurrentTemplatePath()) & "udfs">
<cfset variables.q = "">
<cffunction name="init" access="public" returntype="Any" output="false">
<cfreturn this>
</cffunction>
<cfdirectory action="list" directory="#variables.udfdir#" filter="*.cfm" name="variables.q">
<cfoutput query="variables.q">
<cfinclude template="udfs\#name#">
</cfoutput>
</cfcomponent>
So what exactly is going on?
In a nutshell, here's what’s happening: I have a directory called udfs
in the same directory that I have my UDFs.cfc. This is the directory that I put all of my UDF CFM files. What the UDFs.cfc does is scan this directory when it is called and automatically includes each CFM file it finds. Thus it automatically loads any UDFs in the UDFs folder into itself (commonly called a "mixin").
So my goal is reached! I have each UDF in its own file so I don't have to scroll through a huge component file to find it. I can now open and edit it easily. By just looking at the directory, I know what UDFs my application is using. I can automatically add a UDF from CFLib.org by just saving the text from browser into a file in the directory. Plus if I no longer need to use the UDF in my application, I simply delete the file from the directory and it's removed from my application during the next re-init. All this is done without having to touch the main UDFs.cfc file.
Below is an example of what one of the UDF CFM files looks like. The file is called fullLeft.cfm
and resides in the UDFs directory.
<!--- fullLeft --->
<cffunction name="fullLeft" access="public" displayname="fullLeft" returntype="string" output="false">
<cfargument name="str" type="string" required="true">
<cfargument name="count" type="numeric" required="true">
<cfif not refind("[[:space:]]", arguments.str) or (arguments.count gte len(arguments.str))>
<cfreturn Left(arguments.str, arguments.count)>
<cfelseif reFind("[[:space:]]",mid(arguments.str,arguments.count+1,1))>
<cfreturn left(arguments.str,arguments.count)>
<cfelse>
<cfif count-refind("[[:space:]]", reverse(mid(arguments.str,1,arguments.count)))>
<cfreturn Left(arguments.str, (arguments.count-refind("[[:space:]]", reverse(mid(str,1,arguments.count)))))>
<cfelse>
<cfreturn left(arguments.str,1)>
</cfif>
</cfif>
</cffunction>
回答2:
I think this depends on your programming style, go with whichever style you are most comfortable with. I find the easiest way is in application.cfm, set a variable in the application scope to a cfcomponent with all my utility functions:
<cfif not isDefined("application.utilities")>
<cfset application.utilities = createObject("component", "Utilities")>
</cfif>
Now you can call methods in application.utitlies from anywhere. Note that if you make changes to your cfcomponent, you have to refresh your application variable with a new instance of Utilities.
回答3:
if you are using Application.cfc (if you are not i would strongly suggest migrating to it from Application.cfm - its very easy to do) you can build a baseComponent.cfc with all your UDF methods and have the Application.cfc inherit from baseComponent. then in the onRequestStart method set a variable called request.app=this;
for the entiure request, you can then use request.app.methodname() to access the UDF. its very nice and simple way of handling UDFs
also, if you like you can have all your cfcs inherit from the same baseComponent so that all your cfcs have those util functions as native methods. makes unit testing cfcs very easy because the cfcs dont need to reply on a passed (injected) reference to the UDf component, they are decedents of it!
one challenge with this approach is that the extends attribute of a cfc cannot be an expression... so depending on how you package your components, this can be hard to implement. the easiest way to handle it is with a coldfusion mapping.
hth
Jon
回答4:
We use .cfm files for function libraries and call the appropriate file with cfinclude. Some of the .cfm files have been downloaded from cflib.org and others are written by us. The files are in a directory named UDF which is a subdirectory of another directory which is mapped to the forward slash character. The cfinclude statement is simply:
<cfinclude template="/UDF/filename.cfm">
This approach makes the functions available to all applications on the server.
We also prefer the several small library approach. Each library is topic specific (math, string, list-array etc)
回答5:
Option: Do you use a large single file with cffunctions and cfinclude it?
A: I have done that but I do it less and less. I like taking advantage of inheritance and cfcexplorer
Option: Do you use a large single file as a cfcomponent and call creatobject/cfinvoke?
A: Yes I often do this
Option: Do you put each utility cffunction in its own cfc and call createobject/cfinvoke?
A: I might do this if I expect additional functions to be added later
Option: Do you use the cfimport taglib syntax?
A: I do i18n stuff that way
Option: Do you use the CustomTags
A: Not in a long time. cfc's are better at this
Option: or cfmodule?
A: Not in a long time. cfc's are better at this. caller.* scope can make it hard to debug
回答6:
I realize this is an old question, but I use a little bit of a different approach for these issues.
Utility Function/Singleton Approach with 'Injection'
I create a 'core' or 'utility' cfc. In it I pack all my utility type functions that are:
- Frequently used all the time everywhere (such as a generic
viewRecord()
dao and a core checkSecurity()
function, et al.)
- Are base functions that imho should be core in CF (such as
lpad()
, capitalize()
, et al)
- Are wrappers of some tags that allow me to use
cfscript
ubiquitously (such as exit()
which wraps <cfexit>
)
On onApplicationStart()
, I create an instance of this object and assign it to the Application
scope thus creating a static singleton of sorts.
Then instead of extending or re-including this into nearly all my cfc's, which allows me to use extension for more traditional type of inheritance, I then inject these methods in the constructor (the init) of all my cfc's I build. I do this by calling a method on the utility object itself such that:
public/remote any function init() {
structAppend( Variables, Application.MyApp.Objects.oCore.injectCoreMethods() );
return this; // Return instance of this object
}
The injectCoreMethods()
method selectively returns a structure of the utility functions I want virtually extended into all my objects. It does not necessarily inject all the utility methods. Less frequently used ones, including injectCoreMethods()
itself, still would need to be addressed via the full singleton application pointer such that Application.MyApp.Objects.oCore.infrequentMethod()
.
By injecting into the Variables
scope, which is protected, these methods will effectively be private methods. So any dumps of the objects will not show these utility functions but are perfectly accessible within the cfc by all its direct methods.
File organization:
I have generally fallen into the pattern of having one cfc per folder. In each folder, I have one cfc file for the component and the init. All other methods I break out into cfm files and incude in that cfc. I do this to:
- Avoid giant cfc files of 1000+ lines which can slow down my IDE (I use aptana/cfeclipse)
- Allow changes to be recorded/tracked more discretely on a file per file basis and thus recorded in my SCM/Version control software.
- Allow more than one person to work on a given object with out bumping code into each other.
So a dao object which contains 4 crud methods would look something like this:
/code/dao/dao.cfc
/code/dao/_removeRecord.cfm
/code/dao/_addRecord.cfm
/code/dao/_viewRecord.cfm
/code/dao/_editRecord.cfm
The cfc just contains the init()
and self-documenting comments and in the pseudo-constructor area I include the four methods. This also lets me grab any cfc by its folder and move it somewhere.
Same for the utility cfc. It sits in its own folder and has about 30 odd functions amongst 10 or so cfm files (some simple functions I leave in the same file such as _string.cfm
which actually contains lpad()
, rpad()
, etc all string related. You get the idea.)
Modules and Custom Tags
I avoid these at all costs because they need to be registered and hamper easy move/deployments. I don't like things that don't just self configure on drag and drop from one environment to another. CF5- you had to do things this way a lot more. But since CF6 and the ability to use objects in a real OOP pattern, why would you want to? There are very few cases you would want/need to if any.
Other
I used to put 'core' functions into the base.cfc which is automatically extended into all cfc's generated by CF (look for it, add a function and voila! Kinda like adding things to prototype in js). I used to really like this but it was a problem for deployment/maintenance.
To some degree, I take a Factory approach. I often put a fair amount of static cfc's up in the Application like the core one. A controller reads a generic control table and sets up all the objects in a loop along with a bunch of other things on app start like app variables. But some objects are instantiated as needed, obviously heavy objects and objects that contain manipulable [semi] persistent data fall into that category
I have, in some ways, been doing this since CF7. With CF9+ it is getting pretty easy, mature and slick.