在 Ruby 中惯用地创建外观

Creating facade idiomatically in Ruby

我来自 Java,正在尝试用惯用的 Ruby 实现 Facade。我可以看到 Rails' ActiveRecord 喜欢使用 class 方法来处理 find_by(criteria) 之类的事情,并且不为该任务使用存储库模式。

我的 Facade 使用多种方法包装特定的 Web 服务。我最初的想法是API类似于ActiveRecord(模仿学习):

class MyEntity
  # ....

  def get_name
    @loaded_name + @loaded_surname
  end

  def delete
    @entity_access_service.delete(@id)
  end

  def save 
    @entity_access_service.save(@id, @loaded_name , @loaded_surname)
  end

  def self.find(id)
    data = @entity_access_service.get_data_for(id)
    MyEntity.new(data) #Or whatever way to populate my entity
  end
end

从理论上讲,这会很有效:

e = MyEntity.find(10)
p e.get_name
e.delete

或者:

e = MyEntity.new(some stuff)
e.save

问题: 为了使 savedelete 实例方法起作用,我需要以某种方式获取 EntityAccessService 的实例。该实例应该是可模拟的,以便在隔离环境中对其进行测试。正确的做法是什么?

我希望我的测试看起来尽可能简单并且没有一些奇怪的技巧,因为我试图实现的东西看起来相当微不足道。

我想到了几种选择:

  1. 有一个 class 级变量保存 entity_access_service 由应用程序中创建的所有实体使用。这样的话,应该在哪里初始化这个字段呢?例如:

    class MyEntity
      @@entity_access_service = nil
    end
    
    # Somewhere else (where?):
    MyEntity.entity_access_service = MyEntityService.new(some_params_from_env)
    

    这样,在我的测试中,我必须在开始时 initialize/mock 它。

  2. 与1类似,但在class中初始化。这看起来很奇怪,特别是如果我知道我的测试根本没有填充必需的 ENV 参数。

  3. 多了一个constructor/attribute来设置entity_service。这不会起作用,因为 save 不会初始化此字段。

  4. 创建一个Repositoryclass。这会工作得很好,但似乎不是 Ruby 人们所做的。

按照 ActiveRecord 的示例,您可以在 class 本身或派生其他 class 的基础 class 上创建一个方法。

ActiveRecord 提供了一个方法ActiveRecord::Base.connection,其中returns 连接对象,所有模型都使用它来访问数据库。你可以做类似的事情:

class MyEntity
    ....

    def self.entity_access_service
      # return your service object
    end

    def self.find(id)
        MyEntity.entity_access_service.get_data_for(id)
        MyEntity.new(data) # Or whatever way to populate my entity
    end

    def save() 
        MyEntity.entity_access_service.save(@id, @loadedName, @loadedSurname)
    end
end

就初始化而言,您要么必须在您的应用程序(和测试套件)中有一个初始化步骤,其中服务凭证从配置文件中读取并传递到您的 MyEntity 对象中, 您的 entity_access_service 方法可以在第一次访问时使用非常常见的 Ruby 惯用语懒惰地创建对象 returns:

def self.entity_access_service
  @entity_access_service || = # build entity_access_service object
end

请注意,通过将 class 级实例变量包装在 class 级访问器方法中,您可以避免使用 @@ 这是一个 recommended best practice .