我最近阅读了一篇文章/代码片段,其中显示了使用多态替换条件的示例。这是代码:
在:
def log_msg(log_type):
msg = 'Operation successful'
if log_type == 'file':
log_file.write(msg)
elif log_type == 'database':
cursor.execute('INSERT INTO log_table (MSG) VALUES ('?')', msg)
在:
class FileLogger(object):
def log(self, msg):
log_file.write(msg)
class DbLogger(object):
def log(self, msg):
cursor.execute('INSERT INTO log_table (MSG) VALUES ('?')', msg)
def log_msg(obj):
msg = 'Operation successful'
obj.log(msg)
Here是我从中得到它的地方。
现在我的问题是,第二种方法比第一种更好吗?据我所知,如果我想使用第二种方法,每次我想记录某些东西时都必须做这样的事情:
if log_type == 'file':
log_msg(FileLogger())
elif: log_type == 'database':
log_msg(DbLogger())
我是否错过了非常明显的观点?
答案 0 :(得分:5)
当您在整个代码中看到相同的条件时,Replace Conditional with Polymorphism重构最有效。当您需要添加新类型的行为时,您必须查找并更改每个条件以适应新选项。相反,我们将条件逻辑集中在一个地方 - 创建多态对象的代码 - 并且让OO的语义为我们处理其余部分。
这是一个比较恶劣的稻草人形式的记录示例。
if log_type == "file":
log_file.write("DEBUG: beginning script")
elif log_type == "database":
cursor.execute("INSERT INTO log_table (Level, Msg) VALUES ('DEBUG', 'beginning script')")
try:
file = open("/path/to/file")
lines = file.readlines()
if log_type == "file":
log_file.write("INFO: read {} lines".format(len(lines)))
elif log_type == "database":
cursor.execute("INSERT INTO log_table (Level, Msg) VALUES ('INFO', 'read {} lines')".format(len(lines)))
except:
if log_type == "file":
log_file.write("ERROR: failed to read file")
elif log_type == "database":
cursor.execute("INSERT INTO log_table (Level, Msg) VALUES ('ERROR', 'failed to read file')")
raise
finally:
if log_type == "file":
log_file.write("INFO: closing file")
elif log_type == "database":
cursor.execute("INSERT INTO log_table (Level, Msg) VALUES ('INFO', 'closing file')")
file.close()
您可以看到检查日志类型的条件逻辑执行了三次,每次都略有不同。如果我们需要添加新类型的日志记录,例如通过电子邮件记录错误,我们必须遍历整个脚本并在每个日志语句中添加另一个elif
,这容易出错并且很麻烦。
很难一目了然地看到剧本实际上在做什么,因为它已经淹没了实际进行日志记录的细节。
因此,这是替换条件多态性的一个很好的候选者。以下是重构后的记录器类:
class AbstractLogger:
def debug(self, msg):
self.log("DEBUG", msg)
def info(self, msg):
self.log("INFO", msg)
def error(self, msg):
self.log("ERROR", msg)
def log(self, level, msg):
raise NotImplementedError()
class FileLogger(AbstractLogger):
def __init__(self, file):
self.file = file
def log(self, level, msg):
self.file.write("{}: {}".format(level, msg))
class DatabaseLogger(AbstractLogger):
def __init__(self, cursor):
self.cursor = cursor
def log(self, level, msg):
self.cursor.execute("INSERT INTO log_table (Level, Msg) VALUES ('{}', '{}')".format(level, msg))
我使用了继承来避免在FileLogger和DatabaseLogger类之间重复过多的代码。
这是脚本:
# create the logger once at the start
if log_type == "file":
logger = FileLogger(log_file)
elif log_type == "database":
logger = DatabaseLogger(cursor)
logger.debug("beginning script")
try:
file = open("/path/to/file")
lines = file.readlines()
logger.info("read {} lines".format(len(lines)))
except:
logger.error("failed to read file")
raise
finally:
logger.info("closing file")
file.close()
现在添加新类型的日志记录要容易得多:只需编写EmailLogger
并修改创建它的单个条件。代码也更清晰:记录器类隐藏了一组简单的方法后面的所有细节,这些方法使用了面向日志的名称。
答案 1 :(得分:1)
关键是你通常只在程序的某个早期点创建了一个记录器对象。那么你只需做log_msg(myLogger)
,它会自动做正确的事情,无论你最初是否决定使用基于文件或基于数据库的日志记录。
换句话说,您的代码看起来像这样
# beginning of file
from logmodule import FileLogger, DBLogger, log_msg
myLogger = FileLogger()
# lots of other code here. . .
# later if you want to log something:
log_msg(myLogger)
稍后,您可以返回并将开头更改为myLogger = DBLogger()
,一切仍然有效。我们的想法是在progrma的开头创建记录器,一旦你创建了它,你就不必担心你创建的那种,你也可以使用它。
请注意,此示例(包括您最初发布的代码)只是一个骨架;它不是你可以直接使用的代码。首先,此代码不提供任何指定日志文件名的方法。我在这里描述的只是为什么要重构这样的代码。
答案 2 :(得分:1)
我认为您真正要寻找的是这样的东西,您可以避免使用字典和复制实例化以及适当的set()方法实例化的许多if子句。我承认,这与多态性没有太大关系,但是可以解决if子句实例化的问题。
import copy
class Father:
def __init__(self):
self.string = "Wargs"
def printString(self):
print(self.string)
class A(Father):
def __init__(self):
pass
def set(self, anything):
self.string = "A:"+anything
class B(Father):
def __init__(self):
pass
def set(self, anything):
self.string = "B:"+anything
class_dict = {"A": A(),
"B": B()}
A_copy = copy.deepcopy(class_dict["A"])
A_copy.set("Happy")
A_copy.printString()
#Also possible:
class_dict2 = {"A": A,
"B": B}
A_cl = class_dict2["A"]()
A_cl.set("Happy")
A_cl.printString()
我不确定,但是我认为使用函数装饰器也可以实现这种行为,那么您将克服运行时期间对哈希表的访问,甚至更快。