When inheritance and mixins become a design issue
Posted on 09. Mar, 2011 by Guilherme Silveira in design, java, ruby
One object, more than 300 public methods, 560 instance and 300 class or static methods. It is easy to see that those objects tipically present low cohesion, more than one responsibility, tight coupling and a huge interface. A pattern known as “God Class”.
You can create them in Java using class inheritance, or in Ruby using class inheritance or module mix-ins. It is the “inheritance way”. Typically connected with “global variables and methods” and “thread locals”.
In this post we will go through an example of such object with hundreds and methods and show how to decouple our code from that. Moving away from the “Inheritance way” into the “Favor composition over inheritance” way, totally unrelated to language of choice.
Nick Kallen has recently described how to use OO principles to compose behavior in any desired aspect. Yehuda Katz showed how to implement the same behavior in Ruby.
Nick complains about some Ruby code he has seen around, while Yehuda about the Java, and this is the real problem: there is bad OO design out there, no matter which language. Both are correct in their design complains, and both languages can provide the good design techniques and results they are expecting. The question is not about the language, but about the design pattern.
For instance, Rob Olson suggested that, for a typical inheritance problem, the solution was to use meta programming. Yehuda came up with a simple and better solutions, simply use OO: “super”. The original problem arrived because mixing in a module present the same kind of inheritance coupling as from inheriting from a class. So Yehuda’s solution is good for a module with a couple of methods, but dangerous for the design when several mix-ins are included (is 30 too many?) and hundreds of methods made available in the same scope.
But does Ruby code have to end up with hundreds of methods in one object? Douzens of mixins? Surely not. Although, sometimes, it does, as we will see in the following example. As with some Java code sometimes ends up with amazingly huge inheritance trees.
There is no problem in using mix-ins or inheritance once you are aware of its effects in your design. Pretty much in the same way, anyone can do a lot of bad code in Java (and any other OO language) if he is not aware of good OO design principles.
Let’s move to an example of bad usage of inheritance, that can be done either in Java (through mutiple class inheritance and interface implementation) and Ruby (through class inheritance and multiple mixins).
Mixins, coupling issues and the single responsibility principle
The example of “over usage” of inheritance and mix-ins: Rails has greatly improved web applications productivity due to its Convention over Configuration approach. Still, its internal design choices show some famous object oriented design issues. Between those, one that is easily spotted is the need to inherit from ActionController and ActiveResource.
Class inheritance or mix-ins inclusions in Ruby imply in tighter coupling, and a simple test can show how much this coupling implies in 560 methods being inherited and due to Ruby design, private methods also affect this coupling:
rails console > ActionController::Base.instance_methods.size 306 > OMG NameError: uninitialized constant Object::OMG > (ActionController::Base.instance_methods + ActionController::Base.private_instance_methods).size 410
An object having 36 ancestors, 410 instance methods and 353 class methods indicate loss of cohesion, and breaks the SRP and ISP principles from solid.
Usually those approaches with a few scopes and several methods come from procedural programming, while functional programming would use functions as a a higher order element
for composing behavior – which would be great! Unfortunately in this case, those methods are used as almost-global procedures. This can be easily found in other OO languages and frameworks too, just try to find code based in inheritance which provides a huge interface with hundreds of methods being exposed.
This loss of cohesion can be seen when a controller has in its scope methods related to html rendering,
http request and response processing, uri lookup, scope access and more: everything within an instance of ActionController::Base. Cohesion gets weaker when adding new unrelated methods to an ApplicationController, the typical approach.
Unit testing, a technique that easily shows loss of cohesion and tight coupling, easily shows the problem. One needs to either test the class alone by setting expectations to methods contained in the parent class (such as any invocations to param and path methods), or test it only with its parent, by
creating an instance and invoking the desired method. Both approaches show that to create a Controller one needs information from several
different sources, with different goals. This is the sign of coupling and loss of cohesion.
This post implements a work around Rails to show how to remove that coupling and avoid inheritance and mix-ins, while defining a framework (on top
of Rails) that is capable of providing the same functionalities. It is just a working proof of concept. Although it can be used, it needs a lot of enhancements
to provide helpers for every controller-coupled-functionality.
The first example is an action using a few of those different-responsibilities contained within the controller to log in a client:
class ClientsController < ApplicationController
def create
@client = Client.find(params[:client])
session[:current_user] = @client
flash[:message] = "Client logged in unit!"
redirect_to client_home_path(@client)
end
end
Some of those methods could be extracted to ApplicationController, with the same lack of cohesion (those methods would be inherited).
Instead of having the responsibility to deal with parameters, let’s leave it to the framework:
class ClientsController < ApplicationController
def create(client)
session[:current_user] = client
flash[:message] = "Client logged in unit!"
redirect_to client_home_path(client)
end
end
class ApplicationController
private
def send_action(sym)
params = ParamsExtractor.extract_for(method(sum), self)
super(sym, *params)
end
end
Instead of containing the responsibility of the flash scope, a conversational artificial “http” scope; the session lookup;
and the redirecting, all in one object, those responsibilities can be broken and one would be able to:
class ClientsController < ApplicationController
def create(client, current_user, redirect_to)
current_user.login(client)
redirect_to.client_home(client)
end
end
class CurrentUserHelper
def initialize(controller)
@controller = controller
end
def login(client)
@controller.session[:current_user] = client
@controller.flash[:message] = "Client logged in unit!"
end
end
Now its time to implement the parameters provider:
class ParamsExtractor
def extract_for(method, controller)
types = method.parameters
if has_parameters(types)
provide_instances_for(types, controller)
else
[]
end
end
private
def has_parameters(types)
types.size!=0 && types[0]!=[:rest]
end
def provide_instances_for(params, controller)
params.collect do |param|
providers.provide(param[1], controller)
end
end
end
One needs to implement a provider for each and every desired behavior. For example, to instantiate AR objects:
class Instantiate
def extract(controller, name)
name.camelize.constantize.new(controller.params[p])
end
def handles?(controller, name)
defined?(name.camelize.constantize)==true && name.camelize.constantize.is_a?(ActiveRecord::Base)
end
Inheritance as a way to get access to new methods is not required any more. Notice how unit testing will provide feedback as higher cohesion and lesser coupling:
class ClientsControl # no inheritance FTW
def create(client, current_user, redirect_to)
current_user.login(client)
redirect_to.client_home(client)
end
end
Therefore its time to trick Rails to “remove” the inheritance need internally:
def method_for_action(method)
# TRICKing rails... no inheritance support
method
end
def send_action(sym)
type = self.class.name
control_type = remove_ler_from_controller(type)
if (class_exists?(control_type, sym))
type = control_type.constantize
control = di_instantiate(type)
params = extract_params_for(control.method(sym))
ret = control.send sym, *params
control.instance_variables.each do |x|
value = control.instance_variable_get x
instance_variable_set x, value
end
super(sym)
else
params = extract_params_for(method(sym))
super(sym, *params)
end
end
But Rails will complain that at some point the type did not even exist. It shows that checking the existence of the type, method and so on is used in more than one place (loss of cohesion, tighter coupling). Unfortunately, changing all of them requires knowing them all, therefore let’s hack it:
def pimp(what)
eval "class #{what.to_s.camelize}Controller < ApplicationController; end"
end
pimp :clients
Someone with more knowledge of Rails codebase could remove the pimp hack itself but will soon realize that several points of Rails rely on the fact that this inheritance exists, portraying the high level of coupling and low cohesion of the original procedural approach. The problem with a simple scope control
(one huge object, lots of methods, lots of internal state) is that as more and more code is produced against those expectations, this God object becomes
more and more tighter coupled and the cohesion drops.
This is a proof of concept. Other design change that can be applied is removing the use of thread local variables through public global acessors, which can be redesigned into well-controller scoped objects. Let’s do it?
SUBSCRIBE TO OUR RSS