第十篇:面向对象系列之三大特性

Posted littlefivebolg

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十篇:面向对象系列之三大特性相关的知识,希望对你有一定的参考价值。

 

  本篇主要介绍面向对象的三大特性及 封装、继承、多态。

一、封装

 第一层意义上的封装:

  封装:从封装本身的理解,装 -- 将一系列东西(苹果、梨子、香蕉等)一股脑的塞进一个盒子内,而封--自然就是密封,即用胶带啥的为其封口;同样编程中的封装也可以这样理解:上述的盒子我们可以理解为类,即将一大堆的代码塞进一个类中,这个类中可以有属性和方法,当然我们可以通过特殊的方法(在盒子周边开许多个小洞,每个小洞对应取的属性和方法)取出其中的属性和方法进行使用。

class Div(object):
    banana_num = 20
    apple_num = 30
    def __init__(self):
        pass
    def pack(self):
        pass

div1 = Div()

# 例如上述,有个盒Div存放着许多东西,由这个盒子生出一个小盒子div1,这个小盒子通过管道与大盒子连着可以取出其内的东西。

 

 第二层意义上的封装:

  第二层意义上的封装是通过隐藏来实现的,即通过定义单下划线和双下划线来定义属性和方法,使得在类的外边无法调用该属性和方法来实现封装。

  单下划线:

技术分享图片
# 在模块aa中定义一个Dogs类
class Dogs(object):
    _breed = "Huskie"  # 规定品种都是哈士奇
    def __init__(self,name,age):
        self.name =name
        self.age = age
    def info(self):
        print("The dag‘s name is %s"%(self.name))

dog1 = Dogs("alex",22)
print(dog1._breed)   # 本模块中是可以直接调用的,输出:Huskie

# 在模块bb导入aa
import aa

d = aa.Dogs("amanda",22)
print(d._bread)   #会报错:AttributeError: ‘Dogs‘ object has no attribute ‘_bread‘


#而这种方式可以导入单下划线私有属性
from aa import Dogs
d2 = Dogs("egon",33)
print(d2._breed)   # 输出为:Huskie
单下划线私有属性的调用范围-01

 

  从01-例子中,若我们不想别人导入模块时使用该属性,我们可以将属性私有化。但是通过from xxx import xxx导入该模块还是可以得到该私有属性。

  双下划线:

技术分享图片
class Dogs(object):
    _breed = "Huskie"  # 规定品种都是哈士奇
    __gender = "male"
    def __init__(self,name,age):
        self.name =name
        self.age = age
    def info(self):
        print("The dag‘s name is %s"%(self.name))

dog1 = Dogs("alex",22)
# print(dog1.__gender)
# print(Dogs.__gender)

#但是当我们查看属性字典时会发现实则属性字典中还是有该属性,只是属性名发生变化
print(Dogs.__dict__)  # 由__gender => ‘_Dogs__gender‘: ‘male‘, ‘
print(Dogs._Dogs__gender)  # 输出为:male

#注:这种属性名重构并不是严格上的限制外部访问,可以通过_类名__属性名的方式还是可以访问,
# 仅仅是一种语法意义上的变形,主要用来限制外部的直接访问
双下划线私有属性-02

  从02-例子中,若我们想在类中真正将某些属性私有化,将该属性完全封装起来即可以使用双下划线的方式实现,防止在类的外部直接调用。当然在类的内部肯定是可以直接调用的。

技术分享图片
#正常情况
class A(object):
    def __init__(self):
        pass
    def bar(self):
        print("in the A")
    def foo(self):
        self.bar()
class B(A):
    def bar(self):
        print("in the B")

b = B()
b.foo()   # in the B
b.bar()   # in the B

#--------------------------------------------------
class A(object):
    def __init__(self):
        pass
    def __bar(self):
        print("in the A")
    def foo(self):  #仅仅为一个接口,用来直接调用__bar私有属性
        self.__bar()
class B(A):
    def __bar(self):
        print("in the B")

b = B()
b.foo()  # in the A
# b.__bar()  #报错

