第26天面向对象编程之组合,多态,封装

发布时间:2019-03-22 21:44:33编辑:auto阅读(1672)

    组合

    人生三问:

    什么是组合
      组合就是将一个对象A定义为另一个对象B的属性。从而使得B不仅能够访问自己的属性,还会具备A的属性,就像是把对象A和对象B组合到一块一样。 为什么要用组合
      和继承一样为了减少类与类之间的代码冗余。
      问题来了既然已经有了继承,为什么还要有组合呢?主要是为了解决一些没有父子关系的类之间的一些属性使用。 怎么使用组合
    创建一个类:
      class A:
        pass
      class B:
        pass
      创建对象,运用组合的特性
      a_obj = A()
      b_obj = B()
      a_obj.b = b_obj 就是把b对象放在了a对象的名称空间中

    组合的使用

    # 总结:组合就是将一个对象当作属性赋值给了另一个对象,使得另一个对象不仅可以使用自己的属性
    #       还可以间接的使用之前那个对象的属性,从而减少了代码的冗余。
    class Foo:
        xxx = 111
        def func1(self):
            print('this is func1')
    
    class Bar:
        yyy = 222
        def func2(self):
            print('this is func2')
    
    # 根据上面的两个类创建了两个对象
    f_obj = Foo()
    b_obj = Bar()
    # 使用对象f_obj访问类里面的属性是很容易的
    f_obj.func1()
    print(f_obj.xxx)
    # 但是现在有一个需求,我发现func2的功能用着很舒服,以及有时候
    # 我还会需要使用到Bar里面的变量,怎么办呢?使用组合
    f_obj.b = b_obj
    f_obj.b.func2()
    print(f_obj.b.yyy)

    案例分析:为什么要用组合,需求:还是创建一个老男孩选课系统

    # 老男孩选课系统
    class Parent:
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    
    class Student(Parent):
        def choose_course(self):
            print('choose course')
    
    
    class Teacher(Parent):
        def __init__(self, name, age, gender, level, salary):
            Parent.__init__(self, name, age, gender)
            self.level = level
            self.salary = salary
    
    s = Student('egon', 11, 'male')
    t = Teacher('egon', 11, 'male', 10, 1000)
    print(t.gender)
    print(s.gender)
    根据需求创建的教师,学生和父类

    问题一: 既然是选课,肯定是要有课程的,因此我们需要为每个学生的特征上面重新添加上一些课程信息,包括的有课程名,课程时长,课程价格。因此出现了下面的修改的代码。

    # 老男孩选课系统
    class Parent:
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    
    class Student(Parent):
        # 在学生类中添加课程信息
        def __init__(self, name, age, gender, course_name, course_price, course_period):
            Parent.__init__(self, name, age, gender)
            self.course_name = course_name
            self.course_price = course_price
            self.course_period = course_period
        def choose_course(self):
            print('choose course')
    
    class Teacher(Parent):
        # 在教师类中添加了课程信息
        def __init__(self, name, age, gender, level, salary, course_name, course_price, course_period):
            Parent.__init__(self, name, age, gender)
            self.level = level
            self.salary = salary
            self.course_name = course_name
            self.course_price = course_price
            self.course_period = course_period
    
    # 创建了三个学生
    s1 = Student('egon', 20, 'male', 'python', 1000, '5months')
    s2 = Student('alex', 19, 'male', 'python', 1000, '5months')
    s3 = Student('hu', 18, 'male', 'python', 1000, '5months')

    问题二:课程已经添加到学生和教师的特征上面了,但是现在观察创建学生类的时候出现了两个问题:1. 在学生的__init__函数和教师的__init__函数中出现了代码的冗余 2. 在每次创建对象的时候都要重新输入课程信息,可能大量学生或者教师的课程信息都是一样的。 3. 每创建一个对象都要保存一份课程信息,造成了内存的大量浪费。

    # 老男孩选课系统
    class Parent:
    # 将课程信息都放在父类中,减少了代码的冗余,而且内存中也只会在类中存储一份,减少了内存的浪费
    def __init__(self, name, age, gender, course_name, course_price, course_period): self.name = name self.age = age self.gender = gender self.course_name = course_name self.course_price = course_price self.course_period = course_period class Student(Parent): def choose_course(self): print('choose course') class Teacher(Parent): def __init__(self, name, age, gender, level, salary, course_name, course_price, course_period): Parent.__init__(self, name, age, gender, course_name, course_price, course_period) self.level = level self.salary = salary # 创建了三个学生 s1 = Student('egon', 20, 'male', 'python', 1000, '5months') s2 = Student('alex', 19, 'male', 'python', 1000, '5months') s3 = Student('hu', 18, 'male', 'python', 1000, '5months')

    问题三:按照之前的继承问题基本上已经把大部分的信息都给解决了,但是依然存在以下几个问题:1. 每创建一个对象都要求我们输入相应的课程信息,很麻烦,不过这还是小事,重点在第二个问题。2. 我们现在的课程选课系统中只有学生和教师这两个类,但是如果我们加入一个管理员类呢,管理员类可是不需要课程信息的,因此,我们还是不能把课程信息放在父类中的,因为课程信息对于老男孩里面的每一个对象而言不是必须的,有的人是没有必要选课程信息的。 这样就和我们之前的有点矛盾了,我们是为了减少代码的冗余以及减少内存空间才去使用这个继承的,但是目前因为需求的问题我们又不能用继承。 此时我们应该能够想到用组合了吧。首先创建一个课程类,然后组合到学生类和教师类中。

    # 老男孩选课系统
    class Parent:
        def __init__(self, name, age, gender):
            self.name = name
            self.age = age
            self.gender = gender
    class Student(Parent):
        def choose_course(self):
            print('choose course')
    class Teacher(Parent):
        # 在教师类中添加了课程信息
        def __init__(self, name, age, gender, level, salary):
            Parent.__init__(self, name, age, gender)
            self.level = level
            self.salary = salary
            
    class Course:
        def __init__(self, course_name, course_price, course_period):
            self.course_name = course_name
            self.course_price = course_price
            self.course_period = course_period
            
        def tell_info(self):
            print('课程:<%s> 周期:{%s} 价格: [%s]' % (self.course_name, self.course_period, self.course_price))
    # 创建课程对象
    python = Course('python', 1000, '5months')
    # 创建学生对象,然后组合课程对象
    s1 = Student('egon', 20, 'male')
    s1.course = python
    s2 = Student('alex', 18, 'male')
    s2.course = python

    总结: 

    能够减少类与类之间的代码冗余的方法区别
      继承主要是抽取子类中相同的特征和技能来减少代码冗余   组合主要是抽取子类中部分相同的特征来重新创建一个类来减少代码冗余

    封装

    人生三问:

    什么是封装
      装就是将一堆的属性装到一个容器中,也就是说的类。
      封就是隐藏内部的属性,这种隐藏是对外不对内的,只提供一个接口给我们使用。
    为什么要用封装   数据属性的封装
      将数据属性封装起来,类外部的使用就无法直接操作该数据,需要类内部开一个接口给使用者,类的设计者
    可以在接口之上附加任意逻辑,从而控制使用者对数据属性的操作。
      功能属性的封装:隔离复杂度

    怎么使用封装
      只需要在属性前面加上__开头,该属性就会被隐藏起来,该隐藏具备的特点
      1. 只是一种语法意义上的变形,即__开头的属性会在测试语法时发生变形
      2. 这种隐藏式对外不对内的,因为类内部检测语法的时候是检测所有的代码
      3. 这种变形只是在类的定义阶段变形一次,之后添加的__开头的属性不会发生改变
      4. 如果父类不想让子类覆盖自己的属性,可以在属性前面加上__开头

    封装的特点:

    1. 只是语法上的一种变形而已

    class Foo:
        xx = 11
        def func1(self):
            print('Foo Func1')
    
        def func2(self):
            print('Foo Func2')
    
    # 打印当前Foo中的名称空间
    print(Foo.__dict__)
    print(Foo.xx)
    print(Foo.func1)
    print(Foo.func2)
    # 结果:
    # {'__module__': '__main__', 'xx': 11, 'func1': <function Foo.func1 at 0x007E4F60>, 'func2': <function Foo.func2 at 0x007E4930>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
    # 11
    # <function Foo.func1 at 0x007E4F60>
    # <function Foo.func2 at 0x007E4930>
    没有__开头的属性
    class Foo:
        __xx = 11
        def __func1(self):
            print('Foo Func1')
    
        def func2(self):
            print('Foo Func2')
    
    # 打印当前Foo中的名称空间
    print(Foo.__dict__)
    # 我们会发现加上__开头之后的属性我们访问不到了,为什么会出现这种情况呢?
    # 并不是说python直接把内部的变量给删除了,只是说python在定义类的时候发现有__开头的变量会自动帮我们改一个名字存在了__dict__中
    # 如果我们坚持要去访问还是可以通过_Foo变量名去访问的,如下面
    print(Foo._Foo__xx)
    print(Foo._Foo__func1)
    print(Foo.func2)
    # 结果:
    # {'__module__': '__main__', '_Foo__xx': 11, '_Foo__func1': <function Foo.__func1 at 0x02D14F60>, 'func2': <function Foo.func2 at 0x02D14930>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
    # 11
    # <function Foo.__func1 at 0x02D14F60>
    # <function Foo.func2 at 0x02D14930>

    2. 隐藏是对外不对内

    class Foo:
        __xx = 11
        def __func1(self):
            print('Foo Func1')
    
        def func2(self):
            # 隐藏是对外不对内,因为变形是在类定义阶段就已经完成的了
            # 也就是说在定义阶段也是变成了self._Foo__xx
            print(self.__xx)   # 此时的相当于是print(self._Foo__xx)
            print('Foo Func2')
    # 创建一个对象并且去调用方法func2函数
    obj = Foo()
    obj.func2()
    # 结果:
    # 11
    # Foo Func2

    3. 之后添加的__开头的变量并不会对其进行隐藏

    class Foo:
        __xx = 11
        def __func1(self):
            print('Foo Func1')
    
        def func2(self):
            print('Foo Func2')
    
    obj = Foo()
    # 变形只是在类的定义阶段进行的,之后进行添加的__变量是不会进行变形的
    # 因此直接通过变量名是可以直接进行访问的
    obj.__yy = 22
    print(obj.__yy)
    # 结果
    # 22

    4. 父类可以通过__开头的属性覆盖子类的属性

    # 默认子类的属性会覆盖掉父类的属性
    class Bar:
        def func1(self):
            print('Bar Func1')
    
        def func2(self):
            print('Bar Func2')
            self.func1()
    
    class Foo(Bar):
        def func1(self):
            print('Foo Func1')
    
    # 根据属性查找顺序  对象-->类-->父类
    # 其实就是子类的属性会覆盖掉父类的属性
    obj = Foo()
    obj.func2()
    # 结果
    # Bar Func2   因为对象和类中都没有func2,所以去父类中查找,找到之后执行func2.打印效果
    # Foo Func1   但是之后还会执行self.func1()但是此时还是先从对象中找,对象中没有,所以到类Foo中找,有,所以打印结果
    默认子类的属性会覆盖掉父类的属性

    如果不想让他覆盖父类的属性,我们需要通过__开头的属性

    # 默认子类的属性会覆盖掉父类的属性
    class Bar:
        def __func1(self):   # _Bar_func1()
            print('Bar Func1')
    
        def func2(self):
            print('Bar Func2')
            self.__func1() # 相当于self._Bar__func1()
    
    class Foo(Bar):
        def func1(self):
            print('Foo Func1')
    
    # 根据属性查找顺序  对象-->类-->父类
    obj = Foo()
    obj.func2()
    # 结果
    # Bar Func2   因为对象和类中都没有func2,所以去父类中查找,找到之后执行func2.打印效果
    # Bar Func1   此时相当于执行相当于self._Bar__func1(),对象中没有_Bar__func1方法,类中也没有,所以只有父类中有

    数据封装的作用

    # 封装的作用
    # 对于数据的封装主要就是为了能够控制使用者对属性的操作
    class Student:
        def __init__(self, name, age, gender):
            self.__name = name
            self.age = age
            self.gender = gender
    
        def show_name(self):
            # 之前查看的时候在外部直接通过name就可以查看
            # 现在封装之后,我就可以对其进行格式上的展示
            if '__name' not in self.__dict__:
                print('当前没有名字!')
                return
            print('名字:<%s>' % self.__name)
    
        def modify_name(self, new_name):
            # 封装之后,就不允许你随便的更改属性
            # 当你更改的属性不是str类型的时候,是不行的
            # 从而限制了当前使用者对属性的操作
            if type(new_name) is not str:
                print('hi名字只能是str类型的')
                return
            self.__name = new_name
            print('名字修改成功')
    
        def clear_name(self):
            del self.__name
            print('名称已经删除')
    
    stu1 = Student('egon', 11, 'male')
    stu1.show_name()
    stu1.modify_name('EGON')
    stu1.show_name()
    stu1.clear_name()
    stu1.show_name()

    函数封装的作用

    函数封装的作用
      主要是为了降低复杂度,例如:我现在要开机电脑,如果我告诉你要第一步插电,第二步操作bios。。。。等等的时候,肯定就要疯掉了,因此我告诉你只需要按一下开机键就可以开机了,这个就是封装的过程,就是告诉用户对于一系列复杂的操作我已经帮你
    做好了一个接口,你以后要用这个功能的时候你只需要记住这个接口就ok了。

    多态

    什么是多态
      多态就是多种形态的意思。 为什么要用多态
      为了统一标准 怎么使用多态

    多态的使用:

    # 一个动物类,动物都会叫, 因此在animal类中定义了spark方法
    # 但是我们都知道不同的动物都有不同的叫声对吧,这个就是多态的一种表现
    class Animal:
        def spark(self):
            pass
    
    class People(Animal):
        def spark(self):
            print('People spark')
    
    class Pig(Animal):
        def spark(self):
            print('哼哼哼')
    
    class Dog(Animal):
        def spark(self):
            print('汪汪汪')
    
    p = People()
    pig = Pig()
    dog = Dog()
    # 当我们实例化对象之后,我们就不必在去关注他是哪一种类型的对象,
    # 因为在每一个子类中都会有spark这个方法去使用
    p.spark()
    pig.spark()
    dog.spark()

    强制多态的方法

    # 强制多态的意思就是在父类中一旦定义了强制多态方法,在子类中就必须有这样的一个方法
    # 没有将会报错
    import abc
    class Animal(metaclass=abc.ABCMeta):
        @abc.abstractmethod
        def spark(self):
            pass
    
    class People(Animal):
        # 如果将下面的spark方法给注释掉,将会报错
        # def spark(self):
            # print('People spark')
        pass
    class Pig(Animal):
        def spark(self):
            print('哼哼哼')
    
    class Dog(Animal):
        def spark(self):
            print('汪汪汪')

    结论:python本身是崇尚一种高度自由思想,因此这种强制多态的方法并不是特别的完美。在日常工作过程中,我们更应该一一种约定俗称的标准来规划我们代码,而不应该以这种强制的形式去规定我们代码。

    在python运用到多态思想:

    例如:__len__方法,我们没有必要去考虑具体的类型是什么,都可以使用此方法。

关键字