How much should an object know about itself?

by Erik Lane 26. July 2007 00:27

I understand and agree with the Single Responsibility Principle.  But have we gone too far the other way where an object doesn't do much except for carry around data and doesn't know what to do with its own data?  Say you have a Person object that holds all of the data for a person - what do you do when you want to update that person in the data store?  More times than not you will pass your Person object to another object that knows how to update the person.

Example:

IPersonUpdaterProvider provider = new PersonUpdaterProvider();
provider.UpdatePerson(person);

Whatever happened to the Person object knowing how to update itself?  Internal to the person you may have your PersonUpdaterProvider that is read from a config file or use the provider given as a parameter etc..but the code accessing and needing to update the person only needs to call a version of Person.Update().

Example:

Person customer = new Person();
 //...update some stuff
person.Update();
Person customer = new Person(customProvider);
 //...update some stuff
person.Update();

The functionality is all the same and the flexibility is still there.  Internally, the person doesn't know anymore than to call the UpdatePerson() method of the provider and passes itself.  I think we're OK from a single responsibility perspective but we've also added in Tell, Don't ask and a dash of Encapsulation.  In my example, the calling code doesn't need to know about how the person gets updated (unless they really have a need to know).  The person knows what to do - so why can't we leave it to the person?  This helps keep things clear when a developer is working with the person object.  They can see an update method on the object and see what they need to use to get the person's data updated.  No need to ask around to find out what provider/class is used to get a person updated in the data store.

I look at this and I can see that maybe we have the PersonUpdaterProvider created for each instance of a person when they could all use the same one.  Mark the default provider inside the Person object with static and now only one default provider will get created and they can all use it.  You can have an instance variable to store the custom provider so it can be used if needed without forcing all of the other person objects to use it too.

So it might look something like this.

namespace Eriks
{
    public class Person
    {
        private static readonly IPersonUpdaterProvider _provder = 
new DefaultPersonUpdaterProvider(); private IPersonUpdaterProvider _customProvider; public Person() { } public Person(IPersonUpdaterProvider customProvider) { _customProvider = customProvider; } public void Update() { if (_customProvider != null) { _customProvider.Update(this); } else { _provder.Update(this); } } } internal class DefaultPersonUpdaterProvider : IPersonUpdaterProvider { public void Update(Person person) { // update the data the default way... } } public interface IPersonUpdaterProvider { void Update(Person person); } internal class CustomUpdaterProvider : IPersonUpdaterProvider { public void Update(Person person) { // update the dat in a custom way... } } }

Example Usage:

namespace Eriks
{
    public class PersonTest
    {
        public void DoSomething()
        {
            Person person = new Person();
            person.Update();
            Person person2 = new Person(new CustomUpdaterProvider());
            person2.Update();
        }
    }
}

Giddy Up!

Tags:

Comments

Chris Davis
Chris Davis on 7/26/2007 2:55:00 AM

I can totally see your point about the seperation, I guess it's down really to personal preference and readability..but i like your idea of having it in one class, then defering it to another class....makes the code easy to read.

Mads Kristensen
Mads Kristensen on 7/26/2007 3:07:00 AM

I totally agree. You should check out Rocky Lhotka's CSLA framwork. I've been using the principles for some years now, and it does exactly what you describe but a lot of other stuff as well.

dave
dave on 7/26/2007 5:49:00 AM

Are there any concerns with the IPersonUpdaterProvider _provder being static in a Multi threaded environment?

eriklane
eriklane United States on 7/26/2007 6:12:00 AM

@Chris - I agree.  For me it is more deliberate and easy to read.

@Mads - Thanks for the tip.  I've heard a lot about CLSA but never used it.  It may be time for me to check it out.

@Dave - Yes, the private static readonly instantiates the singleton before any threads can run on it so compiler guarantees the thread safety.

Mads Kristensen
Mads Kristensen on 7/26/2007 9:20:00 AM

If you're serious about trying out CSLA, then I would suggest that you read the book instead of downloading the framework. The framework isn't as important as the principles and you learn them easier from the book than going through thousands of lines of code.

Bil Simser
Bil Simser on 7/26/2007 11:18:00 PM

I don't agree here. The responsibility of Person should be isolated to just the Person class. In the domain, an object is defined by it's behavior not it's data (although it still needs the data to work). I'm not a fan of Active Record (CSLA) where there are methods like Person.Save, Person.Add, etc. Even if the Person class delegate to a service or DAL, it still isn't doing its job. I'm interface driven so I would have an IPerson that gets passed to the provider and during execution the service layer can pass a concrete Person object down for persisting. Injecting a provider into the domain is just another way of implementing Active Record, but it's like injecting IDs into the domain just because you need them for the persistance (or UI) layer. I would avoid this as much as possible.

eriklane
eriklane United States on 7/27/2007 12:04:00 AM

@Bil - Thanks for your comments and some good insight.  I like to think that I am looking at this in terms of behavior.  When I look at myself as a person - if I need to update my personal data somewhere I go and update it (bank, HR, etc..).  I don't actually input my data directly into their DB but I enter it into their system and click save.  I'm telling them my information and telling them when I'm done and ready to update.  They are providing me with the system in which I need to update (delegation) my data but I'm still the one doing it.

The opposite, having them update it for me, would be like having to go into the bank and go through an interview each time I just wanted to update my street address.

This is just my opinion though.  Thanks!

Eric
Eric on 7/29/2007 2:08:00 AM

How would this work out in terms of dependency injection? Using Spring for IoC, one has no problem injecting dependencies into the business and data access layers. Using Hibernate, one has no problem fetching domain objects in those layers. I cannot think of a way to have Hibernate and Spring collaborate to return initialized domain objects with their dependencies injected.
Without this, the data layer is explicitly binding to the implementations..

eriklane
eriklane United States on 7/30/2007 2:02:00 AM

@Eric - I can't to pretend to know enough about Spring or Hibernate.  So I'll tell you how I'm doing it.  

I'm using the Enterprise Library to handle IoC.  I use it to create my PersonUpdaterProviders.  All of the settings are in a config file and with no parameter in the constructor I'm creating the one that marked as default (DefaultPersonUpdaterProvider()).  I could, however, provide a name in the constructor and Enterprise Library will create that specific one from the config file (DefaultPersonUpdaterProvider("customOne")) or just mark a different one as default to change which one is used in the application.

This is just a simple example and I'm sure it would be refactored accordingly.

Comments are closed