组合\封装\property装饰器\多态

组合与重用性

软件重用的重要方式除了继承之外还有另外一种方式,即:组合

  • 组合:是指,在一个类中以另一个类的对象作为数据属性,称为类的组合
# 在一个类中以另一个类的对象作为数据属性
# 类的组合
class Equip:    #武器装备类
    def fire(self):
        print('release Fire skill')

class Riven:    #英雄Riven的类,需要武器装备
    camp = 'Noxus'
    def __init__(self,nickname):
        self.nickname = nickname
        self.equip = Equip() #用Equip类产生一个装备,赋值给实例的equip

r1 = Riven('锐雯')
r1.equip.fire()    # 可以使用组合的类产生的对象所只有的方法

组合与继承都是有效地利用已有类的资源的重要方式.但是二者的概念和使用场景借不同

  • 1.继承的方式:
    通过继承建立了派生类与基类之间的关系,它是一种’是’的关系,比如人是动物,树是植物.
    当类之间有很多相同的功能,提供这些共同的功能作为基类,用继承比较好,如老师在学校,学生也在学校
  • 2.组合的方式
    用组合的方式建立了类与组合的类之间的关系,它是一种’有’的关系,比如教授有生日,教授有python和linux课程,教授有学生s1.s2.s3…

    我们把类看成一个工厂,有两个类,就是两个工厂.工厂一是用来造机器人(工厂一对象)的,工厂二是用来造机器人胳膊的,工厂二造一个机器人胳膊(工厂二对象),工厂一把这个胳膊装到自己的机器人上(作为对象的一个属性),工厂二创建的对象是工厂一创建对象的一个属性.我们通过工厂一创建的对象就能访问到工厂二创建的对象的所有数据属性和函数属性

class Penple:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

class Course:
    def __init__(self,name,period,price):    # 参数名字 哪一期 价格
        self.name = name
        self.period = period
        self.price = price

    def tell_info(self):
        print('<%s %s %s>' %(self.name,self.period,self.price))

class Teacher(Penple):
    def __init__(self,name,age,sex,job_title):
        Penple.__init__(self,name,age,sex)
        self.job_title = job_title
        self.course = []
        self.student = []

class Student(Penple):
    def __init__(self,name,age,sex):
        Penple.__init__(self,name,age,sex)
        self.course = []
    def tell_info(self):
        print('<%s %s %s %s>' % (self.name, self.age, self.sex, self.course))


# 创建类Penple的对象老师和学生
egon = Teacher('egon',18,'male','沙河霸道金牌讲师')
wood = Student('wood',18,'male')
# 创建类Course的对象课程
python = Course('python','9mons',20000)
linux = Course('linux','11mons',20000)

#给老师egon和学生wood添加课程
egon.course.append(python)
egon.course.append(linux)
wood.course.append(python)

#为老师egon添加学生wood
egon.student.append(wood)

# 使用
for obj in egon.course:
    obj.tell_info()

for obj in egon.student:
    obj.tell_info()

当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好

  • 使用组合解决了两个问题:
    • 1.解决了代码冗余的问题,减少了代码
    • 2.解决了同一数据多次存储,浪费内存空间的问题

封装

什么是封装?

装:是讲一堆属性装进一个容器.
封:是把这些东西隐藏起来,但这种隐藏式对内不对外的

为何要封装?

封装不是单纯意义的隐藏

  • 封装数据属性的目的:
    将数据属性封装起来,类外部的使用就无法直接操作该数据属性了,需要类内部开一个接口给使用者,类的设计者可以在接口之上附加任意逻辑,从而严格控制使用者对属性的操作
  • 封装函数属性的目的:
    隔离复杂度

如何封装?

只需要在属性前加上__开头,该属性就会被隐藏起来.
尝试一:

class Foo:
    # 给属性x前加上__,隐藏
    __x = 111
    def __init__(self,m,n):
        self.__m = m
        self.n = n

    def __func(self):
        print('Foo.func')