# 从结构可以看出,这种方式可以防止子类重构自己的方法,可以将自己的属性或者方法是私有化。
父类不想让子类重构自己的方法-03

  总结:通过上述例子我们可以知道:

  第二层面上的封装即将不想让类的外部进行使用时,可以将属性或者方法私有化,达到将该属性或者方法完全封装的效果。

 

 第三层意义上的封装:

  首先我们需要知道的是,封装的真正意义在于明确的区分内外,封装的属性可以直接在内部使用,而不能在外部直接使用,然而定义属性的目的终归是要使用的,外部想要使用类隐藏的属性,需要我们为其开辟接口,让外部间接的可以使用到我们隐藏起来的属性。那么这样有什么好处呢?

  1、封装数据:将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制(copy)。

class Teacher:
    def __init__(self,name,age):
        # self.__name=name
        # self.__age=age
        self.set_info(name,age)  # 自动为用户设置属性

    # 用户只能通过我的接口函数tell_info来得到用户信息,但是无法直接访问我类中的定义的self.__name,self.__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(Alex,18)
t.tell_info()  #姓名:Alex,年龄:18

t.set_info(Alex,19)
t.tell_info()  # 姓名:Alex,年龄:19

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

  注:编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

 

   四、封装的拓展性

  在上述若我们定义了接口函数,用户只能通过接口函数访问设计者提供的属性操作得到的值,这个过程完全取决于设计者。同时当我们修改接口函数或者修改内本身的属性,对用户的使用而言用于并不能直接的发现封装了的类中属性的变化,即使得类实现者可以修改封装内的东西而不影响外部调用者的代码。

技术分享图片
# copy
#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_info(self): #对外提供的接口,隐藏了内部的实现细节,此时提供的只有面积
        return self.__width * self.__length


#使用者
r1=Room(卧室,alex,100,100,10)
r1.tell_info() #使用者调用接口tell_info  -- 10000

# -----------------------------------------------------------
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high

     #对外提供的接口,隐藏内部实现,此时我们想提供的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变化
    def tell_info(self):
        return  (self.__width * self.__length * self.__high)


#对于仍然在使用tell_info接口的人来说,根本无需改动自己的代码,就可以用上新功能
r1=Room(卧室,alex,100,100,10)
r1.tell_info()  # 100000
封装及扩展

  

  总结:其实python内部很多方法也是这样实现,例如我们调用一个类中的函数,这个函数在用户看来仅仅是实现了某个特定的功能,至于该函数在内部是怎样调用属性和方法的,用户一概不关心。但是用户想要直接去修改或者调用这些属性和方法时是无法做到,这样的封装即满足了用户的需求,也保证了代码的完整性和保护了设计者的心血。

 


 二、继承

  2.1 继承的概念

  从表面字意不然理解是儿子遗传了父亲的一些特征或者儿子拥有父亲的一些能力等,在面向对象编程也是如此,继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类,新建的类称为派生类或子类,子类可以拥有父类的属性和方法。如下:

class ParentA(object):
    pass
class ParentB(object):
    pass

# 子类SonC继承了父类ParentA
class SonC(ParentA):
    pass
# 也可以多继承,既继承了ParentA,也继承了ParentB;
class SonD(ParentA,ParentB):
    pass

  1、通常在Python2中通常规定显式的继承了object的类及其子类均为新式类,没有显式的继承object的类及其之类即为经典类,而在Python3中无论有没有显式继承object的类,都默认继承object类即均为新式类

  2、在类中有许多灰魔法(例如__str__等)这些之所以在类定义时,该类就会拥有这些灰魔法,是因为在父类object中已经封装定义好的。

 

  2.2 继承和重用性

  首先我们看例子:

技术分享图片
#定义一个狗类
class Dogs(object):
    def __init__(self,name):
        self.name = name
    def eat(self):  #
        pass
    def drink(self):  #
        pass
    def pull(self):  #
        pass
    def pee(self):  #
        pass

    def bark(self):  #
        print("the dog is wangwangwang")

# 定义一个猫类
class Cats(object):
    def __init__(self,name):
        self.name = name
    def eat(self):  #
        pass
    def drink(self):  #
        pass
    def pull(self):  #
        pass
    def pee(self):  #
        pass

    def bark(self):  #
        print("the cat is miaomiaomiao")

# 从上述的代码中,猫和狗均有吃、喝、拉、撒等功能,在两个类中均定义了吃喝拉撒的方法,同样的代码书写了两次,那如果有更多的动物也具有吃喝拉撒的功能,那么这样的代码可能要复制更多次,这样显然是不符合代码重用的原则;

----------------------------------------------------------------------------------
 #  通过继承的方式来解决这个问题:

class Animals(object):
    def __init__(self,name):
        pass
    def eat(self):  #
        pass
    def drink(self):  #
        pass
    def pull(self):  #
        pass
    def pee(self):  #
        pass

class Dogs(Animals):
    def __init__(self,name):
        super().__init__()
    def bark(self):  #
        print("the dog is wangwangwang")

class Dogs(Animals):
    def __init__(self):
        super().__init__()
    def bark(self):  #
        print("the cat is miaomiaomiao")

# 由于所有的动物均具有吃喝拉撒的特征,所有定义一个父类Animals,让不同种类的动物去继承这个父类即可,即实现了代码的重用性。
继承的方式可以减少代码的重用

  在开发的过程中,我们定义了一个类A和一个类B,发现类A的大部分属性和方法与类B是相同或者说是相似的,那么我们可以通过定义一个类C作为父类,将两者共同的属性和方法放入类C,让类A和类B去继承类C,这样就可以达到减少在开发过程中的代码的复制粘贴,实现代码的重用。

 

  2.3 继承和派生

  派生:当子类继承父类时,子类也可以添加自己新的属性或者在自己重新定义这些属性(不会影响到父类);相当于儿子遗传了父亲的许多特征和能力,但是也会有变异即也会有新的特征和能力,或者在父亲特征的基础上特征或者能力发生改变,这就是派生。

  相信很多人都玩过LOL或者DOTA等游戏 ,而这些游戏使用的过程中会经常开发出一些新的英雄,比如新出的佐伊、新出的卡莎等新英雄,当然在游戏开发的过程中,每个英雄均有生命值、攻击力、移速、护甲值,均能平A、移动等功能,当然每个英雄也有其独特的特征外形、特殊的技能(比如盖伦的大宝剑、李青的救死扶伤脚等)等功能,那么开发者在开发的过程中,不可能每出一个新英雄就为其重新开发,而是通过继承所有英雄具备相同特征和功能的类,随后再在子类中为该英雄派生出新的特征和方法。例如:

  李青大战盖伦

技术分享图片
#!/usr/bin/env python
# -*- coding:utf-8 -*-

# 定义一个英雄类
class Hero(object):
    def __init__(self,name,life_value,attack_value,speed,armor): # 名字、生命值、攻击力、移速、护甲值
        self.name = name
        self.life_value = life_value
        self.attack_value = attack_value
        self.speed = speed
        self.armor = armor

    def move_forward(self):  # 向前移动
        print(%s move forward % self.name)

    def move_backward(self): # 向后移动
        print(%s move backward % self.name)

    def attack(self,obj):  # 攻击
        obj.life_value -= self.attack_value


# 定义一个盖伦类,继承英雄类      
class Galen(Hero):
    def __init__(self,name,life_value,attack_value,speed,armor,skin):  # 派生了新的属性-皮肤
        super().__init__(name,life_value,attack_value,speed,armor)
        self.skin = skin

    # 派生出新的技能
    def silence(self,obj):  # 使敌人沉默的技能
        obj.life_value -= self.attack_value +100
        print("The skill will make other hero silence")  # 该技能会让敌人沉默

    def spin(self,obj):  # 转圈圈
        obj.life_value -= self.attack_value + 300
        print("The skill will make %s spin forver."%self.name)  # 该技能会让盖伦一直转圈圈


# 定义一个李青类,继承英雄类
class LiSon(Hero):
    def __init__(self,name,life_value,attack_value,speed,armor,skin):  # 派生了新的属性-皮肤
        self.skin = skin
        super().__init__(name,life_value,attack_value,speed,armor)
    
    #派生出新的技能
    def sky_sound_wave(self,obj):  # 天音波
        obj.life_value -= self.attack_value + 200
        print("The skill will make LiSon fly to enemy")  # 该技能会让李青飞向敌人

    def yiku(self,obj):  # 一库
        obj.life_value -= self.attack_value + 500
        print("The skill will fly kick enemy.")  # 该技能会踹飞敌人


