🍖类的封装

引入

先来举个例子 :

当你摁下电脑开机键, 你不需要考虑主板是怎么通电的、 磁盘是怎么转动的、系统的信息是怎么加载的、里面的一系列化学或者物理变化是怎么样的,你面对的就是一个开关键, 摁下它, 电脑就开起来了.

v2-cfc14c7293afd962ecbd1dc31dafd002

又比如:

一个玩具制造厂, 制作一个机器人, 我们需要去考虑机器人的每一个细节: 手臂、腿、头、躯干等等,制作简单的玩具不要紧, 如果设计的玩具非常的复杂, 并且这工厂还有很多其他类型的玩具生产(小熊, 佩奇, 芭比公主,飞机模型等等)

497915717af56b63a5454a26b127916

上面我们使用面向过程的思想去设计, 在程序中就会让我们的代码很长, 很杂乱

于是工厂听取了小王的建议, 引进了一台制造机器人玩具的机器, 我们只需要摁下机器的开关, 它就会自动制造机器人

这个时候我们不再去关注是先制造手还是脚还是躯干这些细节, 我们面对的就只是一台机器, 这机器就是一个对象

原理 : 以前我们需要关注制作玩具的每个步骤, 现在我们将步骤都封装到一个机器里面, 留给我们的只剩一个开关, 我们只需要摁开关就可以生产玩具了, 这就是面向对象的三大特性之一 : 封装, 下面我们将详细展开介绍

一.封装

1.什么封装

  • 封装是Python中面向对象的三大特性之一
  • 封装就是隐藏对象的属性和实现细节, 仅对外提供公共访问方式

2.为什么使用封装

  • 提高安全性 : 避免用户对类中属性或方法进行不合理的操作
  • 隔离复杂度 : 就以上面的例子为例, 你只需要机器提供给你的开关, 内部结构不需要知道
  • 保证内部数据结构完整性 : 很好的避免了外部对内部数据的影响,提高了程序的可维护性
  • 提供代码的复用性

3.封装的原则

  • 将不需要对外提供的功能都给隐藏起来
  • 把属性都隐藏, 提供公共访问方法

二.隐藏

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

  • 其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
  • 类中所有双下划线开头的名称如__xxx都会在类定义时自动变形成:_[类名]__xxx的形式
class Eat:
    __breed = "五花肉"        # 变形 : "_Eat__breed"

    def __init__(self):     
        self.__meat = "大块"  # 变形 : "_Eat__meat"

    def __eat(self):          # 变形 : "_Eat__eat"
        print(f"吃了{self.__meat}的{self.__breed}")

    def eat_meat(self):
        self.__eat()          # 只有在类的内部才可以通过"__eat"的形式访问到

P1 = Eat()

🍔调用类提供的接口(方法)
P1.eat_meat()      # 吃了大块的五花肉

🍔调用隐藏的属性/方法,报错
print(P1.breed)    # 报错 "AttributeError" 没有该属性
print(P1.__breed)  # 报错 "AttributeError" 没有该属性
P1.__eat()         # 报错 "AttributeError" 没有该属性

🍔调用变形后的属性/方法(不推荐这么做)
print(P1._Eat__breed)  # 五花肉
print(P1._Eat__meat)   # 大块
P1._Eat__eat()         # 吃了大块的五花肉

隐藏属性的查找示例

class Bar:
    def __f1(self):  # 变形 "_Bar__f1"
        print("i am Bar_f1")

    def f2(self):
        print("i am Bar_f2")
        self.__f1()  # 等同于 "self._Bar__f1"

class Foo(Bar):
    def __f1(self):  # 变形 "_Foo__f1"
        print("i am Foo_f1")

Foo().f2()
'''输出
i am Bar_f2
i am Bar_f1
'''

重要步骤 : 当找到 self.__f1() 的时候, 其实是找到 self._Bar__f1, 于是先去自己(Foo)里面去找, 结果找不到自己里面的, 然后到父类中去找, 最终打印 "i am Bar_f1"

三.封装的两个层面

1.第一层面(公有 : public)

  • public: 公有属性的类变量和类函数,在类的外部、类内部以及子类中,都可以正常访问
🍔我们来制作一个机器人,分别要考虑制作不同部件,每个方法都得调用一下
class Rebot:
    def Head(self):
        print("制作头")

    def Hand(self):
        print("制作手")

    def Foot(self):
        print("制作脚")

    def Body(self):
        print("制作躯干")

    def Fit(self):
        print("机器人合体")

