Aug 23, 2007

going OO (part 1)

Posted by: charlie griefer

(if you're just joining us... you might want to read the "preface" to part 1)

so... getting right down to it, let's delve into the beginning of the first leg of the journey towards OO.

i wanted to create some sample code... and the concept of the 'employee' seems to be a fairly standard example in learning OO. i'm making a very conscious effort to take baby steps here, so i made this ridiculously simple (fetus steps, if you will). my database currently has one table named 'employee' with columns 'employeeID', 'employeeFirstName', and 'employeeLastName'.

now normally, i'd create one Employee.cfc (for those following along at home, it's good practice to capitalize CFC names, as it follows the convention of capitalizing class names in java). Employee.cfc would have all of the methods that relate to an employee. for example, getEmployeeByID(), getAllEmployees(), getEmployeesByDepartment(), etc. this, however, isn't quite... "right" in the world of OO.

it's been suggested that the "better" (more OO-ish) way would be to create three CFCs that relate to an employee:

  • Employee_Bean.cfc
  • Employee_DAO.cfc
  • Employee_Gateway.cfc

here's the kicker. i don't know why (yet). realize that a lot of this journey is going to involve leaps of faith, and this is one of them. even though i believe that i understand the theory behind each of the above CFCs (i'll get into that below), i don't yet understand the advantages of having the three versus having a single CFC that models an employee. but i trust that before i get much further along, it'll become clear.

right then... so what is involved in each of the three employee CFCs?

a bean is a CFC that represents a single instance of a particular object (in this case, a single employee). i don't want to mire this down too much with the OO terminology... but the bean contains getters and setters (also referred to as accessors and mutators). any time i want to read a property of an employee (in this case my only choices are ID, firstname, and lastname), i would do so using a getter. for example, i'd have a method called getEmployeeID() or getEmployeeFirstName(). similarly, if i wanted to modify any of the properties, i'd use a setter method such as setEmployeeFirstName() or setEmployeeLastName(). bear in mind... these setters are setting the values for the object in memory... NOT for a record in the database. for that, we use a DAO. what's a DAO? glad you asked...

a DAO (data access object) is where the CRUD methods (create, read, update, delete) reside. the create method inserts a new record into the database. the read method retrieves a single record from the database. the update method updates a single record in the database, and the delete method... yeah, it deletes a single record from the database. each of these methods would receive an instance of the bean in memory as an argument in order to know which record to perform the requested operation on. the CRUD methods will only ever manipulate a single record. to interact with multiple records, you'd use a gateway.

a gateway is a CFC that allows your application access to the database. in my example code, my gateway currently has one method... getAllEmployees(). as you may have guessed, it simply retrieves all employee records from the database. as time goes on, i'm sure i'll be adding more (perhaps a getEmployeesByDepartment(), for example).

below is the code for all three CFCs. peruse if you'd like.

Employee_Bean.cfc:

<cfcomponent output="false"     hint="A bean which models the Employee_Bean form.">

     <cfset variables.instance = structNew() />

     <cffunction name="init" access="public" returntype="Employee_Bean" output="false">
          <cfargument name="employeeID" type="string" required="false" default="" />
          <cfargument name="employeeFirstName" type="string" required="false" default="" />
          <cfargument name="employeeLastName" type="string" required="false" default="" />

          <!--- run setters --->
          <cfset setEmployeeID(arguments.employeeID) />
          <cfset setEmployeeFirstName(arguments.employeeFirstName) />
          <cfset setEmployeeLastName(arguments.employeeLastName) />

          <cfreturn this />
     </cffunction>

     <cffunction name="validate" access="public" returntype="errorHandler" output="false">
     </cffunction>

     <!--- ACCESSORS/MUTATORS --->
     <cffunction name="setEmployeeID" access="public" returntype="void" output="false">
          <cfargument name="employeeID" type="string" required="true" />
          <cfset variables.instance.employeeID = trim(arguments.employeeID) />
     </cffunction>
     <cffunction name="getEmployeeID" access="public" returntype="string" output="false">
          <cfreturn variables.instance.employeeID />
     </cffunction>

     <cffunction name="setEmployeeFirstName" access="public" returntype="void" output="false">
          <cfargument name="employeeFirstName" type="string" required="true" />
          <cfset variables.instance.employeeFirstName = trim(arguments.employeeFirstName) />
     </cffunction>
     <cffunction name="getEmployeeFirstName" access="public" returntype="string" output="false">
          <cfreturn variables.instance.employeeFirstName />
     </cffunction>

     <cffunction name="setEmployeeLastName" access="public" returntype="void" output="false">
          <cfargument name="employeeLastName" type="string" required="true" />
          <cfset variables.instance.employeeLastName = trim(arguments.employeeLastName) />
     </cffunction>
     <cffunction name="getEmployeeLastName" access="public" returntype="string" output="false">
          <cfreturn variables.instance.employeeLastName />
     </cffunction>

     <cffunction name="getMemento" returntype="struct" hint="I contain the contents bro!">
          <cfreturn variables.instance />
     </cffunction>

     <!--- DUMP --->
     <cffunction name="dump" access="public" output="true" return="void">
          <cfargument name="abort" type="boolean" default="false" />

          <cfdump var="#variables.instance#" />
          <cfif arguments.abort>
               <cfabort />
          </cfif>
     </cffunction>

</cfcomponent>

 

Employee_DAO.cfc:

<cfcomponent output="false" hint="Employee DAO to handle the CRUD methods">

     <cffunction name="init" returntype="Employee_DAO" output="false">
          <cfargument name="dsn" type="string" required="true" />

          <cfset variables.dsn = arguments.dsn />

          <cfreturn this />
     </cffunction>

     <!--- CREATE --->
     <cffunction name="insertEmployee" returntype="void" output="false">
          <cfargument name="employee" type="Employee_Bean" required="true" />

          <cfset var qInsertEmployee = "" />
          <cfset arguments.employee.setEmployeeID(createUUID()) />

          <cfquery name="qInsertEmployee" datasource="#variables.dsn#">
               INSERT INTO employee (
                    employeeID,
                    employeeLastName,
                    employeeFirstName
               ) VALUES (
                    <cfqueryparam value="#arguments.employee.getEmployeeID()#" cfsqltype="cf_sql_char" />,
                    <cfqueryparam value="#arguments.employee.getEmployeeLastName()#" cfsqltype="cf_sql_char" />,
                    <cfqueryparam value="#arguments.employee.getEmployeeFirstName()#" cfsqltype="cf_sql_char" />
               )
          </cfquery>
     </cffunction>

     <!--- READ --->
     <cffunction name="getEmployee" returntype="void" output="false">
          <cfargument name="employee" type="any" required="true" />

          <cfset var qGetEmployee = "" />

          <cfquery name="qGetEmployee" datasource="#variables.dsn#">
               SELECT
                    employeeID,
                    employeeLastName,
                    employeeFirstName
               FROM
                    employee
               WHERE
                    employeeID = <cfqueryparam value="#arguments.employee.getEmployeeID()#" cfsqltype="cf_sql_char" />
          </cfquery>

          <cfif qGetEmployee.recordcount GT 0>
               <cfset employee.setEmployeeID(qGetEmployee.employeeID) />
               <cfset employee.setEmployeeLastName(qGetEmployee.employeeLastName) />
               <cfset employee.setEmployeeFirstName(qGetEmployee.employeeFirstName) />
          </cfif>
     </cffunction>

     <!--- UPDATE --->
     <cffunction name="updateEmployee" returntype="void" output="false">
          <cfargument name="employee" type="Employee_Bean" required="true" />

          <cfset var qUpdateEmployee = "" />

          <cfquery name="qUpdateEmployee" datasource="#variables.dsn#">
               UPDATE
                    employee
               SET
                    employeeLastName      = <cfqueryparam value="#arguments.employee.getEmployeeLastName()#" cfsqltype="cf_sql_char" />,
                    employeeFirstName     = <cfqueryparam value="#arguments.employee.getEmployeeFirstName()#" cfsqltype="cf_sql_char" />
               WHERE
                    employeeID = <cfqueryparam value="#arguments.employee.getEmployeeID()#" cfsqltype="cf_sql_char" />
          </cfquery>
     </cffunction>

     <!--- DELETE --->
     <cffunction name="deleteEmployee" returntype="void" output="false">
          <cfargument name="employee" type="Employee_Bean" required="true" />

          <cfset var qDeleteEmployee = "" />

          <cfquery name="qDeleteEmployee" datasource="#variables.dsn#">
               DELETE FROM
                    employee
               WHERE
                    employeeID = <cfqueryparam value="#arguments.employee.getEmployeeID()#" cfsqltype="cf_sql_char" />
          </cfquery>
     </cffunction>

     <!--- SAVE --->
     <cffunction name="save" returntype="void" output="true">
          <cfargument name="employee" type="Employee_Bean" required="true" />

          <cfif isValid('UUID', arguments.employee.getEmployeeID())>
               <cfset updateEmployee(arguments.employee) />
          <cfelse>
               <cfset insertEmployee(arguments.employee) />
          </cfif>
     </cffunction>

</cfcomponent>

 

Employee_Gateway.cfc:

<cfcomponent output="false">

     <cffunction name="init" returntype="Employee_Gateway" output="false">
          <cfargument name="dsn" type="string" required="true" />

          <cfset variables.dsn = arguments.dsn />

          <cfreturn this />
     </cffunction>

     <cffunction name="getAllEmployees" returntype="query" output="false">
          <cfset var qGetAllEmployees = "" />

          <cfquery name="qGetAllEmployees" datasource="#variables.dsn#">
               SELECT
                    employeeID,
                    employeeLastName,
                    employeeFirstName
               FROM
                    employee
          </cfquery>

          <cfreturn qGetAllEmployees />
     </cffunction>

</cfcomponent>

 

i'll post entries that examine each of the three more closely, starting with the bean. in the meantime, if you're following along to learn, please feel free to post questions in the comments area and i'll do my best to answer (or to find an answer). if you're following along as a guide, please feel free to point out anything that i may have stated incorrectly, or to elaborate on anything that i may not have given the attention it deserved. your participation is greatly appreciated :)