# 通过类调__x,__func
Foo.__x
Foo.__func()
'''运行结果
AttributeError: type object 'Foo' has no attribute '__x'
AttributeError: type object 'Foo' has no attribute '__func'
找不到
'''
# 创建一个对象obj
obj = Foo(10,20)
# 用对象分别来调n,__x,__func
print(obj.n)
print(obj.__x)
obj.__func()
'''执行结果:
20
AttributeError: 'Foo' object has no attribute '__x'
AttributeError: 'Foo' object has no attribute '__func'
'''
# 可以看到没有隐藏的n可以找到,隐藏的找不到
# 我们会想到有一个__dict__可以查看类里的属性和对象里的属性
print(Foo.__dict__)
print(obj.__dict__)
'''执行结果:
{'_Foo__x': 111,'_Foo__func': <function Foo.__func at 0x0000023278E888C8>,...}
{'_Foo__m': 10, 'n': 20}
'''
# 我们会发现在他的属性了其实是有这两个的,只是改了名字,在前面又加了个_Foo
# 如果我们用_Foo__x或者_Foo__func去在对象或者类里调用
Foo._Foo__x
Foo._Foo__func()
print(obj._Foo__x)
obj._Foo__func()
'''执行结果:
111
Foo.func
111
Foo.func
'''
# 显而易见是可以找到的,但我们不会那么去掉,我们隐藏它就是为了不给你直接调,你还要去调,那不是傻吗
  • 根据上面的尝试一,我们来说隐藏具备的特点一:
    隐藏只是一种语法意义上的变形,即__开头的属性会在检查语法时发生变形,变成:_类名__属性名,如:_Foo__x

尝试二:

# 我们已经了解到在检查语法时,__开头的会变形为_类名__属性名
class Foo:
    # 给属性x前加上__,隐藏
    __x = 111     # _Foo__x
    def __init__(self,m,n):
        self.__m = m   # _Foo__m
        self.n = n
    def __func(self):     # _Foo__func
        print('Foo.func')
    #在内部写一个函数,来调用类里的隐藏属性
    def tell_info(self):
        print(self.__x,self.__m,self.n)   # self._Foo__x,self._Foo__m
        self.__func()    #self._Foo__func()

obj = Foo(10,20)
Foo.tell_info(obj)
obj.tell_info()
'''执行结果:
111 10 20
Foo.func
111 10 20
Foo.func
'''
# 结果显示能找到,我们也可以分析出原因
  • 根据上面的尝试二,我们来说隐藏具备的特点二:
    这种隐藏是对外不对内的,因为在类内部检测语法时所有的代码统一发生了变形

尝试三

class Foo:
    # 给属性x前加上__,隐藏
    __x = 111
    def __init__(self,m,n):
        self.__m = m
        self.n = n
    def __func(self):
        print('Foo.func')
obj = Foo(10,20)
# 我们手动给类或者对象加一个带有__的属性
Foo.__y = 222
obj.__z= 3333
print(obj.__y)
print(Foo.__y)
print(obj.__z)
'''执行结果:
222
222
3333
'''
# 手动加的并没有被隐藏
  • 根据上面的尝试三,我们来说隐藏具备的特点三:
    这种变形只在检测语法时发生一次,在类定义之后新增的__开头的属性并不会发生变形

  • 还有一个隐藏具备的特点四:
    如果父类不想让子类覆盖自己的属性,可以在属性前加__开头

class Foo:
    def f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.f1()

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

b=Bar()
b.f2()
'''执行结果:
Foo.f2
Bar.f1
'''
# 子类会覆盖掉父类的属性
# 如果父类不想让子类覆盖自己的属性
class Foo:
	#给父类的属性加上__
    def __f1(self):   # _Foo__f1
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.__f1()   # _Foo__f1  这个如果你想调父类的你就加__,想调子类就不加

class Bar(Foo):
    def f1(self):
        print('Bar.f1')

b=Bar()
b.f2()
'''执行结果:
Foo.f2
Foo.f1
'''

property装饰器

我们有一个例子:

  • 例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
    成人的BMI数值:
    过轻:低于18.5
    正常:18.5-23.9
    过重:24-27
    肥胖:28-32
    非常肥胖, 高于32
      体质指数(BMI)=体重(kg)÷身高^2(m)
      EX:70kg÷(1.75×1.75)=22.86

没有property装饰器

class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height
        
    def bmi(self):
        return self.weight / (self.height ** 2)

obj=People('egon',70,1.82)
obj.bmi()

要求是达到了,但是人吗,总是喜欢自己给自己找问题,有人会说这个bmi不是一个值吗,应该和身高什么一样,我obj.height就可以,这个也obj.bmi就要能调出来,为什么要加()呢,给与程序员来说,你有需求,我满足你,你是上帝.

property装饰器就来了

class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height
	
	# 给这个要把'函数属性'假扮成'数据属性'的函数加property装饰器
    @property
    def bmi(self):
        return self.weight / (self.height ** 2)

