【问题标题】:How to maintain SRP (Single Responsibility Principle) with multiple dependencies?如何维护具有多个依赖项的 SRP(单一职责原则)?
【发布时间】:2017-12-10 17:41:18
【问题描述】:

我对一个类必须依赖于其他因素的情况感到困惑。

例如

class Storage:
    def __init__(self):
        self.logger = Logger()
        self.client = Elasticsearch()

    def index(document):
        try:
            self.client.index(document)
         except ElasticsearchException as e:
             self.logger.error(str(e))

我的班级必须有记录器和Elasticsearch 对象才能执行其操作。在这种情况下,我如何维护 SRP,可能有两种情况需要更改我的类,例如:

  1. 我切换到不同的数据库
  2. 我切换到不同的日志库

有人可能会争辩说,我应该让客户端类处理异常,而不是在这里记录。但是在客户端只是yielding要插入的文档并且可以接受索引操作失败的情况下,客户端类不会担心错误。此外,即使我将异常重新抛出到客户端类,那里也会出现同样的 SRP 问题。

我希望能在我的上下文中给出解释性答案。

【问题讨论】:

    标签: python oop design-patterns single-responsibility-principle


    【解决方案1】:

    我认为部分问题出在标题中:“.. 具有多个依赖项”。您的依赖项是高度耦合的,因为在您的 Storage 类中实例化。 这就是为什么我会使用依赖注入(我有 0 的 python 知识,可能是一些错字):

    interface StorageClientInterface:
        def index(document)    
    
    interface LoggerInterface:
        def error(Exception e) 
    
    class ElasticSearchStorage implements storageClientInterface:
        def index(document):
            // implements ElasticSearch specific storage logic
    
    class MyDefaultLogger implements LoggerInterface:
        def error(Exception e):
            // implements MyDefaultLogger specific logging logic, totally agnostic from ElascticSearch
    
    class Storage:
        def __init__(self, StorageClientInterface storageClient, LoggerInterface logger):        
            self.client = storageClient
            self.logger = logger
    
        def index(document):
            try:
                self.client.index(document)
             except Exception as e:
                 self.logger.error(e)
    
    
    // usage
    elasticSearch = ElasticSearch()
    logger = MyDefaultLogger()
    document = Document();
    storage = Storage(elasticSearch, logger)    
    storage.index(document)
    

    这样,您的 Storage 类不会与您的存储策略或日志记录策略耦合。它只知道它可以使用这两个接口。如果您更改数据库,则不必更改 Storage 类中的任何内容,只要此新存储策略实现您的 StorageClientInterface。如果您更改记录错误的方式,也是如此。只需实例化一个新的具体 Logger,然后注入它。

    【讨论】:

    • 很多人考虑Dependency Injection is EVIL。我倾向于同意。
    • DI 可能不是所有问题的解决方案,但它显然不是邪恶的,在发布者面临的这个特殊问题中,它确实以一种优雅且可维护的方式解决了它。即使您链接的文章的作者也这么说,尽管他显然不喜欢 DI:“依赖注入仅在消费对象具有可以在运行时在多个替代方案之间切换的依赖项时才是一个好主意,并且选择在哪里可以在消费对象之外制作使用的替代品,然后将其注入其中。”。这看起来确实像这里面临的问题
    • 此外,我从上到下阅读了您链接的文章。在阅读完这篇文章后,我唯一确定的是,当我看到他作为示例编写的代码的质量,或者他使用的是自编码 php 框架的事实时,我不会听从这个人所说的任何建议在商业项目中,当我们拥有非常优秀的开源坚如磐石的框架时,成千上万的贡献者可以确保它们的质量。
    【解决方案2】:

    您可以通过引入额外的层来为此功能定义抽象 API 来做到这一点:一个用于数据库,另一个用于记录日志。完成此操作后,您的 Storage 类必须仅限于使用它们,而不是直接调用或由特定库或模块公开的任何“真实”方法。

    这样他们(以及他们的客户,比如重写的Storage 类)就不需要更改,除非由于某种原因必须更改其中一个抽象接口(如果设计得好,这将不是必需的)。这两个抽象接口中的任何一个的任何具体实现都只有一个职责(即通过某些特定日志记录或数据库库中可用的东西来模拟抽象 API)。

    【讨论】:

      【解决方案3】:

      实现此目的的一种方法是使用decorator 模式。装饰器模式将允许您将所有错误处理逻辑封装在一个单独的类中,然后可以使用该类从本质上包装装饰类的行为,在这种情况下,它就是您的 Storage 类。我不是很了解 Python,所以请原谅任何语法错误,但它看起来像这样:

      class Storage:
          def __init__(self, storageClient):        
              self.client = storageClient
      
          def index(document):
              self.client.index(document)
      
      
      class ElasticSearchExceptionPolicy:
          def __init__(self, decorated, logger):
              self.logger = logger
              self.decorated = decorated
      
          def index(document):
              try:
                  decorated.index(document)
              except ElasticsearchException as e:
                  self.logger.error(str(e))
      

      然后可以像这样使用这些对象:

      elasticSearch = ElasticSearch()
      logger = Logger()
      storage = Storage(elasticSearch)    
      loggedStorage = ElasticSearchExceptionPolicy(storage, logger)
      loggedStorage.index(document)
      

      如果您想遵循 Open/Closed 原则,您可能还希望将 ElasticSearch 对象和 Logger 对象传递到它们各自的类中。

      【讨论】:

      • 在我看来,您刚刚将问题从一个班级转移到另一个班级。此外,通过装饰器将责任注入类似乎只是绕过 SRP 设计方法施加的关键限制的一种偷偷摸摸的方式。
      • @martineau 嗯。也许是我对 Python 的误解。这两个类现在只有一个改变的原因,Storage 类如果存储机制发生变化,而 ElasticSearchLogger 如果异常策略发生变化(假设类的依赖项被注入)。
      • 我已经编辑了示例以便注入依赖项。
      • 我确信不同设计方法中的概念(和术语)是重叠的。在这种情况下,协调事情的方法可能是说存在另一个类,其唯一职责是以期望的方式一起使用这两个其他类。
      • 经过一番研究,似乎其他人也声称这是维护 SRP 的一种方式——换句话说,以这种方式使用的装饰器模式可以维护它。标题为How to Implement a Decorator Pattern 的文章中的一个示例称这种方法“相当于对单一职责原则的良好实施”。正如我所说,我个人认为这只是一种躲闪,这只是我的看法。
      猜你喜欢
      • 2011-05-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多