面向对象三大特性之封装

封装

不绕弯子,面向对象的编程思想有三大特性,分别是:封装、继承、多态。

其中,封装是面向对象的最基本的概念。我们之前提到,对象是一个容器,装的是数据和功能。这本身就是一种封装思想,将数据和功能封装到一个对象中。

对象封装了数据和功能,那对象(类)就有了管理数据和功能的权利,即对象或者类可以把数据和功能给隐藏起来或者开放给使用者,限制对象(类)的使用者权利,或者控制/规范使用者的使用方式。


隐藏属性

如果类的设计者不想使用者直接访问到属性,就可以将属性给隐藏起来。属性分为数据属性和功能属性,即这两类属性都可以被隐藏。

隐藏属性可以隐藏类中的共有属性和对象的私有属性。

# 使用双下划线开头命名的属性将会被隐藏
class Foo:
    
    __x = 1 	# 隐藏类中的共有数据属性

    def __init__(self, name, age):
        self.__name = name		# 隐藏对象的私有属性
        self.age = age		

    def __f1(self):  			# 隐藏类内定义的函数属性
        print('from test')

    def f2(self):				# f2没有被隐藏,可以被访问到
        print(self.__x) 
        print(self.__f1)
############################################################
print(Foo.__x)			# 访问不到
print(Foo.__f1)			# 访问不到
print(Foo.f2)			# 可以访问到
print(Foo._Foo__x, Foo._Foo__f1)		# 可以访问

obj = Foo('xliu', 18)	# 实例化对象obj
print(obj.__name)		# 访问不到
print(obj.age)			# 可以访问到

Foo.__y = 2				# 增加类Foo的数据属性
print(Foo.__y)			# 可以访问到

注意点

# 1 在类外部无法直接访问双下滑线开头的属性,在类内部可以访问到

# 2 在类定义阶段,双下划先开头的属性会发生变形,变为 _Foo__x, _Foo__f1, _Foo__name, 所以在在类外无法直接通过过 .__x 的方式访问。但是可以通过变形后的 _Foo__x访问。但这是没有意义的。所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形。

# 3 之所以在类内部可以直接通过__x 访问,是因为__开头的属性会在检查类体代码语法时统一发生变形(类定义阶段)

# 4 这种变形操作只在检查类体语法的时候发生一次,之后再定义的__开头的属性都不会变形,所以可以直接__y访问到

开放接口

定义属性的目的是为了使用,所以隐藏属性的目的不是单纯的隐藏,而是为了更好的使用。

想要这些属性被使用,就必须提供给使用者一些接口,即没有被隐藏属性。

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

class Student:
    
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        # 通过该接口就可以间接地访问到名字属性
        print(self.__name)

    def set_name(self, new_name):
        # 通过改接口判断用户修改的新名字是否合法;非法则修改,不合法就不修改
        if type(new_name) is not str:
            print('小垃圾,名字必须是字符串类型')
            return
        self.__name = new_name

隐藏函数/方法属性:目的是为了隔离复杂度。例如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

python中有两种方式实现装饰器。第一种是函数装饰器,第二种是类装饰器。比如python3中的property就是一个类装饰器,将来绑定给对象的方法伪造成一个数据属性。它有下面两个基本使用场景。

场景1:将函数属性伪装成数据属性

比如人的BMI指数是体重健康的一种参数标准。

它是计算出来的,但是对用户来说BMI指数却更像数据属性,即用户更愿意通过数据属性来访问它。需求就来了。

解决办法:类内部在函数bmi上面加property语法糖,然后对象就可以像访问数据属性那样来使用bmi这个函数属性

class Poeple:

    def __init__(self, name, w, h):
        self.__name = name
        self.w = w
        self.h = h

    @property
    def bmi(self):
        return self.w / (self.h ** 2)
    
obj1 = Poeple('xliu', 66, 1.75)
print(obj1.bmi)

场景2:统一数据属性的查、改、删操作

# 方案1
class Poeple:
    def __init__(self, name, w, h):
        self.__name = name
        self.w = w
        self.h = h

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, value):
        self.__name = value

    @name.deleter
    def name(self):
        print('不能删')


obj1 = Poeple('xliu', 66, 1.75)
print(obj1.name)
obj1.name = 'egon'
del obj1.name


# 当name 遇到查询时,触发被property装饰的函数的执行
# 当name 遇到赋值操作,即 = 时触发被property.setter装饰的函数的执行
# 当name 遇到删除操作,即 del  时触发property.deleter装饰的函数的执行

上述使用property的流程如下:

1 将想要伪装的系列方法命名成一个相同的名字
2 在查看功能上加上property语法糖
3 在修改和删除功能上加名字.setter和 .deleter语法糖
# 方案2
class People:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, val):
        if type(val) is not str:
            print('必须传入str类型')
            return
        self.__name = val

    def del_name(self):
        print('不让删除')
        # del self.__name

    name = property(get_name,set_name,del_name)

obj1=People('egon')
print(obj1.get_name())
obj1.set_name('EGON')
print(obj1.get_name())
obj1.del_name()

# 将三个函数(get_name,set_name,del_name)统一为一个相同的符号name
# 当name 遇到查询时触发get_name函数的执行
# 当name 遇到赋值操作,即 = 时触发set_name函数的执行
# 当name 遇到删除操作,即 del  时触发del_name函数的执行

# 上面方案1是相同的模式

总结

不论是双下划线开头的属性命名来隐藏属性,还是使用类装饰器property来将函数属性伪装成数据属性,本质上都是类的设计者封装类的行为。这样做的目的都是控制和规范类的使用者。

posted @ 2020-04-08 14:54  the3times  阅读(273)  评论(0编辑  收藏  举报