Title

面向对象三大特性之封装

面向对象三大特性之封装

在讲封装前我们可以先了解什么是组合

一.组合

1.什么是组合?

组合指的就是一个对象中包含这另外一个或多个对象

  • 在继承中:

继承是类与类的关系,子类继承父类的属性和方法,子类与父类属于从属关系

  • 在组合中:

组合是对象与对象的关系,一个对象拥有另外一个对象的属性和方法,是一种包含关系

2.为什么要用组合?

为了减少代码冗余
与继承相比
耦合度低,程序可扩展性高

3.组合怎么用?

# 组合实现
# 父类
class OldPeople:
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

# 学生类(子类)
class OldStudent(OldPeople):
    def __init__(self, name, age, sex):
        super().__init__(name, age, sex)


# 老师类
class OldPeople(OldPeople):

    def __init__(self, name, age, sex):
        super().__init__(name, age, sex)


#日期类:
class Brith:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def tell_brith(self):
        print(f'''
               ===== 出生年月日 =====
                   年: {self.year}
                   月: {self.month}
                   日: {self.day}
               ''')

stu1 = OldStudent('sean', 19, 'female')
Brith_obj = Brith(2000, 1, 1)
stu1.Brith_obj = Brith_obj  # 等于在学生类下面添加了一个self.Brith_obj = Brith_obj
print(stu1.Brith_obj.year, stu1.Brith_obj.month,  stu1.Brith_obj.day)
stu1.Brith_obj.tell_brith()# 会从学生类的对象引用日期类对象的方法

练习需求:

选课系统:

1.有学生、老师类,学生与老师有属性 “名字、年龄、性别、课程”,

2.有方法 老师与学生可以添加课程, 打印学习/教授课程。

class People:
    def __init__(self, name, age, sex):
        self.name = name
        self.age = age
        self.sex = sex

    # 打印出生日期方法
    def tell_birth(self):
        print(f'''
        年: {self.date_obj.year}
        月: {self.date_obj.month}
        日: {self.date_obj.day}
        ''')

    # 添加课程
    def add_course(self, course_obj):
        # self.course_name = course_name
        # self.course_price = course_price
        # self.course_time = course_time
        self.course_list.append(course_obj)

    # 打印当前对象中课程列表的所有课程信息
    # ---》 打印所有课程
    def tell_all_course_info(self):
        # 从当前对象中课程列表中取出所有的课程对象
        for course_obj in self.course_list:
            # 通过课程对象.打印课程信息方法
            course_obj.tell_course_info()


class Student(People):
    # 假设从程序开始到灭亡  在实例化时都不修改该对象的属性
    def __init__(self, name, age, sex):
        super().__init__(name, age, sex)
        self.course_list = []


class Teacher(People):
    def __init__(self, name, age, sex):
        super().__init__(name, age, sex)
        self.course_list = []


class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day


# 定义一个课程类: 课程有: 课程名称, 课程价格, 课程周期
class Course:
    def __init__(self, course_name, course_price, course_time):
        self.course_name = course_name
        self.course_price = course_price
        self.course_time = course_time

    # 定义打印课程方法   : 只打印一个课程信息
    def tell_course_info(self):
        print(f'''
        ====== 课程信息如下 ======
        课程名称: {self.course_name}
        课程价格: {self.course_price}
        课程周期: {self.course_time}
        ''')


# 创建学生对象
stu1 = Student('HCY', 2000, 'female & male')
date_obj = Date('公元19', '11', '11')
stu1.date_obj = date_obj
# stu1.tell_birth()

# 创建课程对象
python_obj = Course('python', 77777, 6)
go_obj = Course('go', 88888, 4)

# 当前学生添加了课程对象
# 添加python课程
stu1.add_course(python_obj)
# 添加go课程
stu1.add_course(go_obj)

# 当前学生打印所有课程信息
stu1.tell_all_course_info()

二.封装

1什么是封装?

封装就是将一堆属性和方法整合到一起封装到对象中

2.为什么要用封装?

封装就是为了方便存取属性和方法,通过"对象."的方式就能存放/获取属性和方法

3.封装怎么用

 class User:
 	x = 10
    def func():
    	pass
 obj = User()
 obj.y = 20           # 添加一个新的属性y = 20,这就是封装
 obj ---> x, func, y  # obj对象可以取用这些属性和方法

同时,在封装到对象或类中的属性和方法,我们可以控制对他们的访问,分两步实现,隐藏和开放接口,我们自己(非官方)命名这种隐藏和开放的方式为限制访问机制