for now, i'm going to try and get thru a few chapters of the object oriented thought process. this book comes highly recommended by one of my OO mentors scott stroz, who i'm sure has been driven to drink on more than one occasion after spending his time trying to help me out. i love you man! :)

 
Moderation is on, so your entries won't show up until a moderator approves them. Verification is on, so you must include an email address and verify it before your comments will appear.
Continuing the discussion ...
Comments
You're starting down the path of the "5:1 Syndrome" where you are going to end up with bean + dao + gateway + manager/service + controller for every single table in your application.

Whilst that may seem a common practice in the ColdFusion, it's not a good one - and since you clearly want to understand OO and write good OO code, let's at least get the word out as to why...

Why do people write bean + dao + gateway? It seems it mostly dates back to some advice I gave in the Mach II Development Guide where I suggested that folks keep their CRUD code that deals with a business object (bean) and their general query code that deals with record sets separate. That was back in 2003 when I was just trying to help folks write better code - better than most of what they were doing. Unfortunately for me, most people slavishly followed my advice without understanding it.

If you read the design patterns literature, you'll see that ColdFusion developers are pretty much unique in this separation and in this naming convention.

The "Data Access Object" name comes from the Core J2EE Design Patterns and is essentially just a synonym for what Fowler (and others) call Table Data Gateway. Even when you look at the Active Record pattern, it expects you to have your general queries in the same object as your base persistence. About the only place that separates them out is where Fowler suggests a "finder" class that contains the general queries when he is talking about Row Data Gateway...

