如何维护多依赖的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,例如:
- 我切换到不同的数据库
- 我切换到不同的日志库
有人可能会争辩说,我应该让客户端 class 处理异常,而不是在此处记录。但是在客户端只是 yield
ing 要插入的文档并且接受索引操作失败的情况下,客户端 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。如果您更改记录错误的方式,情况也是如此。只需实例化一个新的具体记录器,然后注入它。
我对 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,例如:
- 我切换到不同的数据库
- 我切换到不同的日志库
有人可能会争辩说,我应该让客户端 class 处理异常,而不是在此处记录。但是在客户端只是 yield
ing 要插入的文档并且接受索引操作失败的情况下,客户端 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。如果您更改记录错误的方式,情况也是如此。只需实例化一个新的具体记录器,然后注入它。