【python面向对象】封装(面试题常考)
本章节的内容常常在面试题中出现,大家根据本文的思路可以很好的掌握。
文章目录
- 封装浅层次
- 封装深层次
-
- 一、封装设计思想
-
- 课后练习1
- 课后练习2
- 二、__slots__讲解
- 总结
封装浅层次
数据角度讲,将一些基本数据类型复合成一个自定义类型。
封装数据:
敌人(姓名,血量,攻击力,防御力)
二维向量(x,y)
优势:
1、更符合人类的思考方式。
2、将数据与对数据的操作整合在一起。
行为角度讲,向类外提供必要的功能,隐藏实现的细节。
封装行为:
二维列表助手类DoublelListHelper(获取多个元素get_elements
向量(向左╱向右,求模长,求方向等)
优势:以”模块化的方式进行编程,更可以集中精力设计、组织、指挥多个类协同工作。
一、使用方法,封装变量
#使用方法,封装变量class Wife: def __init__(self,name,age, weight): self.name = name # 本质:障眼法 #实质:实际将变量名改为︰_类名__age # self.__age = age self.set_age(age) # self.__weight = weight self.set_weight(weight) # 提供公开的读写方法 def get_age(self): return self.__age def set_age(self,value): if 21<= value <= 31: self.__age =value else: raise ValueError("我不要") # 提供公开的读写方法 def get_weight(self): return self.__weight def set_weight(self,value): if 40<= value <= 80: self.__weight =value else: raise ValueError("我不要")"""#不建议下面的做法w01 = Wife("铁锤公主",87,87)#重新创建了新实例变量(没有改变类中定义的_age)# w01.__age = 107#修改了类中定义的私有变量w01._Wife__age = 107#__dict__:python内置变量,存储对象的实例变量print(w01.__dict__)"""w01 = Wife("铁锤公主",30,57)w01.set_age(25)w01.set_weight(45)print(w01.get_age())print(w01.get_weight())#25#45
练习:
定义敌人类(姓名,攻击力10–50,血量100–200)
创建一个敌人对象,可以修改数据,读取数据。
class Enemy: def __init__(self,name,attack,hp): self.name = name self.set_attack(attack) self.set_hp(hp) def set_attack(self,value): if 10 <= value <= 50: self.__attack = value else: raise ValueError("不行") def get_attack(self): return self.__attack def set_hp(self,value): if 100 <= value <= 200: self.__hp = value else: raise ValueError("不行") def get_hp(self): return self.__hpenemy = Enemy("灭霸",20,100)enemy.set_attack(30)print(enemy.get_attack())#30
进阶:
二、使用property (读取方法,写入方法),封装变量
""" 使用property (读取方法,写入方法),封装变量。"""class Wife: def __init__(self,name,age, weight): self.name = name # self.set_age(age) self.age = age # self.set_weight(weight) self.weight = weight # 提供公开的读写方法 def get_age(self): return self.__age def set_age(self,value): if 21<= value <= 31: self.__age =value else: raise ValueError("我不要") #属性 property对象拦截对age类变量的读写操作 age = property(get_age,set_age) # 提供公开的读写方法 def get_weight(self): return self.__weight def set_weight(self,value): if 40<= value <= 80: self.__weight =value else: raise ValueError("我不要") #属性 property对象拦截对weight类变量的读写操作 weight = property(get_weight,set_weight)w01 = Wife("铁锤公主",30,57)# w01.set_age(25)w01.age = 25print(w01.age)w01.weight = 45print(w01.weight)# 25# 45
练习:改写上面的敌人类
class Enemy: def __init__(self,name,attack,hp): self.name = name self.attack = attack self.hp = hp def set_attack(self,value): if 10 <= value <= 50: self.__attack = value else: raise ValueError("不行") def get_attack(self): return self.__attack attack = property(get_attack,set_attack) def set_hp(self,value): if 100 <= value <= 200: self.__hp = value else: raise ValueError("不行") def get_hp(self): return self.__hp hp = property(get_hp,set_hp) #hp = property(None,set_hp)#只读属性enemy = Enemy("灭霸",20,100)enemy.attack= 30enemy.hp = 120print(enemy.attack)print(enemy.hp)# 30# 120
内存图:
三、使用property ,封装变量
""" 使用property ,封装变量。"""class Wife: def __init__(self,name,age, weight): self.name = name # self.set_age(age) self.age = age # self.set_weight(weight) self.weight = weight # 提供公开的读写方法 #创建property对象,只负责拦截读取操作 @property def age(self): return self.__age #只负责拦截写入操作 @age.setter def age(self,value): if 21<= value <= 31: self.__age =value else: raise ValueError("我不要") #属性 property对象拦截对age类变量的读写操作 @property # 提供公开的读写方法 def weight(self): return self.__weight @weight.setter def weight(self,value): if 40<= value <= 80: self.__weight =value else: raise ValueError("我不要")w01 = Wife("铁锤公主",30,57)# w01.set_age(25)w01.age = 25print(w01.age)w01.weight = 45print(w01.weight)# 25# 45
练习:改写敌人类
class Enemy: def __init__(self,name,attack,hp): self.name = name self.attack = attack self.hp = hp @property def attack(self): return self.__attack @attack.setter def attack(self,value): if 10 <= value <= 50: self.__attack = value else: raise ValueError("不行") @property def hp(self): return self.__hp @hp.setter def hp(self,value): if 100 <= value <= 200: self.__hp = value else: raise ValueError("不行")enemy = Enemy("灭霸",20,100)enemy.attack= 30enemy.hp = 120print(enemy.attack)print(enemy.hp)# 30# 120
四、@property讲解
公开的实例变量,缺少逻辑验证。私有的实例变量与两个公开的方法相结合,又使调用者的操作略显复杂。而属性可以将两个方法的使用方式像操作变量一样方便。
- 定义:
@property def name(self): return self.__name @name.setter def name(self,name): self.__name = name
- 调用:
对象.属性名 = 数据
变量 = 对象.属性名
- 说明:
1、通常两个公开的属性,保护一个私有的变量。
2、 @property负责读取,@属性名.setter负责写入。
3、只写:属性名= property(None,写入方法名)。
封装深层次
设计角度讲
(1)分而治之
- 将一个大的需求分解为许多类,每个类处理一个独立的功能。
- 拆分好处:便于分工,便于复用,可扩展性强。
(2)变则疏之
- 变化的地方独立封装,避免影响其他类。
(3)高内聚(目标一致)
- 类中各个方法都在完成一项任务(单一职责的类)。
(4)低耦合
- 类与类的关联性与依赖度要低(每个类独立)。
例如:【硬件高度集成化,又要可插拔】
一、封装设计思想
""" 封装设计思想 需求︰老张开车去东北"""class Person: def __init__(self,name): self.name = name @property def name(self): return self.__name @name.setter def name(self,value): self.__name = value def go_to(self,str_position,type): """ 去 :param str_position:位置 :param type:方式 """ print(self.name,"去",str_position) type.run(str_position)class Car: def run(self,str_position): """ 行驶 :param str_position: 位置 """ print("汽车行驶到:"+ str_position)lz = Person("老张")car = Car()lz.go_to("东北",car)# 老张 去 东北# 汽车行驶到:东北
练习:请以面向对象的思想,描述下列场景:小明在招商银行取钱。(面试题)
class Person: def __init__(self,name,money): self.name = name self.money = money @property def name(self): return self.__name @name.setter def name(self,value): self.__name = value @property def money(self): return self.__money @money.setter def money(self, value): self.__money = valueclass Bank: def __init__(self,name,money): self.name = name self.money = money @property def name(self): return self.__name @name.setter def name(self,value): self.__name = value @property def money(self): return self.__money @money.setter def money(self, value): self.__money = value def withdraw_money(self,person,value): """ 取钱 """ self.money -= value person.money += value print(person.name,"取了%d钱"%value)person = Person("小明",0)bank = Bank("招商银行",100000)bank.withdraw_money(person,1000)#小明 取了1000钱
方法在哪个类写,由谁承担便有谁来做
课后练习1
请用面向对象思想,描述以下场景∶
张无忌 教 赵敏 九阳神功
赵敏 教 张无忌 化妆
张无忌 上班 挣了 10000
赵敏 上班 挣了 6000
思考∶变化点是数据的不同还是行为的不同。
class Person: def __init__(self,name): self.name = name @property def name(self): return self.__name @name.setter def name(self,value): self.__name = value def teach(self,other,skill): print(self.name,"教",other.name,skill) def work(self, money): print(self.name,"上班挣了%d钱"%money)person01 = Person("张无忌")person02 = Person(" 赵敏 ")person01.teach(person02,"九阳神功")person02.teach(person01,"化妆")person01.work(10000)person02.work(6000)# 张无忌 教 赵敏 九阳神功# 赵敏 教 张无忌 化妆# 张无忌 上班挣了10000钱# 赵敏 上班挣了6000钱
上面的例子体会∶对象区分数据的不同
课后练习2
请用面向对象思想,描述以下场景∶
玩家(攻击力)攻击敌人(血量),敌人受伤(掉血),还可能死亡((掉装备,加分)。
敌人(攻击力)攻击玩家,玩家(血量)受伤(掉血/碎屏),还可能死亡(游戏结束)。
class Player: def __init__(self,atk,hp): self.atk = atk self.hp = hp def attack01(self,other): #打的逻辑 print("玩家攻击敌人") # 通过敌人对象地址﹐调用实例方法。 other.damage02(self.atk) def damage01(self,value): print("玩家受伤了") self.hp -= value if self.hp <= 0: self.__death() # 私有的死亡方法 def __death(self): # 死亡的逻辑 print("玩家死喽") print("游戏结束")class Enemy: def __init__(self,atk,hp): self.atk= atk self.hp = hp def damage02(self,value): print("敌人受伤了") #受伤的逻辑 self.hp -= value if self.hp <= 0: self.__death() # 私有的死亡方法 def __death(self): # 死亡的逻辑 print("死亡") print("掉装备") print("加分") def attack02(self,other): print("敌人攻击玩家") other.damage01(self.atk)p01 = Player(100,1000)e01 = Enemy(10,200)#第一回合p01.attack01(e01)e01.attack02(p01)#第二回合p01.attack01(e01)# 玩家攻击敌人# 敌人受伤了# 敌人攻击玩家# 玩家受伤了# 玩家攻击敌人# 敌人受伤了# 死亡# 掉装备# 加分
此题心得:当这两个名称重复时,程序会默认调用Int型对象,但Int对象没有什么调用可言,就爆出了这个错误(TypeError: ‘int’ object is not callable解决办法),解决方法也很简单,要么更改变量名,要么更改方法名。
上面的例子体会∶类区别行为的不同
二、__slots__讲解
- 作用:限定一个类创建的实例只能有固定的实例变量。
- 语法:
在类中定义__slots__=(“变量名1”﹐”变量名2”...)__slots__=("__age")
- 优点:防止用户因错写属性的名称而发生程序错误。
- 缺点:丧失了动态语言可以在运行时为对象添加变量的灵活性。
总结
封装
- 数据角度: 将多个变量封装到一个自定义类中。(优势:符合人类的思考方式;可以将数据与对数据的操作封装到一起)
- 功能角度: 对外提供必要的功能,隐藏实现的细节。
----私有化∶将名称命名为以双下划线开头,内部修改成员名称
---- 属性:对实例变量的保护(拦截读/写操作)
----记住:通过对象地址调用实例成员
----slots:限定类创建的对象只能有固定的实例变量。 - 设计角度:
----分而治之:将大的需求分解为多个类,每个类负责一个职责。
----变则疏之:遇到变化点单独封装为一个类。
----高内聚∶一个类有且只有一个发生变化的原因。
----低耦合:类与类的关系松散。