In a conversation that I had with Brian Kotek yesterday, I think I finally codified one of the pain points that I am finding with Object Oriented Programming in ColdFusion. I understand that an object is supposed to be "Idealized"; meaning, that it is supposed to model the more perfect version of a "real world" object - the ideal version. But what is "ideal"? Because an ideal object is better than its real counterpart, it is beyond something that I can know - it is only something about which I can hypothesize.
Now, we can't actually figure out what properties of an object are always ideal. After all, ideal is something that is context specific. Functionality required by one application might be totally overkill or fall short for another application - not every system requires that a Person object be able to Befriend() another Person object. Just as in real life, "ideal" is different for every one and for every situation.
| || || |
| || |
| || || |
So, if we can't figure out what is universally ideal, we have to be able to at least figure out what is ideal for a given application. We have to be able to understand the principles behind the "idealization" of an object.
Let's start out by just listing some common CRUD-type methods:
Methods like these are clearly related to the object itself. They are used, for the most part, to modify the data of the object in question. As such, it is very easy to argue that they should be methods of the given object. But what about something like this:
In this case, we have an article that is returning a collection of articles that are of a similar nature; maybe the algorithm behind this is using keyword density; maybe it is using meta tags; maybe it is using categories, or authors - we don't know. What we can probably guess is that this method is not modifying the given object or returning data about the given object. As such, does it make sense to have this method as a method of the object in question?
Let's say Yes, that it does make sense because it is, after all, generating a collection of objects based on data within the given object. I think of this in the same way that I think about:
This method, GetPreviousNode(), uses information contained in the given Node instance to return a related Node instance. Again, we are using or manipulating the data of the given object in some way.
But what about something like:
- Article.GetMostPopularArticles( 10 )
In this case, we have an article returning the 10 most popular articles in the system. Everything in my gut is telling me that this is a bad idea. Because the GetMostPopularArticles() method does not require any of the data contained in the given Article object, this method is forcing the Article to act purely as a utility object, making it no better than a singleton.
Although we have only covered a few situations, I think I am seeing a pattern starting to emerge. For all of the methods that we decided should be part of an object, we made that decision because the method either modified the data of the given object or leveraged the data in some way. So really, what we're beginning to understand is that an "idealized" object contains all the methods that either access/mutate its data or leverage its data. If a method does not concern the data in a given object, it should not be there.
I'm feeling really good about this. I am sure there will be exceptions to this type of rule (there are always exceptions); but, as a core principles go, I think this will help me effectively model my domain.
Looking For A New Job?
Just thinking out loud, but would the article.getSimilarArticles() make more sense in a service object?
articleService.getSimilarArticles( myArticleObject )
I would think that object should not even begin to "think" that there could be other articles that are similar to it, but the service would "know" that there were potentially many articles, all with some similar property.
the one on the right. definitely.
Sounds like a pretty good rule of thumb. In the CF world, you'll often see the getMostPopularArticles() type methods dropping into an ArticleService class which relates to a collection of Articles (getRelatedArticles() may return a collection, but it's all about the Article in the same way that getOrderItems() is a natural method in Order as an order should be able to return it's composed items). Of course, there are other approaches. Ruby and Smalltalk devs probably would put getMostPopularArticles into an Article as class methods which have some similarities to the responsibilities of a service class in CF, and if you can come up with an appropriate domain abstraction for a collection of a given object, you might have a completely different type of object handling the collection functions - or even multiple of them with different objects handling different business responsibilities, but the general rule of thumb you're describing is great.
When you think about it, OO is mainly about encapsulation (yeah, yeah, I know - inheritance and polymorphism too) - allowing you to put data and methods that operate on that data in one place to make it easier to contain the implications of the inevitable changes in your design. It's why you get suggestions like "tell, don't ask" which suggest (for instance) telling an object to object.validate() rather than asking it for all of its properties and then validating them externally in a procedural service class method.
No doubt someone will come up with counter examples or arguments, but it seems to me like you're starting to get pretty comfortable with all of this stuff :-)
@Marc, Nice to see you get straight to the heart of the matter :-)
Oh, and yeah, +1 for the one on the right.
well, peter and marc have settled that matter.
next post please, ben!
This is a slippery slope. If you don't want an Article to think about other articles, then you probably have to move Validate() and Save() out of the Article. After all, Validate() might require some sort of related logic. For example, you can't have two articles with the same Title (horrible example, but you get the point).
I know that some part of this decision is based on fear - a desire for my world to be black-and-white. But, I need the comfort of knowing that if someone one day asks me Why I made a design choice, I must be able to explain myself. It's like that saying - if you can't explain something to a 5 year old, then you don't really understand the concept yourself.
That is my fear - that I don't understand what I am doing. As such, if there are things that exist in a gray area, I'd rather just come up with an arbitrary rule - a set of "best practices", if you will - that backs up my decisions.
It's like variable naming conventions - I can name a variable anything I want, but I try to follow my own personal guidelines such that if anyone asks me "why" I named something a certain way, I will be able to concretely say that my choice followed strict conventions.
I am desperately trying to find the patterns. Like the movie PI (only minus the drilling a whole in my head) :)
Now, of course, I am only talking about an object's API at this point. It is very possible, and likely that I will actually have the business object turn around and call the Service object internally. I feel comfortable having the implementation in the Service layer, but also comfortable making the arbitrary decision to have all data-based methods API'd in the business object.
You can take the one on the right, but I wouldn't let the one on the left find out... she might get angry and rip your arms off :)
First off I have really enjoyed your trip through OO land, keep up the great work. I think I have really simplified a lot of this but until I figure it out my self I have some questions. Why does everyone insist on putting validation methods inside an object. Should we not be treating validation as an action and injecting validation components into our objects? I know this does not make sense, I will try and come up with some examples later..
I think its important to realize that there is a difference between the API of an object and the implementation. Once we define the API on an object, we can change the implementation as we see fit. As Peter Bell points out, one of the main advantages of OOP is the encapsulation of information (a huge part of which is the encapsulation of implementation).
What I am wrestling with first is the API - calling methods on the business objects. Next is the implementation, whether it be injecting validation objects, using service methods, etc.
Good points Ben. Again, I was questioning any of you great minds I was just throwing that out there!
Question all you want ;) That's how I learn. Heck, I'm just shooting form the hip here.
Good thoughts here Ben.
My first thought about the related articles was kind of like Gareth's, but I don't know if I am a fan of the whole service thing yet. I asked myself "what object contains or better yet, 'knows' about your articles?" I would be tempted to have a blog object or site object which contain the collection of article objects. Then I would tell the site, "Here is an article object, can you find me the related ones?"
In the end, the code might boil down the same, that seems to make more sense to me-- of course in an ideal world :)
At first, I liked the .validate() and .save() method being part of the article objects, but then when I questioned it, I wasn't sure if an article is supposed to "know" how to validate itself. The point about the titles needing to be unique across multiple articles was something I hadn't quite considered. That is more something that the parent site object holding all the articles would need to decide. But if I make that jump, then I am tempted to ALSO let the site object do the saving of its articles. I don't like that slippery slope either, because it is leading me towards a bunch of anemic objects that don't do anything.
Now, I'm not sure what objects SHOULD be required to know or do. Thanks for screwing up my perfectly good Wednesday Ben. :)
I don't *know* either. That's why I am trying to come up with a rule. I think too man of the decisions could go either way; in such cases it is easy to be inconsistent. By creating a rule, I can be more consistent with my code (at least I hope). I am not saying that this way is right or wrong - I am only saying that I am setting up an environment where I can explain my actions in a consistent manner.
Fingers crossed, of course.
The idea, as I see it, is that an article may not need to "know" how to (for example) validate itself - but it should know that it can be validated. It will then say, "OK, I'll validate myself." From our point of view, perhaps we have some sort of service doing the validation - but from the article's point of view, it just gets a mysterious "voice from the heavens" saying "Fine, you're valid" and then it can proceed.
Well, if you follow Object Think literally, you would have no services - no "manager" objects - and all the other objects would be smart and self-organizing. That doesn't quite work in most systems - in typical MVC web applications we pretty much have to have some sort of controller object at least - but it's a good principle to follow in general.
I posted a long blog post back in July 2006 about this:
Nice write up and explanation. However, I am not sure that what I am laying out is mutually exclusive with services and manager-type objects. I am only saying that methods that relate to or require data from an object should have a method signature in that object.
1. That does not mean that other object will not have similar methods. To keep the objects small, I like the idea of having a "Fake it till you make it mentality" where the business objects "talk" a big game, but really, they are just turning around and calling the appropriate service object:
return( variables.MyService.Save( THIS ) );
2. There is still plenty of functionality that is not object specific. For example, PhotoService.UploadPhoto() or PhotoService.GetRecentUploads(). These are utility methods and are not tied to the data of any specific Photo object. Furthermore, I think all general search-type methods would be in services: PhotoGalleryService.GetByJumpCode(). While a jump code is related to data of a single object, we don't know if that is true until we can pull it out the persistence layer.
These are just a few examples, but clearly there will be many methods that do not use the data of a specific object.
3. I like the concept, or rather teaching, of the Milking an OO Cow, but at the same time, I think my point #1 above goes against that. For example, if the service object will ultimately persist my business object, then it will have to call many GetXXX() methods on the target business object.
Is that a bad thing? I don't think so (again, I am just learning). I like the idea of the object being able to Save() itself from the outsider's point of view, but I am very comfortable with the business object not knowing the actual implementation.
This is all very fun and exciting :)
I know I'm like way late on this but definitely the one on the right. I may drive a Harley but I'd rather have the surfer sitting on the back of my bike than me sitting on the back of 'her' (the one of the left) bike...
This is really a great series of articles Ben some of the ideas you are talking about are kinda new to me and some of them I am really comfortable with, but you are really bringing it together with great dialog!
Thanks a lot man; I am having a lot of fun with this. I have taken these "idealized" object concepts and applied them to the live OOPhoto projects: