编程范式
编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算器如何去执行任务的过程,一个程序员为了得到一个结果而编写的一组指令集合,当然,实现一个任务可以有很多种不同的方式,对这些不同的编程方法的特点,进行归纳总结得出来的编程方式类别,即为编程范式,打多数语言只支持一种编程方式,当然也有一些语言可以同时支持多种编程范式。
常见的编程范式:
1.面向过程:根据业务逻辑从上到下垒代码。
2.函数式:将某功能封装在函数中,日后便于无需写重复的代码,仅调用函数即可。
3.面向对象:对函数进行分类和封装,让开发“更快更好更强”。
语言没有高低好坏之分,编程范式也没有高低好坏之分,决定因素在于你用在什么场景下,什么语言合适你这个场景,还有开发者自身的水平如何等。
面向过程编程(Procedural Programming)
就是将程序从上到下一步步执行,从头到尾的解决问题,基本设计思路就是程序一开始是要解决一个大的问题,然后把一个大的问题分解成很多个小问题或子过程,这些子过程再执行继续分解,直到小问题简单到可以在一个小步骤范围内解决。
举个典型的面向过程的例子,数据库备份,分为三步,连接数据库,备份数据库,测试备份文件可用性。
1 def db_conn(): 2 print('connectin db...') 3 4 def db_backup(dbname): 5 print('导出数据库...',dbname) 6 print('减备份文件打包,移动至相应目录...') 7 8 def db_backup_test(): 9 print('减备份文件导入测试库,看导入是否成功') 10 11 def main(): 12 db_conn() 13 db_backup('mysqlname') 14 db_backup_test() 15 16 if __name__ == '__main__': 17 main()
如果你要对程序进行修改,对你修改的那部分有依赖的各部分你都得跟着修改,举个例子,如果程序开头你设置了一个变量值 为1 , 但如果其它子过程依赖这个值 为1的变量才能正常运行,那如果你改了这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程 , 那就会发生一连串的影响,随着程序越来越大, 这种编程方式的维护难度会越来越高。
所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。
面向对象(Object-Oriented Programming)
OOP编程是利用'类'和'对象'来创建各种模型来实现对真实世界的描述,使用面向对象的原因,一方面是因为他可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率,另一方面,基于面向对象的程序可以使他人更加容易理解你的代码逻辑,从而使团队开发变得跟从容。
面向对象的几个核心特性如下:(Class类,Object对象,Encapsulation封装,Inheritance继承,Polymorphism多态)
Class类:
一个类即是对一类拥有相同属性的对象的抽象,蓝图,原型。在类中定义了这些对象都具备的属性,共同的方法。
Class是关键字,表示类
创建对象,类名称后加括号即可
ps:类中的函数第一个参数必须是self(详情见Encapsulation封装)
类中定义的函数叫'方法'
#创建类 class School: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex # 创建类中的函数 def python_teach(self): print('python的老师是%s,年龄%s,性别%s' %(self.name,self.age,self.sex)) def linux_teach(self): print('linux的老师是%s,年龄%s,性别%s' %(self.name,self.age,self.sex)) # 根据类School创建对象obj obj = School('jack',23,'male') obj.linux_teach() # 执行linux_teach方法 obj.python_teach() # 执行python_teach方法
面向对象:[创建对象][通过对象执行方法]
函数编程:[执行函数]
总结:函数式的应用场景--》各个函数之间是独立且无共用的数据
Object对象:
一个对象即是一个类的实例化后的实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象也可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之间有共性,也有不同。
Encapsulation封装:
在类中对数据的赋值,内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含这类的数据和方法。
封装,顾名思义就是将内容封装到某个地方,以后在去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
1.将内容封装到某处
self是一个形式参数,当执行obj1 = School('jack',25)时,self等于obj1.
当执行obj2 = School('rain',30)时,self等于obj2.
所以,内容其实被封装到了对象obj1和obj2中,每个对象都有name和age属性,在内存里类似于下图保存
2.从某处调用被封装的内容
上图展示了对象obj1和obj2在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名。
1 class School: 2 def __init__(self, name, age): 3 self.name = name 4 self.age = age 5 6 obj1 = School('jack', 25) 7 print(obj1.name) # jack 直接调用obj1对象的name属性 8 print(obj1.age) # 25 直接调用obj1对象的age属性 9 10 obj2 = School('rain', 30) 11 print(obj2.name) # rain 直接调用obj2对象的name属性 12 print(obj2.age) # 30直接调用obj2对象的age属性
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接直接或间接获取被封装的内容。
练习一:在终端输出如下信息
小明,10岁,男,喜欢打游戏
小明,10岁,男,去郊游
小明,10岁,男,看电影
老李,30岁,男,喜欢打游戏
老李,30岁,男,去郊游
老李,30岁,男,看电影
# ########## 函数式编程########## def game(name,age,gender): print('%s,%s岁,%s,喜欢打游戏' %(name,age,gender)) def ouiting(name,age,gender): print('%s,%s岁,%s,去郊游' % (name, age, gender)) def film(name,age,gender): print('%s,%s岁,%s,看电影' % (name, age, gender)) game('小明',10,'男') ouiting('小明',10,'男') film('小明',10,'男') game('老李',30,'男') ouiting('老李',30,'男') film('老李',30,'男')
# ##########面向对象############### class Character: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def game(self): print('%s,%s岁,%s,喜欢打游戏' %(self.name,self.age,self.gender)) def outing(self): print('%s,%s岁,%s,去郊游'%(self.name,self.age,self.gender)) def file(self): print('%s,%s岁,%s,看电影' %(self.name,self.age,self.gender)) obj1 = Character('小明',10,'男') obj1.game() obj1.outing() obj1.file() obj2 = Character('老李',30,'男') obj2.game() obj2.outing() obj2.file()
上述对比可以看出,如果使用函数式编程,需要在每次执行函数时传入相同的参数,如果参数多的话,又需要赋值粘贴...;而对于面向对象只需要在创建对象时,减所需要的参数封装在当前对象中,之后再次使用时,通过self间接去当前对象中取值即可
练习二:游戏人生程序 1.创建三个游戏人物,分别是: 碧瑶,女,18,初始战斗力1000 小凡,男,20,初始战斗力1800 雪琪,女,19,初始战斗力2500 2.游戏场景,如下: 碧火天冰湖战斗,消耗200战斗力 自我修炼,增加100战斗力 青云大战,消耗500战斗力
class Game_Life: def __init__(self,name,sex,age,fig): self.name = name self.sex = sex self.age = age self.fight = fig def bhtd(self): '碧火天冰湖,消耗200战斗力' self.fight = self.fight - 200 def zwxl(self): '自我修炼,增加战斗力300' self.fight = self.fight + 300 def qydz(self): '青云大战,消耗战斗力500' self.fight = self.fight - 500 def defail(self): '当前对象的详细信息' temp = '姓名:%s,性别:%s,年龄:%s,战斗力:%s' %(self.name,self.sex,self.age,self.fight) print(temp) # #####创建角色 biyao = Game_Life('碧瑶','女',18,1000) xifan = Game_Life('小凡','男',20,2500) xueqi = Game_Life('雪琪','女',19,3000) # ######第一次参加战斗 biyao.bhtd() xifan.qydz() xueqi.bhtd() # ###########输出当前所有人的详细情况 biyao.defail() # 姓名:碧瑶,性别:女,年龄:18,战斗力:800 xifan.defail() # 姓名:小凡,性别:男,年龄:20,战斗力:2000 xueqi.defail() # 姓名:雪琪,性别:女,年龄:19,战斗力:2800 # #########第二次参加战斗 biyao.qydz() xifan.bhtd() xueqi.qydz() # ############输出当前所有人的详细情况 biyao.defail() # 姓名:碧瑶,性别:女,年龄:18,战斗力:300 xifan.defail() # 姓名:小凡,性别:男,年龄:20,战斗力:1800 xueqi.defail() # 姓名:雪琪,性别:女,年龄:19,战斗力:2300
在Python中以单下划线和双下划线开头的名字都应该是内部的,私有的。如(socket._socket,sys._home,sys._clear_type_cache),python并不会真正意义上阻止你访问私有的属性,模块也遵循这种约定,,原则上这些都是供内部使用,作为外部的你,一意孤行也是可以的,只不过这会显得你稍微傻逼一些。
单下划线举例
class People: _star='earth' def __init__(self,id,name,age,salary): self.id=id self.name=name self._age=age self._salary=salary def _get_id(self): print('我是私有方法啊,我找到的id是[%s]' %self.id) #我们明明约定好了的,只要属性前加一个单下划线,那他就属于内部的属性,不能被外部调用了啊,为何还能调用??? print(People._star) p1=People('3706861900121221212','alex',28,10) print(p1._age,p1._salary) p1._get_id()
双下划线举例
class People: __star='earth' def __init__(self,id,name,age,salary): self.id=id self.name=name self.__age=age self._salary=salary def _get_id(self): print('我是私有方法啊,我找到的id是[%s]' %self.id) print(People.__dict__)#__star存到类的属性字典中被重命名为_People__star p1=People('333333','alex',18,10) print(p1.__dict__)#__age存到类的属性字典中被重命名为_People__age print(People.star) #好像不能访问了喔! print(p1.age)# 好像也不能访问了喔! 双下滑线开头的不能被访问了
class People: __star='earth' def __init__(self,id,name,age,salary): self.id=id self.name=name self.__age=age self._salary=salary def _get_id(self): print('我是私有方法啊,我找到的id是[%s]' %self.id) print(People.__dict__)#__star存到类的属性字典中被重命名为_People__star p1=People('333333','alex',18,10) print(p1.__dict__)#__age存到类的属性字典中被重命名为_People__age # print(People.star) #好像不能访问了喔! # print(p1.age)# 好像也不能访问了喔! print(People._People__star) #我曹,又能访问了why? print(p1._People__age)#我曹,又能访问了why? 诡异的事情发生了
双下划线开头的属性在继承给子类时,子类是无法覆盖的(原理也是基于Python自动做了双下划线开头的名字的重命名工作)
class People: __star='earth' def __init__(self,id,name,age,salary): self.id=id self.name=name self.__age=age self._salary=salary def _get_id(self): print('我是私有方法啊,我找到的id是[%s]' %self.id) class Korean(People): __star = '火星' pass print(People.__dict__)#__star存到类的属性字典中被重命名为_People__star print(Korean.__dict__) # print(Korean.__star)#傻逼,这么访问当然报错啦,__star被重命名了,忘记了? print(Korean._Korean__star) print(Korean._People__star) 双下划线开头在继承中的应用
python中真正的封装含义应该是:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用.
封装示例
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_area(self): #此时我们想求的是面积 return self.__width * self.__length r1=Room('卫生间','alex',100,100,10000) area=r1.tell_area() print(area)
# 封装二
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_area(self): #此时我们想求的是体积,内部逻辑变了,而外部调用感知不到,仍然使用该方法 return self.__width * self.__length * self.__high r1=Room('卫生间','alex',100,100,10000) area=r1.tell_area() print(area)
Inheritance继承:
一个类可以派生出子类,在这个父类里面定义的属性,方法自动被子类继承。
例如:
猫可以:喵喵叫,吃,喝,拉,撒。
狗可以:汪汪叫,吃,喝,拉,撒。
如果我们要分别为猫和狗创建一个类,那么就需要为猫和狗实现他们所有的功能,如下所示:
######伪代码############# class 猫: def 喵喵叫(self): print('喵喵叫') def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something class 狗: def 汪汪叫(self): print('汪汪叫') def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something
上述代码不难看出,吃,喝,拉,撒是猫和狗都具有的功能,而我们却分别为猫和狗的类中编写了两次,如果使用继承的思想,如下实现:
动物:吃,喝,拉,撒
猫:喵喵叫(猫继承动物的功能)
狗:汪汪叫(狗继承动物的功能)
# ########伪代码######### class 动物: def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 猫(动物): def 喵喵叫(self): print('喵喵叫') # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 狗(动物): def 汪汪叫(self): print('汪汪叫')
class Animal: def eat(self): print('%s吃' %self.name) def drink(self): print('%s喝' %self.name) def shit(self): print('%s 撒'%self.name) def pee(self): print('%s 撒' %self.name) class Cat(Animal): def __init__(self,name): self.name = name self.breed = '猫' def cry(self): print('喵喵叫', self.breed) class Dog(Animal): def __init__(self,name): self.name = name self.breed = '狗' def cry(self): print('汪汪叫', self.breed) # ###########执行############ c1 = Cat('赖英家的小猫猫') c1.eat() # 赖英家的小猫猫吃 c1.drink() # 赖英家的小猫猫喝 c1.pee() # 赖英家的小猫猫 撒 c1.cry() # 喵喵叫 猫 c2 = Dog('赖英家的小狗狗') c2.eat() # 赖英家的小狗狗吃 c2.drink() # 赖英家的小狗狗喝 c2.pee() # 赖英家的小狗狗 撒 c2.cry() # 汪汪叫 狗
# #########归一化式继承 # 声明某个子类兼容于某基类,定义一个接口类,子类继承接口类,并且实现接口中定义的方法 #一切皆文件 import abc class All_file(metaclass=abc.ABCMeta): @abc.abstractmethod def read(self): '子类必须实现读功能' pass @abc.abstractmethod def write(self): '子类必须实现写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类必须实现父类的方法 class Txt(All_file): def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read()
实践中,普通的继承含义意义并不大,甚至常常的有害的,因为它使得子类于基类出现强耦合性。
如上图所示,继承的第二种含义非常重要,它叫"接口继承"。
接口继承实质上是要求"做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体的细节,可一视同仁的处理实现了特定接口的所有对象" __这在程序设计上,叫做归一化
归一化使得高层的外部使用者可以不叫区分的处理所有接口兼容的对象集合——就好像linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存,磁盘,网络还是屏幕(当然,对底层设计者,当然也可以区分出"字符设备"和"块设备",然后做出针对性的设计,细致到什么程度,视需求而定)。
所以,对于面向对象的继承来说,其实就是将多个类共用的方法提取到父类中,子类仅需继承父而不必一一实现每个方法。
注:除了子类和父类的称谓,你可以看到过派生类和基类,他们于子类和父类只是叫法不同而已。
问题来了,多继承呢?
是否可以继承多个类
如果继承的多个类,每个类都有相同的函数,那么哪一个会被使用呢?
1.python的类可以继承多个类,Java和c#中则只能继承一个类
2.Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
当类是经典类时,多继承的情况下,会按照深度优先的方式去查找。
但类是新式类时,多继承的情况下,会按照广度优先的方式查找。
经典类和新式类,从字面上可以看出一个老一个新,新的必然包含了更多的功能,也是之后推荐的写法,在Python3.x都是新式类,从写法上区分的话,如果当前类或者父类继承了Object类,那么该类便是新式类,否则便是经典类。
class D: def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> D --> C # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar() 经典类多继承
class D(object): def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> C --> D # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar() 新式类多继承
经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
子类中调用父类的方法
子类继承了父类的方法,然后想进行修改,注意了是基于原有的基础上修改,那么就需要在子类中调用父类的方法
方法一:父类名.父类方法()
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): Vehicle.__init__(self,name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) Vehicle.run(self) line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run() 新年愿望是买一辆地铁
方法二:super()
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' class Vehicle: #定义交通工具类 Country='China' def __init__(self,name,speed,load,power): self.name=name self.speed=speed self.load=load self.power=power def run(self): print('开动啦...') class Subway(Vehicle): #地铁 def __init__(self,name,speed,load,power,line): #super(Subway,self) 就相当于实例本身 super(Subway,self).__init__(name,speed,load,power) self.line=line def run(self): print('地铁%s号线欢迎您' %self.line) super(Subway,self).run() class Mobike(Vehicle):#摩拜单车 pass line13=Subway('中国地铁','180m/s','1000人/箱','电',13) line13.run() 摩拜单车
不用super引发的惨案
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' #每个类中都继承了且重写了父类的方法 class A: def __init__(self): print('A的构造方法') class B(A): def __init__(self): print('B的构造方法') A.__init__(self) class C(A): def __init__(self): print('C的构造方法') A.__init__(self) class D(B,C): def __init__(self): print('D的构造方法') B.__init__(self) C.__init__(self) pass f1=D() print(D.__mro__) #python2中没有这个属性
当你使用super()函数时,Python会在MRO列表上继续搜索下一个类,只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历
完整个MRO列表,每个方法也只会被调用一次
#_*_coding:utf-8_*_ __author__ = 'Linhaifeng' #每个类中都继承了且重写了父类的方法 class A: def __init__(self): print('A的构造方法') class B(A): def __init__(self): print('B的构造方法') super(B,self).__init__() class C(A): def __init__(self): print('C的构造方法') super(C,self).__init__() class D(B,C): def __init__(self): print('D的构造方法') super(D,self).__init__() f1=D() print(D.__mro__) #python2中没有这个属性 使用super
Polymorphism多态:
多态是面向对象的重要特性,通俗一点讲就是"一个接口,多种实现",指一个基类中派生出多个不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
编程其实就是将一个具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来,再通过这个抽象的事物,与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为,比如,你的老板让所有的员工在9点开始工作,他只要在9点的时候说:"开始工作"即可,而不需要对销售人员说"开始销售工作",对技术人员说:"开始技术工作",因为"员工“是一个抽象的事物,只要是员工就可以开始工作,他知道这一点就可以了,至于每个员工,当然会各司其职,做各自的工作。
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向子类型的对象,调用的方法是该子类型的方法,这里引用和调用方法的代码编译前就已经决定好了,而引用所指向的对象可以在运行期间动态绑定。
Python中的标准类型就是一个多态概念的一个很好的示范,如(str.__len__(),list.__len__(),tuple.__len__可以len(str),len(list),len(tuple))
class H2O: def __init__(self,name,temperature): self.name=name self.temperature=temperature def turn_ice(self): if self.temperature < 0: print('[%s]温度太低结冰了' %self.name) elif self.temperature > 0 and self.temperature < 100: print('[%s]液化成水' %self.name) elif self.temperature > 100: print('[%s]温度太高变成了水蒸气' %self.name) class Water(H2O): pass class Ice(H2O): pass class Steam(H2O): pass w1=Water('水',25) i1=Ice('冰',-20) s1=Steam('蒸汽',3000) def func(obj): obj.turn_ice() func(w1) func(i1) func(s1) w2=Water('水',101) func(w2) 多态
总结
1.面向对象是一直编程方式,此编程方式的实现是基于类和对象的使用
2.类是一个模版,模版中包装了多个"函数"供使用
3.对象,根据模版创建的实例(即:对象),实例用于调用被包装在类中的函数
4.面向对象三大特性:封装,继承,多态
Python中关于OOP的常用术语
抽象/实现
抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于绘程序结构,从而实现这种模型,抽象不仅包括这些模型的数据属性,还定义了这些数据的接口
对某种抽象的实现就是对此数据及与之相关接口的现实化,现实化这个过程对应客户程序应当是透明而且无关的。
封装/接口
封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数,通过任何客户端直接对数据的访问,无视接口,与封装都是背道而驰的,除非程序员运行这些操作。做为现实的一部分,客户端根本就不需要知道在封装之后,数据是如何组织的,在Python中,所有的类属性都是公开的,但名字可能被"混淆"了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了,这就需要在设计时,对数据提供相应的接口,以免客户通过不规范的操作来存期封装的数据属性。
真正的封装是,经过深入的思考,做出了良好的抽象,给出"完整且最小"的接口,并使得内部细节可以对外透明。
(对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意思不到内部细节的存在)
合成
合成扩充了对类的树,使得多个不同的类合成为一个大的类来解决现实问题,合成树是一个异常复杂的系统,比如一个类由其他类组成,更小的组件也可能是其他的类,数据属性及行为,所有这些合在一起,彼此是"有一个"的关系。
派生/继承/继承结构
派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其他的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多"代"派生,可以述成一个"族谱",连续的子类,与祖先都有关系。
泛化/特化
基于继承
泛化表示所有的子类与其父类以及祖先都有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其他祖先类不同。
多态
多态的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需要考虑他们具体的类。
多态表明了动态绑定的存在,允许重载及运行时类型确定和验证。
举例:
水是一个类
不同温度,水被实例化成了不同的状态:冰,水蒸气,雾(然而很多人就理解到这一步就任务此乃多态,错,多态是运行时绑定的存在)
(多态体现在由同一个类实例化出的多个对象,这些对象执行相同的方法时,执行的过程和结果是不一样的)
冰,水蒸气,雾,有一个共同的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的两个过程,虽然调用的方法都一样。
自省/反射
自省也称做反射,这个特性展示了某对象是如何在运行期间取得自身信息的,如果传一个对象给你,你可以查看他有什么功能,这是一项强大的特性,如果Python不支持某这形式的自省功能,dir和type内建函数,将很难正常工作,还有那些特殊的属性,如__dict__,__name__,__doc__.