什么是面向对象
说面向对象之前我们先来看一下什么是面向过程
面向过程的设计是核心是过程,过程即是解决问题的思路,从开始到结束,一步接着一步,完成整个流程。
优点是:极大的降低了程序的复杂度
缺点是:只能做一件事,不够通用。
应用场景:一般应用在很少改变的场景中。如Linux内核、Apache HTTPServer等。
什么是面向对象编程
面向对象的程序设计核心是对象,要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。
面向对象的程序设计的
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方
为什么要使用面向对象
- 隐藏了具体功能的实现细节,程序逻辑更加清晰,结构化程度增强
- 避免写重复代码,减少代码量
- 需求变更时,只需要修改函数内的代码逻辑,其余引用函数的地方无需修改,保持了一致性,增强程序的易维护性和可扩展性
类和对象
- 一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象共同具备的属性、方法
- 是一个类实例化后的实例
- 类必须实例化后才可以调用
- 一个类可以实例化多个对象
- 每个对象有不同的属性,就像人类是指人,每个人是指具体的对象,人与人之间有共性亦有不同。
对象的创建
- 对象句柄:用于区分不同的对象(实例化出来的对象名称即是句柄)
- 属性:实例变量,也可称为成员变量、成员属性
- 方法:类的成员函数
定义类
先来看一个类的样例
class Dog(object): age = 22 # 类变量, def __init__(self,name, type, sex): # 初始化函数(构造函数) # 以下是实例变量,也可以称为成员变量、成员属性 self.name = name # d.name=name self.type = type # d.type=type self.__sex = sex # 私有属性 只定义时变形_Dog__sex,所以Dog.__sex不等调用 # 成员函数、实例方法、对象方法 def balk(self): #self=d print('balk is %s'% (self.name)) # self.name=d.name def eat(self,food): print('balk is %s'% (self.name), food)# 实例化类d = Dog('li', 'F', 23) # d是Dog的实例化对象 Dog.__init__(d,'li','F',23) #d可调用的方法d.eat('aaa') # 即Dog.eat(d,'aaa')d.balk() 就是在d.__dict__ 中找balk的名字,
类变量
- 存在于类的内存地址中,可以被所有实例化的对象共享引用,
- 作为默认公有变量
- 可以全局修改
- 可以增加新属性
实例变量
- 即成员属性
- 每个实例存储在自己内存空间里的属性
- 通过构造函数实现的
类的属性
- 公有属性:即类变量
- 私有属性:不能在类外调用,只能在实例内部各函数(方法)中调用,使用双下划线开头
对象之间的交互
class AAA: def __init__(self,val): self.val = val def aaa(self,enemy): print('aaa-->',enemy)class BBB: def __init__(self,val): self.val = val def bbb(self,enemy): print('bbb-->',enemy.val,self.val) enemy.val -= self.val # a.val -= b.val a = AAA(150)b = BBB(50)b.bbb(a)print(a.val)返回:bbb--> 150 50100
继承是基于抽象的结果,通过编程语言去实现他。所以定义继承关系之前我们应该先抽象,在总结,找出一类事物的共同部分和不同部分。
抽象只是分析和设计过程中的一个动作,通过抽象可以得到类
继承是为了重用代码
class A: aaa = 'AAA'class B(A): # 类B继承类A passb = B()print(b.aaa) # 类B实例化后可以调用类A的属性
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
其中B是A的派生类
A继承与object,是新式类
继承的方式
- 通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如人是人类
- 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好
组合的方式
- 组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
- 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,
- 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
接口与归一化设计
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。
为什么要有抽象类
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
比如我们有香蕉的类,有苹果的类,有桃子的类,从这些类抽取相同的内容就是水果这个抽象的类,你吃水果时,要么是吃一个具体的香蕉,要么是吃一个具体的桃子。。。。。。你永远无法吃到一个叫做水果的东西。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的,即将揭晓答案
import abcclass AllFile(metaclass=abc.ABCMeta): @abc.abstractmethod # 定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod # 定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' passclass Text(AllFile): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('text read') def write(self): print('text write')class Log(AllFile): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('text Log') def write(self): print('text Log')T = Text()L = Log()#这样大家都是被归一化了,每个继承AllFile的类都有read和write方法T.read()L.read()
class A(object): def test(self): print('from A')class B(A): def test(self): print('from B')class C(A): def test(self): print('from C')class D(B): def test(self): print('from D')class E(C): def test(self): print('from E')class F(D,E): # def test(self): # print('from F') passf1=F()f1.test()print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性#新式类继承顺序:F->D->B->E->C->A#经典类继承顺序:F->D->B->A->E->C#python3中统一都是新式类#pyhon2中才分新式类与经典类继承顺序
对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__[, , , , , , ]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。它实际上就是合并所有父类的MRO列表并遵循如下三条准则:- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查(从左到右)
- 如果对下一个类存在两个合法的选择,选择第一个父类(找到即结束)
多态
多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)
如动物有多种形态:人,狗,猪
import abcclass Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): passclass People(Animal): #动物的形态之一:人 def talk(self): print('say hello')class Dog(Animal): #动物的形态之二:狗 def talk(self): print('say wangwang')class Pig(Animal): #动物的形态之三:猪 def talk(self): print('say aoao')
多态性
- 多态与多态性是两种概念
- 多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。
- 在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
- 增加程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用
- 增加了程序的可扩展性
封装
在类中对数据的赋值、内部调用、对外部是透明的,这使类变成了一个胶囊,里面的类包含着数据和方法
- 创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
- 类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
特性
class A: def __init__(self,name): self.__name = name @property # 隐藏调用时的括号 def name(self): # 可以获取私有属性 return self.__name @name.setter # 可以检查属性,是字符串才可以修改 def name(self,val): if isinstance(val, str): self.__name = vald = A('fff')print(d.name)
静态方法和类方法
静态方法
是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操作
class Date: def __init__(self,year,month,day): self.year=year self.month=month self.day=day @staticmethod def now(): #用Date.now()的形式去产生实例,该实例用的是当前时间 t=time.localtime() #获取结构化的时间格式 return Date(t.tm_year,t.tm_mon,t.tm_mday) #新建实例并且返回 @staticmethod def tomorrow():#用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间 t=time.localtime(time.time()+86400) return Date(t.tm_year,t.tm_mon,t.tm_mday)a=Date('1987',11,27) #自己定义时间b=Date.now() #采用当前时间c=Date.tomorrow() #采用明天的时间print(a.year,a.month,a.day)print(b.year,b.month,b.day)print(c.year,c.month,c.day)
类方法
类方法是给类用的,类在使用时会将类本身当做参数传给类方法的第一个参数
class A: x=1 @classmethod def test(cls): print(cls,cls.x)class B(A): x=2B.test()'''输出结果:2'''