[Python基础13]面向对象特征封装|继承|多态


面向对象程序设计最主要的有三个特征: 封装、继承、多态

1. 封装

1.1 封装的意义

在我们程序开发过程中,定义好类型之后就可以通过类型来创建对象
如:我们定义一个中华人民共和国公民的类型

# 创建一个人的类型
class Person(object):
    def __init__(name, age):
        self.name = name
        self.age = age

此时如果我们创建好对象之后,对对象的数据进行如下修改,大家是否认为合适呢?

# 创建一个人的对象
xiaoMing = Person("小明", 18)
# 修改属性
xiaoMing.age = 1000

我们会发现,上面的代码在运行时是正确的,也就是可以修改age属性为1000
此时我们需要明确一个概念:代码运行正确,但是不代表符合业务逻辑,这样的代码我们一般会说代码处理不合法!

1.2 实现封装的过程

对于上面这样的问题,我们应该怎么处理呢

常规的方案就是:

  1. 定义一种这样的属性,属性只有在当前类的内部可以访问
    类的外部不能访问这个属性,只能通过类提供的方法来进行属性的取值和赋值
  2. 在取值或者赋值的方法中,就可以添加一定的限制处理的代码了
  3. python中,提供了这样的一种特殊的变量,变量名称使用两个下划线开头,这样的变量智能在类的内部访问,类的外部是访问不了的,我们称之为私有属性
# 定义类型
class Person(object):
    def __init__(self, name, age):
        self.__name = name;
        self.age = age
# 创建对象
xiaoMing = Person("小明明", 18)
# 访问属性
print(xiaoMing.age)
~ 18,可以访问,age只是一个普通的成员属性
print(xiaoMing.__name)
~ 出现错误,AttributeError: 'Person' object has no attribute '__name'

这样我们就限制了变量的访问范围。
但是变量定义出来就是为了被访问和操作的,如果上述代码中的name一旦限制了不让访问,就木有存在的价值了。所以,我们通过自定义的方法给name属性添加访问控制

# 创建一个人的类型
class Person(object):  
    # 对象的初始化方法
    def __init__(self, name):
       # 初始化私有成员变量__name
        self.__name = name
    # 定义获取__name的函数
    def get_name(self):
        return self.__name
    # 定义设置__name的函数
    def set_name(self, name):
        self.__name = name
# 根据类型创建对象
xiaoMing = Person("小明");
# 访问数据
xiaoMing.set_name("小明明");
print(xiaoMing.get_name()) 
# 执行结果:小明明

OK,通过以上对属性进行私有化(属性名称双下划线开头),给属性提供set/get的访问方法完成封装过程,此时就可以对本文开头的年龄设置问题进行一定的逻辑限制了

# 定义一个人的类型
class Person(object):
    # 初始化变量
    def __init__(self, name, age):
        self.__name = name
        if(age >= 0 and age <= 100):
            self.__age = age
        else:
            age = 18
    # 定义访问属性的get方法
    def get_name(self):
        return self.__name
    def get_age(self):
        return self.__age
    # 定义访问属性的get方法
    def set_name(self, name):
        self.__name = name;
    def set_age(self, age):
        if(age >= 0 and age <= 100):
            self.__age = age
        else:
            print("您设置的年龄不合法,还原默认值")
# 创建对象
p = Person("张小凡", 19)
p.set_age(1000)
print(p.get_age())
# 执行结果
~ 您设置的年龄不合法,还原默认值
~ 19

以上执行的结果,才是我们想要的结果

什么是封装

封装,就是将对象敏感的数据封装在类的内部,不让外界直接访问,但是提供了让外界可以间接访问的set/get方法,我们可以在set/get方法中添加数据的访问限制逻辑,完善我们的代码,提高程序的健壮性

1.3 封装的高级使用方式

我们从上面的代码中已经看到了,可以通过函数操作的形式来进行属性的处理
但是某些情况下,函数操作的形式并不是特别美妙,我们突发奇想~想再提供了set/get访问方法的情况下,对属性的操作还能像以前那样直接赋值或者取值进行操作

# 封装以后通过函数操作的方式
p.set_name("tom")
print(p.get_name())
# 封装以前通过属性直接操作的方式
p.name = "tom"
print(p.name)

