2016年9月

如何选择面向对象编程与面向过程编程?

可能我们脑海里天生就有面向过程编程的思想,大致就是把一件事情分成几步,一步一步地完成。不假思索写出来的代码,自然就是面向过程的。

对于面向过程编程,变量(数据)与函数(行为)是分开的,它们都被安排在某个模块(文件)里。模块就好像是一个对象(类),因此,面向过程编程就好比在一个默认的类中编程,而这个类往往不被注意到。

对象同时包含数据和行为。如果只考虑数据,使用列表、字典等 Python 数据结构即可。如果只关注行为而不存储任何数据,那一个简单的函数更合适。

听起来有点枯燥,我们通过例子来理解。

计算多边形的周长

面向过程

通常来说,我们会以一个存储着各个顶点的列表来代表多边形。每个顶点以一个二维元组(x, y)建模,该元组描述顶点的位置坐标。

square = [(1, 1), (1, 2), (2, 2), (2, 1)]

计算周长,我们只需要计算相邻两个顶点之间的距离,然后加起来。因此需要一个计算两点之间的距离的函数。编码如下:

import math

def distance(p1, p2):
    return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)

def perimeter(polygon):
    perimeter = 0
    points = polygon + [polygon[0]]
    for i in range(len(polygon)):
        perimeter += distance(points[i], points[i+1])
    return perimeter

这是面向过程的解决办法,调用方法如下:

square = [(1, 1), (1, 2), (2, 2), (2, 1)]
perimeter(square)

4.0

面向对象

上面的函数已经很简洁明了,但不妨来看看面向对象的版本是怎样的。

import math

class Point(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def distance(self, p2):
        return math.sqrt((self.x - p2.x) ** 2 + (self.y - p2.y) ** 2)

class Polygon(object):

    def __init__(self):
        self.vertices = []

    def add_point(self, point):
        self.vertices.append(point)

    def perimeter(self):
        perimeter = 0
        points = self.vertices + [self.vertices[0]]
        for i in range(len(self.vertices)):
            perimeter += points[i].distance(points[i+1])
        return perimeter

这段代码比上面的面向过程版本长了很多,几乎是上面的两倍。我们先来看看它是如何调用的。

square = Polygon()
square.add_point(Point(1, 1))
square.add_point(Point(1, 2))
square.add_point(Point(2, 2))
square.add_point(Point(2, 1))
square.perimeter()

4.0

是不是通俗易懂,它比函数版本的代码更易读。对于函数版本的代码,我们可能需要写更多的文档以表明它的用法。

我们还可以对 Polygon 类进行优化,使它更容易使用,让它像函数版本那样接受一个元组的列表。

def __init__(self, points=[]):
    self.vertices = []
    for point in points:
        if isinstance(point, tuple):
            point = Point(*point)
        self.vertices.append(point)

如果我们要为多边形增加一些特征,如 color 或者 texture,将这些数据封装进类会变得更有意义。一般来讲,拥有复杂的数据集,需要对它进行特定操作,这时使用具有属性和方法的类会很有帮助。它不仅可以使代码易读,还可以使逻辑紧凑。

如何选择

在一个具体的场景里,如果只需要计算多边形的周长,一个函数可能是最简单、最快的开发方法。但是如果我们需要对多边形进行多种处理(计算周长、面积、和其它多边形的交点等),那么我们需要一个对象来满足这些需求。所以,对于较复杂的问题,一般采用面向对象编程。