OOP, DCI and Ruby - what your system is vs. what your system does

by Jim Gay

If you’ve read my previous article on the tip of the iceberg with DCI then you might be curious to find out more about it. You read a very simple example that was only used to make a point. But for sure, you’re left wondering a bit:

All of these, and more, are good questions. But first you need to get your head around why you would use a pattern like DCI. What does the approach with Data, Context, and Interaction have to do with Object Oriented Programming?

Your first challenge

You need to unlearn what you know to be OOP.

Not quite all of it, but you need to stop and reconsider what it means to be “object oriented.”

It’s very likely that you’ve been building programs that are “class oriented” and not “object oriented.” Take a moment and look at your latest project and consider if that’s true.

Class Oriented Programming

You might call your program “class oriented” if your classes define both what an object is and what it does. If you need something else to happen to or with that object then what’s your answer?

Do you add more methods to it’s class or do you make a new class of thing to abstract the management of some task?

If you do either of those, it could be called class oriented.

Think about what this comment from Joe Armstrong might mean in Coders at Work: “The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.”

Can that be applied to your objects?

Object Oriented Programming

When we program with OOP we care about objects, not only classes.

What DCI attempts to do is separate what the system is from what the system does. With this type of approach you’ll get a truer mental model of your program. And your code concepts will be far closer to what you experience in the real world.

“But classes represent what things are! And different things behave in certain ways” you might say. This may, on the outset, seem to be true, but if you think about the things in the real world, they don’t do anything unless they are playing some role.

Objects and Roles

The problem with relying upon classes and inheritance to define object behavior is that the behavior is fixed at compile time and your objects are limited in what actions they perform.

For example, you might have a Person class that is the basic model representation of a human being. That person is also a student, so you make a Student subclass. Eventually the student needs to get a job to pay for books and you need to make an Employee class. Should employees inherit from students? No, that’s not right. Should the student inherit from employees? That’s not right either.

Really, what you have is an object, a person, that needs to play many roles. With DCI, the approach is obvious: when the object needs to play a role, it is given that role.

Obvious Code

Yeah, but how often does this happen? Isn’t your example contrived?

This type of inheritance problem may not happen often in your programs, but it does happen and you’re likely to run into it at some point. And when it does happen what will you do?

I’ll make another object to manage the work. Maybe the Person has many tasks and I can just make a TaskPerformer object to handle that.

While making another object to handle behavior may solve the problem with better encapsulation, your code is less representative of how the real world actually works. By making your code less like the real world, it makes it more difficult for you and others to reason about it’s functions. And by introducing an abstract object, you’ve introduced something that doesn’t make sense in the real world. Does a person have a task performer or does a person just perform a task?

The benefit in approaching objects and roles like this is that it makes your code more obvious. In the context of some event, an object is given a role and has methods defined to perform some action. By explicitly assigning roles to objects in a context, your code is instantly more decipherable.

Let’s look at a simple example:

current_user.extend Admin
current_user.grant_permission(other_user)
current_user.extend Notifier
current_user.send_thank_you_to(other_user)

In the example code, we see when an object gets a role, and performs some action. Looking at that code, one could assume that the modules used to extend the objects define the methods used.

Compare that code with this:

current_user.grant_permission(other_user)
current_user.send_thank_you_to(other_user)

Are those methods defined on the class? Are they in another module that’s included in the class? Perhaps. And yet you might need to break out grep to look around for def grant_permission in your project to find out exactly what that method does.

By defining these methods directly in an object’s class, we’re convoluting what the system is with what the system does. By separating the actions in to Roles, we’re drawing a clean line between our representation of data (what it is) and the use cases that our system is designed to implement (what it does).

Separate Object from Role

With Ruby, we can easily just define new methods on an object with extend. This gives you the ability to easily break apart your concerns.

Here’s something to try: the next time you need to implement a feature begin by writing some pseudo-code. Take the details of what needs to be done and add them as comments. Then gather your objects and assign to them some roles that make sense for what needs to be done.

