面向对象

概述

  • 面向过程:就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
  • 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
  • 面向对象:面向对象是指一种程序设计范型,同时也是一种程序开发的方法。对象指的是类的集合。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性

面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。

  • 优点是:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
  • 缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
  • 应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。

面向对象的程序设计:核心是对象二字,(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。

  • 优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
  • 缺点:
  • 1. 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
  • 2. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。
  • 应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方

面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。

面向过程实质上就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while True
    if cpu利用率 > 90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接
  
    if 硬盘使用空间 > 90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接
  
    if 内存占用 > 80%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接

 随着时间的推移,开始使用了函数式编程,增强代码的重用性和可读性,就变成了这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def 发送邮件(内容)
    #发送邮件提醒
    连接邮箱服务器
    发送邮件
    关闭连接
  
while True
  
    if cpu利用率 > 90%:
        发送邮件('CPU报警')
  
    if 硬盘使用空间 > 90%:
        发送邮件('硬盘报警')
  
    if 内存占用 > 80%:
        发送邮件('内存报警')

面向对象(Object Oriented,OO)是软件开发方法。面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD技术、人工智能等领域。面向对象是一种对现实世界理解和抽象的方法,是计算机编程技术 [1]  发展到一定阶段后的产物。

面向对象设计

面向对象设计:
类:把一类事物相同的特征和动作整合到一起,是一个抽象概念
对象:基于类而创建的一个具体的事物(具体存在的)也是特征和动作整合到一起;

面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。

  类就是一个模板,模板里可以包含多个函数,函数里实现一些功能

  对象则是根据模板创建的实例,通过实例对象可以执行类中的函数

 

静态属性、类方法、静态方法

1. 静态属性:在函数前加@property,将函数逻辑”封装“成数据属性,外部直接调用函数名,如同调用属性一样。这个函数是可以调用类和实例的属性的,
    静态属性的作用是把类的方法隐藏起来(可以把背后的逻辑隐藏起来),让用户感觉是在调用属性,而不是方法;

复制代码
class Room:
    res = "alex"
    def __init__(self,name,owner,width,length,heigh):
        self.name = name
        self.owner = owner
        self.width = width
        self.length = length
        self.heigh = heigh
    @property #即可调用实例属性也可调用类属性
    def cal_area(self):
        print(p1.width * p1.length,Room.res)
p1 = Room("四合院","alex",10,10,20)
p1.cal_area
#print(Room.__dict__)#'cal_area': <property object at 0x00000219606DD4F8>
复制代码

2. 类方法:在类的方法前添加@classmethod,不需要实例化,直接调用类的该方法,可以访问类的数据属性,但是不可以访问对象的数据属性。
     @classmethod类方法是通过类里面的函数调用类本身的属性(不跟实例捆绑只和类捆绑,不用实例化)
     类方法的定义只是为了类去调用

复制代码
class Room:
    res = "alex"
    def __init__(self,name,owner,width,length,heigh):
        self.name = name
        self.owner = owner
        self.width = width
        self.length = length
        self.heigh = heigh
    @classmethod #类方法只能调用类属性,不能调用实例属性
    def cal_area(cls):
        print(Room.res) #调用类的数据属性
#p1 = Room("四合院","alex",10,10,20) #不需要实例化
Room.cal_area()
复制代码

3. 静态方法:在类的方法前加@staticmethod,该方法只是名义上的归属类管理,实例和类的属性均不可以访问,仅仅是类的工具包。
      可以理解为静态方法只是借用类的大树下的一个独立函数

复制代码
class Room:
    res = "alex"
    def __init__(self,name,owner,width,length,heigh):
        self.name = name
        self.owner = owner
        self.width = width
        self.length = length
        self.heigh = heigh
    @staticmethod #类属性和实例属性都不可以调用
    def cal_area(x,y):
        print(x,y,)
p1 = Room("四合院","alex",10,10,20) #不需要实例化
Room.cal_area(1,2)
p1.cal_area(1,2)
复制代码

 

多态

多态的特性是调用不同的子类将会产生不同的行为,而无需明确知道这个子类实际上是什么

说白了就是,不同的对象调用相同的方法,产生不同的行为

例如:s1是字符串类型,w1是列表,两个完全不同的对象,他们都可以调用len方法,而得出的结果不同

多态实际上是依附于继承的两种含义:"改变"和"扩展"本身就意味着必须有机制去选用你改变/扩展过的版本,多态实质上就是继承的实现细节

子类实例化后调用基类的方法,w1.turn_ice()叫做多态;

复制代码
class H20:
    def __init__(self,name,temerature):
        self.name = name
        self.temerature = temerature
    def turn_ice(self):
        if self.temerature < 0:
            print("[%s]温度太低结冰了"%self.name)
        elif self.temerature > 0 and self.temerature < 100:
            print("[%s]液化成水" % self.name)
        elif self.temerature > 100:
            print("[%s]温度太高变成水蒸气" % self.name)
class Water(H20):
    pass
class Ice(H20):
    pass
class Steam(H20):
    pass
w1 = Water("水",25)
i1 = Ice("冰",-20)
s1 = Steam("水蒸气",3000)
w1.turn_ice() #执行过程中不同的对象调用相同的方法
i1.turn_ice()
s1.turn_ice()
#或者定义一个接口来执行上面的调用
def func(obj):
    obj.turn_ice()
func(w1)
func(i1)
func(s1)
复制代码

 

封装

封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入

口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更

多的处理逻辑,从而严格控制使用者的访问)

  第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去