obj = People('wood',80,1.8)  #好像暴露体重了
print(obj.bmi)
'''执行结果
24.691358024691358
'''
# 需求可以了
# 如果我长个了
obj.height = 1.85
print(obj.bmi)
'''执行结果:
23.37472607742878
'''
# 也可以,伪装应该成功了
# 那bmi现在是数据属性了,我应该可以直接改
obj.bmi = 23.1231231
print(obj.bmi)
'''执行结果:
AttributeError: can't set attribute
'''
# 报错不能修改,想想函数怎么能修改呢?所以我们还要继续伪装加功能
  • 我们来说一下property装饰器的用法
    写法一(提倡使用):
class People:
    def __init__(self,name):
    	# 封装了name属性
        self.__name=name
	
	# 做一个接口,使用property装饰器
    @property
    def name(self):
    	# 隐藏数据通过接口严格控制使用者对属性的操作
        return '<name:%s>' %self.__name
	
	# 当property装饰器装饰name后,会生成一个name.setter装饰器,装饰的是修改name的接口
    @name.setter
    def name(self,new_name):
        if type(new_name) is not str:
            print('名字必须是str类型')
            return
        self.__name=new_name
	
	# 当property装饰器装饰name后,会生成一个name.deleter装饰器,装饰的是删除name的接口
    @name.deleter
    def name(self):
        del self.__name
# 查就调用查的接口
obj=People('egon')
print(obj.name)
# 改就调用改的接口
obj.name=123
print(obj.name)
# 删就调用删的接口
del obj.name
print(obj.__dict__)

写法二(出现要能看得懂):

lass People:
    def __init__(self,name):
        self.__name=name
	# 分别写删/改/查的三个接口
	# 注意: 1.接口函数名没有要求,正常的下划线格式你能辨认就好
	#      2.三个函数定义的先后顺序也没有要求,无所谓先后
    def get_name(self):
        return '<name:%s>' %self.__name

    def set_name(self,new_name):
        if type(new_name) is not str:
            print('名字必须是str类型')
            return
        self.__name=new_name

    def del_name(self):
        del self.__name
	
	# 注意:有硬性要求:
	# 依次对应property(查看接口,修改接口,删除接口) 
    name=property(get_name,set_name,del_name)

obj=People('egon')
print(obj.name)

obj.name=123
print(obj.name)

del obj.name
print(obj.__dict__)

现在我们在来写一下例一:

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)

    @bmi.setter  # 注意:被setter装饰器装饰的函数必须有两个参数,因为它装饰的是改,还需要接收一个修改的值得参数
    def bmi(self,new_bmi):
        print('bmi是根据身高体重算出不能修改')

    @bmi.deleter
    def bmi(self):
        print('bmi是根据身高体重算出不能删除')

obj = People('wood',80,1.8)
obj.weight = 70
print(obj.bmi)
obj.height = 1.85
print(obj.bmi)
obj.bmi = 12
print(obj.bmi)
del obj.bmi

多态

  • 什么是多态?
    同一种事物的多种形态,比如会水有固态/液态/气态,它们有各自的特性,又有共同的特性.我们很容易就会想到继承,子类是父类的多种形态.
  • 为何要用多态?
    多态性:指的是可以在不用考虑对象具体类型的前提下而直接使用对象下的方法
  • 如何用多态?
import abc
class Animal(metaclass=abc.ABCMeta):  #动物
	# 这个装饰器是用来统一规范的,子类必须也有这个方法
	@abc.abstractmethod
    def speak(self):
        pass
    
class People(Animal):  #形态一:人
    def speak(self):
        print('say hello')

class Dog(Animal):    #形态二:狗
    def speak(self):
        print('汪汪汪')

class Pig(Animal):    #形态三:猪
    def speak(self):
        print('哼哼哼')
peo = People()
dog=Dog()
pig=Pig()
# peo、dog、pig都是动物,只要是动物肯定有speak方法
# 基于多态性 于是我们可以不用考虑它们三者的具体是什么类型,而直接使用speak方法
peo.talk()
dog.talk()
pig.talk()
# 更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()
  • 多态性的好处
    其实多态没有什么新知识,它就是一个概念
    好处:

    • 1.增加了程序的灵活性
      不论对象怎么变化,使用者都是同一种形式调用

    • 2.增加了程序额可扩展性
      通过animal类创建的派生类(子类),使用者无需更改自己的代码,还是同一种形式调用

    • 鸭子类型
      我们在学校Python的过程中会发现,我们硬性规定的不多,有很多都是约定俗称的规格,这个我们叫鸭子类型.
      意思是:我们认为,如果看起来像,叫声性/走路也像鸭子,那它就是鸭子,不是也是.
      这个就好多态性概念有点像

  • 我们一直用的多态性带来的好处:Python的序列类型有多种形态:字符串,列表,元祖,多态性就体现了

#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))

#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()
# 定义出统一的接口使用
len(s)
len(l)
len(t)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值