Sean Corfield's Gravatar Posted by: Sean Corfield - Aug 23, 2007 11:34 PM

...the Row Data Gateway pattern which is where you have a simple bean that represents a single row in the database but has no other associated behavior.

He compares it to Active Record and says that in the latter, you'd expect to see business logic methods as well as persistence methods.

So... where does this leave you?

My point is don't just write bean + dao + gateway because someone told you to.

OO is all about objects. If you start from the database, you're already at a disadvantage because you push your relational (non-OO) model up through your code and you end up with a non-OO design.

Something else you'll hear is "anemic domain model" which is where your objects hold data but don't do much. Your employee bean is like that - it's a struct on steroids.

My recommendation is, for now, to keep all your SQL methods in one CFC (and it doesn't really matter whether you call it a DAO or a Gateway) and remember that employee-related logic belongs in the Employee CFC (unless it really feels like it doesn't fit).

You want a smart Employee object. You want a "data layer" (containing all your SQL, organized into as many or as few CFCs as you feel comfortable).

If you go on to create an EmployeeRole object, you might as well put its CRUD and getAll() in the EmployeeGateway as well since it's a related object.

Part of what seems so intimidating about learning OO and "getting it right" is that lots of people suggest you have lots of objects and then it looks overly complex!

Sean Corfield's Gravatar Posted by: Sean Corfield - Aug 23, 2007 11:42 PM

I don't like underlines in my Objects, it looks ugly, but thats just me. Also, I'd agree with Sean here, looks like you are entering the wonderfully code-heavy world of Anemic Domain Model (over-glorified structs and objects that transform them).

This isn't necessarily bad (and in some degrees required in the world of Flex and passing neutralized data across many application layers), but leaning towards richer business objects will result in more expressive, logical objects for sure.

My thing with ADM within a single tiered application model (ie: no Flex UI to worry about) is that you end up writing hundreds of lines of code that you basically end up saying to yourself "wow, a struct would have been faster". Classically, your "bean" object will have no business logic of any kind in it, its merely a container for data, or a "short bus" :)

It is also true that moving in the world of a non-OO construct (relational tables) into developing an object model can often times be stifling because your mind is trying to translate them and its not easily done without creating a mess of useless (or repetative) code which defeats the hole point altogether.

Now, having said that, I don't think this approach should be ignored and I'd say your on a good track, continue to develop an app that is anemic and you will learn its strengths and weaknesses.

Derek P.'s Gravatar Posted by: Derek P. - Aug 24, 2007 2:29 AM

Sean covered a lot of this, but some additional comments.

Three files instead of one isn't "more OO". Check out Ruby or SmallTalk and you'll see lots of cases where there is just one class file per business object.

I've never been a supporter of the Gateway:
http://www.pbell.com/index.cfm/2007/1/1/Proposed-New-Years-Resolution-No-More-Gateways

I DO use a DAO for encapsulating the SQL relating to each business object as I find it a useful separation of concerns, and if you ever want to change DB's (e.g. SQL server to MySQL), you know which one bean you're gonna have to change for each business object . . .



Peter Bell's Gravatar Posted by: Peter Bell - Aug 24, 2007 6:58 AM

I also use a Service layer with a UserService for the User object and so on. There are two approaches to the Service layer. The core intent(s) are usually (a) to separate concerns between single object operations and those that may affect multiple objects and/or (b) to provide a single class that can act (via a thin facade) as a remote API for web service, flex and AJAX calls.

There are different approaches, but I typically put a new() method in my service classes to return an instantiated bean, and a complete CRUD API for getting by filter, getting by property, saving, and deleting by filter and property along with any custom reports, etc. In that way remote calls can easily access my service layer via a remote facade.

I generate one service class per business object. Sean prefers a less naive approach and recommends often having one service class per group of business objects. I agree that if you're architecting each application by hand that may make sense, but because I generate my apps I find generating one Service class per business object actually works very well in practice.

Peter Bell's Gravatar Posted by: Peter Bell - Aug 24, 2007 7:01 AM

MORE MORE, I crave more OO info. I'm a lot like you in that I have had trouble grasping OO, CFCs, etc. I really appreciate you taking the time to document your process...it will save me, and many more time and effort.

So, keep up the good work and THANKS!

JD's Gravatar Posted by: JD - Aug 24, 2007 8:28 AM

My only advice is to be ready for a looong trek towards the top of "Mount OO" ("MountEw!"). Internalizing the reasons WHY you do certain things in OO is just really hard. Reading some of the books on Patterns helps, since the motivations for the Patterns will reveal a lot of the fundamental reasons why you make certain decisions and trade-offs.

Sean Said: It seems it mostly dates back to some advice I gave in the Mach II Development Guide where I suggested that folks keep their CRUD code that deals with a business object (bean) and their general query code that deals with record sets separate. That was back in 2003 when I was just trying to help folks write better code - better than most of what they were doing. Unfortunately for me, most people slavishly followed my advice without understanding it.

See, it's all Sean's fault! Let's get him!


Brian Kotek's Gravatar Posted by: Brian Kotek - Aug 24, 2007 9:08 AM

After reading the intro I was really fired up about following along on this OO trip.

I guess I just need to keep my mouth shut but this all seems like overkill. I mean split out the database interaction from the app.. fine. Pull out some commonly used functions into your cfc's.. great. Isn't this true OO concept? Does there really need to be 3 files for each of my DB tables for the fringe case I might access them.

I frequently end up getting the job fixing someones OO cluttered mess. Someone who was doing their finish work with a sledge hammer and ended up with 3 times the number of files and 4 times the amount of code they needed to do the job. Where's the wisdom there?