P1 = Rebot()

🍔没有进行封装,我们需要进行每一个部件的调用
P1.Hand()  # 制作手
P1.Head()  # 制作头
P1.Foot()  # 制作脚
P1.Body()  # 制作躯干
P1.Fit()   # 机器人合体

🍔当我们进行封装,提供给用户一个"Auto"接口(方法)
    def Auto(self):  # 提示:这个Auto方法是在类里面的,方便讲解我才放在这个位置
        self.Hand()
        self.Head()
        self.Foot()
        self.Body()
        self.Fit()
        
🍔用户只需要调用"Auto"这个方法就可以一步完成上面的所有步骤
P1.Auto()
'''输出
制作手
制作头
制作脚
制作躯干
机器人合体
'''

如果这样的话, 使用者还是可以调用里面机器人的制作细节, 如果我们不想让使用者使用到那些方法, 我们就可以将细节部分给隐藏起来, 只提供一个"Auto"方法,👇👇

2.第二层面 (私有 : private)

  • private:私有属性的类变量和类函数,只有在类的内部使用,类的外部以及子类都无法使用
    • 如果类中的变量和函数,其名称以双下划线__开头,则该变量或函数为私有的
    • 如果以单下划线_开头的属性和方法,我们约定俗成的视其为私有, 就是上面隐藏里说到的变形后的结果, 虽然能正常调用, 但不建议这么做
class Rebot:
    def __Head(self):    # 变形 : "_Rebot__Head"
        print("制作头")

    def __Hand(self):    # 变形 : "_Rebot__Hand"
        print("制作手")

    def __Foot(self):    # 变形 : "_Rebot__Foot"
        print("制作脚")

    def __Body(self):    # 变形 : "_Rebot__Body"
        print("制作躯干")

    def __Fit(self):     # 变形 : "_Rebot__Fit"
        print("机器人合体")

    def Auto(self):      # 提供给使用者的接口(方法)
        self.__Hand()
        self.__Head()
        self.__Foot()
        self.__Body()
        self.__Fit()

P1 = Rebot()

P1.Auto()
'''输出
制作手
制作头
制作脚
制作躯干
机器人合体
'''

# P1.__Hand()  # 报错 : "AttributeError" 没有该属性
# P1.__Head()  # 报错 : "AttributeError" 没有该属性

🍔如果特别想调用, 可以这么调用, 但非常不建议
P1._Rebot__Hand()  # 制作头
P1._Rebot__Head()  # 制作手

如此就实现了给使用者只看到能让他用的东西

不给用户直接使用的东西隐藏起来

四.小示例

  • 定义一个人类, 然后实例出一个对象, 该对象的名字不能以 "sb" 开头
  • "name" 这个属性隐藏起来, 外部访问不到
  • "name" 隐藏起来后, 对象自己该如何获取这个属性? 提供一个打印名字的方法
  • 定义一个修改名字的方法
class Person:
    def __init__(self,name):
        if not name.startswith("sb"):
            self.__name = name
        else:
            print("名字不能以'sb'开头")

    def print_name(self):        # 打印名字的方法
        print(self.__name)

    def change_name(self,name):  # 修改名字的方法
        if not name.startswith("sb"):
            self.__name = name
            print("修改成功")
        else:
            print("名字不能以'sb'开头")

P1 = Person("sb_hahah")    # 名字不能以'sb'开头
P2 = Person("shawn")

# print(P2.__name)         # 报错 : "AttributeError" 没有该属性
# print(P2.name)           # 报错 : "AttributeError" 没有该属性

P2.print_name()            # shawn

P2.change_name("sb_kkkk")  # 名字不能以'sb'开头
P2.change_name("xing")     # 修改成功
P2.print_name()            # xing

如此一来保证了数据的安全

补充 :

  • 模块中也可以使用隐藏属性, 在属性前加 "_", 例 : "_name" (单双下划线都可以)
  • 希望只在模块内部使用, 而外部无法使用
  • 针对的是from xxx import * 方法中的 * 星号
  • 如果想使用, 可以以 from xxx impoer _name 这种方式进行导入
posted @ 2020-12-26 16:42  给你骨质唱疏松  阅读(474)  评论(0编辑  收藏  举报