Python条件替换多态

时间:2014-07-01 18:32:35

标签: python oop polymorphism conditional-statements

我最近阅读了一篇文章/代码片段,其中显示了使用多态替换条件的示例。这是代码:

在:

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())

我是否错过了非常明显的观点?

3 个答案:

答案 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()

我不确定,但是我认为使用函数装饰器也可以实现这种行为,那么您将克服运行时期间对哈希表的访问,甚至更快。