Python面向对象(Object Oriented)及设计原则

类和对象

语法

  1. 类:一个抽象的概念,即生活中的”类别”
    例如:学生、水果。

  2. 对象:类的具体实例,即归属于某个类别的”个体”
    例如:张三同学、苹果

  3. 类是创建对象的”模板”.

  • 数据成员:名词性的状态。例如:姓名
  • 方法成员:动词性的行为。例如:学习
  1. 类与类的行为不同,对象与对象的数据不同。

定义类

  1. 代码:
    class 类名:
    “”“文档说明”""
    def __init__(self,参数列表):
          self.实例变量 = 参数
          方法成员

2.说明:

  • 类名所有单词首字母大写
  • __init__叫做构造函数,创建对象时被调用,也可以省略。
  • self变量绑定的是被创建的对象,名字通常叫做”self”。

创建对象(实例化)

变量 = 构造函数(参数列表)

实例成员

实例变量

  1. 语法:
    【1】定义:对象.变量名称
    【2】调用:对象.变量名称

  2. 说明:
    【1】首次通过对象赋值为创建变量,再次赋值为修改。
    【2】通常在构造函数(init)中创建。
    【3】每个对象存储一份,通过对象地址访问。

  3. 作用:描述所有对象的共有数据

  4. __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. 说明
  • 至少有一个形参,用于绑定调用该方法的类,一般命名为” cls”。
  • 使用@classmethod修饰的目的是调用方法时隐式传递类。
  • 类方法中不能访问实例成员,实例方法可以访问类成员。
  1. 作用:操作类变量

静态方法

  1. 语法
    【1】定义:
    @staticmethod
    def 方法名称(参数列表):
    方法体
    【2】调用:类名.方法名称(参数)
    不建议通过对象访问静态方法

  2. 说明
    【1】将函数转移到类中,就是静态方法
    【2】使用@staticmethod修饰的目的是该方法不需要隐式传递参数。
    【3】静态方法不能访问实例成员和类成员

  3. 作用:统一管理函数(定义在.py文件中的函数)
    表达不需要使用实例成员和类成员时,使用静态方法。

class Vector2:
	# 实例方法
	def fun01(self):
		pass
	
	# 类方法
	@classmethod
	def fun02(cls):
		pass
	
	# 静态方法:得不到对象地址/也得不到类名
	@staticmethod
	def fun03():
		pass

v01 = Vector2()
v01.fun01()#隐式传递对象地址
Vector2.fun02()#隐式传递类名
Vector2.fun3()

封装

定义

  1. 从数据角度,将一些基础变量复合为一个自定义类型。符合人类思考方式,将数据与对数据的操作封装起来。

  2. 从行为角度讲,向类外提供必要的功能,隐藏实现的细节。
    比如:random.randint(1,100)
    使用者可以不必操心实现过程。

e.g.私有成员变量

# 注意:只是通过两个方法,读写私有变量
class Wife02:
	def __init__(self,name="",age=0):
		self.name = name
		# 缺点:缺乏对象数据的封装,外界可以随意赋值
		# 私有成员:障眼法(解释器会改变双下划线开头的变量名)
		self.__age = age
		
	def get_age(self):
		return self.__age
	
	def set_age(self,value):
		self.__age = value

w01 = Wife02()
print(w01.__age)#找不到双下划线开头的数据
print(w01._Wife02__age)#通过下划线+类名可以访问双下划线开头的数据
  1. 从设计角度讲:

【1】分而治之

  • 一个类中,一个方法,一条语句
  • 将一个大的需求分解为许多类,让每个类处理一个独立的功能。
  • 优点:便于分工,便于复用,可扩展性强。

【2】封装变化

  • 需求可能会变化的功能要单独封装,避免影响其他类。

【3】高内聚

  • 类中各个方法都在完成一项任务(单一职责的类)

【4】低耦合

  • 类与类的关联性与依赖度要低,让一个类变化,尽少影响其他的类。

[例如:硬件高度集成化,又要可插拔]
最高的内聚莫过于类中仅包含1个方法,将会导致高内聚高耦合。
最低的耦合莫过于类中包含所有方法,将会导致低耦合低内聚。

作用

  • 简化编程,使用者不必了解具体的实现细节,只需要调用对外提供的功能。
  • 松散耦合,降低了程序各部分之间的依赖性。
  • 数据和操作相关联,方法操作的是自己的数据。
  1. 作用:无需向类外提供的成员,可以通过私有化进行屏蔽。
  2. 做法:命名使用双下划线开头。
  3. 本质:障眼法,实际也可以访问。