访问里面的名字,这本身就是一种封装。

1
2
3
4
5
print(m1.brand) #实例化对象(m1.)
print(motor_vehicle.tag) #类名(motor_vehicle.)
-------------输出结果--------------
春风
fuel oil

  注意:对于这一层面的封装(隐藏),类名.和实例名.就是访问隐藏属性的接口

  第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或

者留下少量接口(函数)供外部访问。

  Python中私有化的方法也比较简单,即在准备私有化的属性(包括方法、数据)名字前面加两个下划线即可。

  类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

1
2
3
4
5
6
7
8
class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.  

  这种自动变形的特点:

    1、类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。

    2、这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

    3、在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父

类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

  注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后

外部就可以使用了

  这种变形需要注意的问题是:

  1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属

性,然后就可以访问了,如a._A__N

1
2
3
4
5
6
7
8
= A()
print(a._A__N)
print(a._A__X)
print(A._A__N)
--------输出结果--------
0
10
0

  2、变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形

1
2
3
4
5
6
7
= A() #实例化对象a
print(a.__dict__) #打印变形的内容
a.__Y = 20 #新增Y的值,此时加__不会变形
print(a.__dict__) #打印变形的内容
---------输出结果----------
{'_A__X'10}
{'_A__X'10'__Y'20#发现后面的Y并没有变形

  3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A: #这是正常情况
    def fa(self):
        print("from A")
    def test(self):
        self.fa()
 
class B(A):
    def fa(self):
        print("from B")
 
= B()
b.test()
--------输出结果----------
from B

  看一下把fa被定义成私有的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A: #把fa定义成私有的,即__fa
    def __fa(self): #在定义时就变形为_A__fa
        print("from A")
    def test(self):
        self.__fa() #只会与自己所在的类为准,即调用_A__fa
 
class B(A):
    def __fa(self): #b调用的是test,跟这个没关系
        print("from B")
 
= B()
b.test()
-------输出结果---------
from A

 

封装与扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一

个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说

,只要接口这个基础约定不变,则代码改变不足为虑。 

1
2
3
4
5
6
7
8
9
10
11
12
#类的设计者
class room: #定义一个房间的类
    def __init__(self,name,owner,length,width,high):
        self.name = name
        self.owner = owner
        self.__length = length #房间的长
        self.__width = width #房间的宽
        self.__high = high #房间的高
    @property
    def area(self): #求房间的平方的功能
        return self.__length * self.__width #对外提供的接口,隐藏了内部的实现细节,\
                                            # 此时我们想求的是房间的面积就是:长x宽

  实例化对象通过接口,调用相关属性得到想要的值:

1
2
3
4
5
#类的使用者
r1 = room("客厅","michael",20,30,9#实例化一个对象r1
print(r1.area) #通过接口使用(area),使用者得到了客厅的面积
-------------输出结果--------------
600 #得到了客厅的面积

  扩展原有的代码,使功能增加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class room: #定义一个房间的类
    def __init__(self,name,owner,length,width,high):
        self.name = name #房间名
        self.owner = owner #房子的主人
        self.__length = length #房间的长
        self.__width = width #房间的宽
        self.__high = high #房间的高
    @property
    def area(self): #对外提供的接口,隐藏内部实现
        return self.__length * self.__width,\
               self.__length * self.__width * self.__high #此时我们增加了求体积,
        # 内部逻辑变了,只需增加这行代码就能简单实现,而且外部调用感知不到,仍然使
        # 用该方法,但是功能已经增加了

  对于类的使用者,仍然在调用area接口的人来说,根本无需改动自己的代码,就可以用上新功能:

1
2
3
4
5
#类的使用者
r1 = room("客厅","michael",20,30,9#实例化一个对象r1
print(r1.area) #通过接口使用(area),使用者得到了客厅的面积
--------------输出结果---------------
(6005400#得到了新增的功能的值

 

反射

说简单点 --> 就是利用字符串的形式去对象(模块)中操作(寻找/检查/设置/删除)成员

  • hasattr(object,"name")
  • getattr(object,"func","没有这个属性")
  • setattr(object,"alex","38")
  • delattr(object,"addr")

 

ok

posted @ 2020-03-24 19:13  AnthonyWang  阅读(114)  评论(0)    收藏  举报