V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
shinonome
V2EX  ›  Python

《 Python 工匠》中多态的使用有一些疑惑

  •  
  •   shinonome · 18 小时 5 分钟前 · 676 次点击

    文中给了一个案例

    class FancyLogger:
        """日志类:支持向文件、Redis 、ES 等服务输出日志"""
    
        _redis_max_length = 1024
    
        def __init__(self, output_type=OutputType.FILE):
            self.output_type = output_type
            ...
    
        def log(self, message):
            """打印日志"""
            if self.output_type == OutputType.FILE:
                ...
            elif self.output_type == OutputType.REDIS:
                ...
            elif self.output_type == OutputType.ES:
                ...
            else:
                raise TypeError('output type invalid')
    
        def pre_process(self, message):
            """预处理日志"""
            # Redis 对日志最大长度有限制,需要进行裁剪
            if self.output_type == OutputType.REDIS:
                return message[:self._redis_max_length]
    
    

    FancyLogger 类在日志输出类型不同时,需要有不同的行为。因此,我们完全可以为“输出日志”行为建模一个新的类型:LogWriter ,然后把每个类型的不同逻辑封装到各自的 Writer 类中。

    class FileWriter:
        def write(self, message):
            ...
            
    
    class RedisWriter:
        max_length = 1024
    
        def write(self, message):
            message = self._pre_process(message)
            ...
    
        def _pre_process(self, message):
            """Redis 对日志最大长度有限制,需要进行裁剪"""
            return message[:self.max_length]
    
    
    class EsWriter:
        def write(self, message):
            ...
    

    基于这些不同的 Writer 类,FancyLogger 可以简化成下面这样:

    class FancyLogger:
        """日志类:支持向文件、Redis 、ES 等服务输出日志"""
    
        def __init__(self, output_writer=None):
            self._writer = output_writer or FileWriter()
            ...
    
        def log(self, message):
            self._writer.write(message)
    

    文中对这样的写法好处解释为 代码利用多态特性,完全消除了原来的条件判断语句。另外你会发现,新代码的扩展性也远比旧代码好。

    但是在我看来, 你要传什么 output_writer 不还是要通过 if 来选择吗, 只是把一个地方的 if 换到了另外一个地方,

    扩展性 这个模块看起来确实好了, 但是总感觉和上面一样, 这里提高了, 但是其他地方就要更多 if, TVT, 面向对象还是没有入门

    8 条回复    2025-01-24 16:10:10 +08:00
    shinonome
        1
    shinonome  
    OP
       18 小时 4 分钟前
    @piglei 作者大佬能看看我的问题吗
    zepc007
        2
    zepc007  
       17 小时 51 分钟前
    emmm, 你可以理解为设计模式
    yooomu
        3
    yooomu  
       17 小时 27 分钟前
    抽离出 writer 之后,添加新的 writer 类型不再需要修改原来的类了,只需要编写一个新的 writer 实现就行了。体现了设计模式的开闭原则
    zhu327
        4
    zhu327  
       17 小时 24 分钟前
    这里如果有个工厂模式来实例化不同的 logger 的话,从上层看的话就不需要理解不同的 if 之间的差异了,可以理解为一种封装形式,隐藏不同 logger 之前的细节,对外提供统一的用户接口
    ajunno
        5
    ajunno  
       17 小时 19 分钟前
    > 你要传什么 output_writer 不还是要通过 if 来选择吗, 只是把一个地方的 if 换到了另外一个地方

    这个成立吗?换到哪里了呢?我的观点是,`FancyLogger`内部解耦了类型判断——针对事物的行为建模,而不是对事物本身建模——分支语句是完全消失了,而不是转移(到外部)了。从外部使用者的角度来说,是完全一样的。
    newaccount
        6
    newaccount  
       17 小时 2 分钟前
    改之前:FancyLogger 需要知道每一个 Writer 类型,所以必须有 if 判断来根据不同的参数选择不同的具体实现

    改之后:FancyLogger 与 Writer 解耦,具体的输出工作由 Writer 来完成,FancyLogger 不知道也不关心具体实现

    你所指的 if 判断由 FancyLogger 转移到调用方的这个前提是不存在的,调用方直接实例化一个 Writer 实现类,或者通过工厂方法来获取配置的具体 Writer 对象
    dajj
        7
    dajj  
       16 小时 30 分钟前
    实际上取决于谁写,如果两个类都是一个人维护,没意义
    如果 Writer 需要其它的人写,那就有意义了
    增加了灵活度,也增加了复杂度
    两种都是正确的做法,不要厚此薄彼
    NoOneNoBody
        8
    NoOneNoBody  
       15 小时 35 分钟前
    FancyLogger 就是个“通用代码”,参考#7
    FancyLogger 相当于一个 switcher

    代码创建者 A 完成三个 Writer 类,给下游未知的人使用
    如果有 FancyLogger 这段通用代码,不管谁,只需要知道类名就可以了
    writer = FancyLogger(classname())
    如果没有这段通用代码,使用者 B 可能需要三个 class 都研判一次,写出 if 逻辑才能使用
    当然,如果使用者 B 熟悉这三个 class ,他也可以不使用 FancyLogger ,而自己另外写代码

    还有一种非常好用的情况
    _writer = read_from(...) 反序列化或者从其他途径获得,这时使用者 C 甚至不知道 classname 是哪个,只知道 FancyLogger ,因为不知道具体 class 就无法写 if 逻辑,这时也能使用
    writer = FancyLogger(_writer) 导入并继续之前的工作
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1023 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 23:45 · PVG 07:45 · LAX 15:45 · JFK 18:45
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.