面向对象

概念

谈到面向对象,很多程序员会抛出三个词:封装、继承和多态;或者说抽象、一切都是对象之类的话,然而这会让初学者更加疑惑。下面我想通过一个小例子来说明一下

面向对象一般是和面向过程做对比的,下面是一个简单功能的面向过程和面向对象形式

1
2
3
4
5
6
7
a = 2
# 要实现 a+2

# 面向过程的方法
sum(a, 3)
# 面向对象的方法(这条在R中还不可执行,只是类似这个意思)
a.sum(3)

差别一:侧重点不同

我们可以把调用函数理解为主谓宾的结构

  • 面向过程就是我们平时调用的函数,通常是这种形式:动作(主语,宾语) ,动作是主要的,主语和宾语分别作为参数传入进行计算
  • 而面向对象的重点则在于这个主语,是这个主语调用了特定的动作,再把宾语作为参数实现运算。

差别二:定义方式不同

  • 面向过程只要定义一个函数sum,指定它有两个参数,然后return二者的和即可
  • 面向对象则复杂一些。首先要定义一个类,在这个类中定义这个类别可以使用的各种方法(可以理解为函数)。然后产生一个类的实例,用这个实例调用这个方法完成计算
  • 举一个通俗的例子,这里的类和我们生活中的类没有什么区别。比如定义一个“鸟”类,再指定这个类有“飞翔”这个方法(即函数、动作)。然后我们抓到一只具体的鸟,这只鸟就是“鸟”类的一个实例,它就可以调用“飞翔”这个方法。如果拉过来一只狗,它不属于“鸟”类,就不能调用“飞翔”这个方法。狗可能属于“狗”类,经过定义就可以调用“叫”这个方法。
  • 在sum的例子中,a是一个整数,它是”整数“这个类的一个实例,你也可以定义b=3则是另外一个实例。它们就具备”整数“这个类可以使用的所有方法,比如加一个数,减一个数之类的。而如果 c=”hello”,它就属于“字符串”这个类,可以调用字符串的方法
  • 这样做有一个好处,相当于自动对变量进行分类,每一个变量都是一个对象,属于一个特定的类,它可以调用的方法也都是固定的,这样我们拿到一个字符串,就知道可以对它进行哪些处理。

差异三:调用

在实际使用中,如果要对许多对象进行相同的操作

  • 面向过程就是定义一个函数,或者许多函数,然后对每一个对象都套用这个函数即可实现
  • 而面向对象则是定义一个类,指定类的方法,然后每一个对象创建为这个类的实例,实例再调用方法进行计算

当这种需求多起来

  • 面向过程会出现这种情况,今天对A类数据创建了一些函数,明天对B类数据创建了另外一些函数,可能第二天的函数还调用了第一天的函数,函数全部放在一起,拿到数据看哪个好用就拿来用,这样容易乱套。而且每次拿到一个数据都要审视一下之前的这个函数可以处理这个数据吗,处理完可以得到想要的结果吗
  • 而面向对象则每一个类型的对象的方法都放在一起进行管理,都在这个类之下进行定义,这样我们只要看这个对象是这个类的,就自然可以调用这个类的方法。
  • 有的人可能会想,这样面向对象岂不是每一个类都要重新实现那些函数,本来可以调用之前定义好了的函数的。然而类的继承很好地解决了这个问题。比如定义了“鸟”和“狗”类,它们都有“吃”这个方法,但是鸟类可以“飞”,狗类可以“跑”,那么“吃”这个方法其实不需要在两个类中分别定义一次。因为可以先定义一个”动物”类,这个类有“吃”“睡”等方法,然后“鸟”和“狗”类分别从这个类继承下来,便获得了“动物”这个类的所有方法,然后它们可以在自己的类中定义自己特定的方法“飞”“跑”等

数据封装

当我们要定义很多函数要调用相同参数时,面向对象的使用会明显更方便,我们看下面一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person: 
'''细心的读者可能注意到这里的定义和前面形式不一样,没有object,在这里说明一下
python对象系统分为经典类和新式类,py2中不加object默认经典类,加了是新式类
py3中则默认不加也是新式类,所以以后我们定义时都不加object(虽然加也是一样的)'''

def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height

def description(self):
print("Description: name is %s, age is %s, height is %s"% (self.name,self.age,self.height))

def my(self):
print("My name is %s, and height is %s, and age is %s. "% (self.name,self.height,self.age))

调用实例

1
2
3
4
5
6
7
8
9
10
bob = Person("Bob",24,170)
mary = Person("Mary",10,160)
bob.description()
bob.my()
mary.description()
mary.my()
# Description: name is Bob, age is 24, height is 170
# My name is Bob, and height is 170, and age is 24.
# Description: name is Mary, age is 10, height is 160
# My name is Mary, and height is 160, and age is 10.

对象操作

有时候我们要实现一个对象的动态过程,即一个对象做了什么,再做什么,面向过程就很难实现了,看下面一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Individual:

def __init__(self,full=10): # 默认值则创建实例时可以不用传入
self.full = full

def fruit(self):
self.full += 1
return self # 使作用完这个方法后还可以调用其他方法

def meat(self):
self.full += 2
return self

def run(self):
self.full -= 3
return self

上面定义了一个个体的类,他有一个属性可以理解成能量,他的方法有吃水果、吃肉、跑步,每一个方法调用后都会改变他的能量值。下面创建一个实例来调用

1
2
3
4
5
6
7
8
anyone = Individual()
anyone.full # 10
anyone.meat() # 让他吃一次肉
anyone.full # 12
anyone.run() # 让他跑一次
anyone.full # 9
anyone.meat().run() # 吃完再跑
anyone.full # 8

我们还可以进行区分,定义两个类,继承上面的类

1
2
3
4
5
6
7
8
9
10
11
class Boy(Individual):
'''定义男生一天吃肉跑,再吃再跑再吃'''
def oneday(self):
self.meat().meat().run().meat().fruit().run().meat()
print(self.full)

class Girl(Individual):
'''定义女生每天吃的少但是不跑'''
def oneday(self):
self.meat().fruit()
print(self.full)

用面向对象很多时候调用方法的时候都非常符合人的思维习惯,如上面的做完一件事再做一件事就用.连着写下去。这样的类还有很多,比如下面的字符串例子

  • 面向对象 a.casefold().join(['a','b']).strip().find('c')
  • 面向过程 find(strip(join(casefold(a),['a','b'])),'c')

总结面向对象编程的好处

  • 将对象进行分类,分别封装它们的数据和可以调用的方法,方便了函数、变量、数据的管理,方便方法的调用(减少重复参数等),尤其是在编写大型程序时更有帮助。
  • 用面向对象的编程可以把变量当成对象进行操作,让编程思路更加清晰简洁,而且减少了很多冗余变量的出现