活在当下的程序员应该都听过“面向对象编程”一词,也常常有人问能不能用一句话解释下什么是“面向对象编程”,咱们先来看看比较正式的说法。html
把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象概括为类(class),经过类的封装(encapsulation)隐藏内部细节,经过继承(inheritance)实现类的特化(specialization)和泛化(generalization),经过多态(polymorphism)实现基于对象类型的动态分派。git
这样一说是否是更不明白了。因此咱们仍是看看更通俗易懂的说法,下面这段内容来自于知乎。程序员
说明:以上的内容来自于网络,不表明做者本人的观点和见解,与做者本人立场无关,相关责任不禁做者承担。github
以前咱们说过“程序是指令的集合”,咱们在程序中书写的语句在执行时会变成一条或多条指令而后由CPU去执行。固然为了简化程序的设计,咱们引入了函数的概念,把相对独立且常常重复使用的代码放置到函数中,在须要使用这些功能的时候只要调用函数便可;若是一个函数的功能过于复杂和臃肿,咱们又能够进一步将函数继续切分为子函数来下降系统的复杂性。可是说了这么多,不知道你们是否发现,所谓编程就是程序员按照计算机的工做方式控制计算机完成各类任务。可是,计算机的工做方式与正常人类的思惟模式是不一样的,若是编程就必须得抛弃人类正常的思惟方式去迎合计算机,编程的乐趣就少了不少,“每一个人都应该学习编程”这样的豪言壮语就只能说说而已。固然,这些还不是最重要的,最重要的是当咱们须要开发一个复杂的系统时,代码的复杂性会让开发和维护工做都变得举步维艰,因此在上世纪60年代末期,“软件危机”、“软件工程”等一系列的概念开始在行业中出现。编程
固然,程序员圈子内的人都知道,现实中并无解决上面所说的这些问题的“银弹”,真正让软件开发者看到但愿的是上世纪70年代诞生的Smalltalk编程语言中引入的面向对象的编程思想(面向对象编程的雏形能够追溯到更早期的Simula语言)。按照这种编程理念,程序中的数据和操做数据的函数是一个逻辑上的总体,咱们称之为“对象”,而咱们解决问题的方式就是建立出须要的对象并向对象发出各类各样的消息,多个对象的协同工做最终可让咱们构造出复杂的系统来解决现实中的问题。bash
说明:固然面向对象也不是解决软件开发中全部问题的最后的“银弹”,因此今天的高级程序设计语言几乎都提供了对多种编程范式的支持,Python也不例外。网络
简单的说,类是对象的蓝图和模板,而对象是类的实例。这个解释虽然有点像用概念在解释概念,可是从这句话咱们至少能够看出,类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每一个对象都是独一无二的,并且对象必定属于某个类(型)。当咱们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就能够定义出一个叫作“类”的东西。数据结构
在Python中可使用class
关键字定义类,而后在类中经过以前学习过的函数来定义方法,这样就能够将对象的动态特征描述出来,代码以下所示。编程语言
class Student(object):
# __init__是一个特殊方法用于在建立对象时进行初始化操做
# 经过这个方法咱们能够为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))
# PEP 8要求标识符的名字用全小写多个单词用下划线链接
# 可是不少程序员和公司更倾向于使用驼峰命名法(驼峰标识)
def watch_av(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
print('%s正在观看岛国爱情动做片.' % self.name)复制代码
说明:写在类中的函数,咱们一般称之为(对象的)方法,这些方法就是对象能够接收的消息。函数
建立和使用对象
当咱们定义好一个类以后,能够经过下面的方式来建立对象并给对象发消息。
def main(): # 建立学生对象并指定姓名和年龄 stu1 = Student('骆昊', 38) # 给对象发study消息 stu1.study('Python程序设计') # 给对象发watch_av消息 stu1.watch_av() stu2 = Student('王大锤', 15) stu2.study('思想品德') stu2.watch_av() if __name__ == '__main__': main() 复制代码
对于上面的代码,有C++、Java、C#等编程经验的程序员可能会问,咱们给Student
对象绑定的name
和age
属性到底具备怎样的访问权限(也称为可见性)。由于在不少面向对象编程语言中,咱们一般会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不容许外界访问,而对象的方法一般都是公开的(public),由于公开的方法就是对象可以接受的消息。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,若是但愿属性是私有的,在给属性命名时能够用两个下划线做为开头,下面的代码能够验证这一点。
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)
if __name__ == "__main__":
main()复制代码
可是,Python并无从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来“妨碍”对它们的访问,事实上若是你知道更换名字的规则仍然能够访问到它们,下面的代码就能够验证这一点。之因此这样设定,能够用这样一句名言加以解释,就是“We are all consenting adults here”。由于绝大多数程序员都认为开放比封闭要好,并且程序员要本身为本身的行为负责。
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__ == "__main__":
main()复制代码
在实际开发中,咱们并不建议将属性设置为私有的,由于这会致使子类没法访问(后面会讲到)。因此大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类以外的代码在访问这样的属性时应该要保持慎重。这种作法并非语法上的规则,单下划线开头的属性和方法外界仍然是能够访问的,因此更多的时候它是一种暗示或隐喻,关于这一点能够看看个人《Python - 那些年咱们踩过的那些坑》文章中的讲解。
面向对象有三大支柱:封装、继承和多态。后面两个概念在下一个章节中进行详细的说明,这里咱们先说一下什么是封装。我本身对封装的理解是“隐藏一切能够隐藏的实现细节,只向外界暴露(提供)简单的编程接口”。咱们在类中定义的方法其实就是把数据和对数据的操做封装起来了,在咱们建立了对象以后,只须要给对象发送一个消息(调用方法)就能够执行方法中的代码,也就是说咱们只须要知道方法的名字和传入的参数(方法的外部视图),而不须要知道方法内部的实现细节(方法的内部视图)。
class Clock(object):
""" 数字时钟 """
def __init__(self, hour=0, minute=0, second=0):
""" 构造器 :param hour: 时 :param minute: 分 :param second: 秒 """
self._hour = hour
self._minute = minute
self._second = second
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def __str__(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
clock = Clock(23, 59, 58)
while True:
print(clock)
sleep(1)
clock.run()
if __name__ == '__main__':
main()复制代码
from math import sqrt
class Point(object):
def __init__(self, x=0, y=0):
""" 构造器 :param x: 横坐标 :param y: 纵坐标 """
self.x = x
self.y = y
def move_to(self, x, y):
""" 移动到指定位置 :param x: 新的横坐标 "param y: 新的纵坐标
""" self.x = x self.y = y def move_by(self, dx, dy): """
移动指定的增量
:param dx: 横坐标的增量
"param dy: 纵坐标的增量 """
self.x += dx
self.y += dy
def distance_to(self, other):
""" 计算与另外一个点的距离 :param other: 另外一个点 """
dx = self.x - other.x
dy = self.y - other.y
return sqrt(dx ** 2 + dy ** 2)
def __str__(self):
return '(%s, %s)' % (str(self.x), str(self.y))
def main():
p1 = Point(3, 5)
p2 = Point()
print(p1)
print(p2)
p2.move_by(-1, 2)
print(p2)
print(p1.distance_to(p2))
if __name__ == '__main__':
main()复制代码
说明:本章中的插图来自于Grady Booch等著做的《面向对象分析与设计》一书,该书是讲解面向对象编程的经典著做,有兴趣的读者能够购买和阅读这本书来了解更多的面向对象的相关知识。