我正在编写一个新的库,我想保留一些对象。我想使用mixin或某种适配器,所以我不必立即实现数据库。我现在正在使用泡菜来存储对象。
假设我有一个User类。如果泡菜存在,我想从文件夹中加载用户。我编写了一个Persistor类,该类接受一个对象并将其写入指定的位置。我可以使User类继承自Persistor类吗?如果是这样,当实例化User类时,如果出现泡菜,该如何用已加载的对象替换该对象?还是创建UserPersistor类?我只是想从User类中抽象出状态的加载和保存。
class User(Persistor???):
"""
Central class to hold all user attributes and relationships.
"""
def __init__(
self,
first_name: str,
username: str,
date_of_birth: datetime.date
):
self.first_name = first_name
self.username = username
self.date_of_birth = date_of_birth
import pickle
import os
class Persistor:
"""
Class whose job is to save and load state until we need a database.
"""
def __init__(
self,
persistence_key
):
self.persistence_key = persistence_key
self.persistence_path = "data/" + persistence_key
@property
def save_exists(self):
return os.path.exists(self.persistence_path)
def save(self):
outfile = open(self.persistence_path, 'wb')
pickle.dump(self, outfile)
outfile.close()
def load(self):
if self.save_exists:
infile = open(self.persistence_path, 'rb')
db = pickle.load(infile)
infile.close()
return db
def delete(self):
if self.save_exists:
os.remove(self.persistence_path)
答案 0 :(得分:0)
简单的答案(这与Python特定的FWIW无关,这只是普通的OO设计):从语义上讲,继承表示“是a”关系-因此,如果B继承自A,那么B也是A(参见{ {3}})。因此,真正的问题是:您是否认为User
是Persistor
?
Liskov substitution principle是另一种常见,合理且被广泛接受的设计原则,该原则指出每个组件应具有单一的,明确定义的责任感。因此,问问自己自己是否真的希望持久性实现详细信息成为您的User
类职责的一部分...
关于最后一对OO设计指南,在GoF的“设计模式”书的(长期)介绍(实际上是关于OO设计FWIW的最佳文字之一)中重复了-即使是模式目录也很重要):您应该更喜欢组合/委托而不是继承(从技术上讲,继承是一种受限的组合/委托形式),并且应该始终编程为接口(Java术语中的“ interface”-在在大多数语言中,这意味着抽象的基类),而从没有实现(具体的类)。通过将两者结合在一起,您将获得易于测试,维护和发展的良好分离的代码。例如,如果您使User
类继承自Persistor
(这是一个具体类),则无法轻松地切换到其他Persistor实现,而如果您使用组合/委托和抽象基类定义唯一的Persistor API,您可以(理论上...)使用您的User
类的任何Persistor实现。
现在这是理论上的事情,现实是某些抽象趋向于泄漏-例如,要使用pickle协议进行持久化,您的User
类必须是可腌制的,所以这不是<完全是透明的,因此将上述原则与一小撮盐结合起来-比起黄金法则,更可作为思考的食物。就我而言,我可能会在这里使用组合/委托作为最安全的选择,并最终在实施过程中重新考虑整个设计,因为实施通常会掩盖设计缺陷(如果至少可以发现它们)。
编辑
据我回忆,由于多重继承,您不需要在Python中使用接口。这会改变仅适用于Python的“是”规则吗?
实际上,继承有两个目的:子类型化(如上所述),还有代码重用-这就是GoF将继承描述为“组合/委托的受限形式”的原因(子类对它的引用父级并自动委派其部分操作。
子类型化是OO中非常重要的一部分,因为它构成了通用代码和多态调度的基础-因此,给定的代码片段可以与来自不同具体对象的对象集合完全相同地工作。 em>类型(实现),只要它们实现相同的接口即可。
语义上,“接口”是一组定义的(公共)功能(方法,属性,属性等)。现在,技术上定义界面的方式取决于语言。
静态类型的语言需要类实现的接口的正式定义,以便它可以执行编译时检查并最终进行优化。请注意,我写了“ interfaceS”-一个类可以实现多个接口,这实际上是一种很常见的情况。
对于支持多重继承的(静态)语言,显而易见的解决方案是将抽象基类用于接口定义。 Java不支持多重具体继承(这是一种设计选择),因此它具有一个名为“ interfaces”的独特构造-但这实际上是纯抽象基类的另一种名称(“ pure”:仅定义,完全没有实现)>
在动态语言中,实际上并不需要(技术上的)正式接口定义(因为没有正式的类型检查),因此“接口”为the single responsability principle-在动态语言中,您经常会发现诸如“列表”之类的文档术语-like”,“类似于文件”等(表示行为类似于列表或文件的对象)。
现在,尽管这在实践中很有效(至少在大多数情况下是这样),但是对于形式化接口定义,仍然有很多要说的东西,即使是仅用于文档(以及仅用于简单的多态分派并且您必须检查您的对象以找出您应该使用的对象-考虑对任意嵌套的字典和各种对象列表的结构进行递归)-因此Python 也有quite often informal,并且实际上,建议将它们用于任何不重要的项目。
在所有情况下,“子关系”在某种程度上仍然成立(即使子类不一定按照Liskov的定义是正确的子类型),因为子类确实继承了其父类。隐式接口。
NB:Python也有“协议”,它们是类可以实现的形式更正式的特殊方法集,它们将赋予此类实例某些特殊行为(参见abstract base classes或{{3 }}。
我可以让我的用户从object和Persistor继承,这将是python表示获得该功能的方式吗?
当然可以,您对此没有任何技术限制,这只是一个设计问题:您的User类也应该是Persistor,还是“更好”(取决于“更好”的定义),有两个明显不同的类-一个用于域模型(用户),一个技术性用于持久性(例如UserPersistor)。
如果我将持久性逻辑委托给Persistor类,我仍然需要从User类代码中调用它
不一定...如果您确定持久性不是用户的责任,那么您可以反过来查看:持久化调用您的User对象-在这种情况下,您实际上并不甚至根本不需要组成/委派。
如果您有自己的GoF,我强烈建议您在继续进行设计决策之前花一些时间阅读它,并注意第一部分。不过只是警告:完成后,避免使用项目中所有和所有模式的意图,请始终问问自己对于当前的具体问题是否有意义以及是否会产生额外的复杂性(如果有)对您代码的这一部分有用;-)