# A user submits a form to request permission to access our system
# If there is an available opening
team.extend AvailabilityChecker
if team.available_opening?
  applicant = User.find(params[:id])
  applicant.extend Applicant
  # and the user has completed the application, send it to processing queue
  if applicant.completed_application?
    applicant.prepare_for_acceptance
  else
    # If the user hasn't completed the application, ask them to complete it
    render 'edit', :notice => 'Oops! Please finish this.'
  end
else
  # If there is no available opening, display a message
  redirect_to :index, :notice => "Bummer, dude! We're all out of space."
end

The sample I gave is completely unreal and off the top of my head; don’t read too much into it. But if you take a glance you’ll see that it’s pretty obvious where I intend these methods to be defined. It’s likely that others on my team would find it obvious too.

Another benefit is that I didn’t add any new methods to my user class. My code won’t infect any other feature or test developed by others and by not adding more methods to the user class I don’t add any overhead to understanding it.

Once I have that pseudo-code that describes the behavior, I can comment it out and start writing tests while I re-implement each tested piece.

Try this approach to see whether the experience is good or bad. (Note that this doesn’t have to happen in your controller. You might implement this in a separate object, a Context which coordinates the knowledge of these objects and roles).

Does it make your code more obvious? Did it make testing any easier? What did others on your development team think when they saw it?

Clean Up Your Code

If you want more information about cleaning up your objects, classes, and code, then check out Clean Ruby, an ebook which will describe ways to keep your code clean, maintainable, and focused on business value. Make your OOP more obvious, easier to understand, easier to test, and easier to maintain.



Comments

Philippe Creux said on Wednesday, October 05, 2011:

Good and clear introduction to DCI with Ruby.

I didn't try to extend roles on the fly yet. But I separate Object from Role using Concerns:

class BankAccount
  include FundingSource
  include Verifiable
  # [...]
end

Luis Hurtado said on Wednesday, October 05, 2011:

I really like what you are doing with these articles, keep going!.

Dirk Porsche said on Wednesday, October 05, 2011:

Hi Jim

Today is the first time I heard of DCI. What I don't get is the real difference between DCI and correct OOP (for example in the DDD sense). What are roles other then Objects of a specific class with methods that do the work by wrapping the Entity Object in a specific purpose API. It looks like an (maybe extended) application of the facade pattern.

The roles have to have knowledge about the Entities (or Data Objects in your parlance) to do something with the encapsulated data. So effectively they are coupled and reuse is limited to data objects providing those API-Parts that are needed.

Maybe you can enlighten me.

Regards
Dirk

Jim Gay said on Wednesday, October 05, 2011:

@Phillipe, that's a good idea. It helps order your code, but in the end your object still knows more than it has to for most of the time.

By always including those concerns, they infect code and tests for unrelated functions. And the you need to understand a lot more about that class in every place you use it.

I'd love to hear what you think when you try it.

Jim Gay said on Wednesday, October 05, 2011:

@Luis thanks! I will.

@Dirk
This pattern is similar to facades, presenters or decorators but an important part of this approach is that these roles are not objects. With DCI you use the object of concern and thereby avoid self-schizophrenia (the topic of my next article).

Dirk Porsche said on Wednesday, October 05, 2011:

I don't see the importance of this distinction (yet).

In my understanding (I have literally no practice in AOP) concerns aka aspects ARE objects (in most programming languages) whose identity is irrelevant, which don't have any state and can't reasonably treated standalone. Only the defined methods (the behavior) regarding the "wrapped" entity are of any importance. What makes them different? Why not use "normal" Objects for that purpose?

I'm looking forward to your next article.

Regards
Dirk

zimbatm said on Wednesday, October 05, 2011:

Interesting approach but it doesn't look really practical. Do you have a real project that you can share that uses that approach ? My impression is that you are going to have the following issues:

Object role exclusion and lifetime: Because it's not possible to uninclude a module 1, you have to make sure to collect the object if the role is not applicable anymore. Example: user is no longer an admin but was already extended with the AdminRole methods. It's not an issue in a rails controller because the lifetime of an object is bound to the life of the request. I don't know a better workaround that having all admin methods check for is_admin?.

Object and methods no longer have a clear relationship: Because methods are added on the fly, you take the risk of having some of the user object that don't respond_to? :completed_application?. Clearly in the example it's not an issue but if the user object is passed from multiple sources to a method it might be the case. I can see how you might ending up always extending an object in some part of your code just to make sure that it behaves correctly. Did you see these kind of issues ?

Method name collision as with sub-classing: You have to make sure that modules that are going to be used on the same object don't provide the same methods. If it happens, it's even worse than when sub-classing; depending on the module inclusion order, you will get different behavior. It's possible to avoid that with conventions like prefixing your methods with the role name.

In general, I think that these issues can be solved nicely with object composition. Here is my take of it:

class UserApplication
  attr_reader :user
  def initialize(user); @user = user end
  def completed_application?; ... ; end
  def prepare_for_acceptance; ... ; end
end

class User
  def application; UserApplication.new(self); end
end

When you have that application object you are sure what methods it hold and how they work. You can also generalize it by checking the object passed in the UserApplication initializer with duck-typing with the methods that you will need in #completed_application? and #prepare_for_acceptance. If you want some of the user methods on your UserApplication instance, you can also use the stdlib forwardable module which I also find under-utilized:

require 'forwardable'
class UserApplication
   extend Forwardable
   def_delegators :@user, :name, :email, :admin?
end

Cheers, zimbatm

Mel Riffe said on Thursday, October 06, 2011:

Jim,

I'm not sure if we've formally met; I believe we know a lot of the same folks. I'm just starting to read and learn about DCI (thanks to your articles). Plus, I'm looking for a November speaker for my Ruby Users Group: http://cvreg.org.

If you have the bandwidth and the enthusiasm, I think a few folks would appreciate a DCI primer. Feel free to contact me directly: mriffe [at] gmail.

Cheers, Mel

Jim Gay said on Thursday, October 06, 2011:

Thanks, everyone, for your comments. I'm traveling for ArrrrCamp and need to catch up with my responses. Bear with me. I'll reply here after the conference (or during if I can steal away some time) and will be writing more about aspects of DCI in the future.

Maxi said on Thursday, October 06, 2011:

Great article! thanks

trans said on Sunday, October 09, 2011:

Maybe if Ruby's mixin system supported efficient unmixing, this would be workable, but as is I think zimbatm is about right. However I can envision makeing it easier with an API, something like.

user.as(Applicant).completed_application?

Jim Gay said on Sunday, October 09, 2011:

@zimbatm there is a downside to this approach both in the mental model of the code as well as the appearance of self-schizophrenia, but I'll be writing about that aspect next.

@trans there are some gems that allow you to unmix functionality by tapping into the underlying C code. And I have a working example of code using almost the exact same method you mention: "as"

Emmanuel Gomez said on Monday, October 17, 2011:

The gem 'mixology' provides an `unextend` method that removes a module from the singleton class' ancestor chain, but it is not pure Ruby: it is implementation-specific and MRI & JRuby are currently supported. Perhaps there are other, similar approaches of which I am not aware.

My point being, I personally find it rather questionable to attempt building something like DCI on such a niche foundation.

To be fair, I have never experienced the 'self-schizophrenia' issues alluded to in the DCI literature, but I have also never seen an example of this phenomenon furnished. I remain convinced that composition is a perfectly suitable approach for the Role/Data relationship.

If need be, a Role's identity test can be forwarded to the role-player (Data), just like every other method. In the absence of a concrete example, I'm left to conclude that the fear of self-schizophrenia is a red herring, or perhaps a concern born in other languages where object identity tests are not overridable methods.