4.什么是限制访问机制

凡是在类内部定义的属性或方法,以__(双下划线)开头的属性或方法名,都会被限制,外部不能 “直接访问” 该属性原型。
PS: 看着像将该属性或方法隐藏起来了。
# python特有的:注意: 凡是在类内部定义 __开头的属性或方法,都会变形为 _类名 __属性/方法。




为什么要使用这种机制:
    就是为了不让外部轻易获得被隐藏的属性和方法
    

举个例子:
    # 隐藏属性
class User:
 # 以__开头的属性
	__name = 'sean'  # __name变成了_User__name

 # 以__开头的方法
 	def __eat(self):  # __eat变成了_User__eat
  		print('from user')
        
        
    def func(self):  # 在类的内部可以直接访问变形的方法或属性
        self.__eat()

obj = User()
# print(obj.__name) # 报错,提示没有此属性
print(obj._User__name)  # __name变成了_User__name
# obj.__eat()   # 报错,提示没有此方法
obj._User__eat() # __eat变成了_User__eat
obj.func()  




# 注意:
1.在类外部无法直接访问双下划线开头的属性,但是知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,所以说这种限制没有严格意义上的限制外部访问,仅仅只是语法意义上的变形
    
2.在类内部是可以直接访问双下划线开头的属性的(注意不能跨类访问)
因为在类定义阶段内部双下滑线开头的属性统一发生了形变


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 A

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



# 定义属性就是为了使用,所以隐藏不是目的,将数据隐藏只是为了限制类外部直接对数据进行操作,然后类内部应该提供相应的接口允许类外部间接的操作接口,接口之上可以附加额外的逻辑来对数据的操作进行严格的控制

举例:隐藏函数的属性
    class ATM:
    # 取钱功能:
    # 1.插入磁卡
    def __insert_card(self):
        print('开始插卡...')
        pass

    # 2.输入密码
    def __input_pwd(self):
        print('输入密码...')
        pass

    # 3.输入取款金额
    def __input_bal(self):
        print('输入取款金额...')
        pass

    # 4.吐钱
    def __output_money(self):
        print('开始吐钱...')
        pass

    # 5.打印流水账单
    def __print_flow(self):
        print('打印流水账单...')
        pass

    # 取款顺序规范接口:
    def withdraw(self): # 只需要一个对外取款接口,其他的都可以隐藏起来
        # 1.插入磁卡
        self.__insert_card()

        # 2.输入密码
        self.__input_pwd()

        # 3.输入取款金额
        self.__input_bal()

        # 4.吐钱
        self.__output_money()

        # 5.打印流水账单
        self.__print_flow()


amt_obj = ATM()
amt_obj.withdraw()




# 隐藏据属性:
>>> 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

    
    

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

5.property

  • 什么是poperty

    是一个python内置的装饰器,可以装饰在"类内部的方法"上。可以将该方法调用方式由 ----> 对象.方法() ---> 对象.方法,就是将调用函数方法伪装成调用数据属性,当调用此方法时会将该函数方法的结果返回出来

  • 为什么要使用property

    PS: 在某些场景下,调用的方法只是用来获取计算后的某个值。

    PS: 必须通过 对象.方法() 方式调用,让该方法看起来像动词。

  • 怎么使用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)
print(obj.bmi)  # 不需要通过方法()就可以得到结果,伪装成通过属性得到结果 



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

class Foo:
    def __init__(self, val):
        self.__NAME = val  # 将属性隐藏起来

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

    @name.setter  # 如果需要修改值,必须再写一次方法,并且需要修改的方法名字要与被property装饰器后的方法一样,括号里面要添加一个value
    def name(self, value):
        if not isinstance(value, str):  # 在设定值之前进行类型检查
            raise TypeError('%s must be str' %value)

        self.__NAME = value  # 通过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter  # 删除是在内部删除,在外部也要写上 del f.name
    def name(self):
        raise PermissionError('Can not delete')


f = Foo('lili')
f.name
# lili
f = Foo('lili')
print(f.name)
# # lili
# # f.name = '123'  # 触发name.setter对应的的函数name(f,123),抛出异常TypeError
f.name = 'egon'  # 触发name.setter装饰器对应的函数name(f,’Egon')
print(f.name)
# # del f.name  # 触发name.deleter对应的函数name(f),抛出异常PermissionError
# 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
posted @ 2019-11-27 21:22  Mr江  阅读(208)  评论(0编辑  收藏  举报