将类中的set/get方法操作的形式,转换成属性直接操作的形式,python中是可以的

首先:给get方法上添加@property注解,(关于注解的东东,之前的函数装饰器章节中已经有使用,可以参考一下操作原理),就可以让get方法当成属性进行直接取值操作了
其次,@property同时它会自动生成一个@get方法名称.setter注解,将@get方法名称.setter注解写在set方法上,就可以让set方法进行直接赋值操作了,代码如下:

class Person(object):
    def __init__(self, name):
        self.__name = name;
    @property
    def get_name(self):
        return self.__name
    @get_name.setter
    def set_name(self, name):
        self.__name = name
# 创建对象
p = Person("tom")
print(p.get_name)
p.set_name = "jerry"
print(p.get_name)
# 执行结果
~ tom
~ jerry

上述代码我们可以看出来,set/get方法已经可以当成普通的属性取值赋值的方式进行快捷的操作了,我们继续改造一下上述代码,让set/get更加符合属性取值赋值的方式

class Person(object):
    def __init__(self, name):
        self.__name = name;
    @property
    def name(self):
        return self.__name
    @name.setter
    def name(self, name):
        self.__name = name
# 创建对象
p = Person("tom")
print(p.name)
p.name= "jerry"
print(p.name)
# 执行结果
~ tom
~ jerry

此时,你还能在不看原来类型定义中的get/set,区分出来name是否是Person类型的属性还是方法呢?

封装的注解方式,在一定程度上,能隐藏我们方法在底层的实现,让调用者的操作变得简单。但是同时也降低了代码的可读性,后续的操作中,我们还是遵循原来封装的操作方案将类的属性私有化,提供set/get方法进行属性的操作。

2. 继承

2.1 继承的意义

继承是让我们抽象的对象之间存在一定的所属关系
在继承关系中,我们一定要明确会出现这样的一种关系~父类、子类,子类继承自父类,可以继承父类中的公开的属性和方法(不能继承私有的属性或者方法)

其实我们在前面定义对象的时候已经使用过了继承,python中所有的对象都是直接或者间接继承自object对象的

class Person(object):
    pass

上述代码中,我们定义了一个类型Person,这个Person后面有一个括号,括号中就是继承的类型;python中继承的语法是

class 类型名称(父类名称):
    pass

下面是一个简单的继承的案例

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个人的类型
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class Person(object):
    def __init__(self, name,  age, gender):
        self.__name = name
        self.__age  = age
        self.__gender = gender
    # 定义get获取属性的方法
    def get_name(self):
        return self.__name
    def get_age(self):
        return self.__age
    def get_gender(self):
        return self.__gender
    # 定义set设置属性的方法
    def set_name(self, name):
        self.__name = name
    def set_age(self, age):
        self.__age= age
    def set_gender(self, gender):
        self.__gender= gender
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个男人的类型,继承自Person类型
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class Man(Person):
    def __init__(self, name, age):
        Person.__init__(self, name, age, "男")
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个女人的类型,继承自Person类型
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class Women(Person):
    def __init__(self, name, age):
        Person.__init__(self, name, age, "女")

# 创建男人对象和女人对象,测试是否可以给属性进行赋值取值
m = Man("tom", 18)
w = Women("jerry", 16)
print(m.get_gender())   # 执行结果:男
print(w.get_gender())    # 执行结果:女

我们可以看到,在自定义类Man和Women中,只是简单定义了一个init方法,没有其他的代码,但是我们创建的Man类型的对象和Women类型的对象,却可以使用父类Person中定义的方法get_gender()以及其他,在一定程度上,简化了我们的开发,同时提高了程序的扩展性

继承:就是将一部分表示数据类型相似的类,抽取他们共同的属性和方法单独封装成父类,让这些类继承这个父类,实现代码的复用的同时提高程序的扩展性。

2.2 继承中类型的关系

  • 继承是类型之间的关系:继承中,首先必须是两个或者两个以上的类型之间的关系,注意是类型之间的关系

  • 继承中的父类和子类:被继承的称为父类,实现继承的称为子类,子类继承自父类,实现的是一种A is a B的关系
    如:猫是一种类型,继承自动物这种类型,反映出来的是猫这种类型可以具备动物具备的属性和行为,同时猫是一种动物。(切记,A继承B反映的是A is a B的关系,不能反过来,猫是一种动物,不能说动物是猫)

  • 多继承机制:一个父类可以有多个子类,一个子类同样也可以有多个父类

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个人的类型,男人是人,女人也是人
# 一个父类,多个子类
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 父类
class Person(object):
    pass
# 子类
class Man(Person):
    pass
# 子类
class Women(Person):
    pass

某些情况下,我们生活中会出现这样的情况,一个小孩既是父亲的儿子,要具备儿子应该具备的功能,同时也是一个学生要具备学生应该具备的功能,此时就需要使用Python中的多继承来实现了

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# 定义一个儿子的类型~要孝顺
# 定义一个学生的类型~要好好学习天天向上
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class Son(object):
    def fealty(self):
        print("孝顺父母")
class Student(object):
    def study(self):
        print("好好学习,天天向上")
# 定义子类,继承儿子类型和学生类型
class Person(Son, Student):
    pass
# 创建对象,使用父类的方法
p = Person()
p.fealty()
p.study()
# 执行结果
~ 孝顺父母
~ 好好学习,天天向上
  • 数据继承和访问,在python的继承机制中,私有的属性是不允许被继承和互相访问的,子类不能继承和访问父类中私有的属性和方法,父类同样也不能访问子类中私有的属性和方法
    子类只能继承父类中公开的属性和方法
    子类中可以通过父类的名称或者super()来访问父类的属性和方法
# 父类
class Person(object):
    def __init__(self, name, age):
        self.__name = name
        self.__age = age
    def play(self):
        print(self.__name + "在玩游戏")
# 子类
class Man(Person):
    def __init__(self, name, age):
        # 通过父类名称访问父类中初始化的方法
        Person.__init__(self, name,age)
# 子类
class Women(Person):
    def __init__(self, name, age):
        # 通过super()访问父类初始化的方法
        # super(Women, self).__init....super中的参数可以省略
        super().__init__(self, name, age)

2.3. 继承时的方法重写(方法覆盖)

在子类继承自父类之后,可以直接使用父类中定义的公开的方法进行操作

# 父类
class Person(object):
    def play(self):
        print("Person中玩游戏的方法执行了...")
# 子类
class Children(Person):
     pass
# 创建子类对象
c = Children()
c.play()
# 执行结果
~Person中玩游戏的方法执行了...

在子类中,我们可以重新编写继承自父类的方法

# 父类
class Person(object):
    def play(self):
        print("Person中玩游戏的方法执行了...")
# 子类
class Children(Person):
     # 重写父类中play方法
     def play(self):
        print("Children中玩游戏的方法执行.....")
# 创建子类对象
c = Children()
c.play()
# 执行结果
~Children中玩游戏的方法执行.....

在继承关系下,在子类中将继承自父类的方法进行重新定义的过程,称为方法重写或者方法覆盖,经过重写或者覆盖的方法,子类执行该方法时,执行的就是重写过的方法。

3. 多态

3.1 多态的意义

多态是让我们的程序在运行的过程中,在不同的状态下进行动态的切换,实现复杂的功能为目的的一种程序开发手段

在之前的章节中,实现了类型的继承关系之后,其实我们已经见过多态的一种操作了:方法重写实现的运行时多态,对象在执行具体的方法时,会直接执行父类中继承的对应的方法,如果该方法在子类中重写了,就会执行子类中重写过的方法,实现的是一种运行过程中的多态处理,代码如下:

# 定义父类
class Person(object):
    def __init__(self, name):
        self.__name = name
    def playing(self):
        print(self.__name + "正在游戏中...")
# 定义子类,继承自Person
class Man(Person):
    def __init__(self, name):
        Person.__init__(self, name)
# 定义子类,继承自Person
class Women(Person):
    def __init__(self, name):
        Person.__init__(self, name)
    def playing(self):
        print(self.__name + "正在游戏封装找茬中...")
# 创建对象
man = Man("tom")
women = Women("jerry")

man.playing()    # 子类中没有重写,直接执行从父类继承的playing()函数
women.playing() #子类中重写了,直接执行子类中的playing()函数

3.2. 多态的扩展