What, after all, does `extend`/`unextend` give you that `class DCI::Role < SimpleDelegator` lacks?

miros said on Saturday, October 29, 2011:

A splendid introduction to DCI. But rather single-sided. Yes, roles (interaction part) are very important in DCI, but context is as important. Context gives you a way to represent a use case of application in a nice procedural way. Instead of classical OOP way: we have bunch of objects, and now they begin to send messages and somehow interact, and it is impossible to infer a use case by reading classes code. In classical OOP all object interactions are in runtime (in your code there are only classes). But with Context and roles code is much more closer to runtime object configuration.

Also I thing you did not mention clear enough an idea of separation of Data and Interaction. Data is made up of basic Domain objects. All their methods and data structures are specific only to Domain. They know nothing about application specific use cases, but they try to grasp the essence of Domain model (like in DDD), its main concepts, association, maybe some basic domain logic. Ideally, you should be able to pack your Data objects as a gem and reuse them in applications of the same Domain again and again (although i don think that it really should be done, it is more like a metric whether your data object are separated enough from Interactions).

miros said on Saturday, October 29, 2011:

@ Gomez

You don need to unextend your objects in DCI.
Objects are created and live only for duration of one Context.

Mario T. Lanza said on Wednesday, November 09, 2011:

If you wanted roles to be less "sticky" you could use a proxy instead of a mixin:

tom = Person.new(:fname =&gt; &#39;Tommy&#39;, :mname =&gt; &#39;Lee&#39;, :lname =&gt; &#39;Jones&#39;)
tom.greet # =&gt; &quot;Hi, I&#39;m Tommy&quot;
tom = Actor.new(tom)
tom.greet # =&gt; &quot;Hi, I&#39;m Tommy Lee Jones&quot; (overridden)
tom.pretend # (Actor verb)
tom.walk # (Person verb, accessed via message forwarding)

A factory method might even tell you what roles are registered:

tom.roles #=&gt; [:actor, :chef]
tom.as(:actor) # =&gt; same as Actor.new(tom)

Graeme Nelson said on Tuesday, November 15, 2011:

I've spent the last couple of weeks wrapping my head around the DCI architecture, and most of it makes sense. The one bit that I haven't really figured out and that's how persistence works in a DCI architecture.

My initial thought would be to represent the User model as a plain old object. Then when I want to save an instance to a data store, I might have a SaveUserContext and a Persist role.

I am just curious how other's are handling persistence in a DCI architecture.

Hao said on Sunday, December 11, 2011:

Though it makes sense to me that we should split what our system is and what our system does, but impact on the performance by introducing this programming practice is huge, here's a snippet showing the performance impact, https://gist.github.com/1459333

Dave Aronson said on Sunday, December 11, 2011:

Just remembered what we had been discussing earlier. I found the missing background information.

Long story short, an extending module ("extender") can have a self.included method that will get called when the module gets included in something else ("base"). This method gets an arg that represents the base. So, it can use respond_to? on that, to see whether it supports various messages, has various attributes, etc. This can be used to enforce DBC (Design By Contract).

Also, since we're talking about stuff being changed dynamically, another idea pops to mind. The extender could just stash the identity of the base, and check later. That could eliminate dependencies on the order in which various extensions are added.

Denver website design said on Wednesday, January 18, 2012:

Good post!I have read all the statement of this blog.I know that there are a little difference in the class oriented and object oriented programming. classes and inheritance to define object behavior is that the behavior is fixed at compile time and your objects are limited in what actions they perform.

Thanking you.

Dean said on Wednesday, August 22, 2012:

Enjoying your disscusion on DCI. I'm interested in your Clean Ruby book, but was wondering if there were a sample chapter to look over. At least for me, 40-50 dollars is a fairly steep commitment.

Thanks

Post a comment


(required, but not displayed)

(optional)



(required)

1999 - 2013 © Saturn Flyer LLC 2321 S. Buchanan St. Arlington, VA 22206

Call Jim Gay at 571 403 0338