-
-
Save justinko/1585371 to your computer and use it in GitHub Desktop.
class PostCreationContext | |
def initialize(post) | |
@post = post | |
@post.extend PostNotifier | |
@post.extend PostIndexer | |
@post.extend PostCacher | |
end | |
def execute | |
@post.save | |
@post.notify | |
@post.index | |
@post.cache | |
end | |
end |
class PostsController < ApplicationController | |
def create | |
post = Post.new | |
post.extend PostNotifier | |
post.extend PostIndexer | |
post.extend PostCacher | |
post.save | |
post.notify | |
post.index | |
post.cache | |
end | |
end | |
# VS | |
class PostsController < ApplicationController | |
def create | |
context = PostCreationContext.new(Post.new) | |
context.execute | |
end | |
end |
Well, there's two things here really: First we have the way we think about DCI and the second is how do we implement it in this language.
There's really not a big difference between:
class SomeFeature < BasicObject
def initialize(data)
@data = datta
end
def method_missing(*a,&b) @data.send(*a,&b) end
# my custom methods
end
foo = SomeFeature.new(bar)
and:
module SomeFeature
# my custom methods
end
bar.extend SomeFeature
Both captures the basic idea in DCI: That you have plain data objects (with core methods; e.g. push, pop on Array) and when you need to do something more complex with it, you put that in another place and include it when you need it. Two different implementations that solves the same problem.
Of course, one thing that is possible with composition is storing separate state. That's something that's very unlike DCI. State belongs in Data or Context.
Not all OO languages have the capabilites to implement the DCI pattern well. You need to be able to inject code into existing classes: try doing this in Java. In C++ it is possible using templates, but only in a static manner. The devil is really in the detail.
http://larskurth.wordpress.com/2009/11/11/not-your-grandfathers-oo-data-context-interaction/
Here is the article on DCI. All of the examples inject methods into the data object. For Scala, they use "traits". For C++, they use "templates":
http://www.artima.com/articles/dci_vision.html
In Ruby, we're going to have to use extend
.
My comment was more on architecture in general, not on how to best implement DCI.
The example on that page looks exactly like the original LISP mixins.
What I like about composition rather than DCI is the better separation of logic layers. For instance, on business logic level, the semantics of delete
might be different from the semantics of delete
on persistance layer (remove from database vs. mark as removed). DCI does not solve this issue.
What I like about composition rather than DCI is the better separation of logic layers.
By "separation", you mean classes vs modules? Regarding "layer", if you mean a separate Ruby object, then yes it is more of a separation. But there is overhead to that approach...
the semantics of delete might be different from the semantics of delete on persistence layer
Define your own delete
method in a "role" (module). Use super
to hit the persistence layer delete
method.
By "separation", you mean classes vs modules?
I mean composition vs inheritance.
But there is overhead to that approach...
Agreed. But you avoid leaky abstractions.
Define your own
delete
method in a "role" (module). Usesuper
to hit the persistence layerdelete
method.
What if some code on the persistent layer wants to call delete
and by that means "remove from the database" vs. some business logic code that calls delete
and means "remove from the user interface"?
Agreed. But you avoid leaky abstractions.
The leakiness potential is worth not having an extra layer.
What if some code on the persistent layer wants to call delete and by that means "remove from the database" vs. some business logic code that calls delete and means "remove from the user interface"?
This is a scenario that I just don't think will come up. When would you ever need to call a delete
method on the extended object and then actually need to delete it in the db? A more real life scenario would be calling an archive
method then the delete
method.
This is all just experimentation for me. I might switch to composition if it gets painful - we'll see.
I haven't really looked into DCI, but wouldn't it be better to use composition (i.e. to wrap post rather than to use extend?). This would play nicer with call site caches and you would not have to worry about Post instances being reused, say by an identity map or whatever.