getters, setters and Anemic Domain Models
October 14, 2008
Ben Nadel has added an interesting post on his blog OOP Getters() And Setters() - A New Programmer's Frustration. I posted my comments and then Sean Corfield added one which said everything I was trying to say (badly) and more. I think Sean hits the nail on the head with the trap that many people fall into when moving to OO (myself included). Here's what he has to say:
Part of the problem here is that it's very hard for (most) procedural programmers to shift their thinking to OO.
Getters and setters are a basic aspect of encapsulation but they don't make something an Object. They are the result of a data-focused approach to a problem rather than a behavior-focused approach. OO really requires a behavior-focused approach.
Yes, if you need to get data out of an object - or push new data into an object - then you should use getters and setters rather than raw public data members. That's encapsulation.
But the real issue is that it is "better" OO to ask an object to do something (with its own data) than to ask the object for its data and perform an operation on that data outside the object.
Exposing data (via getters and setters) should be a "last resort" if it is too hard / too complex to embed the desired behavior inside the object. A good example of this is the boundary between business objects and the database (even if you use an ORM) because you need to get raw data in/out at that point. However, when manipulating objects in general, the following (anti-)pattern in your code should ring alarm bells:
data = obj.getData();
doSomething(data);
obj.setData(data);
Consider whether the following would be better / easier:
obj.doSomething();
Yes, it can make for "big" objects but it is better encapsulation - the data doesn't even leave the premises - and it is more in keeping with the goals of OO.
Hope that helps.
The key part to me is that objects shouldn't just be a data store with accessors for each instance variable (this is sometimes referred to as an Anaemic Domain Models). Whilst it's fair to say that a lot of time, we are simply retrieving and saving data, ideally Objects should have a kind of intelligence and you should be able to ask your object questions.
If you need to manipulate data outside your object then have another look at your model and ask "do I really need to do that?".
- Posted in:
- ColdFusion
- OOP
15 comments
Leave a comment
If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Photo.Delete()
Photo.Save()
Photo.Validate()
... but, from an implementation stand point, they are really just a smart proxy for the actual behavior, ex:
PhotoService.Validate( THIS )
This requires my "service" class to access the data in the business object (BO) via getters in order to validate (as well as to Save, Delete, etc).
So, while my BOs appear to have behavior, my actual implementation requires the use of getters from Service to BO.
Perhaps this points out that my implementation is really no good at all??? Very frustrating.
Comment by Ben Nadel – October 14, 2008
Why is your service class accessing the data inside an object?
It seems to me that the service should tell the object to validate, save, or delete itself.
Comment by Bradley Moore – October 14, 2008
One technique I like is to create a shadow clone of my object, load the data into it, see if it is valid and if it is then pass the data into my true copy which can then save it as required.
I'm not saying this is the right approach just one that makes sense to me :)
Comment by John Whish – October 14, 2008
To parallel that in the OOP world, I don't think a BO knows *how* to save itself, but I think it knows *who* to ask:
BO.Save(){
. . . . Service.Save( BO );
}
The Service, in my mind, has a greater understanding of the system as a whole and the relationship between existing BOs. However, in order to save, the Service needs to access the data in the BO:
UPDATE
[table]
SET
name = #BO.GetName()#
WHERE
id = #BO.GetID()#
So really, it comes down to a responsibility vs. implementation. YES, I believe that it is the BO's responsibility to save itself... but I believe that implementation should be in the Service.
Comment by Ben Nadel – October 14, 2008
Comment by John Whish – October 15, 2008
The problem with calling a DAO directly is that you run into the same issue; unless you pass simple values to the DAO as individual arguments, the DAO still probably pulls property values out of the BO that is passed to it.
Plus, I like the idea of letting the service do the saving (even if it calls a DAO) because I feel like then you can add things like logging or whatever else needs to be done in the periphery of the save that perhaps the DAO should not have to know about.
I too am just learning all this stuff too. Slowly I am starting to form opinions.
Comment by Ben Nadel – October 15, 2008
In your service layer, couldn't you pass an instance of your photo object to your DAO? Something like this:
<cfset photo = createObject("component", "Photo").init( id=1,caption='CF rocks',file='cfrocks.jpg' ) />
<cfset photoDAO = createObject("component", "PhotoDAO").init( DSN="foobar" ) />
<cfset photoDAO.save( photo ) />
Then in your save method in photoDAO would be something like:
<cffunction name="update">
<cfargument name="photo" hint="Photo instance" />
<cfset var qInsert = "" />
<cfquery name="qInsert" datasource="#getDatasource()#">
INSERT INTO Photos (
id
, caption
, file
)
VALUES (
<cfqueryparam
value="#arguments.photo.getID()#"
cfsqltype="cf_sql_bigint" />,
<cfqueryparam
value="#arguments.photo.getCaption()#"
cfsqltype="cf_sql_varchar" />,
<cfqueryparam
value="#arguments.photo.getFile()#"
cfsqltype="cf_sql_varchar" />
)
</cfquery>
</cffunction>
This way the service doesn't need to know about the data of the photo object.
Comment by John Whish – October 15, 2008
Yes, but the DAO still needs to make calls like this:
arguments.photo.getCaption()
This is my point. By separating out concerns, it seems that one *must* use accessor methods to get at the data.
What I am wondering is, does this mean that my logic is bad?
Comment by Ben Nadel – October 15, 2008
IMHO, just because you are using accessors doesn't mean that your logic is wrong. Accessors are only bad if that's *all* your object consists of (and in effect it is just acting as a struct).
Comment by John Whish – October 15, 2008
Your BO should be the one giving information to the DAO to store. The BO creates a bean and sends it to the DAO. The DAO knows what the bean looks like and can use bean.getX() to pull out information needed.
Comment by Bradley Moore – October 15, 2008
My Bean knows how to validate itself, but it has no knowledge of how to persist itself, this is my DAO's job. From what everyone is saying, my Bean should have this knowledge as well. So, does this mean that I would need to inject my DAO object into my Bean instead of my Service Object?
Comment by Dean Lawrence – October 16, 2008
If you use your service layer to talk to the DAO, then what happens if your BO needs to talk to two DAOs in the future? In this instance, wouldn't it be better to have the BO call the DAO so your code only changes in one place?
Marc Esher has written an awesome post of the various trade-offs which I recommend: blog.mxunit.org/2008/10/one-said-getter.html
Comment by John Whish – October 16, 2008
I've been working with ColdFusion for over 12 years, but I don't have a computer science background, so it is very slow going trying to transition into an OOP approach. There is just so much competing information on the subject and everyone seems to have their own opinion. Which makes it very hard for people to pick this up.
Comment by Dean Lawrence – October 16, 2008
I think that there are no short cuts to learning OO and you need to know the various pros and cons of patterns to decide which one suits your needs.
Comment by John Whish – October 16, 2008
A simplistic approach where an Interface/Class combination with getters and setters for each member (which is often only a primitive) is fine for simple database applications but it is no good for complex models that have many complex relationships between types, where the ability to hide the data from clients (to prevent invalid relationships) and to override behavior in mutator method becomes hugely important.
Comment by Alan Parker – November 26, 2008