python设计模式(1)

系列文章目录

提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加
例如:第一章 Python 设计模式原则


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


设计模式

1. 概述

设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。

1.1 起源

1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆(Richard Helm)、拉尔夫·约翰森(Ralph Johnson)、约翰·威利斯迪斯(John Vlissides)等 4 位作者合作出版了 《设计模式:可复用面向对象软件的基础》 一书,在此书中收录了 23 个设计模式,这是设计模式领域里程碑的事件,导致了软件设计模式的突破。

1.2 设计模式四要素

设计模式有四个基本要素:

  1. 模式名称(pattern name):一个助记名,它用一两个词来描述模式的问题、解决方案和效果。
  2. 问题(problem):描述了应该在何时使用模式。
  3. 解决方案(solution):描述了设计的组成成分,它们之间的相关关系以及各自的职责和协作方案。
  4. 效果(consequences):描述了模式应用的效果以及使用模式应该权衡的问题。

1.3 设计模式分类

创建型模式(5个):与对象的创建有关,将对象的创建与使用分离。
单例、原型、工厂方法、抽象工厂、建造者
结构型模式(7个):与对象的组装有关,将类或对象按某种布局组成更大的结构。
代理、适配器、桥接、装饰、外观、享元、组合
行为模式(11个):类或对象之间的沟通协调有关,类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。
模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器

1.4 Python中的设计模式特点

Python支持动态类型(duck-typing),函数是一等
公民,并且一些模式(例如,迭代器和修饰器)是内置特性。



2 面向对象

2.1 面向对象设计的基本原则

  1. 针对接口编程,而不是针对实现编程
  • 编程时针对超类型进行编程,而不是具体某个子类。
  • 超类型中各个方法的具体实现不在超类型中,而在各个子类中。
  • 超类型通常是一个接口或者一个抽象类。
  1. 优先使用对象组合,而不是类继承。
  • 用聚合/组合复用,去代替继承复用。可以避免继承所带的方法污染问题。
  • 把一些特征和行为抽取出来,形成工具类。
  • 然后通过聚合/组合成为当前类的属性。
  • 再调用其中的属性和行为达到代码重用的目的。
class Plane:
	def fly():
		print("fly in the sky")

class Ship:
	def swim():
		print("swim in the sea")
		
class ManKind:
	p = Plane()
	s = Ship()
	def fly():
		p.fly()
	def swim():
		s.swim()
  1. 封装变化,将不变的与变化的内容分开
  • 找出应用中可能需要的变化之处,把他们独立出来(封装),不要和哪些不需要变化的代码混在一起。
    • 我们实现一个鸭子,且这个鸭子有很多种,且有各个属性。我们应该如何设计这个鸭子呢?
    • 首先鸭子不变的属性有哪些? 外观,游泳。等(先定义这两个)。
    • 鸭子变的属性有哪些? 有的会叫,有的会飞等。


3 UML图

3.1 面向对象分析设计工具

统一建模语言 (Unified Modeling Language,UML) 是用来设计软件的可视化建模语言。

3.2 UML 2.2中一共定义了14种图示

结构性图形(Structure diagrams) 强调的是系统式的建模:

  • 静态图(static diagram)
    • 类图
    • 对象图
    • 包图
  • 实现图(implementation diagram)
    • 组件图
    • 部署图
  • 剖面图
  • 复合结构图
    行为式图形(Behavior diagrams)强调系统模型中触发的事件:
  • 活动图
  • 状态图
  • 用例图
    交互性图形(Interaction diagrams),属于行为图形的子集合,强调系统模型中的资料流程:
  • 通信图
  • 交互概述图(UML 2.0)
  • 时序图(UML 2.0)
  • 时间图(UML 2.0)

3.3 UML中的一些概念

UML从来源中使用相当多的概念。下面仅列代表性的概念。

对于结构而言

  • 执行者,属性,类,组件,接口,对象,包。

对于行为而言

  • 活动,事件,消息,方法,状态,用例。

对于关系而言

  • 聚合,关联,组合,相依,广义化(或继承)。

其他概念

  • 构造型—这规范符号应用到的模型
  • 多重性—多重性标记法与资料库建模基数对应,例如:1, 0…1, 1…*
  • 角色

3.4 类图表示

@startuml
class Flight {
flightNumber : String
departureTime : DateTime
check_cancel()
}
@enduml

3.5 类与类的关系

依赖
@startuml
Class1 …> Class2
@enduml
关联
@startuml
Class1 --> Class2
@enduml
泛化
@startuml
Class1 --|> Class2
@enduml
实现
@startuml
Class1 …|> Class2
@enduml



4 设计原则-SOLID原则

原则不是具体描述操作步骤和细节,而是给出抽象的要求。用原则来判断我们的设计是否满足要求。

4.1 SOLID 原则

