一共有36种设计模式,23种里面有些还需要细分,你去看菜鸟教程网站的设计模式,那就不止罗列了23个例子,达到三十多种了,有些是变种,有些是python独有例如猴子补丁技术。
虽然这是我自己亲自写得36个python文件例子,但介绍23种设计模式的博客 教程 网站文档 烂大街了,毫不稀奇。
但是这个项目里面介绍的 oop四部转化公式,是项目最重要的部分,没有任何人是我这么总结如何写oop代码的,学校老师和书本只会教你背诵 封装 继承 多态 这几个词语本身.你只会看到下面这段类似的话:
面向对象编程(OOP) 是一种以对象为核心的编程范式,将数据(属性)和操作(方法)封装成类,通过继承实现代码复用,多态实现灵活扩展。 适用于复杂系统(如游戏、GUI),代表语言:Java、C++、Python。
核心特点:
- 封装:隐藏细节,暴露接口,增强安全性。
- 继承:子类复用父类功能,减少冗余代码。
- 多态:同一方法在不同类中有不同实现,提高灵活性。
好处:
✅ 模块化:代码结构清晰,易于维护。
✅ 复用性:继承减少重复开发。
✅ 扩展性:新增功能不影响旧代码。
✅ 易协作:对象抽象更贴近现实,方便团队开发。
然后呢,你背诵上面了这段空洞理论的话,并没有什么作用,因为话术很空洞很虚幻,说了好像又什么都没说,无法有固定实操套路落地,没有给你实操步骤,也没有给你具体例子比较面向过程,没有比较根本原因为什么需要oop。
编程时候,设计新的模块或者项目,下不了笔,要思考三天三夜怎么设计,主要是没学设计模式。
编程时候,有时候脑子一瞬间感觉 灵光出现,觉得这么写台好了太合适了,这样写节约代码 扩展高,瞬间感到这是个天才想法,
主要是由于没有专门学习设计模式,只能偶尔灵机一动,形成不了一套稳定的输出,所以才会有这种突然感觉某个代码设计是天才设计这种感受。
不要纠结是23种还是36种了,有细分的,举个例子 观察者模式和发布订阅模式,是有了少许变化,多了解一种总没啥坏处。
为什么是36种,因为是摘自菜鸟教程或者w3cschool网站,这两个网站都是36种,包含了j2ee模式,多了解没坏处。
不愿意学oop和设计模式,天天加班写bug解bug无限复制粘贴扣字老代码写新代码,这种写法瞎忙也没用。
比如韩信3万军队歼灭70万大军,靠的不是大无畏的胡乱冲刺,而是因为韩信看了孙子兵法36计兵书,靠的是策略,所以我要称之为36种设计模式。
设计模式十分有用,是通过大量实践对比得出来的,比如和自己国王写代码的纵向对比总结,和公司n多个现有代码项目的横向对比总结得出来的结论。
使用设计模式或oop转化公式,能使几乎任意项目代码减少50%至90%行,编码速度快一倍。
扩展性和维护性提高数十倍,bug减少5倍。
如果是使用极端纯面向过程(或者加入了少量无效废物类(假装 OOP 的类壳代码))的方式来写python代码,
我不用看,就知道一定很low。
学弟啊,你代码能不能不要写得这么烂 https://www.toutiao.com/a6739759206997426696/
面向对象和面向过程分别是什么? https://www.zhihu.com/question/28790424
如何通俗易懂地举例说明「面向对象」和「面向过程」有什么区别? https://www.zhihu.com/question/27468564
对于程序员来说,设计模式和算法哪个更重要呢? https://www.zhihu.com/question/25432487
为什么我们需要学习(设计)模式 https://zhuanlan.zhihu.com/p/19835717
使用这个公式,不需要背设计模式,不需要每次写新文件先花几天想破头皮怎么设计布局代码成为高扩展和高维护。
忘掉设计模式,使用最强最稳定最有固定套路的万能编程思维-python oop四步转化公式
这个oop四步走转化公式是最重要的,其他的36个设计模式python例子你在很多地方都能看到类似的。
先简要说明四步转化公式,具体的看上面链接的文档
第0步是灵魂步骤,后面3步是死板的代码形式转化。你不做这一步,直接做后面三步,那就是写出无效废物装逼滑稽类(假装 OOP 的类壳代码),因为写出来也是面向过程的类。
python模块文件下面多写全局变量,全局变量要多多益善;
多个函数之间不要疯狂传递一大堆相同名字或意义的入参;
每个函数末尾不要疯狂return一大堆中间状态变量;
不要把这些return出来的变量,频繁传递给另外一个函数;
你应该尽可能全部设计成全局变量,然后每个函数里面去修改这一堆全局变量,这样就大幅避免了多个函数频繁传递一大堆入参,也不需要每个函数末尾疯狂return一大堆中间状态变量。
这一步是脑子打草稿,多写全局变量,每个函数少传一大堆入参,少写return一大堆结果变量。
这一步和老师教你要少写全局变量不同,似乎是反智,但请往下看。
就是要把模块级代码移到class 类下面
写个class 类,就是把你之前脑子打的草稿,模块 + 全局变量 + 函数,改成 类+实例属性+实例方法
之前为什么让你多写全局变量,就是为了这一步,全局变量降级为实例属性。你以为最终目的是为了多写全局变量吗?多写全局变量肯定是不行的是反语言的。
函数改为实例方法,但是函数第一个入参要加个self。
之前模块函数里面直接用全局变量 var_xx,现在实例方法里面读取和修这个"全局变量",使用 self.var_xx。
转化oop要有灵魂的第0步,如果跳过此步骤,直接执行第1到3步,就会想当然以为转化oop就是函数外面加个class关键字外壳,觉得转化没用。
那是因为你不按照我说的步骤走,造成写成了无效废物装逼滑稽面向过程类(假装 OOP 的类壳代码),当然会觉得不需要转化。oop要的是内涵不要搞形式class外壳。
最根本原因是因为模块是单例的,模块的变量是全局唯一的,例如按照第五章的代码你 描述一个人 吃喝拉撒(函数) 影响人的身高体重(全局变量)。
如果你要调用 小红 小明 小黄 小黑 4个人吃喝拉撒,由于全局变量只有一份,那会互相影响,因为全局变量没有隔离。小红调用吃饭,为什么要增加小明的体重?
所以你为了避免这种全局变量不适合多个人调用吃喝拉撒的问题,就写出了类似5.1教程的代码,写成了多个函数间疯狂传参,疯狂return一大堆变量,把这些变量再外部保存,并在函数间传来传去。
既想写代码简单粗暴,全局变量加函数的思维,又要解决 全局变量只有一个,不能隔离的问题。那就是使用oop开发代码,
oop的类天然就是多实例的,每个实例的属性是互不干扰的,天然解决全局变量只有一份的问题。
oop写代码后,类的每个对象的方法访问和修改实例属性,就像函数里面操作全局变量那样丝滑,并且是每个实例的属性天生是各自的,不会互相干扰的。
你调用 小红.吃饭() 再也不用担心影响到 小明.体重 了。
有的人就是杠精,我说 "考公务员都是精致的利己主义者,就是为了工资高 永不失业 退休金高";
他却辩证法说 "也有考公务员是为了成为高尚的人民公仆,例如雷锋就是不喜欢钱,就是喜欢把国家发的工资和退休金都捐给农村老人用"。
最怕有的人,完全不去思考为什么需要oop,自己懒得要死,上来就来一句 "有时候面向过程编程更简单更好" 的话 或者 "只有大项目才需要oop" 的话。
不要这么使用辩证法偷懒好吗。我没说过任何场景一定要用oop,最最关键的判断依据是你需不需要多实例, 你不需要多实例那就可以不要面向对象写法,
也不需要单例模式装逼写个类,你直接模块加函数就好了,反正不需要全局变量隔离的需求。
例如本文档第5章节,描写一个人吃喝拉撒运动影响了身高体重,这个需求是非常非常简单吧,绝对是你口中的小项目,绝对是你认为的不需要oop。
但事实就是,用oop来完成需求,比面向过程写法实现时候简单多了,最重要的是调用时候简单太多了。
为什么我写的所有包,例如 funboost nb_time nb_log都很好用,都是用法十分简单但功能十分强大的python包,原因就是在于此。
假如你来封装python三方包或框架给别人用,如果你不按我的思路写代码,就会造成不仅你自己实现框架时候很麻烦、很难扩展、爱出错、不好维护,
更重要的是你把这个包发布出来后,即使功能能达到目的,但用户会感觉极端难使用、不好用、不方便。
例如5.1写法封装的模块,用户来调用人.吃饭() 和人.撒尿() ,用户需要风控传一大堆入参和接受并保存一大堆返回,体验太差了。
- 需要多实例 → 用OOP
- 不需要多实例 → 用面向过程(模块+函数)
问函数和类的区别,这简直是牛头不对马嘴的伪问题。
函数和方法可以比区别。函数和类比区别就是问 人和上班 、猪和吃东西 有什么区别一样,简直是没搞清楚比较维度。
类和模块可以比,类主要优势是多实例,多个实例的属性是互不干扰的,每个实例的多个方法都可以直接访问实例属性(成员变量)。
面向过程是一直很low的在函数间把本应该在oop中声明为成员变量的东西弄成在函数间反复传参和return。
类 + 方法 + 实例属性(成员变量) 约等于 模块 + 函数 + 全局变量
1)对于类,类的无数个实例化对象的实例属性(成员变量)是互不干扰的,天然的多实例。
2)对于模块 python的模块在当前解释器中是唯一的,import 一万次 xx.py,xx模块都是唯一的指向同一个地方,
所以使用全局变量 加 函数逇设计方式是行不通的,xx模块是唯一的,xx模块的a 变量 b变量也是惟一的,
所以老是需要设计成吧a和b弄成传参和return而不是全局变量来解决全局变量只有一份的问题。
所以如果需要多实例,类 + 方法 + 实例属性(成员变量) 远好于 模块 + 函数 + 全局变量。
模块 + 函数 + 全局变量 由于要模拟多实例,使全局变量(状态) 不唯一,大部分情况下需要写成 函数反复传参和反复return。
这种面向过程的封装的写法曲折程度毫无疑问被oop暴击了,面向过程导致写代码慢想破头皮搞一堆传参和return。
何况oop不只有封装还有继承和多态。
能写类的人100%就能写函数,能写函数的人不一定能写类(说的是真oop的类,排除无效废物面向过程滑稽类(假装 OOP 的类壳代码)),这是不可逆的。
只爱学python语法,只学怎么使用if else break, 学字典 列表怎么用,代码就很low
写代码很慢,设计慢,想的慢,打字慢
代码不可维护,没有高扩展性
写新文件要想破头皮三天三夜才能开始下笔。
代码体验分为两部分,第一部分是作者自己封装公共代码时候是否爽快容易扩展维护;第二部分是别人调用你封装的公共包是否感觉爽快好用。
三个写法思路分别是: 封装痛苦,调用更苦 封装爽快,调用更爽 封装爽快,调用痛苦
情不自禁纯粹极端面向过程编程还是c语言中毒了,从来都不使用面向对象,不用提设计模式。
有些人完全没思考过面向对象,一味的只会说"大型项目才需要面向对象,小项目面向过程更合适"。简直是胡说八道,不看场景只看项目大小。
比如一个简单需求,描写人,吃饭 增加体重和身高,拉尿体重降低。需求是非常非常的简单了吧,绝对是非常小的项目,单个文件就好了,来验证下是不是小项目面向过程就好了。
这个面向过程代码实现很麻烦,要频繁传很多入入参和return大量参数;调用时候更加痛苦;
所以面向过程开发工程师不仅自己写代码麻烦,还造成封装的公共包使用很麻烦
# 吃饭函数
def eat(name, height, weight, food_weight):
"""吃饭:增加体重和身高"""
new_weight = weight + food_weight
new_height = height + food_weight * 0.01
print(f"{name} 吃了 {food_weight} 千克食物,体重: {new_weight} 千克,身高: {new_height} 厘米")
return new_height, new_weight
# 拉尿函数
def pee(name, height, weight, pee_weight):
"""拉尿:减少体重"""
if pee_weight > weight:
pee_weight = weight
new_weight = weight - pee_weight
print(f"{name} 拉了 {pee_weight} 千克尿,体重: {new_weight} 千克,身高: {height} 厘米")
return height, new_weight
# 测试代码
if __name__ == "__main__":
# 小明的初始状态
xiaoming_height = 170
xiaoming_weight = 60
# 小红的初始状态
xiaohong_height = 160
xiaohong_weight = 50
print(f"小明 初始状态 - 身高: {xiaoming_height} 厘米,体重: {xiaoming_weight} 千克")
print(f"小红 初始状态 - 身高: {xiaohong_height} 厘米,体重: {xiaohong_weight} 千克")
# 小明吃饭和拉尿,调用封装的函数太恶心了,传了一大堆入参,还要return一大堆变量,还要在调用处保存这些变量,如果把小红的体重和小明的姓名传混乱就乱了。
xiaoming_height, xiaoming_weight = eat("小明", xiaoming_height, xiaoming_weight, 2)
xiaoming_height, xiaoming_weight = pee("小明", xiaoming_height, xiaoming_weight, 1)
# 小红吃饭和拉尿,调用封装的函数太恶心了,传了一大堆入参,还要return一大堆变量,还要在调用处保存这些变量
xiaohong_height, xiaohong_weight = eat("小红", xiaohong_height, xiaohong_weight, 3)
xiaohong_height, xiaohong_weight = pee("小红", xiaohong_height, xiaohong_weight, 2)
这个oop面向对象代码,实现时候很丝滑很容易,不需要在每个方法频繁传一大堆入参,和每个方法return 一大堆变量;
用户在调用时候很简单很爽快。
class Person:
def __init__(self, name, height, weight):
self.name = name
self.height = height # 身高,单位:厘米
self.weight = weight # 体重,单位:千克
def eat(self, food_weight):
"""吃饭:增加体重和身高"""
self.weight += food_weight
self.height += food_weight * 0.01
print(f"{self.name} 吃了 {food_weight} 千克食物,体重: {self.weight} 千克,身高: {self.height} 厘米")
def pee(self, pee_weight):
"""拉尿:减少体重"""
if pee_weight > self.weight:
pee_weight = self.weight
self.weight -= pee_weight
print(f"{self.name} 拉了 {pee_weight} 千克尿,体重: {self.weight} 千克,身高: {self.height} 厘米")
# 测试代码
if __name__ == "__main__":
# 创建小明和小红
xiaoming = Person("小明", 170, 60)
xiaohong = Person("小红", 160, 50)
# 小明吃饭和拉尿, 调用太爽了,不用传一大堆入参,不用return一大堆变量,不用在调用处保存这些中间变量
xiaoming.eat(2)
xiaoming.pee(1)
# 小红吃饭和拉尿,调用太爽了,不用传一大堆入参,不用return一大堆变量,不用在调用处保存这些中间变量
xiaohong.eat(3)
xiaohong.pee(2)
面向对象起码有封装,极端面向过程那就实现时候,每个函数结尾需要疯狂的return 一大堆变量,然后将一大堆变量传给另外一个函数进行处理。
而面向对象不需要你把一大堆入参疯狂在各个函数传来传去,return来return去。
如果你给我说,定义一个结构体或者字典来存放 身高 体重 姓名,然后充分利用字典是可变类型,这样既减少了每个函数的入参数量,
又不需要return一大堆变量,还是可以用面向过程来写这个代码,
就避免了上面我说的疯狂传参和疯狂return的弊端,那么继承和多态阁下有如何应对呢?
比如大人吃1kg长0.1kg体重,小孩吃1kg长0.2kg体重,并且我假设eat函数调用了另外一个_eat2的私有函数来具体的执行体重增加计算,
你在面向过程时候,就要加一个 大人eat的函数 加一个 大人_eat2 的函数,
并且大人eat的函数里面调用大人_eat2的函数,在小孩eat的函数里面调用小孩_eat2的函数, 整个链路的就都要替换,
而面向对象仅仅需要增加一个大人的类,继承重写 _eat2这个私有方法就好了。
你想下纯粹极端面向过程实现公共代码时候有多low多复杂,并且调用它的时候有多麻烦。
如果你的思维是c语言中毒了,情不自禁极端面向过程编程,连入门的面向对象封装概念都没有,更别提更高阶的设计模式了。
这个写法思路不需要多个函数之间频繁传参,实现代码时候很丝滑平顺。!!!但是 但是 但是调用时候无法多实例隔离全局变量,实际不可理喻!
老师和任何教程都说过,写代码要少用全局变量,在编程实践中应当尽量减少其使用。csdn或者ai去搜索 "少用全局变量" 的博客一大堆,自己去看为什么,不展开啰嗦了。
这种 使用模块 + 全局变量多多益善 + 函数 的写法,来实现吃喝拉撒影响身高体重,实现代码时候的确很丝滑,
因为这个代码实现时候,完全不需要频繁在不同函数之间传一大堆入参,也不需要每个函数return一大堆变量,所以实现起来很爽快。
但是调用时候,由于全局变量是唯一的,所以调用时候无法多实例隔离全局变量,实际不可理喻!因为你一会儿要小红吃饭撒尿影响小红体重,一会儿要小明吃饭撒尿影响小明体重,但姓名 身高 体重 由于是全局变量在内存中只有一份,虽然实现爽,但实际上可调用性很差。
如果你的场景是脚本单独单次运行,不存在同时需要实现小明 小红 小黑 来复用调用公共包的需求,那你可以使用这种面向过程写法,我从没说过任何场景一定要用oop,一定要你使用oop四步走转化公式写个class类。
这种写法平铺直叙很爽快,适合在单个脚本来单次自己使用,不适合作为公共包被其他地方或其他人调用复用。
所以再次印证了,我上面说的,什么时候应该用面向对象,什么时候可以使用面向过程,不是以项目代码长度来作为依据。而是以是否需要多实例作为依据
- 需要多实例 → 用OOP
- 不需要多实例 → 用面向过程(模块+函数)
# 全局变量多多益善
name = ""
height = 0
weight = 0
def eat(food_weight):
"""吃饭:增加体重和身高"""
global weight, height
weight += food_weight
height += food_weight * 0.01
print(f"{name} 吃了 {food_weight} 千克食物,体重: {weight} 千克,身高: {height} 厘米")
def pee(pee_weight):
"""拉尿:减少体重"""
global weight
if pee_weight > weight:
pee_weight = weight
weight -= pee_weight
print(f"{name} 拉了 {pee_weight} 千克尿,体重: {weight} 千克,身高: {height} 厘米")
# 测试代码 - 写代码很丝滑!
if __name__ == "__main__":
# 小明的情况
name = "小明"
height = 170
weight = 60
eat(2) # 不需要传一堆参数!
pee(1) # 不需要return和接收一堆变量!
# 但是现在要处理小红,全局变量就乱了...
name = "小红" # 这会覆盖掉小明的数据
height = 160
weight = 50
eat(3) # 小红吃饭,但前面小明的状态丢失了
文章第三章介绍了万能的oop四步走转化公式,你可以在脑子里打草稿使用5.3的写法,然后使用4步走转化公式,转化成oop写法。
如果你是面向过程工程师,不知道怎么设计oop。
请你一定要按照5.3的 模块 + 全局变量多多益善 + 函数 实现代码思路,然后套用oop四步走转化公式。
千万不要再使用5.1的 模块 + 无全局变量 + 多个函数之间频繁传一堆参数 + 每个函数末尾风控return一大堆变量 的写法。
你使用5.1的写法,就是在面向过程一条道走到黑,不光自己实现代码麻烦难扩展维护,更严重的别人调用你封装的公共包还贼恶心贼难用。
如何上面用真实代码证明的,何时需要使用oop。
- 需要多实例 → 用OOP
- 不需要多实例 → 用面向过程(模块+函数)
为什么建议不确定是否要多实例时候,全部写成类oop形式?
因为你现在不确定啊,oop是多实例,即使你永远不需要多实例,但你可以只实例化一次,多实例的实现能兼容单例调用。 反过来,你一开始写成模块 + 全局变量 + 函数,那就是个单例,如果你需要多实例,单例不能反过来兼容多实例调用的需求。
就好比你设计一个api接口,一个订单下面的商品,是设计成数组还是单个元素?
如果你设计成单个元素,万一以后老板说一个订单能购买多个不同的商品呢?如果你一开始设计成单个元素,
原来的api接口设计和代码就无法满足需要大改特改了。
反之,如果你之前是设计成订单下的商品是一个数组,即使一个订单只能购买一个商品,你数组也能兼容里面只包含一个元素,
而且也能兼容以后一个订单购买多个商品。
这说的是可逆和不可逆的区别。
例如5.3的代码,你原来只需要小明来调用可以,你后面要小明 小红 小黑都调用,那就全局变量无法隔离,无法满足需求。
如5.3代码所示,全局变量在函数里面使用并修改时候,需要麻烦的使用global 声明一下,这正是python设计之初,担心用户随意修改污染全局变量。
如果吧一组相关功能写一个模块,那要分成很多个py文件,py文件太多了。
而如果把不相关函数放在一个模块,那就很混乱,不知道哪一组函数是负责a功能的,哪一组函数是负责b功能的。
如果写成class类,有代码缩进,通过不同的类名有代码快缩进,就能知道哪些函数是负责什么的,你一个py文件下的100个函数分贝是什么作用能划分更清晰。
先oop再考虑设计模式不迟。只情不自禁使用面向过程,那无法使用所有设计模式。