如何维护多依赖的SRP(单一职责原则)?

How to maintain SRP (Single Responsibility Principle) with multiple dependencies?

我对 class 必须取决于其他因素的情况感到困惑。

例如

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))

这里我的 class 必须有记录器和一个 Elasticsearch 对象来执行它的操作。在可能有两种情况需要更改我的 class 的情况下,我如何维护 SRP,例如:

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

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

如果能根据我的情况给出解释性答案,我将不胜感激。

您可以通过引入额外的层来为此功能定义抽象 API 来实现此目的:一个用于数据库,另一个用于执行日志记录。这样做之后,您的 Storage class 必须仅限于使用它们,而不是直接调用或特定库或模块公开的任何 "real" 方法。

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

完成此操作的一种方法是使用 decorator 模式。装饰器模式将允许您将所有错误处理逻辑封装在一个单独的 class 中,然后可用于实质上包装装饰 class 的行为,在这种情况下将是您的存储 class。我不太了解 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 对象传递到它们各自的 classes 中。

我认为部分问题出在标题中:“.. 具有多个依赖项”。您的依赖项是高度耦合的,因为在您的存储 class 中实例化了。 这就是为什么我会使用依赖注入(我有 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)

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