一、面向对象和面向过程
1、面向过程
1.1 面向过程:核心是过程二字,过程指的是解决问题的步骤,好比如设计一条流水线,是一种机械式的思维方式。
程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程在执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。对应到编程上,设计者用一个main函数概括出整个应用程序须要做的事,而main函数由对一系列子函数的调用组成。对于main中的每个子函数,都又能够再被精炼成更小的函数。反复这个过程,就能够完毕一个过程式的设计。其特征是以函数为中心,用函数来作为划分程序的基本单位,数据在过程式设计中往往处于从属的位置。
1.2 优点:
易于理解,将问题逐步细化的设计方法和大多数人的思维方式接近;
极大降低了写程序的复杂读,只要顺着要执行的步骤,写代码即可。
1.3 缺点:
一套流水线或者流程就是用来解决一个问题,要求设计者一开始就要对要解决的问题有一定的了解。在问题比较复杂的时候,这一点比较困难。开发一个系统的过程往往也是一个对系统不断了解和学习的过程,而过程式的设计方法忽略了这一点。
在面向过程式设计的语言中,一般都既有定义数据的元素,如C语言中的结构,也有定义操作的元素,如C语言中的函数。这样做的结果是数据和操作被分离开,easy导致对一种数据的操作分布在整个程序的各个角落,而一个操作也可能会用到非常多种数据,在这样的情况下,对数据和操作的不论什么一部分进行改动都会变得非常困难。
在面向过程式设计中,main()函数处于一个非常重要的地位。设计者正是在main()函数中,对整个系统进行一个概括的描写叙述,再以此为起点,逐步细化出整个应用程序。然而,这样做的一个后果,是easy将一些较外延和易变化的逻辑(比方用户交互)同程序的核心逻辑混淆在一起
1.4 应用场景
一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便。
2、面向对象
2.1 面向对象
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。就好比女娲造人,不管男人,女人都具人这个对象,他们共同特征(对象的属性)都具有一颗头、两只胳膊、两条腿······还有一些共同技能(对象的方法),比如吃饭、走路、说话······,对于男人和女人这些基本特征和技能都可以从人这个对象中继承而来,然后再给每个不同的人赋予不同的属性(比如高矮胖瘦)以及不同的技能(比如唱跳rap),这样女娲在造人时就可以减少很多重复工作,也能造出各种各样的人来。
面向对象是一种自下而上的程序设计方法。不像过程式设计那样一开始就要用main概括出整个程序,面向对象设计往往从问题的一部分着手,一点一点地构建出整个程序。面向对象设计以数据为中心,类作为表现数据的工具,是划分程序的基本单位。而函数在面向对象设计中成为了类的接口。
面向对象设计自下而上的特性,同意开发人员从问题的局部开始始,在开发过程中逐步加深对系统的理解。这些新的理解以及开发中遇到的需求变化,都会再作用到系统开发本身,形成一种螺旋式的开发方式。(在这样的开发方式中,对于已有的代码,常须要运用Refactoring技术来做代码重构以体现系统的变化。)
2.2 优点
解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
便于程序后期的维护、更新,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
2.3 缺点
可控性差,无法相面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
2.4 应用场景
需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
二、面向对象名词解释
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- 方法:类中定义的函数。
- 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
- 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- 局部变量:定义在方法中的变量,只作用于当前实例的类。
- 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
- 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
- 实例化:创建一个类的实例,类的具体对象。
- 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
三、面向对象实例详讲
3.1 声明一个类和实例化
类是现实生活中一类具有相同属性和方法的对象的集合,当一个类定义完成后就产生了一个类对象。类对象支持两种操作:引用和实例化。引用操作是通过类对象去调用类中的属性或者方法,而实例化是产生出一个类对象的实例,称作实例对象。类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。
1 class Myclass:# 声明一个类 2 n = 12345 # 定义了一个类属性 3 def func(self): # 定义了一个方法 4 print('hello world!') 5 x = Myclass() # 类的实例化 6 '''访问类的属性和方法''' 7 print(x.n) 8 x.func() 9 # 输出结果 10 12345 11 hello world!
3.2 定义类的属性
在类中定义的属性可以分为两种,公有属性和私有属性:
对于公有属性,在定义类后,就可以产生实例化对象了,然后就能通过实例化对象来读取属性。
对于类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
1 class Myclass: 2 __name1 = 'Jack' # 私有属性 3 name2 = 'Michael' # 公有属性 4 def func(self): 5 print(self.__name1) 6 7 P = Myclass() 8 print(P.name2) 9 P.func()
执行结果
1 Michael 2 Jack
私有属性不能直接在类外通过对象名访问,如:当执行print(P.__name1)时,这段程序会报错
print(P.__name1) AttributeError: 'Myclass' object has no attribute '__name1'
提示找不到该属性,因为私有属性是不能够在类外通过对象名来进行访问的。在Python中没有像C++中public和private这些关键字来区别公有属性和私有属性,它是以属性命名方式来区分,如果在属性名前面加了2个下划线'__',则表明该属性是私有属性,否则为公有属性(方法也是一样,方法名前面加了2个下划线的话表示该方法是私有的,否则为公有的)。
3.3 类的内置方法
在Python中有一些内置的方法,这些方法命名都有比较特殊的地方(其方法名以2个下划线开始然后以2个下划线结束)。类中最常用的就是构造方法和析构方法。
构造方法__init__(self,....):在生成对象时调用,可以用来进行一些初始化操作,不需要显示去调用,系统会默认去执行。构造方法支持重载,如果用户自己没有重新定义构造方法,系统就自动执行默认的构造方法。
__init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。
1 class Info: 2 country = 'CHINA' 3 def __init__(self,name,age,job): 4 self.name = name 5 self.age = age 6 self.job = job 7 def show_info(self): 8 print('------info------') 9 print('COUNTRY:%s NAME:%s AGE:%s JOB:%s' % (self.country, self.name, self.age, self.job)) 10 p = Info('alex','23','IT') 11 p.show_info()
执行结果:
1 ------info------ 2 COUNTRY:CHINA NAME:alex AGE:23 JOB:IT
析构方法__del__(self):在释放对象时调用,支持重载,可以在里面进行一些释放资源的操作,不需要显示调用。
1 class Dog(): 2 def __init__(self,name): 3 self.name = name 4 def bark(self): 5 print('Wang Wang Wang!') 6 def __del__(self): 7 print('Dog[%s] has died' % self.name) 8 dog1 = Dog('旺财') 9 dog2 = Dog('小强') 10 dog1.bark() 11 dog2.bark() 12 del dog2
执行结果:
Wang Wang Wang!
Wang Wang Wang!
Dog[小强] has died
Dog[旺财] has died
执行代码del dog2,dog2这个对象被释放掉了,故执行dog2.__del__(),在程序执行完毕,dog1这个对象也会被释放掉,执行dog1.__del__()。
常用内置方法
内置方法 | 说明 |
__init__(self,...) | 初始化对象,在创建新对象时调用 |
__del__(self) | 释放对象,在对象被删除之前调用 |
__new__(cls,*args,**kwd) | 实例的生成操作 |
__str__(self) | 在使用print语句时被调用 |
__getitem__(self,key) | 获取序列的索引key对应的值,等价于seq[key] |
__len__(self) | 在调用内联函数len()时被调用 |
__cmp__(stc,dst) | 比较两个对象src和dst |
__getattr__(s,name) | 获取属性的值 |
__delattr__(s,name) | 删除name属性 |
__setattr__(s,name,value) | 设置属性的值 |
__getattribute__() | __getattribute__()功能与__getattr__()类似 |
__gt__(self,other) | 判断self对象是否大于other对象 |
__lt__(slef,other) | 判断self对象是否小于other对象 |
__ge__(slef,other | 判断self对象是否大于或者等于other对象 |
__le__(slef,other) | 判断self对象是否小于或者等于other对象 |
__eq__(slef,other) | 判断self对象是否等于other对象 |
__call__(self,*args) | 把实例对象作为函数调用 |
四、类属性、实例属性、类方法、实例方法以及静态方法
4.1 类属性和实例属性
类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似。对于公有的类属性,在类外可以通过类对象和实例对象访问。
1 class people: 2 name = 'jack' #公有的类属性 3 __age = 12 #私有的类属性 4 5 p = people() 6 p.job = 'IT' 7 ''' 这个实例属性是实例对象所特有的,类对象并不拥有它''' 8 print (p.name) #正确 9 print (people.name) #正确 10 print (p.__age) #错误,不能在类外通过实例对象访问私有的类属性 11 print (people.__age) #错误,不能在类外通过类对象访问私有的类属性 12 print(p.job) #正确 13 print(people.job) # 错误 类对象people并不拥有它
如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。
1 class Dog: 2 color = 'Blue' 3 sex = 'M' 4 5 dog = Dog() 6 print(dog.color) 7 print(Dog.color) 8 dog.color = 'Yellow'# 实例属性屏蔽掉类属性 9 print(dog.color) 10 print(Dog.color) 11 del dog.color # 删除实例对象属性 12 print(dog.color) # 程序查找属性时,先在实例属性中寻找,找不到再去类属性寻找
执行结果:
1 Blue 2 Blue 3 Yellow 4 Blue 5 Blue
4.2 类方法、实例方法和静态方法的区别
类方法:是类对象所拥有的方法,需要用修饰器"@classmethod"来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以"cls"作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问。
1 class Dog: 2 name = '旺财' 3 #类方法需要用classmethod修饰 4 @classmethod 5 def bark(cls): 6 print('dog[%s] is barking' % cls.name) 7 # 实例方法 8 def eat(self): 9 print('dog[%s] is eating' % self.name) 10 Dog.bark() 11 #Dog.eat()
类方法还有一个用途就是可以对类属性进行修改:
1 class Fruit: 2 variety = 'apple' 3 @classmethod 4 def set_variety(cls,var): 5 cls.variety = var 6 fruit = Fruit 7 print(fruit.variety) 8 Fruit.set_variety('banala') 9 print(fruit.variety)
执行结果:
apple
banala
实例方法:在类中最常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第一个参数,一般以名为'self'的变量作为第一个参数(当然可以以其他名称的变量作为第一个参数)。在类外实例方法只能通过实例对象去调用,不能通过其他方式去调用。
1 class people: 2 country = 'china' 3 #实例方法 4 def getCountry(self): 5 return self.country 6 7 p = people() 8 print (p.getCountry()) #正确,可以用过实例对象引用 9 print (people.getCountry()) #错误,不能通过类对象引用实例方法
静态方法:需要通过修饰器"@staticmethod"来进行修饰,静态方法不需要多定义参数。
1 class people: 2 country = 'china' 3 4 @staticmethod 5 #静态方法 6 def getCountry(): 7 return people.country 8 9 10 print people.getCountry()
对于类属性和实例属性,如果在类方法中引用某个属性,该属性必定是类属性,而如果在实例方法中引用某个属性(不作更改),并且存在同名的类属性,此时若实例对象有该名称的实例属性,则实例属性会屏蔽类属性,即引用的是实例属性,若实例对象没有该名称的实例属性,则引用的是类属性;如果在实例方法更改某个属性,并且存在同名的类属性,此时若实例对象有该名称的实例属性,则修改的是实例属性,若实例对象没有该名称的实例属性,则会创建一个同名称的实例属性。想要修改类属性,如果在类外,可以通过类对象修改,如果在类里面,只有在类方法中进行修改。
从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用