组合、封装、多态
组合
1.什么是组合?
组合是指一个对象中的属性,是另一个对象。
继承:一种类与类的关系,一种什么是什么的关系,子类是父类的从属关系。
组合:对象与对象的关系,一种什么有什么的关系,一个对象拥有另一个对象。
组合优点:让类与类之间解耦,可扩展性高
组合的缺点:编写复杂度高
继承优点:编写复杂度低
继承缺点:耦合度高
class People:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
class Teacher(People):
def __init__(self, name, age, gender):
super().__init__(name, age, gender)
class Student(People):
def __init__(self, name, age, gender):
super().__init__(name, age, gender)
class Date:
def __init__(self, year, mouth, day):
self.year = year
self.mouth = mouth
self.day = day
def tell_birth(self):
print(f'''
=== 出生日期 ===
年:{self.year}
月:{self.mouth}
日:{self.day}
''')
tea1 = Teacher('tank', 17, 'male')
date_obj = Date(2002, 1, 1)
tea1.date = date_obj #(组合) 将date的对象赋值到tea1对象的date属性中
tea1.date.tell_birth()
'''=== 出生日期 ===
年:2002
月:1
日:1'''
2.目的:减少代码冗余
3.组合的练习
选课系统需求:
1.学生类,老师类, 学生和老师都有课程属性, 每一门课程都是一个对象.
课程: 课程名字,课程周期,课程价钱
2.学生和老师都有选择课程的功能, 还有打印所有课程的功能.
class People:
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def add_course(self, course_name):
# 添加每一门课程
self.course_list.append(course_name) # 每一个course_name都是一个course对象
def tell_all_course(self): # 组合,将每一个课程对象都赋予给tell_all_course方法
for course_obj in self.course_list:
# 调用每一个课程对象查看课程信息的方法
course_obj.tell_course_info()
class Teacher(People):
def __init__(self, name, age, gender):
super().__init__(name, age, gender)
self.course_list = [] # 将每个实例化的课程对象都放在列表里
class Student(People):
def __init__(self, name, age, gender):
super().__init__(name, age, gender)
self.course_list = []
class Course: # 课程类
def __init__(self, course_name, course_period, course_price):
self.course_name = course_name
self.course_period = course_period
self.course_price = course_price
def tell_course_info(self):
print(f'''
课程名称:{self.course_name}
课程周期:{self.course_period}
课程价钱:{self.course_price}
''')
teal = Teacher('Lilei', 18, 'male')
python_obj = Course('python', 6, 20000)
linux_obj = Course('linux', 6, 10000)
teal.add_course(python_obj)
teal.add_course(linux_obj)
teal.tell_all_course()
''' 课程名称:python
课程周期:6
课程价钱:20000
课程名称:linux
课程周期:6
课程价钱:10000'''
封装
1.什么是封装
封装是面向对象的核心。
封:比如把一个袋子封起来
装:比如把一堆东西装到袋子里
封装指的是把一堆属性(特征与技能)封装到一个对象中
比喻:对象就好比一个袋子,袋子里面装一堆属性
对象可以‘.’的方式获取属性
2.为什么要封装
(1. 封装数据属性:将数据隐藏起来,从而类的使用者无法直接操作该数据属性,需要类的设计者在类内部开辟接口,让类的使用者通过接口间接的操作数据,类的设计者可以在接口上任意附加逻辑,从而严格控制类的使用者对属性的操作。
class People:
def __init__(self,name,age):
self.__name = name
self.__age = age
def tell_info(self):
print('<%s:%s>'%(self.__name,self.__age))
def set_info(self,name,age):#限定用户输入的数据类型
if type(name) is not str:
raise TypeError('用户名必须是str类型')
if type(age) is not int:
raise TypeError('年龄必须是int')
self.__name = name
self.__age = age
p = People('xiaohua','18')
p.tell_info()
p.set_info('xiaolei',17)
p.tell_info()
'''<xiaohua:18>
<xiaolei:17>'''
(2. 封装的目的是为了方便存取,可以通过对象‘.’属性的方式获取属性,隔离复杂度使复杂的操作简单化
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
a = ATM()
a.withdraw()
'''
插卡
用户认证
输入取款金额
打印账单
取款
'''
3.如何封装
特征:变量--》数据属性
技能:函数——》方法属性
在类内部,定义一堆属性(特征与技能)
通过对象。属性 = 属性值
c语言也有面型对象编程的思想
4.什么是访问限制机制
在类内部定义的,凡是以__开头的数据属性与方法都会被python隐藏起来,外部不能直接访问类(__开头的)内部的属性,内部可以直接访问。(3. 中的例子都是限制访问的方式进行封装的)
访问限制机制的目的:把一堆隐私的属性与不能被外部轻易访问的属性可以隐藏起来,不被外部直接调用。
好处:对重要数据获取的逻辑更加严谨,进而保证了数据的安全。
class Foo:
__n = 1
def __init__(self,name):
self.__name = name
def __f1(self):#_Foo__f1(只是将属性名__f1改成了_Foo__f1,当使用_Foo__f1去调用方法时是可以调用的)
print('f1')
def f2(self):
self.__f1() #self._Foo__f1()
print(self.__name)#self._Foo__name
print(self.__n)#self._Foo__n
obj = Foo('lilei')
obj.f2()
#print(obj.__name)
print(obj.__dict__)
print(Foo.__dict__)
'''
f1
lilei
1
{'_Foo__name': 'lilei'}
{'__module__': '__main__', '_Foo__n': 1, '__init__': <function Foo.__init__ at 0x00000252B98E6048>, '_Foo__f1': <function Foo.__f1 at 0x00000252B98E66A8>, 'f2': <function Foo.f2 at 0x00000252C09A4048>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
Process finished with exit code 0
'''
这种隐藏的特点:
- 只是语法上的变形,会将__开头的属性定义为自己的类名__属性名。
- 该变形只在类定义阶段发生一次,在类定义阶段之后新增的__开头的属性并不会发生变形.
- 隐藏是对外不对内,由于类定义阶段类内的属性都跟着变形了所以可以访问到类内部的属性。
- 如果父类不想让子类覆盖自己的同名的方法,可以将方法定义为私有的,
5.接口
隐私属性可以通过封装一个接口,在接口内做业务逻辑处理,再把数据返回给调用者。
注意:在python中不会强制限制属性的访问,类内部__开头的属性只是对属性的名字做了一种变形。
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('用户认证')
def __input(self):
print('输入取款金额')
def __print_bill(self):
print('打印账单')
def __take_money(self):
print('取款')
def withdraw(self):
self.__card()
self.__auth()
self.__input()
self.__print_bill()
self.__take_money()
a = ATM()
a.withdraw()
'''
插卡
用户认证
输入取款金额
打印账单
取款
'''
property(了解)
1.什么是property(属性)
python内置的装饰器,主要给类内部的方法是用。
2.为什么用property
使用目的:将类内部的方法(def 方法名())变成(def 方法名)
将对象调用某个方法时,将对象.方法()变成 对象.方法 (看起来像一个普通的数据属性)
3.如何使用property
@property
property属性的定义和调用要注意一下几点:
-
定义时,在实例方法的基础上添加 @property 装饰器;并且仅有一个self参数
-
调用时,无需括号
注意:
- 经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法
- 新式类中的属性有三种访问方式,并分别对应了三个被 @property、@方法名.setter、@方法名.deleter 修饰的方法
由于新式类中具有三种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
例子:计算人体的bmi: bmi值 = 体重 / (身高 * 身高)
class People:
def __init__(self,name,weight,height):
self.name = name
self.weight = weight
self.height = height
@property
def bmi(self):
return self.weight/(self.height * self.height)
# 使用装饰器对get_name进行装饰,那么会自动添加一个叫get_name的属性,当调用获取get_n的值时,调用装饰的方法
@property
def get_name(self):
return self.name
@get_name.setter## 使用装饰器对get_name进行装饰,当对get_name设置值时,调用装饰的方法
def set_name(self,val):
self.name = val
@get_name.deleter
def del_name(self):
del self.name
P = People('xiaohua',120,170)
print(P.get_name)
print(P.bmi)# 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
del P.del_name
print(P.get_name)
property方法中有个四个参数
- 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
- 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
- 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
- 第四个参数是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息
由于类属性方式创建property属性具有3种访问方式,我们可以根据它们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
综上所述:
- 定义property属性共有两种方式,分别是【装饰器】和【类属性】,而【装饰器】方式针对经典类和新式类又有所不同。
- 通过使用property属性,能够简化调用者在获取数据的流程
多态:
1.什么是多态
多态:同一种事物的多种形态。
2.多态的目的:
多态也称为多态性,在程序中继承就是多态的表现形式。
多态的目的是为了让多种不同类型的对象在使用相同功能(方法)的情况下,调用同一个名字的方法
父类:定义一套统一的标准。
子类:遵循父类统一的标准。
多态的最终目的:统一子类定义方法的标准(统一之类的编写规范),为了让使用者更方便调用相同功能的方法
class Animal:
# 吃
def eat(self):
pass
# 喝
def drink(self):
pass
# 叫
def speak(self):
pass
# 猪
class Pig(Animal):
# 吃
def eat(self):
print('猪在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('哼哼哼~~~')
# 猫
class Cat:
# 吃
def eat(self):
print('猫在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('喵喵喵~~')
# 狗
class Dog:
# 吃
def eat(self):
print('狗在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('汪汪汪~~~')
# 正确教材
pig = Pig()
cat = Cat()
dog = Dog()
pig.speak()
cat.speak()
dog.speak()
3.如何实现:
——继承
注意:在python中不会强制要求子类必须遵循父类的标准,所以有了抽象类。
抽象类:
- 是什么:abc模块 abstract_class
- 使用的目的与功能:强制要求子类必须遵循父类的标准。
- 如何使用:
import abc - 使用abc时应注意父类下加abc装饰器的方法在子类中必须定义而且必须用同样的方法名,否则会报错:Can't instantiate abstract class 子类名 with abstract methods 方法名,只要子类中有父类强制定义的方法,其他的方法就可以随意定义不会报错。
import abc
class Animal(metaclass=abc.ABCMeta):
# 吃
@abc.abstractmethod
def eat(self):
pass
# 喝
@abc.abstractmethod
def drink(self):
pass
# 叫
@abc.abstractmethod
def speak(self):
pass
# 猪
class Pig(Animal):
# 吃
def eat(self):
print('猪在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('哼哼哼~~~')
# 派生
def run(self):
pass
pig = Pig()
鸭子类型
python不推崇强制,但是推崇类都遵循鸭子类型。
什么是鸭子类型
鸭子类型:在不知道当前对象是何物的情况下,长得像什么就是什么。
鸭子类型的特点:都遵循了同一个定义标准而且不强制使用抽象类,这样即实现了减少耦合,又提高了程序的可扩展性。这样程序的可扩展性就会更高。
# 猪
class Pig:
# 吃
def eat(self):
print('猪在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('哼哼哼~~~')
# 猫
class Cat:
# 吃
def eat(self):
print('猫在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('喵喵喵~~')
# 狗
class Dog:
# 吃
def eat(self):
print('狗在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('汪汪汪~~~')
当开发者默认遵守鸭子类型时,也就不需要在使用父类加抽象类的方法强制开发者遵守规则了,所以就降低了代码的耦合度,使代码扩展性更高。
继承与鸭子类型的比较:
继承:耦合性太高,程序的可扩展性差
鸭子类型:耦合度低,程序的可扩展性强
class Pig:
# 吃
def eat(self):
print('猪在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('哼哼哼~~~')
# 猫
class Cat:
# 吃
def eat(self):
print('猫在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('喵喵喵~~')
# 狗
class Dog:
# 吃
def eat(self):
print('狗在吃饭')
pass
# 喝
def drink(self):
pass
def speak(self):
print('汪汪汪~~~')
dog = Dog()
cat = Cat()
pig = Pig()
def BARK(animal):#当所有的类都按照鸭子类型定义时,对象调用属性的方法会更加的方便
animal.speak()
BARK(dog)
BARK(cat)
BARK(pig)
总结
1.组合:
- 什么是组合
组合指的是一个对象中的属性,是另一个对象.
- 为什么要使用组合
组合目的和继承一样, 为了减少代码冗余.
2.封装:
- 什么是封装?
封装指的是把一堆属性(特征与技能)封装到一个对象中.
存数据的目的是为了取, 对象可以"."的方式获取属性.
- 为什么要封装?
封装的目的为了方便存取,可以通过对象.属性的方式获取属性.
3.访问限制机制:
- 什么是访问限制机制?
在类内部定义, 凡是以__开头的数据属性与方法属性,都会被python内部隐藏起来,让外部不能"直接"访问类内部的__开头的属性.
- 访问限制机制的目的?
一堆隐私的属性与不能被外部轻易访问的属性, 可以隐藏起来,不被外部直接调用.
4.property:
- 什么是property
python内置的装饰器, 主要是给类内部的方法使用.
- 为什么要用property
在对象调用某个方法时,将对象.方法()变成对象.方法(看起来想一个普通的数据属性)
obj.bmi() == obj.bmi
- 如何使用property
@property
def 类内部的方法(self):
5.多态:
- 什么是多态?
多态指的是同一种事物的多种形态.
- 多态的目的:
多态的目的是为了, 让多种不同类型的对象, 在使用相同功能的情况下,调用同一个名字的方法名.
父类: 定义一套统一的标准.
子类: 遵循父类统一的标准.
- 如何实现:
- 继承父类
- 继承抽象类
- 鸭子类型
6.抽象类的目的:
强制子类必须遵循父类的一套标准.
7.鸭子类型:
- 什么是鸭子类型?
在不知道当前对象是何物的情况下,但是你长得像鸭子,那么你就是鸭子类型.
- 继承:
耦合性太高,程序的可扩展性差
- 鸭子类型:
耦合度低,程序的可扩展性强