65 类的封装

188次阅读
没有评论

共计 3630 个字符,预计需要花费 10 分钟才能阅读完成。

引入

先来举个例子 :

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

65 类的封装

又比如:

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

65 类的封装

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

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

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

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

一. 封装

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 这种方式进行导入
正文完
 
shawn
版权声明:本站原创文章,由 shawn 2023-06-16发表,共计3630字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)