Don't get me wrong, I know this is more about learning and trying to understand where all the OO zombies are coming from, and I'm here for the ride. But at some point the "why" conversation needs to be had as well.

Anthony's Gravatar Posted by: Anthony - Aug 24, 2007 10:58 AM

@Brian, Agreed - if you can't solve a problem it's good to at least have someone to blame for it :-> I'm thinking a dunking in the loch at Scotch may be in order!

@Anthony, I think the "why" conversation has been had plenty of times. Check out the old CFCDev archives and all the "CF OO" blogs - there has been plenty discussion of this.




Peter Bell's Gravatar Posted by: Peter Bell - Aug 24, 2007 11:13 AM

@all - appreciate all the comments... i plan on doing a "going OO part 1a" post to address the comments from both this and the previous post.

@anthony - as peter suggests, the "why" discussion has been had. i do have my reasons for wanting to make this journey, and i may give a few bullet points in the next post. but my assumption was that those who are going to follow along will have already had the "why" discussion with themselves, and chosen this particular path.

charlie griefer's Gravatar Posted by: charlie griefer - Aug 24, 2007 11:21 AM

Good to see some discussion going on around a learning process such as this. I want to point out, as I don't think it has been made obvious, that the DAO typically deals with one record at a time while the Gateway deals with multiple records.

While I'm not experienced enough to discuss the Anemic Domain Model, I can tell about my experiences using this methodology. Sean mentioned the "5:1 Syndrome". I avoid that trap by having a service layer and its corresponding controller not be so directly related to a db table. Instead, that service layer will talk to various beans, DAOs and gateways as needed. For example, a Payroll service will talk to the Employee db CFCs and to the hours and money CFCs.

In the first real OO-type app I worked on I did find that I liked the simple bean that represented a table. In this app, I had a basic Person table that held the most basic name stuff. Then I had an Employee and a Customer table that had some additional info related to those. Using inheritance, I was able to have the Employee "extend" Person. Doing this (and getting other plumbing correct) allowed me to use the Employee CFC for both the Person table and Employee table.

Matt Williams's Gravatar Posted by: Matt Williams - Aug 24, 2007 11:30 AM

@Anthony, exactly!

Question everything - ask "why?" all the time.

An example of pragmatism:

I have a site that has about two dozen tables in the database and I use Transfer to manage the persistence.

I have seven service layer objects that each deal with a group of related objects and their interactions.

In Transfer, I have my business objects grouped into five packages and those correspond to five of the service CFCs (the other two deal with site infrastructure and notifications respectively).

Since the site is all about persistence, the service layer handles everything. There would be no benefit to splitting persistence out into a separate layer in this case (because then I would just have to duplicate every method declaration and have the service layer just delegate to the new data layer).

If the objects get richer or the system's behavior gets richer, I will refactor the service layer but there's not much reason to right now.

If I wasn't using Transfer, I would probably have a separate data layer based on Active Records (beans that contain their own CRUD) just to avoid a lot of bloat in the service layer. That would add about two dozen objects to the system.

That decision is based on having "dumb" business objects that have no logic because there is very little in the system. If they were smarter objects (if the system was more complex), I'd probably have separate beans and CRUD objects...

As the Agile folks say: KISS and YAGNI.

Sean Corfield's Gravatar Posted by: Sean Corfield - Aug 24, 2007 11:35 AM

@matt: from the 'dao' section above:

"the CRUD methods will only ever manipulate a single record. to interact with multiple records, you'd use a gateway." :)

charlie griefer's Gravatar Posted by: charlie griefer - Aug 24, 2007 11:36 AM

@Matt, "I want to point out, as I don't think it has been made obvious, that the DAO typically deals with one record at a time while the Gateway deals with multiple records."

That distinction is specific to ColdFusion and dates back to my Mach II Development Guide - four years ago - when I was trying to give simple guidelines to CFers who were new to separation of concerns.

It is precisely what I'm saying people should not slavishly do these days!

In particular, that approach causes the "5:1 Syndrome" unless folks are careful.

Glad to hear you are developing broad controllers and service layer objects. Just remember that "DAO" in most non-CFers minds is pretty much interchangeable with "(Table Data) Gateway" and both can have CRUD + general queries. Separate "DAO" and "Gateway" was a good rule of thumb back then but as our community's experience and knowledge has increased, it needs to be tempered with pragmatism.

Sean Corfield's Gravatar Posted by: Sean Corfield - Aug 24, 2007 11:41 AM