lison = LiSon("LiSon",1000,80,"10m/s",50,"descendants of the dragon")
gelen = Galen("Galen",2000,50,"8m/s",100,"Mars")

lison.sky_sound_wave(gelen) # 李青天音波攻击盖伦
print(gelen.life_value) # 输出盖伦的生命值
添加新英雄为其附加新的属性

  通过上述李青大战盖伦的实例,可以看出派生是在父类的属性和方法的基础上,衍生除了子类特有的属性和方法。

  


 

三、多态

  多态:是指不同的对象调用相同的方法,得出结论是不一样的。或者说将不同的对象作为参数传入不同的函数得出的结果是不一样的。例如:

import abc
# metaclass =abc.ABCMeta -- 规定子类必须重写该方法
class File(metaclass=abc.ABCMeta): #同一类事物:文件
    @abc.abstractmethod
    def click(self):
        pass

class Text(File): #文件的形态之一:文本文件
    def click(self):
        print(open file)

class ExeFile(File): #文件的形态之二:可执行文件
    def click(self):
        print(execute file)

text1 = Text()
exefile = ExeFile()   

text1.click()  # open file
exefile.click()  #execute file

# 即两个对象调用同一个父类的同一个方法时,得到的结果是不一样的,这就体现出了多态的特性

  在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息(!!!obj.func():是调用了obj的方法func,又称为向obj发送了一条消息func),不同的对象在接收时会产生不同的行为(即方法)。

也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。(copy)

  比如:老师.下课铃响了(),学生.下课铃响了(),老师执行的是下班操作,学生执行的是放学操作,虽然二者消息一样,但是执行的效果不同。

  其实多态特性是在继承与派生的基础上实现的,即A与B子类均继承父类C的属性(继承),A重写了父类的某个方法(派生),故A的实例和B的实例同时去调用该属性方法,则会产生不同的结果。例如:

技术分享图片
class Minios(object):
    """MiniOS 操作系统类 """
    def __init__(self, name):
        self.name = name
        self.apps = []  # 安装的应用程序名称列表

    def __str__(self):
        return "%s 安装的软件列表为 %s" % (self.name, str(self.apps))

    def install_app(self, app):
        # 判断是否已经安装了软件
        if app.name in self.apps:
            print("已经安装了 %s,无需再次安装" % app.name)
        else:
            app.install()
            self.apps.append(app.name)


class App(object):
    def __init__(self, name, version, desc):
        self.name = name
        self.version = version
        self.desc = desc

    def __str__(self):
        return "%s 的当前版本是 %s - %s" % (self.name, self.version, self.desc)

    def install(self):
        print("将 %s [%s] 的执行程序复制到程序目录..." % (self.name, self.version))


class PyCharm(App):
    pass


class Chrome(App):
    def install(self):
        print("正在解压缩安装程序...")
        super().install()


linux = MiniOS("Linux")
print(linux)

pycharm = PyCharm("PyCharm", "1.0", "python 开发的 IDE 环境")
chrome = Chrome("Chrome", "2.0", "谷歌浏览器")

linux.install_app(pycharm)
linux.install_app(chrome)
linux.install_app(chrome)

print(linux)

#输出结果结果为:
Linux 安装的软件列表为 []
将 PyCharm [1.0] 的执行程序复制到程序目录...

正在解压缩安装程序...
将 Chrome [2.0] 的执行程序复制到程序目录...

已经安装了 Chrome,无需再次安装
Linux 安装的软件列表为 [PyCharm, Chrome]
安装APP

 

 

  over~~~~~~~~~~,下篇介绍面向对象编程的静态属性、静态方法、以及类方法等、以及组合、反射等知识

以上是关于第十篇:面向对象系列之三大特性的主要内容,如果未能解决你的问题,请参考以下文章

python目录

python全栈开发第十六篇面向对象三大特性——多台和继承补充

第十篇 面向对象的程序设计

python全栈开发第十五篇面向对象三大特性——封装

python全栈开发第十四篇面向对象三大特性——继承

面向对象之三大特性