Factory with a Strategy
Another benefit of localizing your object creation code is to have the flexibility of plugging in various creation strategies transparently without impacting your clients. In a real life Java application, we were having some performance problems, which could be traced down to creation of a large number of some specific objects in memory. While the objects were not too heavy, the sheer numbers were bringing down the performance curve. Luckily we had the creation logic behind a factory abstraction in Java.
public class BorderObjectFactory {
//..
public BorderObject createBorderObject(int type,
double thickness, Color color, BorderCategory category) {
//..
}
//..
}
And all clients were using the factory for creating BorderObject instances. After a careful review of the creation pattern for BorderObjects, we concluded that we need to implement instance sharing through the Flyweight pattern. This will imply a change in the creation logic, which would have impacted all clients had they not been behind the factory firewall. Here goes the flyweight ..
public class BorderObjectFactory {
//.. needs to be a singleton
// pool of flyweights
private Map<String, BorderObject> pool =
new HashMap<String, BorderObject>();
// don't instantiate me directly
private BorderObjectFactory() {}
public BorderObject createBorderObject(int type,
double thickness, Color color, BorderCategory category) {
//..
// make the hash key
String key = new StringBuilder()
.append(type)
.append(thickness)
.append(color.toString())
.append(category.toString()).toString();
BorderObject bo = pool.get(key);
// if it finds from pool, return it
if (bo != null) {
return bo;
}
// first time entry so create
bo = new BorderObject(type, thickness, color, category);
// cache for later use
pool.put(key, bo);
return bo;
}
}
In Ruby, Factories are smell
Ruby has open classes - every class is an object, which can be changed during runtime. If you want to change the creation logic of an object, just open it up and plug in the new logic at runtime. Here is the similar looking Border class in Ruby ..
class Border
attr_reader :width, :height
def initialize(wid, ht)
@width, @height = wid, ht
end
# other logic
end
In case you want to change the creation logic through an implementation of the Flyweight based pooling, just define a separate Flyweight module :
module Flyweight
def Flyweight.included(klass)
klass.instance_eval %{
@_pool = {}
alias :orig_new :new
def new(*key)
p "invoking new on " + self.to_s + " args = " + key.to_s
(@_pool ||= {})[key] ||= orig_new(*key)
end
}
end
end
and mix it in the Border class ..
class Border
include Flyweight
# rest similar
#
end
and clients enjoy the benefit of sharing instances transparently without any change of code. And the best part is they continue to use the same Border.new(..) call to create new Border objects.
b1 = Border.new(2, 10)
b2 = Border.new(4, 20)
b3 = Border.new(4, 20) ## instance sharing with b2
b4 = Border.new(2, 10) ## instance sharing with b1
Depending on the language in which you are designing, the factory implementation may vary in shape and size. In Ruby you don't need to have separate factory abstractions, the factory design pattern is melded into the power of the language. But keeping the object creation logic localized always gives you the extra yard with respect to your design scalability.
2 comments:
Dear Debasish Da,
Please highlight the benefits of Singleton instead of static factory methods in BorderObjectFactory class. Is is only for removing hidden dependency?
One more thing, Java 7 may have some syntax for object creation like Ruby:
BorderObject bo = BorderObject.new();
BorderObject bo = BorderObject.new(1, 2);
etc.
Is is related to this scenario.
Static methods are always a smell as far as unit testing is concerned. It is very difficult to unit test code which has calls to static methods. And singletons are very easy to test - you can always mock them using DI containers like Spring or Guice transparently. But a detailed discussion needs a separate blog post :-). However, the point of the current post is to localize the creation logic - whether u implement the factory as a singleton or static methods is the next step.
Post a Comment