Dependency Injection in Ruby

Tags:

Dependency Injection: Vitally Important or Totally Irrelevant?

Ruby 하는 사람들이 늘 주장하는 것이, “Ruby는 워낙 언어가 동적이어서 DI가 필요없거든요” 이죠. 하지만 웬걸, 잘만 하면서. 어쨌든 사실 DI 를 안하고 하는 방법도 있습니다.

첫번째 방법은 C++의 템플릿을 연상시키는 방법입니다.

def initialize(cls1, cls2)
    @cls1 = cls1.new
    @cls2 = cls2.new
end

두번째 방법은 OpenStruct와 팩토리를 쓰는 방법입니다. 무슨 dynamic 하다는 언어가 tuple도 없나 싶어서 황당해 하고 있었는데, 아닌게 아니라 OpenStruct가 그거군요. (여담이지만 tuple을 C++에서 구현한 Modern C++ Design 을 떠올린다면 언어가 dynamic 하다는게 얼마나 많은 자유를 주는지 정말 상상하기도 힘들 정도.)

factory = OpenStruct.new
factory.cls1 = Class1 # cls1 field is automatically generated for factory instance
factory.cls2 = Class2 # ditto here.

def initialize(factory)
    @cls1 = factory.cls1.new # cls1 is textually substituted with Class1
    @cls2 = factory.cls2.new
end

하여간 별게 다 되는 ruby..

마지막으로 DIM 이라는 Dependency Injection / Minimal 의 소스 코드입니다.

#!/usr/bin/env ruby
#–
# Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.
#
# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#++
#
# = Dependency Injection – Minimal (DIM)
#
# The DIM module provides a minimal dependency injection framework for
# Ruby programs.
#
# Example:
#
# require ‘dim’
#
# container = DIM::Container.new
# container.register(:log_file) { “logfile.log” }
# container.register(:logger) { |c| FileLogger.new(c.log_file) }
# container.register(:application) { |c|
# app = Application.new
# app.logger = c.logger
# app
# }
#
# c.application.run
#
module DIM
# Thrown when a service cannot be located by name.
class MissingServiceError < StandardError; end # Thrown when a duplicate service is registered. class DuplicateServiceError < StandardError; end # DIM::Container is the central data store for registering services # used for dependency injuction. Users register services by # providing a name and a block used to create the service. Services # may be retrieved by asking for them by name (via the [] operator) # or by selector (via the method_missing technique). # class Container # Create a dependency injection container. Specify a parent # container to use as a fallback for service lookup. def initialize(parent=nil) @services = {} @cache = {} @parent = parent || Container end # Register a service named +name+. The +block+ will be used to # create the service on demand. It is recommended that symbols be # used as the name of a service. def register(name, &block) if @services[name] fail DuplicateServiceError, "Duplicate Service Name '#{name}'" end @services[name] = block end # Lookup a service by name. Throw an exception if no service is # found. def [](name) @cache[name] ||= service_block(name).call(self) end # Lookup a service by message selector. A service with the same # name as +sym+ will be returned, or an exception is thrown if no # matching service is found. def method_missing(sym, *args, &block) self[sym] end # Return the block that creates the named service. Throw an # exception if no service creation block of the given name can be # found in the container or its parents. def service_block(name) @services[name] || @parent.service_block(name) end # Searching for a service block only reaches the Container class # when all the containers in the hierarchy search chain have no # entry for the service. In this case, the only thing to do is # signal a failure. def self.service_block(name) fail(MissingServiceError, "Unknown Service '#{name}'") end end end [/code]

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *