如何维护具有多个依赖关系的SRP(单一责任原则)?

时间:2017-12-10 17:41:18

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

在一个班级必须依赖其他因素的情况下,我很困惑。

例如

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

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

  1. 我切换到了不同的数据库
  2. 我切换到了不同的日志库
  3. 有人可能会争辩说,我应该让客户端类处理异常,而不是记录在这里。但是在客户端只是 yield 要插入的文档并且接受索引操作失败的情况下,客户端类不会对错误感到烦恼。此外,即使我将异常重新抛出到客户端类,也会出现相同的SRP问题。

    我很感激在我的背景下给出解释性答案。

3 个答案:

答案 0 :(得分:2)

您可以通过引入其他层来为此功能定义抽象API:一个用于数据库,另一个用于执行日志记录。完成此操作后,您的Storage课程必须仅限于使用它们,而不是直接打电话或任何真实的课程。特定库或模块暴露的方法。

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

答案 1 :(得分:2)

我认为问题的一部分在标题中:“..具有多个依赖性”。您的依赖项是高度耦合的,因为在您的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类不会与您的存储策略或日志记录策略相关联。它只知道它可以使用这两个接口。如果更改数据库,则只要此新存储策略实现StorageClientInterface,就不必更改Storage类中的任何内容。如果您更改记录错误的方式也是如此。只需实例化一个新的具体Logger,然后注入它。

答案 2 :(得分:0)

您可以实现此目的的一种方法是使用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)

如果您想遵循开放/封闭原则,您可能还希望将ElasticSearch对象和Logger对象传递到各自的类中。