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]