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?".

 


15 comments

  1. My problem with this is that from the outside, my business objects appear to understand behavior:

    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
  2. @Ben

    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
  3. Hi Ben, Bradley and I might be misunderstanding but I'm not sure why your service layer needs to access the data in your Business Object. One of the nice things about using CF is you can pass invalid data types (duck typing) into your object and then call an IsValid() method of your BO. If it passes then you can Save() it.
    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
  4. My gut tells me that in order to properly validate / save / delete, etc, a business object needs to know about a world greater than its own sphere of understanding. But, I believe that the BO should know how to get those tasks accomplished. So, as a metaphore, I might not know how to get $100 out of my bank account, but I *do* know who to ask (my ATM or bank teller).

    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
  5. @Ben, I see what you mean, if you use the service to actually save the data, then you will need to know the data. However, I think it is perfectly valid to have your Business Object's Save method call a DAO directly. Having said that, I'm fairly new to this OO stuff so I woudln't recommend it as a best practice :)

    Comment by John Whish – October 15, 2008
  6. @John,

    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
  7. @Ben,

    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
  8. @John,

    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
  9. @Ben,
    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
  10. The DAO's job is to simply persist information. There are exceptions, but generally that is all you want it to do.

    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
  11. My biggest frustration is knowing where to place the persistence. I currently use a Service/Gateway/DAO/Bean model where I inject my Gateway and DAO objects into my Service object. I use my Service object to initially create a new Bean object and then passes it off to my DAO object to save.

    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
  12. Hi Dean, personally I'm not a huge fan of having a separate DAO and Gateway object, this just seems like overkill to me. I prefer the idea of having a "PackageGateway" which combines both. However, one thing I know for sure is that there is not "one true way" so your Business Object calling your DAO is valid.
    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
  13. Thanks for the link John, I will definitely check it out. Your question regarding future access to multiple DAOs is very valid. Whenever I start to have to deal with more than one object or multiple DAOs, I struggle with where everything should go. This is especially true when I need to work with composition and aggregation.

    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
  14. Hi Dean, Yep - totally know how you feel! I don't have a CS background either, sometimes I think this holds me back other times I think it's a benefit. Computer Science teaches OO, but from a C++/Java point of view and OO Coldfusion has it's own quirks. CF is loosely typed, but is poor at object creation, whereas Java is strongly typed and fast at object creation.
    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
  15. I totally agree with your statements. Getters/Setters are NOT OO programming. Unfortunately we have many people at my company who do not understand this, the problem is so bad that we have 'senior' people who insist that serialization of our datamodel objects must be done with getters and setters... argg.

    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

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.