我们定义一个这样的医疗系统,所有的男人、女人、小孩等等都可以去医院看病,然后康复的一个过程。

# 定义一个人的类型
class Person(object):
    def __init__(self, name, age, gender):
        self.__name = name
        self.__age = age
        self.gender = gender
    def health(self):
        print(self.__name + "康复了..")

# 定义医院的类型
class Hospital(object):
    # 定义一个治疗的方法,可以给人治病
    def care(self, person):
        print("正在治病")
        person.health()

# 定义男人类型,继承自Person类型
class Man(Person):
    def __init__(self, name, age):
        Person.__init__(self, name, age, "男")
    def health(self):
        print(self.__name + "~介个男人康复了..")
# 定义女人类型,继承自Person类型
class Women(Person):
    def __init__(self, name, age):
        Person.__init__(self, name, age, "男")
    def health(self):
        print(self.__name + "~介个男人康复了..")

# 创建人物对象
man = Man("小凡", 19)
women = Women("碧瑶",16)

# 创建医院对象
h = Hospital()

# 治病救人
h.care(man)    # 治疗Man类型的对象
h.care(women) # 治疗Women类型的对象

上面的代码中,我们已经可以看到,只要是从Person类型继承过来的类型所创建的对象,都可以在医院Hospital对象的care()中进行治疗。已经是一种多态。

同时如果功能需要扩展,需要多出来一个人物类型:小孩,小孩也会生病,也需要治疗~此时对于功能的扩展非常简洁,值需要添加如下代码就可以搞定:

# 创建一个小孩类型,继承自Person
class Children(Person):
    def __init__(self, name):
        Person.__init__(self, name)

# 创建具体的小孩对象
c = Children("小家伙")

h.care(c) # 执行结果~小家伙康复了

可以看到这里扩展一个功能变得非常的简单,对象和对象之间的协同关系由于继承多态的存在让功能的扩展实现起来比较快捷了。

3.3. 多态的完善

上面的代码中,我们其实是存在一定的缺陷的
上述代码设计的初衷是医院对象可以治病救人,也就是Hosiptal对象的care()函数可以治疗Person派生出来的对象。
但是从代码逻辑中,我们可以看到只要传递给care()函数的参数对象中包含health()函数就可以进行处理,而并非必须是Person对象。

此时需要在函数中进行判断处理,如果是Person对象就进行care()治疗的处理,如果不是Person对象,就提示不做治疗操作。

对象和类型的判断可以通过isinstance(obj, Type)进行类型的判断,如:

# 创建各种对象
lx = [1,2,3,4,5]
ld = {"1":"a", "2":"b"}
ls = {"1", "2", "3"}
man = Man("tom", 22)
women = Women("jerry", 21)

# isinstance(obj, Type)判断是否属于某种类型
isinstance(lx, list)  # 执行结果:True
isinstance(ld, dict)  # 执行结果:True
isinstance(ls, set)  # 执行结果:True
isinstance(man, Man)  # 执行结果:True
isinstance(women, Women)  # 执行结果:True
isinstance(man, Person)  # 执行结果:True
isinstance(women, Person)  # 执行结果:True
isinstance(women, list)  # 执行结果:False

上述代码中,我们可以观察到通过isinstance()函数进行变量所属数据类型的判断了,同时在继承关系中,有一个情理之中的判断结果:man是Man类型的,同时也是Person类型的,因为Man类型是从Person类型继承过来的。

所以可以对之前的Hospital的care()函数进行如下改造:

# 改造Hospital对象
class Hospital(object):
    # 改造care()函数进行处理
    def care(person):
        if isinstance(person, Person):
            print("正在治疗中...")
            person.health()
        else:
            print("不是合适的对象")

此时如果再传递参数,就要求必须是Person类型才可以进行治疗

# 定义一个Animal类型
class Animal :
    def __init__(self, name):
        self.__name = name
    def health(self):
        print(self.__name + "正在康复中...")
# 创建对象
a = Animal("shuke")
# 治疗
h.care(a)
# 执行结果:不是合适的对象

来源:http://www.jianshu.com/p/11e0e0fa0515
https://www.jianshu.com/p/2b1ebcc37ba1
http://www.jianshu.com/p/7263a799d7ea

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值