继承
语法
class 子类(父类): def __init__(self,参数列表): super().__init__(参数列表) self.自身实例变量 = 参数
说明:
— 子类拥有父类的所有成员。
— 子类如果没有构造函数,将自动执行父类的,但如果有构造函数将覆盖父类的。此时必须通过super()函数调用父类的构造函数,以确保父类属性被正常创建。
子类继承父类属性语法:
作用
隔离客户端代码与功能的实现方式。
适用性
多个类在概念上是一致的,且需要进行统一的处理。
创建一个父类:拥有name属性
class Person: def __init__(self, name): self.name = name
子类继承父类name属性语法:super
().__init__(name)
class Student(Person): def __init__(self, name, score): # 通过函数,调用父类方法 super().__init__(name) self.score = score
学生与老师在某种概念上是相同的
他们都是人
class Person: def say(self): print("说") class Student(Person): def study(self): print("学习") class Teacher(Person): def teach(self): print("教")
继承内存图
测试
1.isinstance
,issubclass
函数isinstance()
函数来判断一个对象是否是一个已知的类型,类似type()
。
isinstance()
与type()
区别:
1.type() 不会认为子类是一种父类类型,不考虑继承关系。
2.isinstance() 会认为子类是一种父类类型,考虑继承关系。
如果要判断两个类型是否相同推荐使用 isinstance()。
2.issubclass
()
用于判断参数 class 是否是类型参数 classinfo 的子类
isinstance
语法:
isinstance(object, classinfo)
参数:
- object — 实例对象。
- classinfo — 可以是直接或间接类名、基本类型或者由它们组成的元组。
- 返回值为True,False
issubclass
语法:
issubclass(class, classinfo)
参数:
- class — 类。
- classinfo — 类。
- 返回值True,False
继承-设计思想
面向对象设计原则:
1.开闭原则
开放 | 关闭 |
对扩展 | 对修改 |
允许增加新功能 | 不允许改变(增加/删除/修改)以前的代码 |
2.依赖倒置
即使用抽象的(父类),不使用具体的(子类)
老张开车去东北这个案例(其中:开车这个行为可以变为做飞机、做火车等等。是一个可变的行为)
正常的思路:
- 做一个人的类,然后人的类具有‘走’这个方法,并且可以接受参数(交通工具)
- 然后在做交通工具的类(汽车,飞机,火车等)
- 然后在人的类中的‘走’这个方法里面通过判断参数来打印内容。
- 代码实现如下:
人的类:
class Person: def __init__(self, name): self.name = name def go_to(self, str_type, str_pos): if str_type == "汽车": Car().run(str_pos) elif str_type =="飞机": Airplane().fly(str_pos) elif xxxxx:
交通工具类:
class Car: def run(self, str_pos): print("行驶到", str_pos)
这样写的不好的一点在于,如果添加了新的交通工具,就需要修改Person类中的方法。违反了开闭原则中的关闭原则(不允许改变(增加/删除/修改)以前的代码)
这个时候使用依赖倒置的思想:创建一个新的交通工具类,不使用具体的(各种交通工具)
创建新的类–交通工具类:Vehicle
class Vehicle: """ 交通工具 """ def transport(self, str_pos): # 人为创造一个错误() raise NotImplementedError() # print("儿子们,必须有这个方法啊")
修改Person类中的go_to
方法中的if判断:
def go_to(self, vehicle, str_pos): """ 写代码期间: 使用的是交通工具的,而不是汽车,飞机等 所以无需判断具体类型 运行期间: 传递具体的对象(汽车,飞机) :param vehicle: :param str_pos: :return: """ # 如果传入的对象,不是交通工具,则退出. if not isinstance(vehicle,Vehicle): print("传入的不是交通工具") return vehicle.transport(str_pos)
汽车子类修改为:
class Car(Vehicle): def transport(self, str_pos): print("行驶到", str_pos)
多态
定义
父类的同一种动作或者行为,在不同的子类上有不同的实现。
作用
- 继承将相关概念的共性进行抽象,多态在共性的基础上,体现类型的个性化(一个行为有不同的实现)。
- 增强程序扩展性,体现开闭原则。
重写
子类实现了父类中相同的方法(方法名、参数),在调用该方法时,实际调用的是子类的方法。
内置可重写函数
Python中,以双下划线开头、双下划线结尾的是系统定义的成员。我们可以在自定义类中进行重写,从而改变其行为。
__str__
函数:将对象转换为字符串(对人友好的)
__repr__
函数:将对象转换为字符串(解释器可识别的)
""" 内置可重写函数 练习:exercise01.py """ 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) # 将对象转换为字符串 re = eval("1 + 5") print(re) w02 = eval('Wife("金莲",25)') w03 = eval(input("")) # 创建了新对象 w02 = eval(w01.__repr__()) print(w02) w01.name = "莲莲" print(w02.name)
其中eval()
函数的作用:用来执行一个字符串表达式,并返回表达式的值。
设计原则
开-闭原则(目标、总的指导思想)
Open Closed Principle
对扩展开放,对修改关闭。
增加新功能,不改变原有代码。
类的单一职责(一个类的定义)
Single Responsibility Principle
一个类有且只有一个改变它的原因。
依赖倒置(依赖抽象)
Dependency Inversion Principle
客户端代码(调用的类)尽量依赖(使用)抽象的组件。 抽象的是稳定的。实现是多变的。
组合复用原则(复用的最佳实践)
Composite Reuse Principle
如果仅仅为了代码复用优先选择组合复用
而非继承复用。
组合的耦合性相对继承低。
里氏替换(继承后的重写,指导继承的设计)
Liskov Substitution Principle
父类出现的地方可以被子类替换,在替换后依然保持原功能。
子类要拥有父类的所有功能。 子类在重写父类方法时,尽量选择扩展重写,防止改变了功能。
迪米特法则(类与类交互的原则)
Law of Demeter
不要和陌生人说话。
类与类交互时,在满足功能要求的基础上,传递的数据量越少越好。因为这样可能降低耦合度。
类与类的关系
泛化—-继承
子类与父类的关系,概念的复用,耦合度最高;
B类泛化A类,意味B类是A类的一种;
做法:B类继承A类
关联(聚合/组合)
部分与整体的关系,功能的复用,变化影响一个类;
A与B关联,意味着B是A的一部分;
做法:在A类中包含B类型成员。
依赖
合作关系,一种相对松散的协作,变化影响一个方法,耦合度最低;
A类依赖B类,意味A类的某些功能靠B类实现;
做法:B类型作为A类中方法的参数,并不是A的成员。
依赖关系代码实例:
类的流程图
# 2. 一家公司,有如下几种岗位: # 普通员工:底薪 # 程序员:底薪 + 项目分红 # 销售员:底薪 + 销售额 # 定义员工管理器,记录所有员工,提供计算总薪资方法. # 要求:增加新岗位,员工管理器代码不做修改. # 体会:依赖倒置 class EmployeeManager: """ 员工管理器 """ def __init__(self): self.__all_employee = [] def add_employee(self, employee): # 属于一种依赖关系,Employee类是在EmployeeManager中的一个方法参数 if not isinstance(employee, Employee): return self.__all_employee.append(employee) def get_total_salary(self): """ 计算总薪资 :return: """ total_salary = 0 for item in self.__all_employee: # 编码期间:item 认为是员工 # 运行期间:item 实际是具体员工 total_salary += item.get_salary() return total_salary class Employee: """ 员工类 作用:代表具体员工,隔离员工管理器与具体员工的变化. """ def __init__(self, name, salary): self.name = name self.base_salary = salary def get_salary(self): return self.base_salary class Programmer(Employee): """ 程序员 """ def __init__(self, name, salary, bonus): super().__init__(name, salary) self.bonus = bonus def get_salary(self): # return self.base_salary + self.bonus # 扩展重写 return super().get_salary() + self.bonus class Salesmen(Employee): """ 销售员 """ def __init__(self, name, salary, sale_value): super().__init__(name, salary) self.sale_value = sale_value def get_salary(self): return super().get_salary() + self.sale_value * 0.05 manager = EmployeeManager() manager.add_employee(Salesmen("pp", 3000, 500)) re = manager.get_total_salary() print(re) # 练习:老王转岗 # 销售 --> 程序员 # lw = Salesmen("老王", 3000, 500) # lw = Programmer("老王", 8000, 100000) # 重新创建新对象,替换引用.好比是开除"老王",招聘新"老王" # 要求:对象部分改变,而不是全部改变.
总结
什么是面向过程
首先提出一个问题:根据生日(年月日),返回活了多少天.
然后解决问题:
- 获取数据
- 根据年月日构建时间元组
- 根据构建的时间元组获取时间戳
- 使用当前时间戳 – 生日的时间戳
- 将活的秒数换算为天
import time year = input("输入年份") month = input("请输入月份") day = input("请输入天") # 根据年月日构建时间元组 time_tuple = time.strptime("%d/%d/%d" % (year, month, day), "%Y/%m/%d") # 根据构建的时间元组获取时间戳 now = time.time() old = time.mktime(time_tuple) # 使用当前时间戳 - 生日的时间戳 life_seconds = now - old # time.time() - time.mktime(time_tuple) # 将活的秒数换算为天 result = life_seconds / 60 / 60 // 24 print(result)
按照上面的步骤,思考实现这一个功能。每一行代码亲力亲为,这属于典型的面向过程!
但是在书写上面的代码的时候,我们没有考虑更加具体的实现步骤如:
第一步中的获取数据,我们怎么获取数据,从哪里获取数据!第二步中根据年月日构建时间元组,怎么构建,用上面构建。在这里我们同样不知道。我们是通过调用input()
方法来实现获取数据,通过time()
模块中的类来实现构建元组。
面向对象思想
在这里就体现出了面向对象的思想:我们使唤input()
,time()
模块的中的类来帮我们干活,但是我们并不关心你怎么干。
我们继续将这段代码对象化:上面的5个步骤都可以对其封装,这样我们就可以在其他地方使唤这5个类为我们提供方便。
为了实现面向对象思想就需要要到封装、继承、多态这3大方法。
封装
我觉得封装的意义在于:隐藏复杂性,是我们人类处理更复杂问题的一种常用方法。就像time()
模块中的类一样,他是怎么实现的,这一定很复杂,但是他应用封装,将他的复杂性隐藏了起来,只需要外界向其输入参数,就能给外界返回期望的值,这就是封装的意义。
封装没有绝对意义上的对与错,他更像一种设计美学,就像蒙娜丽莎一样,不同的人觉得他的美是不一样的。
这里我觉得—简洁即是美
继承
继承我觉得他可以重用代码,但这绝对是最基础的用法,甚至可以说是不必要的用法。我觉得重用代码的精髓是在扩展这两个字上,如果不是为了扩展父类功能而用继承,还不如直接使用关联(聚合/组合)。
继承还有一个很好的用处:隔离变化。什么叫隔离变化呢?我觉得和类的单一原则很像,就是这一个类的一个方法要实现一个功能的形式太多且乱,这就不符合简洁美,为了应对这一问题,就要将哪一个类的方法拆分、封装,然后继承父类并重写方法。这样就算以后再来许多形式的变化,也不会影响到客户端的代码。
多态
假如你家里有了三胞胎,他们都是你的孩子这是共性,但是每个小朋友的个性肯定不一样,这就体现了多态。