面向对象三大特性之封装
面向对象三大特性之封装
在讲封装前我们可以先了解什么是组合
一.组合
1.什么是组合?
组合指的就是一个对象中包含这另外一个或多个对象
- 在继承中:
继承是类与类的关系,子类继承父类的属性和方法,子类与父类属于从属关系
- 在组合中:
组合是对象与对象的关系,一个对象拥有另外一个对象的属性和方法,是一种包含关系
2.为什么要用组合?
为了减少代码冗余
与继承相比
耦合度低,程序可扩展性高
3.组合怎么用?
# 组合实现
# 父类
class OldPeople:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 学生类(子类)
class OldStudent(OldPeople):
def __init__(self, name, age, sex):
super().__init__(name, age, sex)
# 老师类
class OldPeople(OldPeople):
def __init__(self, name, age, sex):
super().__init__(name, age, sex)
#日期类:
class Brith:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def tell_brith(self):
print(f'''
===== 出生年月日 =====
年: {self.year}
月: {self.month}
日: {self.day}
''')
stu1 = OldStudent('sean', 19, 'female')
Brith_obj = Brith(2000, 1, 1)
stu1.Brith_obj = Brith_obj # 等于在学生类下面添加了一个self.Brith_obj = Brith_obj
print(stu1.Brith_obj.year, stu1.Brith_obj.month, stu1.Brith_obj.day)
stu1.Brith_obj.tell_brith()# 会从学生类的对象引用日期类对象的方法
练习需求:
选课系统:
1.有学生、老师类,学生与老师有属性 “名字、年龄、性别、课程”,
2.有方法 老师与学生可以添加课程, 打印学习/教授课程。
class People:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
# 打印出生日期方法
def tell_birth(self):
print(f'''
年: {self.date_obj.year}
月: {self.date_obj.month}
日: {self.date_obj.day}
''')
# 添加课程
def add_course(self, course_obj):
# self.course_name = course_name
# self.course_price = course_price
# self.course_time = course_time
self.course_list.append(course_obj)
# 打印当前对象中课程列表的所有课程信息
# ---》 打印所有课程
def tell_all_course_info(self):
# 从当前对象中课程列表中取出所有的课程对象
for course_obj in self.course_list:
# 通过课程对象.打印课程信息方法
course_obj.tell_course_info()
class Student(People):
# 假设从程序开始到灭亡 在实例化时都不修改该对象的属性
def __init__(self, name, age, sex):
super().__init__(name, age, sex)
self.course_list = []
class Teacher(People):
def __init__(self, name, age, sex):
super().__init__(name, age, sex)
self.course_list = []
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# 定义一个课程类: 课程有: 课程名称, 课程价格, 课程周期
class Course:
def __init__(self, course_name, course_price, course_time):
self.course_name = course_name
self.course_price = course_price
self.course_time = course_time
# 定义打印课程方法 : 只打印一个课程信息
def tell_course_info(self):
print(f'''
====== 课程信息如下 ======
课程名称: {self.course_name}
课程价格: {self.course_price}
课程周期: {self.course_time}
''')
# 创建学生对象
stu1 = Student('HCY', 2000, 'female & male')
date_obj = Date('公元19', '11', '11')
stu1.date_obj = date_obj
# stu1.tell_birth()
# 创建课程对象
python_obj = Course('python', 77777, 6)
go_obj = Course('go', 88888, 4)
# 当前学生添加了课程对象
# 添加python课程
stu1.add_course(python_obj)
# 添加go课程
stu1.add_course(go_obj)
# 当前学生打印所有课程信息
stu1.tell_all_course_info()
二.封装
1什么是封装?
封装就是将一堆属性和方法整合到一起封装到对象中
2.为什么要用封装?
封装就是为了方便存取属性和方法,通过"对象."的方式就能存放/获取属性和方法
3.封装怎么用
class User:
x = 10
def func():
pass
obj = User()
obj.y = 20 # 添加一个新的属性y = 20,这就是封装
obj ---> x, func, y # obj对象可以取用这些属性和方法
同时,在封装到对象或类中的属性和方法,我们可以控制对他们的访问,分两步实现,隐藏和开放接口,我们自己(非官方)命名这种隐藏和开放的方式为限制访问机制
4.什么是限制访问机制
凡是在类内部定义的属性或方法,以__(双下划线)开头的属性或方法名,都会被限制,外部不能 “直接访问” 该属性原型。
PS: 看着像将该属性或方法隐藏起来了。
# python特有的:注意: 凡是在类内部定义 __开头的属性或方法,都会变形为 _类名 __属性/方法。
为什么要使用这种机制:
就是为了不让外部轻易获得被隐藏的属性和方法
举个例子:
# 隐藏属性
class User:
# 以__开头的属性
__name = 'sean' # __name变成了_User__name
# 以__开头的方法
def __eat(self): # __eat变成了_User__eat
print('from user')
def func(self): # 在类的内部可以直接访问变形的方法或属性
self.__eat()
obj = User()
# print(obj.__name) # 报错,提示没有此属性
print(obj._User__name) # __name变成了_User__name
# obj.__eat() # 报错,提示没有此方法
obj._User__eat() # __eat变成了_User__eat
obj.func()
# 注意:
1.在类外部无法直接访问双下划线开头的属性,但是知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,所以说这种限制没有严格意义上的限制外部访问,仅仅只是语法意义上的变形
2.在类内部是可以直接访问双下划线开头的属性的(注意不能跨类访问)
因为在类定义阶段内部双下滑线开头的属性统一发生了形变
class A:
def fa(self):
print('from A') # 在定义阶段已经变形了,不能访问到其他类里面
def test(self):
self.fa()
class B(A):
def __fa(self):
print('from B')
b = B()
b.test()
# 输出结果:from A
3.变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形
# 定义属性就是为了使用,所以隐藏不是目的,将数据隐藏只是为了限制类外部直接对数据进行操作,然后类内部应该提供相应的接口允许类外部间接的操作接口,接口之上可以附加额外的逻辑来对数据的操作进行严格的控制
举例:隐藏函数的属性
class ATM:
# 取钱功能:
# 1.插入磁卡
def __insert_card(self):
print('开始插卡...')
pass
# 2.输入密码
def __input_pwd(self):
print('输入密码...')
pass
# 3.输入取款金额
def __input_bal(self):
print('输入取款金额...')
pass
# 4.吐钱
def __output_money(self):
print('开始吐钱...')
pass
# 5.打印流水账单
def __print_flow(self):
print('打印流水账单...')
pass
# 取款顺序规范接口:
def withdraw(self): # 只需要一个对外取款接口,其他的都可以隐藏起来
# 1.插入磁卡
self.__insert_card()
# 2.输入密码
self.__input_pwd()
# 3.输入取款金额
self.__input_bal()
# 4.吐钱
self.__output_money()
# 5.打印流水账单
self.__print_flow()
amt_obj = ATM()
amt_obj.withdraw()
# 隐藏据属性:
>>> class Teacher:
... 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 not isinstance(name,str):
... raise TypeError('姓名必须是字符串类型')
... if not isinstance(age,int):
... raise TypeError('年龄必须是整型')
... self.__name=name
... self.__age=age
...
>>>
>>> t=Teacher('lili',18)
>>> t.set_info(‘LiLi','19') # 年龄不为整型,抛出异常
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in set_info
TypeError: 年龄必须是整型
>>> t.set_info('LiLi',19) # 名字为字符串类型,年龄为整形,可以正常设置
>>> t.tell_info() # 查看老师的信息
姓名:LiLi,年龄:19
总结隐藏属性与开放接口,本质就是为了明确地区分内外,类内部可以修改封装内的东西而不影响外部调用者的代码;而类外部只需拿到一个接口,只要接口名、参数不变,则无论设计者如何改变内部实现代码,使用者均无需改变代码。这就提供一个良好的合作基础,只要接口这个基础约定不变,则代码的修改不足为虑。
5.property
-
什么是poperty
是一个python内置的装饰器,可以装饰在"类内部的方法"上。可以将该方法调用方式由 ----> 对象.方法() ---> 对象.方法,就是将调用函数方法伪装成调用数据属性,当调用此方法时会将该函数方法的结果返回出来
-
为什么要使用property
PS: 在某些场景下,调用的方法只是用来获取计算后的某个值。
PS: 必须通过 对象.方法() 方式调用,让该方法看起来像动词。
-
怎么使用property
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 ** 2)
obj = People('lili', 75, 1.85)
print(obj.bmi) # 不需要通过方法()就可以得到结果,伪装成通过属性得到结果
使用property有效地保证了属性访问的一致性。另外property还提供设置和删除属性的功能
class Foo:
def __init__(self, val):
self.__NAME = val # 将属性隐藏起来
@property
def name(self):
return self.__NAME
@name.setter # 如果需要修改值,必须再写一次方法,并且需要修改的方法名字要与被property装饰器后的方法一样,括号里面要添加一个value
def name(self, value):
if not isinstance(value, str): # 在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME = value # 通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter # 删除是在内部删除,在外部也要写上 del f.name
def name(self):
raise PermissionError('Can not delete')
f = Foo('lili')
f.name
# lili
f = Foo('lili')
print(f.name)
# # lili
# # f.name = '123' # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
f.name = 'egon' # 触发name.setter装饰器对应的函数name(f,’Egon')
print(f.name)
# # del f.name # 触发name.deleter对应的函数name(f),抛出异常PermissionError
# f.name = 'LiLi' # 触发name.setter装饰器对应的函数name(f,’Egon')
# f.name = '123' # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
# del f.name # 触发name.deleter对应的函数name(f),抛出异常PermissionError