zoukankan      html  css  js  c++  java
  • 26、封装

    一 引入

    面向对象编程有三大特性:封装、继承、多态,其中最重要的一个特性就是封装。封装指的就是把数据与功能都整合到一起,听起来是不是很熟悉,没错,我们之前所说的”整合“二字其实就是封装的通俗说法。除此之外,针对封装到对象或者类中的属性,我们还可以严格控制对它们的访问,分两步实现:隐藏与开放接口

    二 隐藏属性

    Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的),但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成“_类名__属性名”的形式:

    class Foo:
        __N=0 # 变形为_Foo__N
        
        def __init__(self): # 定义函数时,会检测函数语法,所以__开头的属性也会变形
            self.__x=10 # 变形为self._Foo__x
            
        def __f1(self): # 变形为_Foo__f1
            print('__f1 run')
        
        def f2(self):  # 定义函数时,会检测函数语法,所以__开头的属性也会变形
            self.__f1() #变形为self._Foo__f1()
    
    print(Foo.__N) # 报错AttributeError:类Foo没有属性__N
    
    obj = Foo()
    print(obbj.__x) # 报错AttributeError:对象obj没有属性__x
    

    这种变形需要注意的问题是:

    1、在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:_ 类名 _ _ 属性,然后就可以访问了,如Foo. _ A _ _ N,所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。

    >>> Foo.__dict__
    mappingproxy({..., '_Foo__N': 0, ...})
    
    >>> obj.__dict__
    {'_Foo__x': 10}
    
    >>> Foo._Foo__N
    0
    >>> obj._Foo__x
    10
    >>> obj._Foo__N
    0
    

    2、在类内部是可以直接访问双下滑线开头的属性的,比如self. _ _ f1(),因为在类定义阶段类内部双下滑线开头的属性统一发生了变形。

    >>> obj.f2()
    __f1 run
    

    3、变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形。

    >>> Foo.__M=100
    >>> Foo.__dict__
    mappingproxy({..., '__M': 100,...})
    >>> Foo.__M
    100
    
    >>> obj.__y=20
    >>> obj.__dict__
    {'__y': 20, '_Foo__x': 10}
    >>> obj.__y
    20
    

    三 开放接口

    定义属性就是为了使用,所以隐藏并不是目的

    3.1 隐藏数据属性

    将数据隐藏起来就限制了类外部对数据的直接操作,然后类内应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制

    >>> 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
    

    3.2 隐藏函数属性

    目的的是为了隔离复杂度,例如ATM程序的取款功能,该功能有很多其他功能组成,比如插卡、身份认证、输入金额、打印小票、取钱等,而对使用者来说,只需要开发取款这个功能接口即可,其余功能我们都可以隐藏起来

    >>> 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()
    ...
    >>> obj=ATM()
    >>> obj.withdraw()
    

    总结隐藏属性与开放接口,本质就是为了明确地区分内外,类内部可以修改封装内的东西而不影响外部调用者的代码;而类外部只需拿到一个接口,只要接口名、参数不变,则无论设计者如何改变内部实现代码,使用者均无需改变代码。这就提供一个良好的合作基础,只要接口这个基础约定不变,则代码的修改不足为虑。

    四 property

    BMI指数是用来衡量一个人的体重与身高对健康影响的一个指标,计算公式为

    体质指数(BMI)=体重(kg)÷身高^2(m)
    EX:70kg÷(1.75×1.75)=22.86
    

    身高或体重是不断变化的,因而每次想查看BMI值都需要通过计算才能得到,但很明显BMI听起来更像是一个特征而非功能,为此Python专门提供了一个装饰器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)
    >>> obj.bmi #触发方法bmi的执行,将obj自动传给self,执行后返回值作为本次引用的结果
    21.913805697589478
    

    使用property有效地保证了属性访问的一致性。另外property还提供设置和删除属性的功能,如下

    >>> class Foo:
    ...     def __init__(self,val):
    ...         self.__NAME=val #将属性隐藏起来
    ...     @property
    ...     def name(self):
    ...         return self.__NAME
    ...     @name.setter
    ...     def name(self,value):
    ...         if not isinstance(value,str):  #在设定值之前进行类型检查
    ...             raise TypeError('%s must be str' %value)
    ...         self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
    ...     @name.deleter
    ...     def name(self):
    ...         raise PermissionError('Can not delete')
    ...
    >>> f=Foo('lili')
    >>> f.name
    lili
    >>> 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
    
  • 相关阅读:
    命令拷屏之网络工具
    PHP 设计模式 笔记与总结(1)命名空间 与 类的自动载入
    Java实现 计蒜客 1251 仙岛求药
    Java实现 计蒜客 1251 仙岛求药
    Java实现 计蒜客 1251 仙岛求药
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 蓝桥杯 算法训练 字符串合并
    Java实现 LeetCode 143 重排链表
    Java实现 LeetCode 143 重排链表
  • 原文地址:https://www.cnblogs.com/amgulen/p/13953091.html
Copyright © 2011-2022 走看看