I am using a code gen with my project, and there are times that I would ask one service of Business Object A to call another service of BO B that would call B's DAO. I wonder if I should just hardcoded the SQL into A's DAO. Is this what is 'preferred' in the modern, post-MarchII dev days?

In that case, I don't even need to wrap CFTRANSACTION's over service function call, which doesn't feel good to me.

Henry's Gravatar Posted by: Henry - Aug 24, 2007 1:34 PM

Instead of reading Java Forums and sites, how about focusing on more on the problems with CF in regards to writing reliable systems (even on applications that only write to one database).

How about working on getting transaction demarcation working correctly? So many frameworks are out for coldfusion - yet none of them address this fundamental issue which makes CF a toy in my opinion.

TRANSACTION_REQUIRES
TRANSACTION_REQUIRES_NEW
TRANSACTION_SUPPORTS

As soon as you start playing around with Services A, calls Service B (Henry's problem), you'll be in a world of hurt as transaction boundaries are rarely static.



George D.'s Gravatar Posted by: George D. - Aug 24, 2007 5:38 PM

Your old employee.cfc is looking better and better all the time :-)

ziggy's Gravatar Posted by: ziggy - Aug 24, 2007 9:23 PM

I think it's unfortunate to read in the comments here that the "why" of using OO is being dismissed as something that's "been covered elsewhere". I was hopeful that this series would at least address some of the why.

I was hopeful that when you started with "here's how I would have done it", but you seemed to jump to a mode of using CFCs which you say is "not OO", but some readers may not have even made that first leap compared to their traditional development.

I guess the question, then, is whether that audience (maybe a step behind you) will have to accept just being left behind. It's certainly your prerogative to take off running. I just sense that some will still be walking, who might have liked very much to go along on the tour.

I suppose comments from others will also help guide your pace. Don't want to sound like I'm complaining. I'm sure that many will learn regardless.

Charlie Arehart's Gravatar Posted by: Charlie Arehart - Aug 24, 2007 11:17 PM

hi charlie a:

i really hadn't intended to do a "why you should learn OO" aspect of this. i've been sold for a while, and had just been having trouble making the leap.

i thought there may have been enough folks in that same boat to justify skipping over the "why" (indeed, a google search on procedural vs oo would yield no shortage of results).

however... if there are folks that are still debating whether or not they should... it may be worthwhile to at least throw some bullet points out there. perhaps i'll do a "going OO (part 1b)" :)

charlie griefer's Gravatar Posted by: charlie griefer - Aug 24, 2007 11:49 PM

Hey Charlie - great initiative on this series. Like yourself I've been sold for quite a while to the why. And I've "played around" quite some already with OO.

I would think that a good way of covering the "why" would be to just include a bunch of links to previous discutions on the questions. Or maybe Charlie (Arehart) would do this, as he's made some really impressive topic link lists before :)

Trond Ulseth's Gravatar Posted by: Trond Ulseth - Aug 25, 2007 1:56 AM

The simpler the better if it serves the purpose. Why? Life is too short... ;)

Vasia's Gravatar Posted by: Vasia - Aug 26, 2007 5:35 PM

I think I get the "Why" part however I would like to gain some perspective as, up till now, the extensive use of CFCs in the few OO structures I have use has seemed slower with a few of the applications that I have committed to using and incredibly complex and difficult in regards to debugging.

While I have been programming CFML for almost 11 years solid, the concepts behind moving to an OO framework is new for me (kudos on this series by the way!)

This is probably my nOOby-ness coming through so I am looking to get sold on the Dao of OO with CFML.


Chris Cantley's Gravatar Posted by: Chris Cantley - Aug 28, 2007 1:05 PM

This blog post has been fantastically helpful. I wish I'd found it earlier.

The OO technique is obviously hugely diverse. This leads to a huge range of opinions with regards to it's correct implementation.

As an OO noob I've been trying to find the correct approach, but it doesn't seem like there is one. My panic to adhere to standards and understand the greatness of OO have left me floundering.

I've been working on a small example very similar to Charlie. I'm almost on the verge of giving up. Not because of OO, but because of the conventions of the OO community. I was going great guns till I hit upon patterns, gateways, DAO's and the like.

I won't give up - Thank you Charlie.

John Mawer's Gravatar Posted by: John Mawer - Oct 19, 2007 9:25 AM

Post a Comment
Comments have been turned off at this time.