《 Python 工匠》中多态的使用有一些疑惑 - V2EX
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 258 天前 2471 次点击
    这是一个创建于 258 天前的主题,其中的信息可能已经有所发展或是发生改变。

    文中给了一个案例

    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, 面向对象还是没有入门

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

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

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

    你所指的 if 判断由 FancyLogger 转移到调用方的这个前提是不存在的,调用方直接实例化一个 Writer 实现类,或者通过工厂方法来获取配置的具体 Writer 对象
    dajj
        7
    dajj  
       258 天前
    实际上取决于谁写,如果两个类都是一个人维护,没意义
    如果 Writer 需要其它的人写,那就有意义了
    增加了灵活度,也增加了复杂度
    两种都是正确的做法,不要厚此薄彼
    NoOneNoBody
        8
    NoOneNoBody  
       258 天前
    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) 导入并继续之前的工作
    piglei
        9
    piglei  
       256 天前   2
    > 但是在我看来, 你要传什么 output_writer 不还是要通过 if 来选择吗, 只是把一个地方的 if 换到了另外一个地方,

    你的理解已经很接近:“多态”确实无法完全消除“if”。这是因为,们总是需要用代码来表达某种“如果/否则”的逻辑。但和普通的“if/else”比起来,面向对象设计的区别在于,它会努力将这些“如果/否则”逻辑封装在不同的实现(`XWriter`)中,让“if/else”代码只存活于代码的边缘区(工厂函数),从核心区中销声匿迹。

    什么是核心区?显而易见,是 FancyLogger 中实际完成日志打印的部分;什么是边缘区?在例子中,用 if/else 分支去创建对应的 Writer 实例的代码,前置于打印日志功能,可以被看作身处边缘区。对比例子中的两种方式:

    1. 无 Writer 抽象时:核心区需要理解**全部的“如果/否则”逻辑**,才能完成日志打印
    2. 有 Writer 抽象时:在边缘区创建 writer 实例,传递给核心区,后者**一视同仁**调用 writer 完成日志打印

    本书中的代码示例,因篇幅原因实现的功能比较简单,不同代码之间所产生的对比可能不够强烈。当业务逻辑变得更复杂后,利用多态特性来提炼并封装“如果/否则”的优势会变得更显著。在我心目中,那是在整个“面向对象”中,最富有魅力的地方之一。
    shinonome
        10
    shinonome  
    OP
       256 天前
    感谢各位大佬的答疑, 有一点知道面向对象在干什么了
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3941 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 04:11 PVG 12:11 LAX 21:11 JFK 00:11
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86