什么是 SOLID原则:

  • S 一 单一职责原则,
  • O 一 开放封闭原则,
  • L 一 里氏替换原则,
  • I 一 接口隔离原则,
  • D 一 依赖倒置原则
    谁提出来的,背景是什么《敏捷软件开发:原则、模式与实践》
    SOLID原则有什么用?
  • 指导程序员开发出易于维护和扩展的软件。
    SOLID的发展史
    这几个原则之间有没有先后顺序?

4.2 单一职责原则

  1. 什么是职责
    功能,逻辑 等。
  2. 使用动机(why)
    若不遵守单一职责原则,即一个类有一个以上的职责,则当一个职责发生变化时,可能会影响其他职责,从而影响代码的维护。
  3. 如何使用(how)
    核心在于职责的分解。不同的职责分开到不同的类的实现中去。
  4. 使用原则
  • 每一个类实现的职责有清晰明确的定义。
  • 一个类的修改只对自身有影响,对其他类没有影响。
  1. 使用示例
    案例:动物奔跑和游泳拆分成两个类

4.3 开放封闭原则

  1. 什么是开闭
  • 软件实体(类、模块、函数等)应该可以扩展,但是不可以修改。
  • 即对于扩展是开放的,对于更改是封闭的。
  • 通俗来说就是对于要增加的新功能或要调整的改动,尽量扩展新代码而不是修改已有代码。
  1. 使用动机(why)
    代码面对需求改变可以保持相对稳定
  2. 如何使用(how)
    创建抽象,隔离。
  3. 使用原则
  • 仅对程序中呈现出频繁变化的部分做出抽象。
  • 不要刻意对每个部分进行抽象,拒绝不成熟的抽象,它和抽象本身一样重要。
  1. 使用示例
  • 计算机算子类:包括±*/;解析器类:括号解析,复数解析

增加功能时,不去改动已有的代码。当修改一个模块时,不影响其他模块。
案例:动物园展示不同动物的活动方式displayActivity。最初是通过if else判断动物的类型来展示活动方式(running,swimming)。根据开闭原则的要求,创建一个动物抽象类,抽象出moving操作,不同的动物继承抽象类,重写moving方法。displayActivity的时候,只需要调用moving方法即可。

4.4 里氏替换原则

  1. 什么是里氏替换
    在软件里面,把父类都替换成它的子类,程序的行为没有变化。简单来说,子类型必须能够替换掉它们的父类型。
  2. 使用动机(why)
    确保父类能够真正复用(继承),子类也能够在父类的基础上增加新的行为。
  3. 如何使用(how)
  • 父类一般使用抽象类或接口。
  • 抽象类定义公共对象和状态;接口定义公共行为。
  • 子类通过继承父类和接口进行扩展。
  1. 使用原则
  • 子类方法的参数类型必须与父类相匹配或更抽象。
  • 子类的返回值类型必须与父类或其子类相匹配。
  • 子类方法的异常必须与父类能抛出的异常(或其子类)相匹配。
  • 子类不应该加强参数条件限制。
  • 子类不能修改父类的私有成员变量。
  1. 使用示例
    企鹅继承鸟,但是企鹅不会飞,违反了里氏替换原则。合理的做法是将飞翔的行为抽象为接口,父类鸟描述状态和公共方法(比如吃),然后会飞的子类再去实现飞翔接口,不会飞的就不用管了。
    只要父类能出现的地方子类就能出现。
    案例:猴子既会跑又会爬树,猴子继承陆生动物,增加新方法climbing,动物园为猴子增加一个新方法。这个新方法只有猴子类能出现。其父类不能出现。

4.5 依赖倒置原则

  1. 什么是依赖倒置
  • 程序不应该依赖细节,细节应该依赖于抽象。
  • 简单来说,就是要针对接口编程,不要针对实现编程。
  1. 使用动机(why)
    面对不同的具体实现做到易拔插,松耦合。
  2. 如何使用(how)
  • 接口或抽象类制定规范,不涉及任何具体的操作。
  • 实现类完成展现细节的任务。
  • 程序中的所有依赖关系都终止于抽象类或接口。
  1. 使用原则
  • 高层模块不应该依赖低层模块,两个都应该依赖抽象。
  • 抽象不应该依赖细节,细节应该依赖抽象。
  1. 使用示例
    计算机接口的统一

把具有相同特征或相似功能的类,抽象成接口或抽象类。让具体的实现类继承这个抽象类,实现对应的接口。抽象类负责定义统一的方法,实现类负责具体功能的实现。
案例:动物吃食物。如果每个动物吃的事物都写成具体的品种(肉,草,虫子)那么就有有两个问题:

  1. 每种动物都需要为其定义一个食物类,高度依赖于细节
  2. 每种动物只能吃一种食物,不符合现实。
    这时候就需要遵循依赖倒置原则,来对食物类重新设计,抽象出一个基类,在根据基类创建实现类。

