遵循工厂模式从控制器调用服务方法的技术问询
工厂模式在Ruby服务类中的正确实现
你提到的直接用EvaluateService.new实例化或者当前的单例实现,确实在一些场景下会有局限——比如硬编码实例化会让代码耦合度高,单例又可能带来测试不便(比如无法轻松替换实例)。下面几种工厂模式的实现方式可以解决这些问题,适配不同的场景:
1. 服务类内置的工厂方法(简单工厂)
如果你的服务逻辑相对单一,可以在服务类里提供一个清晰的工厂方法,替代直接new或者单例的evaluate_service。这种方式既保持了封装性,又比单例更灵活:
class EvaluateService # 私有化构造方法,强制通过工厂方法创建实例 private_class_method :new def initialize # 初始化逻辑,比如依赖的配置、其他服务等 end def get_url # 业务逻辑 end # 工厂方法:对外暴露的实例创建入口 def self.create new end # 如果确实需要复用实例(类似单例但更可控),可以用带参数的工厂方法 def self.instance @instance ||= create end end # 在控制器中调用 class CheckController < ApplicationController def index evaluate_service = EvaluateService.create # 或者如果需要单例:evaluate_service = EvaluateService.instance url = evaluate_service.get_url end end
这里把new设为私有,强制外部通过create或instance获取实例,既规范了实例化方式,也方便后续修改实例创建逻辑(比如需要传入初始化参数时,只改工厂方法就行)。
2. 独立的工厂类(工厂方法模式)
如果你的系统中有多个类似的服务类(比如EvaluateService、ValidateService),或者服务的创建逻辑比较复杂(比如需要根据不同参数创建不同子类实例),可以用独立的工厂类来统一管理实例创建:
# 定义服务接口(Ruby中用模块或父类实现) module Service def get_url; end end class EvaluateService include Service def initialize(config) @config = config end def get_url # 业务逻辑 end end class ValidateService include Service def initialize(config) @config = config end def get_url # 另一种业务逻辑 end end # 独立的工厂类 class ServiceFactory def self.create(service_type, config) case service_type when :evaluate EvaluateService.new(config) when :validate ValidateService.new(config) else raise ArgumentError, "Unknown service type: #{service_type}" end end end # 控制器中调用 class CheckController < ApplicationController def index config = { api_key: ENV['API_KEY'] } evaluate_service = ServiceFactory.create(:evaluate, config) url = evaluate_service.get_url end end
这种方式把实例创建的逻辑从业务类中分离出来,降低了耦合,也让扩展新服务变得更简单——只需要新增服务类和工厂类中的case分支即可。
3. 结合依赖注入的工厂模式(适合测试友好的场景)
如果你的服务有外部依赖(比如数据库连接、其他API客户端),可以用工厂模式配合依赖注入,让测试时能轻松替换依赖:
class EvaluateService def initialize(url_fetcher) @url_fetcher = url_fetcher end def get_url @url_fetcher.fetch end end # 工厂类负责注入默认依赖 class EvaluateServiceFactory def self.create default_fetcher = DefaultUrlFetcher.new EvaluateService.new(default_fetcher) end # 测试时用的工厂方法,允许传入自定义依赖 def self.create_with_fetcher(fetcher) EvaluateService.new(fetcher) end end # 控制器中使用默认实例 class CheckController < ApplicationController def index evaluate_service = EvaluateServiceFactory.create url = evaluate_service.get_url end end # 测试代码示例 RSpec.describe EvaluateService do it "returns the correct url" do mock_fetcher = double("UrlFetcher", fetch: "https://example.com") service = EvaluateServiceFactory.create_with_fetcher(mock_fetcher) expect(service.get_url).to eq("https://example.com") end end
这种方式让服务类的依赖更清晰,测试时可以用mock对象替换真实依赖,极大提升了代码的可测试性。
总结一下,选择哪种工厂模式取决于你的业务场景:
- 简单服务用内置工厂方法即可,简洁又规范;
- 多服务场景用独立工厂类,统一管理创建逻辑;
- 注重测试和可维护性的场景,优先考虑结合依赖注入的工厂模式。
内容的提问来源于stack exchange,提问作者Kingsley Simon
相关产品推荐
相关产品推荐

