一、什么是面向对象的程序设计及为什么要有它
1 面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。 2 3 优点是:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单) 4 5 缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。 6 7 应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。 8 9 10 11 面向对象的程序设计:核心是对象二字,(要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的数据属性和方法属性),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙交互着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取),对象是特征与技能的结合体,基于面向对象设计程序就好比在创造一个世界,你就是这个世界的上帝,存在的皆为对象,不存在的也可以创造出来,与面向过程机械式的思维方式形成鲜明对比,面向对象更加注重对现实世界的模拟,是一种“上帝式”的思维方式。 12 13 优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。 14 15 缺点: 16 17 1. 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。 18 19 2. 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。于是我们经常看到对战类游戏,新增一个游戏人物,在对战的过程中极容易出现阴霸的技能,一刀砍死3个人,这种情况是无法准确预知的,只有对象之间交互才能准确地知道最终的结果。 20 21 应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方 22 23 面向对象的程序设计并不是全部。对于一个软件质量来说,面向对象的程序设计只是用来解决扩展性。
二、类与对象
1 类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体 2 3 那么问题来了,先有的一个个具体存在的对象(比如一个具体存在的人),还是先有的人类这个概念,这个问题需要分两种情况去看 4 5 在现实世界中:先有对象,再有类 6 7 世界上肯定是先出现各种各样的实际存在的物体,然后随着人类文明的发展,人类站在不同的角度总结出了不同的种类,如人类、动物类、植物类等概念 8 9 也就说,对象是具体的存在,而类仅仅只是一个概念,并不真实存在 10 11 在程序中:务必保证先定义类,后产生对象 12 13 这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类 14 15 不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象 16 17 按照上述步骤,我们来定义一个类(我们站在学校老师的角度去看,在班级上课的各位都是学生)
PS:现实世界中是先有对象,在定义类,在程序中是先定义类,在产生对象
1 #方式一、为对象初始化自己独有的特征 2 class People: 3 country='China' 4 x=1 5 def run(self): 6 print('----->', self) 7 8 # 实例化出三个空对象 9 obj1=People() 10 obj2=People() 11 obj3=People() 12 13 # 为对象定制自己独有的特征 14 obj1.name='egon' 15 obj1.age=18 16 obj1.sex='male' 17 18 obj2.name='lxx' 19 obj2.age=38 20 obj2.sex='female' 21 22 obj3.name='alex' 23 obj3.age=38 24 obj3.sex='female' 25 26 # print(obj1.__dict__) 27 # print(obj2.__dict__) 28 # print(obj3.__dict__) 29 # print(People.__dict__) 30 31 32 33 34 35 #方式二、为对象初始化自己独有的特征 36 class People: 37 country='China' 38 x=1 39 def run(self): 40 print('----->', self) 41 42 # 实例化出三个空对象 43 obj1=People() 44 obj2=People() 45 obj3=People() 46 47 # 为对象定制自己独有的特征 48 def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' 49 obj.name = x 50 obj.age = y 51 obj.sex = z 52 53 chu_shi_hua(obj1,'egon',18,'male') 54 chu_shi_hua(obj2,'lxx',38,'female') 55 chu_shi_hua(obj3,'alex',38,'female') 56 57 58 59 60 61 #方式三、为对象初始化自己独有的特征 62 class People: 63 country='China' 64 x=1 65 66 def chu_shi_hua(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' 67 obj.name = x 68 obj.age = y 69 obj.sex = z 70 71 def run(self): 72 print('----->', self) 73 74 75 obj1=People() 76 # print(People.chu_shi_hua) 77 People.chu_shi_hua(obj1,'egon',18,'male') 78 79 obj2=People() 80 People.chu_shi_hua(obj2,'lxx',38,'female') 81 82 obj3=People() 83 People.chu_shi_hua(obj3,'alex',38,'female') 84 85 86 87 88 # 方式四、为对象初始化自己独有的特征 89 class People: 90 country='China' 91 x=1 92 93 def __init__(obj, x, y, z): #obj=obj1,x='egon',y=18,z='male' 94 obj.name = x 95 obj.age = y 96 obj.sex = z 97 98 def run(self): 99 print('----->', self) 100 101 obj1=People('egon',18,'male') #People.__init__(obj1,'egon',18,'male') 102 obj2=People('lxx',38,'female') #People.__init__(obj2,'lxx',38,'female') 103 obj3=People('alex',38,'female') #People.__init__(obj3,'alex',38,'female') 104 105 106 # __init__方法 107 # 强调: 108 # 1、该方法内可以有任意的python代码 109 # 2、一定不能有返回值 110 class People: 111 country='China' 112 x=1 113 114 def __init__(obj, name, age, sex): #obj=obj1,x='egon',y=18,z='male' 115 # if type(name) is not str: 116 # raise TypeError('名字必须是字符串类型') 117 obj.name = name 118 obj.age = age 119 obj.sex = sex 120 121 122 def run(self): 123 print('----->', self) 124 125 126 # obj1=People('egon',18,'male') 127 obj1=People(3537,18,'male') 128 129 # print(obj1.run) 130 # obj1.run() #People.run(obj1) 131 # print(People.run) 132 133 !!!__init__方法之为对象定制自己独有的特征
PS:
1. 站的角度不同,定义出的类是截然不同的,详见面向对象实战之需求分析
2. 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类......
3. 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类
1 #python为类内置的特殊属性 2 类名.__name__# 类的名字(字符串) 3 类名.__doc__# 类的文档字符串 4 类名.__base__# 类的第一个父类(在讲继承时会讲) 5 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 6 类名.__dict__# 类的字典属性 7 类名.__module__# 类定义所在的模块 8 类名.__class__# 实例对应的类(仅新式类中)
!!!补充说明:从代码级别看面向对象 !!!
1 #1、在没有学习类这个概念时,数据与功能是分离的 2 def exc1(host,port,db,charset): 3 conn=connect(host,port,db,charset) 4 conn.execute(sql) 5 return xxx 6 7 8 def exc2(host,port,db,charset,proc_name) 9 conn=connect(host,port,db,charset) 10 conn.call_proc(sql) 11 return xxx 12 13 #每次调用都需要重复传入一堆参数 14 exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;') 15 exc2('127.0.0.1',3306,'db1','utf8','存储过程的名字') 16 17 18 19 20 #2、我们能想到的解决方法是,把这些变量都定义成全局变量 21 HOST=‘127.0.0.1’ 22 PORT=3306 23 DB=‘db1’ 24 CHARSET=‘utf8’ 25 26 def exc1(host,port,db,charset): 27 conn=connect(host,port,db,charset) 28 conn.execute(sql) 29 return xxx 30 31 32 def exc2(host,port,db,charset,proc_name) 33 conn=connect(host,port,db,charset) 34 conn.call_proc(sql) 35 return xxx 36 37 exc1(HOST,PORT,DB,CHARSET,'select * from tb1;') 38 exc2(HOST,PORT,DB,CHARSET,'存储过程的名字') 39 40 41 #3、但是2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量,这些全局变量并没有做任何区分,即能够被所有功能使用,然而事实上只有HOST,PORT,DB,CHARSET是给exc1和exc2这两个功能用的。言外之意:我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了 42 43 class MySQLHandler: 44 def __init__(self,host,port,db,charset='utf8'): 45 self.host=host 46 self.port=port 47 self.db=db 48 self.charset=charset 49 def exc1(self,sql): 50 conn=connect(self.host,self.port,self.db,self.charset) 51 res=conn.execute(sql) 52 return res 53 54 55 def exc2(self,sql): 56 conn=connect(self.host,self.port,self.db,self.charset) 57 res=conn.call_proc(sql) 58 return res 59 60 61 obj=MySQLHandler('127.0.0.1',3306,'db1') 62 obj.exc1('select * from tb1;') 63 obj.exc2('存储过程的名字') 64 65 66 #改进 67 class MySQLHandler: 68 def __init__(self,host,port,db,charset='utf8'): 69 self.host=host 70 self.port=port 71 self.db=db 72 self.charset=charset 73 self.conn=connect(self.host,self.port,self.db,self.charset) 74 def exc1(self,sql): 75 return self.conn.execute(sql) 76 77 def exc2(self,sql): 78 return self.conn.call_proc(sql) 79 80 81 obj=MySQLHandler('127.0.0.1',3306,'db1') 82 obj.exc1('select * from tb1;') 83 obj.exc2('存储过程的名字')
三、属性查找
1. 类的数据属性是所有对象共享的
2. 类的函数属性是绑定给对象用的
1 #类的数据属性是所有对象共享的,id都一样 2 print(id(OldboyStudent.school)) 3 4 print(id(s1.school)) 5 print(id(s2.school)) 6 print(id(s3.school)) 7 8 ''' 9 4377347328 10 4377347328 11 4377347328 12 4377347328 13 ''' 14 15 16 17 #类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样 18 #ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准 19 print(OldboyStudent.learn) 20 print(s1.learn) 21 print(s2.learn) 22 print(s3.learn) 23 ''' 24 <function OldboyStudent.learn at 0x1021329d8> 25 <bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>> 26 <bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>> 27 <bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>> 28 '''
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
1 class Student: 2 school='student' 3 count=0 4 5 def __init__(self, x, y, z): #会在调用类时自动触发 6 self.name = x #stu1.name='耗哥' 7 self.age = y #stu1.age=18 8 self.sex = z #stu1.sex='male' 9 Student.count+=1 10 11 def choose_course(self): 12 print('is choosing course') 13 14 15 # 先从对象自己的名称空间找,没有则去类中找,如果类也没有则报错 16 stu1=Student('耗哥',18,'male') 17 stu2=Student('猪哥',17,'male') 18 stu3=Student('帅翔',19,'female') 19 20 print(Student.count) 21 print(stu1.name) 22 print(stu2.count) 23 print(stu3.count)
四、绑定到对象的方法的特殊之处
1 #改写 2 class OldboyStudent: 3 school='oldboy' 4 def __init__(self,name,age,sex): 5 self.name=name 6 self.age=age 7 self.sex=sex 8 def learn(self): 9 print('%s is learning' %self.name) #新增self.name 10 11 def eat(self): 12 print('%s is eating' %self.name) 13 14 def sleep(self): 15 print('%s is sleeping' %self.name) 16 17 18 s1=OldboyStudent('李坦克','男',18) 19 s2=OldboyStudent('王大炮','女',38) 20 s3=OldboyStudent('牛榴弹','男',78)
4.1-类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数
OldboyStudent.learn(s1) #李坦克 is learning OldboyStudent.learn(s2) #王大炮 is learning OldboyStudent.learn(s3) #牛榴弹 is learning
4.2-类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法
强调:绑定到对象的方法的特殊之处在于,绑定给谁就由谁来调用,谁来调用,就会将‘谁’本身当做第一个参数传给方法,即自动传值(方法__init__也是一样的道理)
s1.learn() #等同于OldboyStudent.learn(s1) s2.learn() #等同于OldboyStudent.learn(s2) s3.learn() #等同于OldboyStudent.learn(s3)
注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
4.3-类即类型
提示:python的class术语与c++有一定区别,与 Modula-3更像。
python中一切皆为对象,且python3中类与类型是一个概念,类型就是类
1 #类型dict就是类dict 2 >>> list 3 <class 'list'> 4 5 #实例化的到3个对象l1,l2,l3 6 >>> l1=list() 7 >>> l2=list() 8 >>> l3=list() 9 10 #三个对象都有绑定方法append,是相同的功能,但内存地址不同 11 >>> l1.append 12 <built-in method append of list object at 0x10b482b48> 13 >>> l2.append 14 <built-in method append of list object at 0x10b482b88> 15 >>> l3.append 16 <built-in method append of list object at 0x10b482bc8> 17 18 #操作绑定方法l1.append(3),就是在往l1添加3,绝对不会将3添加到l2或l3 19 >>> l1.append(3) 20 >>> l1 21 [3] 22 >>> l2 23 [] 24 >>> l3 25 [] 26 #调用类list.append(l3,111)等同于l3.append(111) 27 >>> list.append(l3,111) #l3.append(111) 28 >>> l3 29 [111]
五、对象之间的交互
1 class Garen: #定义英雄盖伦的类,不同的玩家可以用它实例出自己英雄; 2 camp='Demacia' #所有玩家的英雄(盖伦)的阵营都是Demacia; 3 def __init__(self,nickname,aggressivity=58,life_value=455): #英雄的初始攻击力58...; 4 self.nickname=nickname #为自己的盖伦起个别名; 5 self.aggressivity=aggressivity #英雄都有自己的攻击力; 6 self.life_value=life_value #英雄都有自己的生命值; 7 def attack(self,enemy): #普通攻击技能,enemy是敌人; 8 enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
我们可以仿照garen类再创建一个Riven类
1 class Riven: 2 camp='Noxus' #所有玩家的英雄(锐雯)的阵营都是Noxus; 3 def __init__(self,nickname,aggressivity=54,life_value=414): #英雄的初始攻击力54; 4 self.nickname=nickname #为自己的锐雯起个别名; 5 self.aggressivity=aggressivity #英雄都有自己的攻击力; 6 self.life_value=life_value #英雄都有自己的生命值; 7 def attack(self,enemy): #普通攻击技能,enemy是敌人; 8 enemy.life_value-=self.aggressivity #根据自己的攻击力,攻击敌人就减掉敌人的生命值。
六、继承
6.1-什么是继承
继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。
子类会“”遗传”父类的属性,从而解决代码重用问题(比如练习7中Garen与Riven类有很多冗余的代码)
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
6.2-查看继承
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 (<class '__main__.ParentClass1'>,) >>> SubClass2.__bases__ (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
6.3 -经典类与新式类
1.只有在python2中才分新式类和经典类,python3中统一都是新式类 2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类 3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类 3.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类 #关于新式类与经典类的区别,我们稍后讨论
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__ (<class 'object'>,) >>> ParentClass2.__bases__ (<class 'object'>,)
6.4 - 继承与抽象(先抽象再继承)
继承描述的是子类与父类之间的关系,是一种什么是什么的关系。要找出这种关系,必须先抽象再继承 抽象即抽取类似或者说比较像的部分。 抽象分成两个层次: 1.将奥巴马和梅西这俩对象比较像的部分抽取成类; 2.将人,猪,狗这三个类比较像的部分抽取成父类。 抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度) 继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类