4.6 接口隔离原则

  1. 什么是接口隔离原则

  2. 使用动机(why)

  3. 如何使用(how)

  4. 使用原则

  5. 使用示例

建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,接口中的方法尽量少。为不同的类别的类建立专用接口,而不要试图建立一个很庞大的接口供所有依赖它的类调用。
案例,有的动物可以有多种行为,例如蝙蝠会飞,天鹅会飞会游泳会奔跑。因此将行为抽象成接口。

4.7 开闭原则是总纲

开放封闭原则告诉我们要对扩展开放,对修改封闭。开闭原则是整个设计的最终目标和原则。开闭原则是总纲,其他四个原则是对这个原则的具体解释。

4.8 更为简单实用的设计原则

不是所有的软件都要求符合SOLID原则,下面给出一些更为简单实用的原则。

4.9 LoD原则(迪米特原则)

  1. 什么是LoD原则
    如果两个类不必彼此互相通信,那么这两个类就不应当发生直接的相互作用;如果其中一个类需要调用另一个类的某一个方法,可以通过第三者转发这个调用。
  2. 使用动机(why)
    强调类之间的松耦合。类之间的耦合越弱,越有利于复用和扩展。
  3. 如何使用(how)
  • 在类的结构设计上,每一个类都应当尽量降低成员的访问权限,不需要让别的类知道的字段或行为就不要公开。
  • 类之间不直接建立联系,通过中间类来中转。
  1. 使用原则
  • 减少公开方法和变量。
  • 每个类对其他类知道的越少越好。
  • 类不应该知道它所操作的对象的内部细节。
  1. 使用示例
    跨部门办事(类和类通信),例如找运维部门修电脑,最好的做法是通过运维部门的问题反馈入口,通过入口来获取帮助。至于运维部门内部如何运作来给你帮助,由它内部解决,不影响外部使用。
    一个类只需要和直接的对象进行交互,而不用濑户这个对象内部组成。
    例如:a.getB().getC().getProperties()就不是一个好的写法,而应该是
    a.getCProperties()
    至于getCPorperties怎么实现是类A要负责的事情。

4.10 KISS原则

保持简单和愚蠢
ABCD监听过度的反面例子

4.11 DRY

不要重复自己

  1. 函数级别的封装,通用函数
  2. 类级别的抽象,抽象出一个基类
  3. 泛型设计,使用装饰器来消除冗余的代码

4.12 YAGNI原则

只考虑和设计必须的功能,避免过度设计。尽快尽可能简单地让软件运行起来。

4.13 Rule of three

事不过三原则
即某个功能出现三次时,就需要进行抽象化。

4.14 CQS原则

查询命令分离原则
查询(Query):当一个方法返回一个值来回应一个问题的时候,它就具有查询的性质。
命令(Command):当一个方法要改变对象的状态的时候,它就具有命令的性质。



5 设计模式的本质

5.1 模式是针对实践中的问题而产生的

软件开发技术的学习都应该以实践为前提,只有理解实践过程中遇到的种种问题,才能明白那些技术的本质和目的是什么,因为每种新技术都是因某个/某些问题而出现的。
每个模式都描述了一个在我们的环境中不断出现的问题,并描述了该问题的解决方案的核心。通过这种方式,可以无数次地使用那些已有的解决方案,无须在重复相同的工作。
设计模式是在某种情境下,针对某种问题的典型,通用的解决方案。

  • 情境:在特定情境下反复出现的情况,要求使用模式必须分清楚事实
  • 问题:一般来说,就是你要实现的目标或者要解决的差距
  • 解决方案:典型的、通用的、举一反三

学习设计模式,首先弄清楚软件开发过程中的问题在哪里,这些问题会有什么影响。这样才能使其然更知其所以然。
软件开发过程中的问题:

  • 业务的复杂度
  • 实现的复杂度
  • 实现的易理解性,可维护性,可扩展性。
    使用恰当的设计模式是应对软件复杂度的有效方法。
    抽象是指不依赖于具体事物或实例,而是指抽取共同点或基本特征的思维过程。
    设计模式是高层次的抽象方案,并不关注具体的实现细节,比如算法和数据结构。

5.2 模式的范围

模式包括架构模式、设计模式、编码模式、语言惯例。

设计模式的联合使用

  • MCV 模式就是设计模式联合优化的一种模式。严格来说MCV属于架构模式。

OOA/D Object Oriented Analysis and Design 面向对象分析和设计
GRASP原则或模式,是对大量模式的归纳和总结,理解复杂现象背后的本质思想。



6 设计模式的误区

第一个误区是必须先有设计模式。设计模式是为了更好的组织代码的结构。不要为了强迫自己使用已有的设计模式而限制了你的创造力。
第二个误区是设计模式应随处使用。过度使用反而增加了代码的复杂程度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值