"把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。"

    这样一说是不是更不明白了。所以我们还是看看更通俗易懂的说法,下面这段内容来自于。

    之前我们说过"程序是指令的集合",我们在程序中书写的语句在执行时会变成一条或多条指令然后由CPU去执行。当然为了简化程序的设计,我们引入了函数的概念,把相对独立且经常重复使用的代码放置到函数中,在需要使用这些功能的时候只要调用函数即可;如果一个函数的功能过于复杂和臃肿,我们又可以进一步将函数继续切分为子函数来降低系统的复杂性。但是说了这么多,不知道大家是否发现,所谓编程就是程序员按照计算机的工作方式控制计算机完成各种任务。但是,计算机的工作方式与正常人类的思维模式是不同的,如果编程就必须得抛弃人类正常的思维方式去迎合计算机,编程的乐趣就少了很多,"每个人都应该学习编程"这样的豪言壮语就只能说说而已。当然,这些还不是最重要的,最重要的是当我们需要开发一个复杂的系统时,代码的复杂性会让开发和维护工作都变得举步维艰,所以在上世纪60年代末期,"软件危机"、""等一系列的概念开始在行业中出现。

    简单的说,类是对象的蓝图和模板,而对象是类的实例。这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。

    Day08 - 面向对象编程基础 - 图1

    定义类

    在Python中可以使用关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下所示。

    1. def main():
    2. # 创建学生对象并指定姓名和年龄
    3. stu1 = Student('骆昊', 38)
    4. # 给对象发study消息
    5. stu1.study('Python程序设计')
    6. # 给对象发watch_av消息
    7. stu1.watch_movie()
    8. stu2 = Student('王大锤', 15)
    9. stu2.study('思想品德')
    10. stu2.watch_movie()
    11. if __name__ == '__main__':
    12. main()

    访问可见性问题

    对于上面的代码,有C++、Java、C#等编程经验的程序员可能会问,我们给Student对象绑定的nameage属性到底具有怎样的访问权限(也称为可见性)。因为在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头,下面的代码可以验证这一点。

    但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。之所以这样设定,可以用这样一句名言加以解释,就是"We are all consenting adults here"。因为绝大多数程序员都认为开放比封闭要好,而且程序员要自己为自己的行为负责。

    1. class Test:
    2. def __init__(self, foo):
    3. self.__foo = foo
    4. def __bar(self):
    5. print(self.__foo)
    6. print('__bar')
    7. def main():
    8. test = Test('hello')
    9. test._Test__bar()
    10. if __name__ == "__main__":
    11. main()

    在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻,关于这一点可以看看我的《Python - 那些年我们踩过的那些坑》文章中的讲解。

    面向对象有三大支柱:封装、继承和多态。后面两个概念在下一个章节中进行详细的说明,这里我们先说一下什么是封装。我自己对封装的理解是"隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口"。我们在类中定义的方法其实就是把数据和对数据的操作封装起来了,在我们创建了对象之后,只需要给对象发送一个消息(调用方法)就可以执行方法中的代码,也就是说我们只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。

    练习

    练习1:定义一个类描述数字时钟。

    参考答案:

    练习2:定义一个类描述平面上的点并提供移动点和计算到另一个点距离的方法。

    1. from math import sqrt
    2. class Point(object):
    3. def __init__(self, x=0, y=0):
    4. """初始化方法
    5. :param x: 横坐标
    6. :param y: 纵坐标
    7. """
    8. self.x = x
    9. self.y = y
    10. def move_to(self, x, y):
    11. """移动到指定位置
    12. :param x: 新的横坐标
    13. "param y: 新的纵坐标
    14. """
    15. self.x = x
    16. self.y = y
    17. """移动指定的增量
    18. "param dy: 纵坐标的增量
    19. """
    20. self.x += dx
    21. self.y += dy
    22. def distance_to(self, other):
    23. """计算与另一个点的距离
    24. :param other: 另一个点
    25. """
    26. dx = self.x - other.x
    27. dy = self.y - other.y
    28. return sqrt(dx ** 2 + dy ** 2)
    29. def __str__(self):
    30. return '(%s, %s)' % (str(self.x), str(self.y))
    31. def main():
    32. p1 = Point(3, 5)
    33. p2 = Point()
    34. print(p1)
    35. print(p2)
    36. p2.move_by(-1, 2)
    37. print(p2)
    38. print(p1.distance_to(p2))
    39. main()