私有成员

私有成员的名称被修改为:_类名__成员名,可以通过_dict_属性或dir函数查看。

属性@property

公开的实例变量,缺少逻辑验证。私有的实例变量与两个公开的方法相结合,又使调用者的操作略显复杂。而属性可以将两个方法的使用方式像操作变量一样方便。

  1. 定义
class Wife:
	def __init__(self,name="",age=0):
		self.name = name #调用@name.setter修饰的方法
		self.age = age#调用@age.setter修饰的方法
	
	# 拦截读取操作
	# 本质:创建property对象,name存储对象地址
	# 注意:创建对象时,需要传递读取方法
	# 相当于:name = property(get_name,None)
	@property # 拦截读取变量的操作
	def name(self):
		return self.__name
	
	# 拦截写入操作
	@name.setter # 拦截写入变量的操作
	def name(self,value):
		return self.__name = value

	@property
	def age(self):
		return self.__age
	
	@age.setter
	def age(self,value):
		if 20<=value<=30:
			self.__age = value
		else: 
			self.__age = 0
			print("No")

w01=Wife("铁锤",25)
print(w01.name)
print(w01.age)

2.调用:

对象.属性名 = 数据
变量 = 对象.属性名

3.说明:

  • 通常两个公开的属性,保护一个私有的变量。
  • @property 负责读取,@属性名.setter 负责写入
  • 只写:属性名= property(None, 写入方法名)

案例:学生信息管理系统

  1. 需求
    实现对学生信息的增加、删除、修改和查询。

  2. 分析
    界面可能使用控制台,也可能使用Web等等。
    识别对象:界面视图类 逻辑控制类 数据模型类

  3. 分配职责:

  • 界面视图类:负责处理界面逻辑,比如显示菜单,获取输入,显示结果等。
  • 逻辑控制类:负责存储学生信息,处理业务逻辑。比如添加、删除等。
  • 数据模型类:定义需要处理的数据类型。比如学生信息。
  1. 建立交互:
    界面视图对象 <----> 数据模型对象 <----> 逻辑控制对象

  2. 设计

  • 数据模型类: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. 封装数据:将多个基本类型复合为一个自定义类型
    优势:符合人类的思考方式,体现对数据的操作方式
  2. 封装功能:对外提供必要的功能,隐藏实现细节,模块化的编程思想
  3. 分而治之(分解),封装变化(变化点),高内聚(类职责单一),低耦合(类与类的关系松散)

继承

语法

  1. 代码:
	class 子类名称(父类名称):
		def init(self,父类参数,自身参数):
			super().init(父类参数)
			self.自身实例变量 =自身参数
  1. 说明:
  • 子类拥有父类所有成员.
  • 子类如果没有构造函数,将自动执行父类的,但如果有构造函数将覆盖父类的。
    此时必须通过super()函数调用父类的构造函数,以确保父类数据成员被正常创建。
  1. isisntance(对象,类型)函数:返回对象是否兼容类型。

继承语法–方法:

class Person:
	def say(self):
		print("说")

class Student(Person):
	def study(self):
		print("学习")

class Teacher(Person):
	def teach(self):
		print("教")

s01=Student()
s01.study()
s01.say()

# 判断对象是否“兼容”一个类型
isinstance(s01,Person) # True

# 判断前者(类)是否是后者(类)的子类
print(issubclass(Student,Person))#True

继承语法–数据

class Person:
	def __init__(self,name):
		self.name = name

class Student(Person):
	def __init__(self,name,score):
		# 通过super函数,调用父类方法
		super().__init__(name)
		self.score = score

class Teacher(Person):
	def __init__(self,name,salary):
		super().__init__(name)
		self.salary = salary

s01 = Student("zs",100)

继承–设计思想(面向对象设计原则)

  1. 开闭原则:对扩展开放(允许增加新功能),对修改关闭(不允许增加/删除/修改以前的代码)。
  2. 依赖倒置(抽象):使用抽象(父类),而不使用具体(子类)。
class Person:
	def __init__(self,name):
		self.name = name

	def go_to(self,vehicle,str_pos):
		# 写代码期间:使用的是交通工具,而不是汽车、飞机等,所以无需判断具体类型
		# 运行期间:传递具体的对象(汽车、飞机)
		# 如果传入的对象,不是交通工具,则退出
		if not isinstance(vehicle,Vehicle):
			print("传入的不是交通工具")
			return
		vehicle.transport(str_pos)

