Replace Conditional with Polymorphism 重构在您看到 same 条件散布在整个代码中时最有效。当您需要添加一种新的行为类型时,您必须找到并更改每个条件以适应新选项。相反,我们将条件逻辑集中在一个地方 - 创建多态对象的代码 - 让 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()
您可以看到检查日志类型的条件逻辑执行了 3 次,每次都略有不同。如果我们需要添加一种新类型的日志记录,例如通过电子邮件记录错误,我们必须遍历整个脚本并在每个日志语句中添加另一个 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 并修改创建它的单个条件即可。代码也更简洁:记录器类隐藏了它们如何工作的所有细节,隐藏在一组简单的方法后面,这些方法具有面向记录的名称。