首页 > 技术文章 > 封装

xuewei95 2021-04-22 18:20 原文

'''
封装
封装是啥,抛开面向对象,你单去想什么是装,装就是拿来一个麻袋,把小猫、小狗一起装进麻袋,什么是封,封就是把麻袋封上口子。
在面向对象中这个麻袋就是你的类或者对象,类或者对象这两麻袋内部装了数据属性和函数属性,那么对于类和对象来说'封'的概念从何而来,其实封的概念代表隐藏
在学完了面向对象的类和对象相关的知识后,大家都知道了如何把属性装进类或者对象中,那么如何完成封的效果呢?
第一个层面的封装:类就是麻袋,这本身就是一种封装
第二个层面的封装:类中定义私有的,只在类的内部使用,外部无法访问
第三个层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用(这才是真正的封装,具体实现,会在面向对象进阶中讲)
python不依赖语言特性去封装数据,而是通过遵循一定的数据属性和函数属性的命名约定来达到封的效果
'''

# 约定一:任何以单下划线开头的名字都应该是内部的,私有的;python并不会真的阻止你访问私有的属性,模块也遵循这种约定,如果模块名以单下划线开头,那么from module import *时不能被导入,但是你from module import _private_module依然是可以导入的;其实很多时候你去调用一个模块的功能时会遇到单下划线开头的(socket._socket,sys._home,sys._clear_type_cache),这些都是私有的,原则上是供内部调用的,外部也是可以用的
class People:
   '''这是一个类'''
   _star = 'earth'

   def __init__(self, id, name, age, salary):
      self._id = id
      self.name = name
      self.age = age
      self.salary = salary

   def _get_id(self):
      print('我是私有方法啊,我找到的id是%s' % self._id)

print(People.__dict__) # '_star': 'earth','_get_id': <function People._get_id at 0x00000248B6A534C0>
print(People._star) # 外部可以调用,但约定好这本应是私有属性不应被外部调用

p1 = People('360781', 'alex', 20, 10000)
print(p1._star)
print(p1.__dict__)
print(p1._id)
p1._get_id()


# 约定二:双下划线开头的名字
class People1:
   '''这是另一个类'''
   __star = 'earth'

   def __init__(self, id, name, age, salary):
      self.__id = id
      self.name = name
      self.age = age
      self.salary = salary

   def __get_id(self):
      print('我是私有方法啊,我找到的id是%s' % self.__id) # 内部可以直接调用,也可以'_类名__属性';内部直接调用而外部不能,这样也就明确区分了内外

   def get_star(self): # 接口函数(访问函数),这样外部也就能间接访问到私有属性
      print(self.__star)

print(People1.__dict__) # '_People1__star': 'earth','_People1__get_id': <function People1.__get_id at 0x00000248B6A53B80>
# print(People1.__star) # 报错,此时双下划线不能直接这样调用,从上面打印的类属性字典可以看到,key为'_People1__star'
print(People1._People1__star) # 为双下滑线时,调用应'_类名__属性'

p2 = People1('123456', 'lhf', 18, 9000)
print(p2.__dict__) # '_People1__id': '123456'
p2._People1__get_id()
p2.get_star()


# 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
# 正常情况
class A:
   def fa(self):
      print('from A')
   def test(self):
      self.fa()

class B(A):
   def fa(self):
      print('from B')

b = B()
b.test() # from B

# 把fa定义成私有的,即__fa
class A1:
   def __fa(self):  # 在定义时就变形为_A1__fa
      print('from A1')

   def test(self):
      self.__fa()  # 只会以自己所在的类为准,self.__fa()即调用self._A1__fa()

class B(A1):
   def __fa(self):
      print('from B')

b = B()
b.test() # from A1


'''
封装不是单纯意义的隐藏:
封装的真谛在于明确地区分内外,封装的属性可以直接在内部使用,而不能被外部直接使用,然而定义属性的目的终归是要用,外部要想用类隐藏的属性,需要我们为其开辟接口,让外部能够间接地用到我们隐藏起来的属性,那这么做的意义何在???
'''

# 1.封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制
class Teacher:
   def __init__(self,name,age):
      # self.__name=name
      # self.__age=age
      self.set_info(name,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('egon',18)
t.tell_info()

t.set_info('egon',19)
t.tell_info()


# 2.封装方法:目的是隔离复杂度
#取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
#对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
#隔离了复杂度,同时也提升了安全性

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

a=ATM()
a.withdraw()

 

推荐阅读