class Vehicle:
	"""
	交通工具
	"""
	def transport(self,str_pos):
		# 人为创造一个错误,约束子类必须有这个方法
		raise NotImplementedError()	

class Car(Vehicle):
	def transport(self,str_pos):
		print("行驶到",str_pos)

class Airplane(Vehicle):
	def transport(self,str_pos):
		print("飞到",str_pos)	

p01 = Person("老张")
p01.goto(Car(),"东北")

在这里插入图片描述

多继承

一个子类继承两个或两个以上的基类,父类中的属性和方法同时被子类继承下来。

  • 同名方法的解析顺序(MRO,Method Resolution Order):
    类自身 --> 父类继承列表(由左至右) --> 再上层父类
    在这里插入图片描述
class A:
	def m(self):
		print("a--m")

class B(A):
	def m(self):
		print("b--m")

class C(A):
	def m(self):
		print("c--m")

class D(B,C):
	def m(self):
		# 调用父级同名方法,执行顺序(继承列表顺序)
		super().m() # b--m
		print("d--m")

d01 = D()
d01.m()
print(D.mro()) # [class D,class B,class C,class A,class Object]

定义

  1. 重用现有类的功能与概念,并在此基础上进行扩展。
  2. 说明:
  • 子类直接具有父类的成员,还可以具有自己的功能。
  • 事物具有一定的层次、渊源,继承可以统一概念。

例如:公司组织架构
在这里插入图片描述

优点

  1. 一种代码复用的方式
  2. 以层次化的方式管理类

缺点

耦合度高

作用

隔离客户端代码与功能的实现方式

适用性

多个类在概念上是一致的,且需要进行统一的处理。

相关概念

