我的Python成长之路--Day26--面向对象的三大特性(封装和多态)

版权声明:此博客实属作者原创,转载请注明出处! https://blog.csdn.net/Onion_cy/article/details/83343228

介绍面向对象剩下的两个特性之前,先介绍一下和昨天介绍的继承有关的一个知识点:组合

1. 什么是组合
    一个对象的属性是来自于另外一个类的对象,称之为组合

2. 为何用组合
    组合也是用来解决类与类代码冗余的问题
所以和继承的功能有点相似,但是原理又不一样,

3.如何使用组合

调用类1产生一个对象obj1,调用类2产生一个obj2,在obj1的名称空间中为obj1添加一个新的属性obj1.xxx=obj2  使得这个新的属性和obj2关联起来,这样在对obj1中的属性xxx进行调用的时候就相当于调用了obj2,或者说是产生了obj2,就可以使用obj1.xxx.   来对obj2相关的所有属性进行调用,也就是说给到一个obj1可以使用组合的方法添加若干个属性关联不同的对象,并对关联的对象相应的属性进行调用,这样会在一定程度上解决代码冗余的问题

class Foo:
    aaa=1111
    def __init__(self,x,y):  
        self.x=x
        self.y=y

    def func1(self):
        print('Foo内的功能')

class Bar:
    bbb=2222
    def __init__(self, m, n):
        self.m = m  
        self.n = n

    def func2(self):
        print('Bar内的功能')

ob1=Foo(10,20) obj2=Bar(30,40)
obj1.xxx=obj2  在obj1的名称空间中添加一个新的属性xxx,使得这个属性和obj2进行关联,这样就可以使用obj.xxx.   来调用obj2所                          对应的属性了,obj2所有的属性都可以进行调用
ocj.1xxx.x = obj2   在这里其实obj1.xxx是代表obj2

分组的应用:
 

class OldboyPeople:
    school = 'Oldboy'
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

class OldboyStudent(OldboyPeople):
    def choose_course(self):
        print('%s is choosing course' %self.name)

class OldboyTeacher(OldboyPeople):
    def __init__(self, name, age, gender,level,salary):
        OldboyPeople.__init__(self, name, age, gender)
        self.level=level
        self.salary=salary

    def score(self,stu,num):
        stu.num=num
        print('老师%s给学生%s打分%s' %(self.name,stu.name,num))

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_course(self):
        print('课程名:<%s> 价钱:[%s] 周期:[%s]' % (self.course_name, self.course_price, self.course_period))

class Class:
    def __init__(self,class_name):
        self.class_name = class_name


python_obj=Course('python开发',3000,'5mons')
linux_obj=Course('linux运维',5000,'3mons')
PY_class_obj=Class('python1班')
L_class_obj=Class('Linux1班')



stu1=OldboyStudent('egon',18,'male')
stu1.courses=[]
stu1.courses.append(linux_obj)
stu1.courses.append(python_obj)
stu1.courses.append(PY_class_obj)
stu1.courses[1].tell_course()


stu2=OldboyStudent('kevin',38,'male')
stu2.courses =[]
stu2.courses.append(python_obj)
stu2.courses.append(linux_obj)
stu2.courses.append(L_class_obj)
stu2.courses[1].tell_course()

面向对象编程中的封装:

1. 什么是封装
    装指的是把属性装进一个容器
    封指的是隐藏的意思,但是这种隐藏是对外不对内的,对外不对内的意思是说在类的内部还是可以直接对相应封装起来的属性进行调用的,但是在类的外面进行封装是不会被隐藏的.
 2. 为何要封装
    封装不是单纯意义的隐藏
    封装数据属性的目的:将数据属性封装起来,类外部的使用就无法直接操作该数据属性了
    需要类内部开一个接口给使用者,类的设计者可以在接口之上附加任意逻辑,从而严格
    控制使用者对属性的操作
    封装函数属性的目的:隔离复杂度,隔离复杂度就是在类内部将一个完整的功能需要用的函数功能全部放入

3. 如何封装
    只需要在属性前加上__开头,该属性就会被隐藏起来,该隐藏具备的特点:
        1. 只是一种语法意义上的变形,即__开头的属性会在检测语法时发生变形(   _类名__属性名  )
        2. 这种隐藏式对外不对内的,因为在类内部检测语法时所有的代码统一都发生的变形
        3. 这种变形只在检测语法时发生一次,在类定义之后新增的__开头的属性并不会发生变形
        4. 如果父类不想让子类覆盖自己的属性,可以在属性前加__开头
注意:进行隐藏操作的时候,只在要隐藏的属性前面添加__就可以了,不可以在后边也加上__,前后都有__的是python中内置的一些属性,在满足一定条件的时候会自动触发的

特点一、二、三的演示:

class Foo:
    __x=111         #_Foo__x
    def __init__(self,m,n):
        self.__m=m  # self._Foo__m=m
        self.n=n

    def __func(self):  #_Foo__func
        print('Foo.func')

    def func1(self):
        print(self.__m)  #self._Foo__m
        print(self.__x)  #self._Foo__x

