考虑问题从对象的角度出发
1.分析出解决问题的步骤,然后逐步实现。
例如:婚礼筹办
– 发请柬(选照片、措词、制作)
– 宴席(场地、找厨师、准备桌椅餐具、计划菜品、购买食材)
– 婚礼仪式(定婚礼仪式流程、请主持人)
2.公式:程序 = 算法 + 数据结构
3.优点:所有环节、细节自己掌控。
4.缺点:考虑所有细节,工作量大。
1.找出解决问题的人,然后分配职责。
例如:婚礼筹办
– 发请柬:找摄影公司(拍照片、制作请柬)
– 宴席:找酒店(告诉对方标准、数量、挑选菜品)
– 婚礼仪式:找婚庆公司(对方提供司仪、制定流程、提供设备、帮助执行)
2.公式:程序 = 对象 + 交互
3.优点
(1)思想层面:
(2)技术层面:
4.缺点:学习曲线陡峭。
类和类的关系:泛化(继承)、组合(实例变量)、依赖(参数)
抽象:从事物中舍弃个性/非本质的特性,抽出共性的过程
1.类:一个抽象的概念,即生活中的”类别”。
2.对象:类的具体实例,即归属于某个类别的”个体”。
3.类是创建对象的”模板”。
– 数据成员:名词类型的状态。
– 方法成员:动词类型的行为。
4.类与类行为不同,对象与对象数据不同。
class 类名:
“””文档说明”””
#构造函数:
def _init_(self,参数列表):
self.实例变量 = 参数
#方法成员:
def 方法名称(self):
方法体
变量 = 构造函数 (参数列表)
1.语法
(1)定义:对象.变量名
(2)调用:对象.变量名
2.说明
(1)首次通过对象赋值为创建,再次赋值为修改.
w01 = Wife()
w01.name = “丽丽”
w01.name = “莉莉”
(2)通常在构造函数(_init_)中创建。
w01 = Wife(“丽丽”,24)
print(w01.name)
(3)每个对象存储一份,通过对象地址访问。
3.作用:描述某个对象的数据。
4.dict:对象的属性,用于存储自身实例变量的字典。
通过__dict__获取当前对象的所有实例变量
1.语法
(1) 定义:
def 方法名称(self, 参数列表):
方法体
(2) 调用: 对象地址.实例方法名(参数列表)
不建议通过类名访问实例方法:(类名.方法没有传递对象地址)
2.说明
(1) 至少有一个形参,第一个参数绑定调用这个方法的对象,一般命名为"self"。
(2) 无论创建多少对象,方法只有一份,并且被所有对象共享。
3.作用:表示对象行为。
类成员
1.语法
(1)定义:在类中,方法外定义变量。
class 类名:
变量名 = 表达式
(2)调用:类名.变量名
不建议通过对象访问类变量
2.说明
(1) 存储在类中。
(2) 只有一份,被所有对象共享。
3.作用:描述所有对象的共有数据。
1.语法
(1)定义:
@classmethod
def 方法名称(cls,参数列表):
方法体
(2)调用:类名.方法名(参数列表)
不建议通过对象访问类方法
2.说明
(1) 至少有一个形参,第一个形参用于绑定类,一般命名为’cls’
(2) 使用@classmethod修饰的目的是调用类方法时可以隐式传递类。
(3) 类方法中不能访问实例成员,实例方法中可以访问类成员。
3.作用:操作类变量。
1.语法
(1)定义:
@staticmethod
def 方法名称(参数列表):
方法体
(2)调用:类名.方法名(参数列表)
不建议通过对象访问静态方法
2.说明
(1) 使用@ staticmethod修饰的目的是该方法不需要隐式传参数。
(2) 静态方法不能访问实例成员和类成员
3.作用:定义常用的工具函数。
Alt + 回车 --Create parameter ‘…’—>自动填补参数
封装(分),继承(隔),多态(干)
1.定义:
将一些基本数据类型复合成一个自定义类型。
(将多个变量包装成一个对象)
例如:技能(名字/持续时间/消耗…)
2.优势:
将数据与对数据的操作相关联。
代码可读性相比容器更高(类是对象的模板)。
符合人类的思考方式(数据+对数据的操作)
1.定义:
对外提供必要的功能,隐藏实现的细节。
注意:必要!
例如:str类(转换大小写/查找字符位置/替换…)
2.优势:
简化编程,使用者不必了解具体的实现细节,只需要调用对外提供的功能。
3.私有成员(私有化):
私有成员的名称被修改为:_类名__成员名,可以通过_dict_属性或dir函数查看。
4.属性@property:
(拦截 对变量的操作,转移到读写方法中 保护变量)
公开的实例变量,缺少逻辑验证。私有的实例变量与两个公开的方法相结合,又使调用者的操作略显复杂。而属性可以将两个方法的使用方式像操作变量一样方便。
(1)定义:
@property
def 属性名(self):
return self.__属性名
@属性名.setter
def 属性名(self, value):
self.__属性名= value
(2)调用:
对象.属性名 = 数据
变量 = 对象.属性名
(3)说明:
通常两个公开的属性,保护一个私有的变量。
@property 负责读取,@属性名.setter 负责写入
只写:属性名= property(None, 写入方法名)
1.定义:
将一个大的需求分解为许多类,每个类处理一个独立的功能。
(改一个功能比重新写消耗时间更大)
雕版印刷 —> 活字印刷
变化的地方独立封装,避免影响其他类。
类中各个方法都在完成一项任务(单一职责的类)。
类与类的关联性与依赖度要低(每个类独立),让一个类的改变,尽少影响其他类。
2.优势:
便于分工,便于复用(调用,而非复制),可扩展性强。
#### 案例:信息管理系统
##### 需求
实现对学生信息的增加、删除、修改和查询。
##### 分析
界面可能使用控制台,也可能使用Web等等。
1.识别对象:界面视图类 逻辑控制类 数据模型类
2.分配职责:
界面视图类:负责处理界面逻辑,比如显示菜单,获取输入,显示结果等。
逻辑控制类:负责存储学生信息,处理业务逻辑。比如添加、删除等
数据模型类:定义需要处理的数据类型。比如学生信息。
3.建立交互:
界面视图对象 <----> 数据模型对象 <----> 逻辑控制对象
#### 架构
模型Model:负责封装数据
控制Controller:负责逻辑处理
视图View:负责界面逻辑
1)需求:比如说添加学生
2)识别对象:找谁干活
3)建立交互:互相调用(我在那?他在哪?)
#### 设计
数据模型类:StudentModel
数据:编号 id,姓名 name,年龄 age,成绩 score
逻辑控制类:StudentManagerController
数据:学生列表 __stu_list
行为:获取列表 stu_list,添加学生 add_student,删除学生remove_student,修改学生update_student,根据成绩排序order_by_score。
界面视图类:StudentManagerView
数据:逻辑控制对象__manager
行为:显示菜单__display_menu,选择菜单项__select_menu_item,入口逻辑main,
输入学生__input_students,输出学生__output_students,删除学生__delete_student,修改学生信息__modify_student
合理的继承:子类是一种父类(语义)
什么时候用继承?隔!(需要隔离的时候)
永远不要将继承用作代码的复用
1.代码:
class 父类:
def 父类方法(self):
方法体
class 子类(父类):
def 子类方法(self):
方法体
儿子 = 子类()
儿子.子类方法()
儿子.父类方法()
2.说明:
子类直接拥有父类的方法.
isinstance(对象, 类型)
返回指定对象是否是某个类的对象。
issubclass(类型,类型)
返回指定类型是否属于某个类型。
1.代码
class 子类(父类):
def init(self,参数列表[父类参数和子类参数]):
super().init(参数列表[父类参数])
self.自身实例变量 = 参数
2.说明
子类如果没有构造函数,将自动执行父类的,但如果有构造函数将覆盖父类的。此时必须通过super()函数调用父类的构造函数,以确保父类实例变量被正常创建。
定义
重用现有类的功能,并在此基础上进行扩展。
说明:子类直接具有父类的成员(共性),还可以扩展新功能。
一种代码复用的方式。
耦合度高:父类的变化,直接影响子类。
将相关类的共性进行抽象,统一概念(约束子类),隔离变化。
将相关子类所拥有的共性进行抽象化成为父类(抽象相关的概念),统一子类的数据/方法,对子类的方法进行约束,父类隔离调用者(用)与子类的变化(做),
多个类在概念上是一致的,且需要进行统一的处理。
父类(基类、超类)、子类(派生类)。
父类相对于子类更抽象,范围更宽泛;子类相对于父类更具体,范围更狭小。
单继承:父类只有一个(例如 Java,C#)。
多继承:父类有多个(例如C++,Python)。
Object类:任何类都直接或间接继承自 object 类。
一个子类继承两个或两个以上的基类,父类中的属性和方法同时被子类继承下来。
同名方法的解析顺序(MRO, Method Resolution Order):
类名.mro() ----> 查看同名方法的解析顺序
类自身 --> 父类继承列表(由左至右)–> 再上层父类
A
/
/
B C
\ /
\ /
D
父类的同一种动作或者行为,在不同的子类上有不同的实现。
1.在继承的基础上,体现类型的个性化(一个行为有不同的实现)。
2.增强程序扩展性,体现开闭原则。
子类实现了父类中相同的方法(方法名、参数)。
在调用该方法时,实际执行的是子类的方法。(覆盖)
Ctrl + O(重写父类方法)
Python中,以双下划线开头、双下划线结尾的是系统定义的成员。我们可以在自定义类中进行重写,从而改变其行为。
__str__函数:将对象转换为字符串(对人友好的)
__repr__函数:将对象转换为字符串(解释器可识别的)
#eval()返回将字符串两端的引号去掉后的语句,注意:得到的语句Python必须能解析
eval()方法 与 repr() 配合使用 运用与 对象的克隆
定义:让自定义的类生成的对象(实例)能够使用运算符进行操作。
算数运算符
与其他数据运算时会自动调用以下方法
如:Class(5)+2 —> 自动调用__add__()方法 --> Class(5).add(2)
反向算数运算符重载
复合运算符重载
如果不用,则默认使用算数运算符,但会产生新结果
而复合运算符在原有对象基础上修改
比较运算重载
Open Closed Principle
对扩展开放,对修改关闭。
增加新功能,不改变原有(客户端)代码。
#对方法增加语句、删除语句、修改语句都视为改变原有代码
通过继承将相关类的共性进行抽象,统一概念,隔离变化。
下图:受害者 为 从其他具体事物 抽象出来的父类,统一事物的 受伤方法 ,隔离每种事物具体的受伤变化
封装:创建类 手雷/玩家/敌人
继承:受害者隔离了手雷爆炸的逻辑与玩家或者敌人受伤的逻辑
多态:手雷爆炸依赖受害者,实际执行玩家和敌人
开闭:增加/减少新受害者,手雷不用改变
单一:(一个类一件事) 手雷(爆炸) 受害者(隔离变化) 玩家(受伤)…
依赖倒置:手雷依赖受害者,不依赖玩家/敌人
组合复用:手雷的爆炸方法,通过参数“受害者”访问玩家/敌人受伤方法
Single Responsibility Principle
一个类有且只有一个改变它的原因。(一个类处理一件事)
Dependency Inversion Principle
客户端代码(调用的类)尽量依赖(使用)抽象。
抽象不应该依赖细节,细节应该依赖抽象。
下图中:人类调用汽车行驶方法----->依赖细节
人类调用交通工具的运输方法----->依赖抽象
Composite Reuse Principle
如果仅仅为了代码复用优先选择组合复用,而非继承复用。
组合的耦合性相对继承低。
通过变量(参数/实例变量)调用,而不是通过继承调用
Liskov Substitution Principle
父类出现的地方可以被子类替换,在替换后依然保持原功能。
子类要拥有父类的所有功能。
子类在重写父类方法时,尽量选择扩展重写,防止改变了功能。
Law of Demeter
不要和陌生人说话。(低耦合)
(不变与变化之间用变化的父类隔离着)
类与类交互时,在满足功能要求的基础上,传递的数据量越少越好。因为这样可能降低耦合度。
# 定义:图形管理器
# 1. 记录所有图形
# 2. 提供计算总面积的方法
# 需求变化:
# 圆形(pi * r ** 2)
# 矩形(长 * 宽)
# ...
# 测试:
# 创建图形管理器,添加圆形,矩形.
# 调用计算总面积方法.
class GraphicManager:
"""
图形管理器
"""
def __init__(self):
# 存储的是具体图形
# 所以图形管理器与图形是组合关系.
self.__list_graphic = []
@property
def list_graphic(self):
return self.__list_graphic
def add_graphic(self, graphic):
self.__list_graphic.append(graphic)
def get_total_area(self):
total_area = 0
for item in self.__list_graphic:
# 调用父类,执行子类.
total_area += item.calculate_area()
return total_area
class Graphic:
"""
图形,抽象所有具体的图形,统一图形的计算面积方法,隔离具体图形的变化.
"""
def calculate_area(self):
"""
计算面积
:return: 小数类型的面积.
"""
pass
# ------------------------------------------
class Circle(Graphic):
def __init__(self, r):
self.r = r
def calculate_area(self):
return 3.14 * self.r ** 2
class Rectanle(Graphic):
def __init__(self, lenght, width):
self.length = lenght
self.width = width
def calculate_area(self):
return self.length * self.width
# --------------------------------
manager = GraphicManager()
c01 = Circle(5)
manager.add_graphic(c01)
manager.add_graphic(Rectanle(2, 5))
print(manager.get_total_area())
封装:创建类 图形管理器/圆形/矩形
继承:图形隔离了图形管理器计算总面积的逻辑与圆形/矩形获取面积的逻辑
多态:图形管理器依赖图形,实际执行圆形/矩形
开闭:增加/减少新图形,图形管理器不用改变
单一:(一个类一件事)图形管理器(管理图形)图形(隔离变化)圆(获取面积)...
依赖倒置:图形管理器依赖图形,不依赖圆形/矩形
组合复用:图形管理器的计算总面积方法,通过实例变量“所有图形”访问圆形/矩形获取面积方法方法