父类(基类、超类)、子类(派生类)。
父类相对于子类更抽象,使用范围更宽泛;子类相对于父类更具体,使用范围更狭小。
单继承:父类只有一个(例如:java,C#)
多继承:父类有多个(例如:python、C++)
Object类:任何类都直接或者间接继承自Object类。

内置函数

isinstance(obj, class_or_tuple)
返回这个对象obj 是否是某个类的对象,或者某些类中的一个类的对象,如果是则返回True,否则返回False

多态

定义

调用父类同一个方法,但在不同子类间,有不同的表现。

作用

  1. 继承将相关概念的共性进行抽象,多态在共性的基础上,体现类型的个性。
  2. 增强程序扩展性,体现开闭原则。

重写

子类实现了父类中相同的方法(方法名称、参数),在调用该方法时,实际执行的是子类的方法。

内置可重写函数

在python中,以双下划线开头和结尾的是系统定义的成员。我们可以在自定义类中对其重写,从而改变其行为。
__str__函数:将对象转换为字符串(对人友好的)
__repr__函数:将对象转换为字符串(解释器可识别的)

class Wife:
	def __init__(self,name,age):
		self.name = name
		self.age = age
	
	def __str__(self):
		# 返回给人看
		return "奴家叫:%s,年芳:%d" %(self.name, self.age)

	def __repr__(self):
		# 返回给解释器看
		return 'Wife("%s",%d)'%(self.name, self.age)

w01 = Wife("金莲"25)
print(w01)  # 将对象转化为字符串

w02 = eval(w01.__repr__()) # 复制对象
print(w02)

运算符重载

定义:让自定义的类生成的对象(实例)能够使用运算符进行操作。

  1. 算术运算符
    在这里插入图片描述
    e.g. 对象与整数加法:
class Vector:
	'''
	向量
	'''
	def __init__(self,x):
		self.x = x
	
	def __str__(self):
		return "向量的x变量是:%d" % self.x
	
	# 对象 + 
	def __add__(self,other):
		return Vector(self.x + other)

v01 = Vector(10)
v02 = v01 + 5 # 只支持向量在左边
print(v02)
  1. 反向算数运算符重载
    在这里插入图片描述
    e.g. 对象与整数加法(反向):
class Vector:
	'''
	向量
	'''
	def __init__(self,x):
		self.x = x
	
	def __str__(self):
		return "向量的x变量是:%d" % self.x
	
	# + 对象
	def __radd__(self,other):
		return Vector(self.x + other)

v01 = Vector(10)
v02 = 5 + v01 # 只支持向量在右边
print(v02)

e.g. 类与类加法

class Vector:
	'''
	向量
	'''
	def __init__(self,x):
		self.x = x
	
	# 对象 +
	def __add__(self,other):
		return Vector(self.x + other)

	# + 对象
	def __radd__(self,other):
		return Vector(self.x + other)

v01 = Vector(10)
v02 = Vector(15)
v03 = v01 + v02
print(v03.x)
  1. 复合运算符重载
    在这里插入图片描述
class Vector:
	'''
	向量
	'''
	def __init__(self,x):
		self.x = x

	# 累加:在原有对象基础上进行操作,不创建新对象
	def __iadd__(self,other):
		self.x += other
		return self

v01 = Vector(10)
print(id(v01)) #  同一个对象

v01 += 1
print(v01)
print(id(v01)) #  同一个对象
  1. 比较运算符重载
    在这里插入图片描述
class Vector:
	'''
	向量
	'''
	def __init__(self,x):
		self.x = x

	def __lt__(self,other):
		return self.x < other

v01 = Vector(10)
print(v01 < 2 ) #  False

设计原则

开闭原则(目标、总的指导思想)

对扩展开放,对修改关闭。
允许增加新功能,不改变原有代码。

类的单一职责(一个类的定义)

一个类有且只有一个改变它的原因。
外界一个需求的变化,内部一个改变的类。
高内聚。

依赖倒置(依赖抽象)

客户端代码(调用的类)尽量依赖(使用)抽象(父)的组件。
抽象的是稳定的,实现是多变的。

组合复用原则(复用的最佳实践)

如果仅仅为了代码复用优先选择组合关系,而非继承关系。
组合的耦合度低于继承,灵活度高于继承。

里氏替换(继承后的重写,指导继承的设计)

父类出现的地方可以被子类替换,在替换后依然保持原有功能。

  • 子类要拥有父类的所有功能
  • 子类在重写父类方法时,尽量选择扩展重写(先调父类同名方法),不要改变原有功能。

迪米特法则(类与类交互的原则)

原话:不要和陌生人说话。
类与类交互时,在满足功能的基础上,传递的数据量越少越好。
低耦合。

类与类的关系

  1. 泛化(继承):
    子类与父类的关系,耦合度最高;
    B类泛化A类,意味着B类是A类的子类。
  • 做法:B类继承A类。
    class Employee(Job)
  1. 关联(聚合/组合):
    部分与这个整体的关系,耦合度要低于泛化。
    A类关联B类,意味着B是A的一部分。
  • 做法:在A类中包含B类的成员。(类作为一个变量)
    self.job = job
  1. 依赖:
    合作关系,一种相对松散的协作,变化影响一个方法。
    A类依赖B类,意味着A类的某个功能以依靠B类实现。
  • 做法:B类型作为A类中方法的参数,并不是A的成员。
    return job.get_salary()

总结

面向对象:
面向对象考虑问题,要从对象的角度出发

主要思想:
识别对象/分配职责/建立交互
封装变化/隔离变化/执行变化
封装、继承、多态

特征:

  1. 封装:
    【1】数据:将多个基本类型,合成一个自定义类型
    优势:符合人类的思考方式
    比如:学生,向量(x,y)
    【2】行为:对外提供简单的必要的功能,隐藏实现的细节。
    优势:模块化开发,简化编程
    比如:学生管理系统——逻辑控制器,供界面调用
    【3】设计:
    分而治之:分解需求,让多个类协同完成
    封装变化:每个变化点单独做成一个类
    高内聚:类的内部处理一个变化点
    低耦合:类与类的关系,尽量做到互不影响

  2. 继承:重用现有类的概念与功能,并在此基础上进行扩展(子类的共性,子类相比父类更加具体)
    作用:隔离客户端代码与实现方式(隔离用与做)
    比如:交通工具隔离了人与飞机/火车…的变化
    图形隔离了图形管理器与圆形/矩形…的变化

  3. 多态:调用父类一个方法,执行子类方法,不同实现方式不一样,所以表现形态就不一样
    作用:通过重写执行不同变化点
    比如:人调用的是交通工具的运输方法,执行的是飞机/火车的运输方法
    图形管理器调用图形的计算面积方法,执行的是圆形/矩形的计算面积方法

原则:

  • 开闭原则:增加新功能,不修改客户端代码
  • 单一职责:每个类有且只有一个改变的原因
  • 依赖倒置:使用抽象(父类),而不是用具体(子类)
  • 组合复用:使用关联关系,代替继承关系
  • 里氏替换:父类出现的地方,可以被子类替换。替换后,保持原有功能
  • 迪米特法则:低耦合
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值