面向对象回顾
面向过程与面向对象
面向过程:
核心是过程二字,指的是解决问题的步骤,根据某个业务的流程,以流水线的思维方式编写程序
优点:复杂问题流程化,设计思路清晰
缺点:扩展性差,改变了其中一个环节,就会影响其他环节
面向对象:
对象是特征与技能的封装,利用对象具有的属性和方法来实现对象间的交互。
例子,英雄联盟游戏中,各个英雄具生命值,攻击力等属性;拥有攻击这个技能(在编程时表现为方法)。利用对象的交互来实现游戏的开发。
优点:扩展性强,各个对象具有的属性是相互独立的,改变一个并不会影响其它
缺点:复杂度高于面向过程
类和对象
什么是类:
类是对一系列具有相似属性和技能对象的抽象。
例子,学生都具有姓名,年龄,性别等特征,具有听课,做题等技能。那么把这些相似的特征与技能抽象出来就构成了一个学生类。
为什么要使用类:
# 例子
class Student:
job = 'student'
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def learn(self):
print(f'{self.name} is learning')
st1 = Student('st1', 18, 'male')
st2 = Student('st2', 19, 'female')
st1.learn()
st2.learn()
# 假如没有类
# 伪代码
st1.name = 'st1'
st1.age = 18
st1.gender = 'male'
st2.name = 'st2'
....
类就是对象的模具,利用模具可以很方便的生产出相似的对象。简单说就是可以减少代码冗余,方便修改。
现实中的模具生产出的对象都是一样的,我们可以利用__init__
方法传入参数,产生出既有相同属性却又具有各自属性值的对象。
继承
继承是子类与父类之间的关系(类与对象:实例化)
什么是继承:
子类直接继承父类的属性
为什么要使用继承
# 回到学生类的例子
# 假如还要定义老师类,工作人员类,。。。
# 他们同样都具有name, age, gender的属性
# 可先定义一个People类
class People:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Student(People):
# __int__方法会继承于People类,
pass
class Teacher(People):
pass
s = Student('st1', 18, 'male')
t = Teacher('t1', 20, 'female')
父类就是子类的模具,利用模具可以很方便的生产出相似的子类。简单说就是也是可以减少代码冗余,方便修改。
派生
如何在继承父类的同时,又让子类具有自己的属性:使用派生
派生的方法:
- 直接定义新的属性
- 重用父类方法
# 例子:1. 直接定义新的属性
# 让Student类继承People的同时又具有learn的方法和job属性
# 让Teacher类继承People的同时又具有teach的方法和job属性
class Student(People):
job = 'student' # 直接定义一个job属性
def learn(self): # 直接定义一个learn方法
print(f'{self.name} is learning')
class Teacher(People):
job = 'teacher'
def teach(self):
print(f'{self.name} is teaching')
# 例子:2. 重用父类方法
# 让Teacher继承People类的say_hello方法介绍自己的姓名,同时又介绍自己的角色
class People:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def say_hello(self):
print(f'hello every one!')
print(f'my name is {self.name}.')
class Teacher(People):
job = 'teacher'
def teach(self):
print(f'{self.name} is teaching')
def say_hello(self):
super().say_hello() # 重用People类的say_hello方法
print(f'my job is {self.job}') # 加上介绍自己角色的功能
# People.say_hell(self) # 另外一个调用父类方法的方式,要传入self参数(如果是绑定到对象的方法的话)
组合
组合是对象和对象的关系,具体说是一个对象包含另外一个对象
# 例子学生对象包含一个课程对象
class Course:
def __init__(self, name, period):
self.name = name
self.period = period
python = Course('python', 6)
s = Student('st1', 18, 'male')
s.course = python # 让学生对象包含了一个课程对象
s.cousre.name # 查看课程名称
s.cousre.period # 查看课程周期
小结:继承,派生,实例化,组合
- 子类继承父类,类实例化产生对象:父类-->子类-->对象
- 子类继承父类时可以通过派生的方法产生自己的属性
- 一个对象可以包含其他不同类的对象,如学生对象包含了课程对象,这种思想叫组合
封装
什么是封装
把相关联的数据或方法属性放到一起,并提供有限的访问接口,隐藏某些仅供内部使用的私有属性。
例子:把属性封装到对象中,把一些函数和数据封装到模块中
Python中如何隐藏私有属性
# 在属性名前面加上双下划线 __x, 会自动变形为_类名__x的形式
# 例子(伪代码),利用前面选课系统中的Base类和db_handler
# V0, 不隐藏salary属性
class Teacher(Base):
def __init__(self, name, salary):
self.name = name
self.salary = salary # 不隐藏salary属性
self.save()
@classmethod
def select(cls, name):
return db_handler.select(cls, name)
t = select('teacher1')
t.salary = 20000 # 可直接修改
# V1 不允许修改salary,只提供查看的接口,使用__salary隐藏属性
class Teacher(Base):
def __init__(self, name, salary):
self.name = name
self.__salary = salary # 隐藏salary属性
self.save()
def get_salary(self): # 提供一个查看salary的接口
return self.__salary
@classmethod
def select(cls, name):
return db_handler.select(cls, name)
t = select('teacher1')
# t.salary = 20000 # 现在不能在外部使用这种方法改变salary
print(t.get_salary()) # 只提供了一个查看的salary的接口
# 进阶补充 如何使得查看salary不使用一个方法t.get_salary()来查看,而使用t.salary的方法查看:使用property装饰器
# V2 使用property装饰器,隐藏salary属性的同时,提供t.salary的方式查看
class Teacher(Base):
def __init__(self, name, salary):
self.name = name
self.__salary = salary # 隐藏salary属性
self.save()
@property
def salary(self): # 提供一个查看salary的接口
return self.__salary
@classmethod
def select(cls, name):
return db_handler.select(cls, name)
t = select('teacher1')
# t.salary = 20000 # 依然不能在外部使用这种方法改变salary
print(t.salary) # 但可以用t.salary查看,提供了一个更加自然的访问私有属性的方法,对比t.get_salary()方法
小结:封装,Python访问控制机制,property装饰器
- 封装只是一个思想,比如类对属性的封装,模块对数据和函数封装。
- 封装可以理解为两层含义:a. 把东西封装起来,b. 控制私有属性的访问和修改,同时提供必要的接口
- Python中可以使用
__属性
的方式隐藏属性,但在python中并不是强制性的。依然可以通过对象._类名__属性名
访问或修改 - 在python中可以用property这个工具,使得可以隐藏私有属性的同时,提供一个优雅的查询方式(对比:t.get_salary() 和t.salary)
多态性
什么是多态性
# 例子, 伪代码
cat.speak() # miao miao ~
dog.speak() # wang wang ~
cow.speak() # mou mou ~
用同一个方法名称调用,却又有不同的效果,表现为多种不同的形态
如何实现多态
# 1. 使用继承抽象类
import abc
from abc import abstractmethod
class Animal(metaclass=abc.ABCMeta):
@abstractmethod
def speak(self):
pass
class Cat(Animal):
def speak(self): # 必须定义speak, 否则实例化时会报错
print('miao miao ~')
class Dog(Animal):
def speak(self): # 必须定义speak, 否则实例化时会报错
print('wang wang ~')
c = Cat()
d = Dog()
c.speak()
d.speak()
# 2. 使用鸭子类型
class Cat: # 不再继承于某个特定的类
def speak(self): # 用一个统一的函数名来表示动物叫这个方法
print('miao miao ~')
class Dog:
def speak(self): # 用一个统一的函数名来表示动物叫这个方法
print('wang wang ~')
c = Cat()
d = Dog()
c.speak()
d.speak()
什么是鸭子类型:不是一个具体的类,如果某些对象都具有统一的调用方法,我们可以说他们属于同一个鸭子类型。在上个例子中我们可以说Cat和Dog都属于 Animal这样一个鸭子类型(但我们并没有定义Animal, 也没有使用继承,只是我们编写程序时让他们具有了统一的speak方法,看起来像属于同一个类型)
小结:多态性,抽象类和鸭子类型
- 多态性也只是面向对象编程中的一种思想,简单的说就是不同对象有相同的调用方法,却会产生不同的调用结果
- 在python中可以用抽象类和鸭子类型实现多态性
- 在python中推荐使用鸭子类型,而不推荐使用抽象类。