__x =111
print(__x)   在这里是可以直接打印出类的:__x=111

 print(Foo.__dict__)
{'__module__': '__main__', '_Foo__x': 111, '__init__': <function Foo.__init__ at 0x000001F212DE9950>, '_Foo__func': <function Foo.__func at 0x000001F212DE99D8>, 'func1': <function Foo.func1 at 0x000001F212DE9A60>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}
可以看到在进行封装之后,在类的名称空间中,被封装的属性名称发生了变化

在外面按照常规方法也是调用不到的
# Foo.__x   AttributeError: type object 'Foo' has no attribute '__x'
# Foo.__func  AttributeError: type object 'Foo' has no attribute '__func

但是了解了底层原理之后,非得在外面进行调用的话,还是可以调用的到的
# print(Foo._Foo__x)    111
# print(Foo._Foo__func) <function Foo.__func at 0x00000272DCA599D8>

特点四的演示:

class Foo:
    def __f1(self): #_Foo__f1
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        self.__f1() #self._Foo__f1

class Bar(Foo):
    def __f1(self): #_Bar__f1
        print('Bar.f1')

obj=Bar()
obj.f2()

如果不把父类中的f1隐藏,调用obj.f2()的结果是'Foo.f2' 'Bar.f1'
隐藏之后的结果就会发生改变:'Foo.f1' 'Foo.f1' 

封装数据属性的真实意图
class People:
    def __init__(self,name,age):
        self.__name=name
        self.__age=age

    def tell_info(self):
        print('<name:%s age:%s>' %(self.__name,self.__age))

    def set_info(self,new_name,new_age):
        if type(new_name) is not str:               将数据进行封装之后,在给使用者提供的使用数据属性的接口中,可以随意定义逻辑来严格限制
            print('名字必须是str类型')                使用者对数据属性进行修改
            return
        if type(new_age) is not int:
            print('年龄必须是int类型')
            return
        self.__name=new_name
        self.__age=new_age

    def clear_info(self):
        del self.__name
        del self.__age

obj=People('egon',18)

obj.tell_info()
obj.set_info('egon',18)

封装函数属性的真实意图
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()

property装饰器

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值,
可以将property理解为是一种装饰器,他可以将勒种定义的某个函数伪装成一个平常的属性并在类的外部按照类中的属性调用方法进行调用,并且被property装饰过的函数名下面还会衍生出多个可以对伪装后的函数进行操作的函数方法.

示例:

例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
成人的BMI数值:
过轻:低于18.5
正常:18.5-23.9
过重:24-27
肥胖:28-32
非常肥胖, 高于32

 体质指数(BMI)=体重(kg)÷身高^2(m)

  EX:70kg÷(1.75×1.75)=22.86

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)

p1=People('egon',75,1.85)
print(p1.bmi)

为什么要用property

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则

ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开

在python中:python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现

property的标准用法示例:

class Foo:
    def __init__(self,val):
        self.__NAME=val #将所有的数据属性都隐藏起来

    @property
    def name(self):
        return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)

    @name.setter
    def name(self,value):
        if not isinstance(value,str):  #在设定值之前进行类型检查
            raise TypeError('%s must be str' %value)  这一步会返回一个错误,内容是括号中的内容
        self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME

    @name.deleter
    def name(self):
        raise TypeError('Can not delete')

f=Foo('egon')
print(f.name)
# f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'

封装与拓展性:

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

参考吾师讲解:http://www.cnblogs.com/linhaifeng/articles/7340801.html#_label4

面向对象编程之多态与多态性

1.什么是多态:
   多态就是事物具有多种形态,比如动物类包含了人类,狗,猪等动物,这是几个子类是父类动物类不同的表现形式,这就叫做事物的多态性

2.为什么要用多态?
多态性:指的是可以在不用考虑对象具体类型的前提下而直接使用对象下的方法    比如在使用子类狗的时候,应为他是动物类的子类,不用考虑他自己本省有派生出其他什么属性,他一定具有动物类的所有属性,也就是具有父类的所有属性(可能实际不是这样,这样说只是为了更好的理解)

使用多态的好处:

1.增加了程序的灵活性

  以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(animal)

2.增加了程序额可扩展性

  通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用  

import abc

class Animal(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def speak(self):
        pass

调用abc模块的metaclass放到父类中的作用是统一一个标准,所有继承它的子类必须遵循父类制定的规则,或者说子类里边必须有父类下边的所有属性和函数,没有的话,子类不能成功调用产生对象,并且父类不能实例化产生对象,因为父类本身就是用来指定标准的

# Animal() # 父类不能实例化,因为父类本身就是用来制定标准的
class People(Animal):
    def speak(self):
        print('say hello')
    # def jiao(self):
    #     print('say hello')

class Dog(Animal):
    def speak(self):
        print('汪汪汪')

class Pig(Animal):
    def speak(self):
        print('哼哼哼')

peo=People()
dog1=Dog()
pig1=Pig()

peo.speak()
dog1.speak()
pig1.speak()

鸭子类型:

Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’

python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象

也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

具体参考吾师讲解:http://www.cnblogs.com/linhaifeng/articles/7340687.html(多态与多态性)

猜你喜欢

转载自blog.csdn.net/Onion_cy/article/details/83343228