Monday, June 04, 2007

Design Tip: Localize your Object Creation Logic

Always try to localize the logic of object creation. This has been one of the big lessons that the Factory design pattern teaches us. This is also a teaching of the Separation of Concerns, which every well-designed software should honor. Don't let your object creation logic get spitted into the processing code. Always program by the interface returned from the factory contract - this way the processing logic remains independent of individual concrete subclasses. All this we knew from the GOF patterns, right ?

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.

3 comments:

gRoy() said...

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.

Unknown said...

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.

Anonymous said...

情趣用品,情趣用品,情趣用品,情趣用品,情趣,情趣,情趣,情趣,按摩棒,震動按摩棒,微調按摩棒,情趣按摩棒,逼真按摩棒,G點,跳蛋,跳蛋,跳蛋,性感內衣,飛機杯,充氣娃娃,情趣娃娃,角色扮演,性感睡衣,SM,潤滑液,威而柔,香水,精油,芳香精油,自慰套,自慰,性感吊帶襪,吊帶襪,情趣用品加盟AIO交友愛情館,情人歡愉用品,美女視訊,情色交友,視訊交友,辣妹視訊,美女交友,嘟嘟成人網,成人網站,A片,A片下載,免費A片,免費A片下載愛情公寓,情色,舊情人,情色貼圖,情色文學,情色交友,色情聊天室,色情小說,一葉情貼圖片區,情色小說,色情,色情遊戲,情色視訊,情色電影,aio交友愛情館,色情a片,一夜情,辣妹視訊,視訊聊天室,免費視訊聊天,免費視訊,視訊,視訊美女,美女視訊,視訊交友,視訊聊天,免費視訊聊天室,情人視訊網,影音視訊聊天室,視訊交友90739,成人影片,成人交友,美女交友,微風成人,嘟嘟成人網,成人貼圖,成人電影,A片,豆豆聊天室,聊天室,UT聊天室,尋夢園聊天室,男同志聊天室,UT男同志聊天室,聊天室尋夢園,080聊天室,080苗栗人聊天室,6K聊天室,女同志聊天室,小高聊天室,上班族聊天室,080中部人聊天室,同志聊天室,聊天室交友,中部人聊天室,成人聊天室,一夜情聊天室,情色聊天室,寄情築園小遊戲情境坊歡愉用品,情境坊歡愉用品,情趣用品,成人網站,情人節禮物,情人節,AIO交友愛情館,情色,情色貼圖,情色文學,情色交友,色情聊天室,色情小說,七夕情人節,色情,情色電影,色情網站,辣妹視訊,視訊聊天室,情色視訊,免費視訊聊天,美女視訊,視訊美女,美女交友,美女,情色交友,成人交友,自拍,本土自拍,情人視訊網,視訊交友90739,生日禮物,情色論壇,正妹牆,免費A片下載,AV女優,成人影片,色情A片,成人論壇,情趣,免費成人影片,成人電影,成人影城,愛情公寓,成人影片,保險套,舊情人,微風成人,成人,成人遊戲,成人光碟,色情遊戲,跳蛋,按摩棒,一夜情,男同志聊天室,肛交,口交,性交,援交,免費視訊交友,視訊交友,一葉情貼圖片區,性愛,視訊,視訊聊天,A片,A片下載,免費A片,嘟嘟成人網,寄情築園小遊戲,女同志聊天室,免費視訊聊天室,一夜情聊天室,聊天室