Python:如何设计一个元素必须引用其容器的容器
(标题确实不太好。请原谅我的英语,这是我能想到的最好表达方式。)
我正在写一个Python脚本,用来管理电子邮件域名和它们的账户,同时我对面向对象编程(OOP)设计还是个新手。我的两个(相关的?)问题是:
- 域名类(Domain class)需要做一些特殊的工作来添加和删除账户,比如在底层实现中添加或删除它们。
- 如何管理账户上的操作,这些操作必须通过它们的容器来进行。
为了解决第一个问题,我打算在域名类中添加一个工厂方法,这个方法会在该域名下创建一个账户实例,还有一个“删除”(反工厂?)方法来处理删除操作。
至于第二个问题,我觉得这有点“反面向对象”,因为本来应该是对账户的操作(比如,修改密码),却总是要引用包含它的域名。我觉得我必须在账户中添加一个指向域名的引用,利用这个引用来获取数据(比如域名)或调用域名类的方法。
代码示例(元素使用来自容器的数据),管理一个底层的 Vpopmail 系统:
class Account:
def __init__(self, name, password, domain):
self.name = name
self.password = password
self.domain = domain
def set_password(self, password):
os.system('vpasswd %s@%s %s' % (self.name, self.domain.name, password)
self.password = password
class Domain:
def __init__(self, domain_name):
self.name = domain_name
self.accounts = {}
def create_account(self, name, password):
os.system('vadduser %s@%s %s' % (name, self.name, password))
account = Account(name, password, self)
self.accounts[name] = account
def delete_account(self, name):
os.system('vdeluser %s@%s' % (name, self.name))
del self.accounts[name]
另一种选择是让Account.set_password调用一个域名的方法来完成实际工作——这对我来说听起来同样糟糕。
还要注意数据的重复(账户名也作为字典的键),这听起来合理(账户名在一个域名中是“主键”),但账户需要知道自己的名字。
编辑:请注意,上面的代码只是一个快速示例,可以把它当作伪代码。它故意不考虑错误情况或安全问题,并且在类的数据和方法上是不完整的(每个用户的垃圾邮件设置、自动回复、转发、获取邮箱大小等……)。
此外,这只是我手头的一个示例,但我认为它可以推广到其他类似树形结构的不同逻辑结构,其中节点必须知道它们的子节点,而子节点必须调用父节点(或更高层的祖先)来执行某些操作。对我来说,这在逻辑上类似于类的继承,但应用于相互关联的不同类型(类)的实例。
4 个回答
我觉得在Domain
类里不需要有创建或删除账户的方法。我更倾向于这样做:
class Account:
def __init__(self, name, password, domain):
...
def activate(self):
self.domain.add(self)
os.system('vadduser %s@%s %s' % (name, self.domain.name, password))
def deactivate(self):
self.domain.remove(self)
os.system('vdeluser %s@%s' % (name, self.domain.name)
如果你有很多这样的对象之间的关系,我认为标准的做法是使用数据库。在Python中,最流行的数据库工具之一是SQLAlchemy。它可以有效地存储这些关系并进行查找(还有很多其他功能)。不过在你的例子里,这样做显然有点过于复杂,我想唯一的选择就是像我代码里那样手动处理。
在Python中,人们通常会避免使用递归关系,因为Python的垃圾回收机制通常是通过引用计数来实现的。
最简单的解决办法是:在容器内部进行需要的操作。
稍微复杂一点的解决办法是:当容器被查询某个对象时,创建一个临时的代理对象,这个代理对象同时持有对容器和被包含对象的引用,并且实现被包含对象的接口;然后返回这个代理对象,而不是直接返回被包含的对象。
对于你提到的操作,实际上不太清楚你是否真的需要Account
这个东西。它唯一保存的信息就是密码,而这些信息在Domain
里已经有重复了。你可以直接把Domain.accounts
设置成一个查找表,格式是用户名: 密码
。
在你需要之前,别随便增加身份相关的类。
顺便说一下,当你有一些对象是由其他对象拥有时,通常会让这些对象知道它们的“主人”,并在需要的时候向上沟通。Python没有像某些语言那样的内部类的概念来表示这种拥有关系。
另外,别把字符串拼接成命令行来用os.system
;这样做会有很大的安全风险。可以看看subprocess
模块,它提供了一种更安全、更简单的方式来传递参数。