面向对象程序设计

来源:互联网 发布:淘宝贷款还不起怎么办 编辑:程序博客网 时间:2024/06/07 02:51

一. 定义及优点

面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了程序的复杂度
缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。

面向对象的程序设计的核心是对象(上帝式思维)
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,无法预测最终结果。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等。
面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。

二. 类与对象

1. 定义

python中一切皆为对象,且python3统一了类与类型的概念,类型就是类
特征与技能的结合体就是一个对象。从一组对象中提取相似的部分就是类,类是所有对象都具有的特征和技能的结合体
在python中,用变量表示特征,用函数表示技能,因而类是变量与函数的结合体,对象是变量与方法(指向类的函数)的结合体

2. 类

(1) 类的声明

class 类名:   #类名一般首字母大写    pass

(2) 类的引用
类有两种引用方式:属性引用和实例化
属性引用:类名.属性
实例化:类名()
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征。self的作用是在实例化时自动将对象/实例本身传给__init__的第一个参数,self可以是任意名字,但一般默认为self。

3. 对象

(1) 实例化
对象是关于类而实际存在的一个例子,即实例
(2) 引用
对象/实例只有一种引用:属性引用
(3) 特点
对象/实例本身只有数据属性,但是python的class机制会将类的函数绑定到对象上,称为对象的方法,或者叫绑定方法,绑定方法唯一绑定一个对象,同一个类的方法绑定到不同的对象上,属于不同的方法,内存地址都不一样,对象的绑定方法的特别之处在于:obj.func()会把obj传给func的第一个参数。

4. 类名称空间与对象/实例名称空间

(1) 类的属性
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性。而类有两种属性:数据属性和函数属性。其中类的数据属性是共享给所有对象的
类的函数属性是绑定到所有对象的。
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性。
查看名称空间属性:

名字.__dict__

(2) 引用顺序
obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就从左到右找父类,最后都找不到就抛出异常。

三. 继承与派生

1. 继承定义

继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
python中类的继承分为:单继承和多继承
查看继承:

对象.__base__  #查看从左到右继承的第一个父类对象.__bases__   #查看继承的所有父类

2. 继承与抽象

抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
a.从对象中抽取共同部分组成类
b.从类中抽取共同部分组成父类
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

3. 继承的重要性

用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大减少了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大。
子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准。
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异,self参数也要为其传值。

4. 组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同:
a.继承的方式
通过继承建立了派生类与基类之间的关系,它是一种’是’的关系,比如人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好。
b.组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如人有生日。
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好。

5. 继承的顺序

a.在python中,继承了object的类称为新式类,否则称为经典类,在python3中,类默认继承object类,因此python3中全是新式类。
新式类的继承顺序遵循广度优先搜索,经典类的继承顺序遵循深度优先搜索。
b.查看继承顺序
对于新式类,有一个mro方法,用类名.mro()会得到一个继承顺序列表
继承顺序遵循:
先查找对象,对象没有找类;
类没有找父类,多个父类安装mro的顺序列表从左到右查找。

6. 抽象类

抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
抽象类的实现:

import abcclass File(metaclass = abc.ABCMeta):    @abc.abstractmethod    def read(self):         #定义抽象方法,子类必须有read函数属性        pass    @abc.abstractmethod    def write(self):        #定义抽象方法,子类必须有write函数属性        pass              

7. 多态与多态性

a.多态
多态指的是一类事物有多种形态(一个抽象类有多个子类,因而多态的概念依赖于继承)
比如序列类型有多种形态:字符串,列表,元组
b.多态性
多态性是指具有不同功能的函数可以使用相同的函数名,在面向对象中指对于不同的对象调用相同的函数名实现不同的功能
比如:obj1.func()和obj2.func()

四. 接口与归一化设计

1. 定义

继承有两种用途:
a.继承基类的方法,并且做出自己的改变或者扩展(代码重用)
b.声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能

继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。

2. 使用接口的好处

接口提取了一群类共同的函数,可以把接口当做一个函数的集合,然后让子类去实现接口中的函数。这么做的意义在于归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合,极大地降低了使用者的使用难度。

五.封装

1. 封装:

将函数本身的属性隐藏起来,用户能调用的是包装后的属性。
封装数据的主要原因:保护隐私
封装方法的主要原因:隔离复杂度

2. 封装的实现

第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式

class People:    def __init__(self,name):        self.__name = name #隐藏name属性    def tell_name(self):        print(self.__name) #在函数内部可通过__x的形式调用p = People(‘xiaoming’) p.tell_name()   print(p._People__name) #在函数外部通过_类名__x的形式调用

这种自动变形的特点:
a.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
b.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
c.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
注:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
需要注意的问题:
a.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:类名_属性,然后就可以访问了。
b.变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
c.在继承中,父类如果不想让子类覆盖自己的方法,可以用这种方式将方法定义为私有的
3.特性
特性(property):property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
setter和deleter
函数经过property修饰后变成类似于数据属性的属性,可通过setter和deleter修饰后使其具备赋值和删除方法。

class People:    def __init__(self,name)        self.__name = name  #隐藏name属性    @property               #修饰name方法,使其变为类似于数据属性的属性    def name(self):        return self.__name      @name.setter            #修饰name方法,使其可被赋值    def name(self,value):        self.__name = value    @name.deleter           #修饰name方法,使其可用del删除    def name(self):        del self.__namep = People(‘xiaohuang’)print(p.name)               #‘xiaohuang’p.name = ‘xiaoming’     #将p._People__name修改为’xiaoming’del p.name                  #删除p._People__name

六.绑定方法与非绑定方法

类中定义的函数分成两大类:
a. 绑定方法(绑定给谁,调用时就自动将它本身当作第一个参数传入):
绑定到类的方法:用classmethod装饰器装饰的方法。为类量身定制,自动将类当作第一个参数传入,对象也可调用,但仍将类当作第一个参数传入。
绑定到对象的方法:没有被任何装饰器装饰的方法。为对象量身定制,自动将对象当作第一个参数传入,类可以调用,但是必须按照函数的规则来,没有自动传值。

class People:    @classmethod    def func(cls):        pass

b. 非绑定方法:用staticmethod装饰器装饰的方法
不与类或对象绑定,类和对象都可以调用,但是不能自动传值,就是一个普通工具。

class People:    @staticmethod    def func():        pass