AI人工智能技术基础之面向对象二
本章学习目标
• 理解面向对象的三大特征
• 掌握继承
• 掌握多态
• 了解设计模式
上章讲解了面向对象中类与对象的基本概念,本章主要讲解面向对象的三大特征:封装性、继承性、多态性,这三者的配合使用可以增加代码的安全性、重用性以及可维护性。
11.%2 面向对象的三大特征
面向对象程序设计实际上就是对现实世界的对象进行建模操作。面向对象程序设计的特征主要可以概括为封装性、继承性和多态性,接下来针对这三种特性进行简单介绍。
1.封装
封装是面向对象程序设计的核心思想。它是指将对象的属性和行为封装起来,其载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。例如,计算机的主机是由内存条、硬盘、风扇等部件组成,生产厂家把这些部件用一个外壳封装起来组成主机,用户在使用该主机时,无需关心其内部的组成及工作原理,如图11.1所示。
图11.1 主机及组成部件
2.继承
继承是面向对象程序设计提高重用性的重要措施。它体现了特殊类与一般类之间的关系,当特殊类包含了一般类的所有属性和行为,并且特殊类还可以有自己的属性和行为时,称作特殊类继承了一般类。一般类又称为父类或基类,特殊类又称为子类或派生类。例如,已经描述了汽车模型这个类的属性和行为,如果需要描述一个小轿车类,只需让小轿车类继承汽车模型类,然后再描述小轿车类特有的属性和行为,而不必再重复描述一些在汽车模型类中已有的属性和行为,如图11.2所示。
图11.2 汽车模型与小轿车
3.多态
多态是面向对象程序设计的重要特征。生活中也常存在多态,例如,学校的下课铃声响了,这时有学生去买零食、有学生去打球、有学生在聊天。不同的人对同一事件产生了不同的行为,这就是多态在日常生活中的表现。程序中的多态是指一种行为对应着多种不同的实现。例如,在一般类中说明了一种求几何图形面积的行为,这种行为不具有具体含义,因为它并没有确定具体几何图形,又定义一些特殊类,如三角形、正方形、梯形等,它们都继承自一般类。在不同的特殊类中都继承了一般类的求面积的行为,可以根据具体的不同几何图形使用求面积公式,重新定义求面积行为的不同实现,使之分别实现求三角形、正方形、梯形等面积的功能,如图11.3所示。
图11.3 一般类与特殊类
在实际编写应用程序时,开发者需要根据具体应用设计对应的类与对象,然后在此基础上综合考虑封装性、继承性与多态性,这样编写出的程序更健壮、更易扩展。
11.%2 封装
类的封装可以隐藏类的实现细节,迫使用户只能通过方法去访问数据,这样就可以增强程序的安全性。接下来演示未使用封装可能出现的问题,如例11-1所示。
例11-1
1 class Student:
2 def __init__(self, myName, myScore):
3 self.name, self.score = myName, myScore
4 def __str__(self):
5 return '姓名:' + str(self.name) + '\t成绩:' + str(self.score)
6 s1 = Student('小千', 100)
7 print(s1)
8 s1.score = -68
9 print(s1)
运行结果如图11.4所示。
图11.4 运行结果
在例10-1中,运行结果输出的成绩为-68,在程序中不会有任何问题,但在现实生活中明显是不合理的。为了避免这种不合理的情况,这就需要用到封装,即不让使用者随意修改类的内部属性。在定义类时,可以将属性定义为私有属性,这样外界就不能随意修改。Python中通过在属性名前加两个下画线来表明私有属性,如例11-2所示。
例11-2
1 class Student:
2 def __init__(self, myName, myScore):
3 self.name, self.__score = myName, myScore
4 def __str__(self):
5 return '姓名:' + str(self.name) + '\t成绩:' + str(self.__score)
6 s1 = Student('小千', 100)
7 print(s1)
8 s1.__score = -68
9 print(s1)
运行结果如图11.5所示。
图11.5 运行结果
在例11-2中,self.name为公有属性,self.__score为私有属性,第8行试图修改私有属性的值,从程序运行结果可看出,私有属性的值并没有发生变化。
当属性设置为私有属性后,经常需要提供设置或获取属性值的两个方法供外界使用,如例11-3所示。
例11-3
1 class Student:
2 def __init__(self, myName, myScore = 0):
3 self.name = myName
4 self.setScore(myScore)
5 def setScore(self, myScore):
6 if 0 < myScore <= 100:
7 self.__score = myScore
8 else:
9 self.__score = 0
10 print(self.name,'成绩有误!')
11 def getScore(self):
12 return self.__score
13 def __str__(self):
14 return '姓名:' + str(self.name) + '\t成绩:' + str(self.__score)
15 s1 = Student('小千', -68)
16 print(s1)
17 s1.setScore(100)
18 print(s1.getScore(), s1)
运行结果如图11.6所示。
图11.6 运行结果
在例11-3中,通过setScore()方法设置私有属性self.__score的值,通过getScore()方法设置私有属性self.__score的值。
此外,私有属性在类外不能直接访问,但程序在测试或调试环境中,可以通过“对象名._类名”的方式在类外访问,如例11-4所示。
例11-4
1 class Student:
2 def __init__(self, myName, myScore = 0):
3 self.name = myName
4 self.setScore(myScore)
5 def setScore(self, myScore):
6 if 0 < myScore <= 100:
7 self.__score = myScore
8 else:
9 self.__score = 0
10 print(self.name,'成绩有误!')
11 def getScore(self):
12 return self.__score
13 def __str__(self):
14 return '姓名:' + str(self.name) + '\t成绩:' + str(self.__score)
15 s1 = Student('小千', 100)
16 print(s1)
17 s1._Student__score = 90
18 print(s1)
运行结果如图11.7所示。
图11.7 运行结果
在例11-4中,第17行通过“对象名._类名”的方式在类外修改私有属性的值。
11.%2 继承
在自然界中,继承这个概念非常普遍,例如:熊猫宝宝继承了熊猫爸爸和熊猫妈妈的特性,因此长着大大的黑眼圈和熊猫的鼻子,人们不会把它错认为是狒狒。在程序设计中,继承是面向对象的另一大特征,它用于描述类的所属关系,多个类通过继承形成一个关系体系。
11.3.1 单一继承
单一继承是指生成的派生类只有一个基类,如学生与教师都继承自人,如图11.8所示。
图11.8 单一继承
单一继承由于只有一个基类,继承关系比较简单,操作比较容易,因此使用相对较多,其语法格式如下:
class 基类名(object): # 等价于class 基类名:
类体
class 派生类名(基类名):
类体
上述代码表示派生类继承自基类,派生类可以使用基类的所有公有成员,也可以定义新的属性和方法,从而完成对基类的扩展。注意Python中所有的类都继承自object类,上章中出现的类省略了object。
接下来演示如何定义单一继承,如例11-5所示。
例11-5
1 class Person(object):
2 def __init__(self, name):
3 self.name = name
4 def show(self):
5 print('姓名:', self.name)
6 class Student(Person):
7 pass
8 s1 = Student('小千')
9 s1.show()
运行结果如图11.9所示。
图11.9 运行结果
在例11-5中,Student类继承自Person类,第9行使用派生类实例对象调用基类中的公有方法。
有读者可能会有疑问,派生类的构造方法名与基类的构造方法名相同,创建派生类实例对象如何调用构造方法,接下来演示这种情形,如例11-6所示。
例11-6
1 class Person(object):
2 def __init__(self, name):
3 print('Person类构造方法')
4 self.name = name
5 def show(self):
6 print('姓名:', self.name)
7 class Student(Person):
8 pass
9 class Teacher(Person):
10 def __init__(self, name):
11 print('Teacher类构造方法')
12 s1 = Student('小千')
13 t1 = Teacher('小锋')
运行结果如图11.10所示。
图11.10 运行结果
在例11-6中,派生类Student中没有定义构造方法,第12行创建Student类实例对象调用基类的构造方法,派生类Teacher中显式定义构造方法,第13行创建Teacher类实例对象调用自身的构造方法。
如果派生类的构造函数中需要添加参数,则可以在派生类的构造方法中调用基类的构造方法,如例11-7所示。
例11-7
1 class Person(object):
2 def __init__(self, name):
3 print('Person类构造方法')
4 self.name = name
5 def show(self):
6 print('姓名:', self.name)
7 class Student(Person):
8 def __init__(self, name, score):
9 print('Teacher类构造方法')
10 super(Student, self).__init__(name)
11 self.__score = score
12 def __str__(self):
13 return '姓名:'+ str(self.name) + ' 分数:' + str(self.__score)
14 s1 = Student('小千', 100)
15 print(s1)
运行结果如图11.11所示。
图11.11 运行结果
在例11-7中,第10行通过super()方法调用基类的构造方法,该行也可以写成如下两行中的任意一种形式,具体如下所示:
super().__init__(name)
Person.__init__(self, name)
如果派生类定义的属性和方法与基类的属性和方法同名,则派生类实例对象调用派生类中定义的属性和方法,如例11-8所示。
例11-8
1 class Person(object):
2 def __init__(self, name):
3 self.name = name
4 def show(self):
5 print('姓名:', self.name)
6 class Student(Person):
7 def __init__(self, name, score):
8 self.name, self.__score = name, score
9 def show(self):
10 print('姓名:', self.name, ' 分数:', self.__score)
11 s1 = Student('小千', 100)
12 s1.show()
运行结果如图11.12所示。
图11.12 运行结果
在例11-8中,第8行在派生类中定义与基类实例属性名相同的属性self.name,第9行在派生类中定义与基类实例方法名相同的方法show(),从运行结果可看出,派生类实例对象s1调用派生类中定义的属性与方法。
另外,读者需特别注意,基类的私有属性和方法是不会被派生类继承的,因此,派生类不能访问基类的私有成员,如例11-9所示。
例11-9
1 class Person(object):
2 def __init__(self, name):
3 self.__name = name
4 def __show(self):
5 print('姓名:', self.__name)
6 class Student(Person):
7 def test(self):
8 print(self.__name)
9 self.__show()
10 s1 = Student('小千')
11 s1.test()
运行结果如图11.13所示。
图11.13 运行结果
在例11-9中,第8行在派生类中访问基类中的私有属性__name,程序运行后报错,提示实例对象没有_Student__name属性。当一个类中定义了私有成员,Python会在该成员名前添加“_类名”,因此错误中会提示没有_Student__name属性。
同理,将上例中第8行代码注释,再次运行程序,则运行结果如图11.14所示。
图11.14 运行结果
11.3.2 多重继承
在现实生活中,在职研究生既是一名学生,又是一名职员,在职研究生同时具有学生和职员的特征,这种关系应用在面向对象程序设计上就是用多重继承来实现的,如图11.15所示。
图11.15 多重继承
多重继承指派生类可以同时继承多个基类,其语法格式如下:
class 基类1(object):
类体
class 基类2(object):
类体
class 派生类(基类1, 基类2):
类体
上述代码表示派生类继承自基类1与基类2,接下来演示如何定义多重继承,如例11-10所示。
例11-10
1 class Student(object):
2 def __init__(self, name, score):
3 self.name, self.score = name, score
4 def showStd(self):
5 print('姓名:', self.name, ' 分数:', self.score)
6 class Staff(object):
7 def __init__(self, id, salary):
8 self.id, self.salary = id, salary
9 def showStf(self):
10 print('ID:', self.id, ' 薪资:', self.salary)
11 class OnTheJobGraduate(Student, Staff):
12 def __init__(self, name, score, id, salary):
13 Student.__init__(self, name, score)
14 Staff.__init__(self, id, salary)
15 g1 = OnTheJobGraduate('小千', 100, '110', 10000)
16 g1.showStd()
17 g1.showStf()
运行结果如图11.16所示。
图11.16 运行结果
在例11-10中,派生类OnTheJobGraduate继承自基类Student与Staff,则派生类同时拥有两个基类的公有成员。第12行在派生类中定义构造方法并调用两个基类的构造方法,第15行创建派生类实例对象并对其属性进行初始化。
在多重继承中,如果派生类中存在与基类同名的方法,Python按照继承顺序从左到右在基类中搜索方法,如例11-11所示。
例11-11
1 class Student(object):
2 def __init__(self, name, score):
3 self.name, self.score = name, score
4 print('Student类', self.name)
5 def show(self):
6 print('姓名:', self.name, ' 分数:', self.score)
7 class Staff(object):
8 def __init__(self, name, salary):
9 self.name, self.salary = name, salary
10 print('Staff类', self.name)
11 def show(self):
12 print('姓名:', self.name, ' 薪资:', self.salary)
13 class OnTheJobGraduate(Student, Staff):
14 def __init__(self, name1, score, name2, salary):
15 Student.__init__(self, name1, score)
16 Staff.__init__(self, name2, salary)
17 g1 = OnTheJobGraduate('小千', 100, 'xiaoqian', 10000)
18 g1.show()
运行结果如图11.17所示。
图11.17 运行结果
在例11-11中,基类Student与Staff中存在相同的属性名与方法名,第18行派生类实例对象调用基类Student中的show()方法。此处读者需注意基类Student中show()方法输出self.name为'xiaoqian',而不是'小千',因为派生类调用构造方法时,先调用Student类中的构造方法,此时self.name为'小千',接着调用Staff类中的构造方法,此时会覆盖掉之前的内容,最终self.name为'xiaoqian'。
如果将上例中第13行代码中Student与Staff交换位置,具体如下所示:
class OnTheJobGraduate(Staff, Student):
再次运行程序,则运行结果如图11.18所示。
图11.18 运行结果
11.%2 多态
Python中加法运算符可以作用于两个整数,也可以作用于字符串,具体如下所示:
1 + 2 # 将整数1与2相加,结果为3
'1' + '2' # 将字符'1'与'2'拼接,结果为'12'
上述代码中,加法运算符对于不同的对象类型执行不同的操作,这就是多态。在程序中,多态是指基类的同一个方法在不同派生类对象中具有不同的表现和行为,当调用该方法时,程序会根据对象选择合适的方法,如例11-12所示。
例11-12
1 class Person(object):
2 def __init__(self, name):
3 self.name = name
4 def show(self):
5 print('姓名:', self.name)
6 class Student(Person):
7 def __init__(self, name, score):
8 super(Student, self).__init__(name)
9 self.score = score
10 def show(self):
11 print('姓名:', self.name, ' 分数:', self.score)
12 class Staff(Person):
13 def __init__(self, name, salary):
14 super(Staff, self).__init__(name)
15 self.salary = salary
16 def show(self):
17 print('姓名:', self.name, ' 薪资:', self.salary)
18 def printInfo(obj):
19 obj.show()
20 s1 = Student('小千', 100)
21 s2 = Staff('小锋', 10000)
22 printInfo(s1)
23 printInfo(s2)
运行结果如图11.19所示。
图11.19 运行结果
在例11-12中,Student与Staff都继承自Person类,三个类中都存在show(),第22行与23行分别调用show()方法,程序会根据对象选择调用不同的show()方法,这种表现形式称为多态。
11.%2 设计模式
设计模式描述了软件设计过程中经常碰到的问题及解决方案,它是面向对象设计经验的总结和理论化抽象。通过设计模式,开发者就可以无数次地重用已有的解决方案,无需再重复相同的工作。本节将简单介绍工厂模式与适配器模式。
11.5.1 工厂模式
工厂模式主要用来实例化有共同方法的类,它可以动态决定应该实例化哪一个类,不必事先知道每次要实例化哪一个类。例如在编写一个应用程序时,用户可能会连接各种各样的数据库,但开发者不能预知用户会使用哪个数据库,于是提供一个通用方法,里面包含了各个数据库的连接方案,用户在使用过程中,只需要传入数据库的名字并给出连接所需要的信息即可,如例11-13所示。
例11-13
1 class Operation(object):
2 def connect(self):
3 pass
4 class MySQL(Operation):
5 def connect(self):
6 print('连接MySQL成功')
7 class SQLite(Operation):
8 def connect(self):
9 print('连接SQLite成功')
10 class DB(object):
11 @staticmethod
12 def create(name):
13 name = name.lower()
14 if name == 'mysql':
15 return MySQL()
16 elif name == 'sqlite':
17 return SQLite()
18 else:
19 print('不支持其他数据库')
20 if __name__ == '__main__':
21 db1 = DB.create('MySQL')
22 db1.connect()
23 db2 = DB.create('SQLite')
24 db2.connect()
运行结果如图11.20所示。
图11.20 运行结果
在例11-13中,第10行定义DB类,类中定义了一个静态方法create (),该方法的参数为类名,可以根据类名创建对应的对象,因此称为工厂方法。从程序运行结果可看出,工厂方法可以根据类名创建相应的对象。
11.5.2 适配器模式
适配器模式是指一种接口适配技术,实现两个不兼容接口之间的兼容,例如原程序中存在类Instrument与Person,其中Instrument实例对象可以调用play()方法,Person实例对象可以调用act()方法,新程序中增加类Computer,其实例对象可以调用execute()方法,现要求类Instrument与Person的实例对象通过execute()调用各自的方法,具体如例11-14所示。
例11-14
1 class Instrument(object): # 乐器类,原程序存在的类
2 def __init__(self, name):
3 self.name = name
4 def play(self):
5 print(self.name, '演奏')
6
7 class Person(object): # 人类,原程序存在的类
8 def __init__(self, name):
9 self.name = name
10 def act(self):
11 print(self.name, '表演')
12 class Computer(object): # 计算机类,新程序添加的类
13 def __init__(self, name):
14 self.name = name
15 def execute(self):
16 print(self.name, '执行程序')
17 class Adapter(object): # 适配器类,用于统一接口
18 def __init__(self, obj, adaptedeMthods):
19 self.obj = obj
20 self.__dict__.update(adaptedeMthods)
21 if __name__ == '__main__':
22 obj1 = Instrument('guitar')
23 Adapter(obj1, dict(execute = obj1.play)).execute()
24 obj2 = Person('xiaoqian')
25 Adapter(obj2, dict(execute = obj2.act)).execute()
运行结果如图11.21所示。
图11.21 运行结果
在例11-14中,第17行定义适配器类,第20行使用类的内部字典__dict__,它的键是属性名,值是相应属性对象的数据值。第23、25行通过适配器类的实例对象实现了新程序与原程序的兼容。
11.%2 小案例
小伙伴在童年时都看过《猫和老鼠》,本案例实现猫捉老鼠游戏,猫与老鼠的位置用整数代替,游戏开始时,猫与老鼠的位置随机,之后老鼠移动的步数在[-2, -1, 0, 1, 2]中随机选择一个,猫移动的步数通过用户输入,当老鼠位置与猫位置相同时,游戏结束,具体实现如例11-15所示。
例11-15
1 import random
2 # 动物类
3 class Animal(object):
4 step = [-2, -1, 0, 1, 2]
5 def __init__(self, gm, point = None):
6 self.gm = gm
7 if point is None:
8 self.point = random.randint(0, 50)
9 else:
10 self.point = point
11 def move(self, aStep = random.choice(step)):
12 if 0 <= self.point + aStep <= 50:
13 self.point += aStep
14 # 猫类,继承自动物类
15 class Cat(Animal):
16 def __init__(self, gm, point = None):
17 super(Cat, self).__init__(gm, point)
18 self.gm.setPoint('cat', self.point)
19 def move(self):
20 aStep = int(input('请输入猫移动的步数:'))
21 super(Cat, self).move(aStep)
22 self.gm.setPoint('cat', self.point)
23 # 老鼠类,继承自动物类
24 class Mouse(Animal):
25 def __init__(self, gm, point = None):
26 super(Mouse, self).__init__(gm, point)
27 self.gm.setPoint('mouse', self.point)
28 def move(self):
29 super(Mouse, self).move()
30 self.gm.setPoint('mouse', self.point)
31 # 地图类
32 class GameMap(object):
33 def __init__(self):
34 self.catPoint, self.mousePoint = None, None
35 # 设置猫或老鼠的位置
36 def setPoint(self, obj, point):
37 if obj == 'cat':
38 self.catPoint = point
39 if obj == 'mouse':
40 self.mousePoint = point
41 # 判断猫与老鼠是否相遇
42 def catched(self):
43 print('老鼠:', self.mousePoint, '\t猫:', self.catPoint)
44 if self.mousePoint is not None and self.catPoint is not None \
45 and self.mousePoint == self.catPoint:
46 return True
47 # 测试
48 if __name__ == '__main__':
49 gm = GameMap()
50 mouse = Mouse(gm)
51 cat = Cat(gm)
52 while not gm.catched():
53 mouse.move()
54 cat.move()
55 else:
56 print('猫抓住老鼠,游戏结束!')
运行结果如图11.22所示。
图11.22 运行结果
在例11-15中,定义了四个类(Animal、Cat、Mouse、GameMap),其中Cat与Mouse继承自Animal并分别改写了父类中move()方法。另外,Animal、Cat、Mouse 类中构造方法的第二个参数接受GameMap 类的实例对象。
11.%2 本章小结
本章主要介绍了面向对象的三大特征,包括封装、继承与多态。在掌握面向对象编程后,读者还需了解设计模式,它为大型应用程序编写提供了思路。学习完本章内容,读者应加深对面向对象程序设计的理解,并将该方法运用到实际编程中。
11.%2 习题
1.填空题
(1) 是指将对象的属性和行为封装起来。
(2) 是面向对象程序设计提高重用性的重要措施。
(3) 是指一种行为对应着多种不同的实现。
(4) Python中所有的类都继承自 类。
(5) 继承分为单一继承与 。
2.选择题
(1) Python中通过在属性名前加( )个下画线来表明私有属性。
A.0 B.1
C.2 D.4
(2) 下列选项中,与class A等价的写法是( )。
A.class A:object B.class A:Object
C.class A(Object) D.class A(object)
(3) 下列选项中,关于多重继承正确的是( )。
A.class A:B, C B.class A:(B, C)
C.class A(B, C): D.class A(B:C):
(4) 派生类通过( )可以调用基类的构造方法。
A.__init__() B.super()
C.__del__() D.派生类名
(5) 若基类与派生类中有同名实例方法,则通过派生类实例对象调用( )中方法。
A.基类 B.派生类
C.先基类后派生类 D.先派生类后基类
3.思考题
(1) 简述面向对象的三大特征。
(2) 简述什么是工厂模式?
4.编程题
编写程序,定义动物Animal类,由其派生出猫类(Cat)和狮子类(Lion),二者都包含sound()实例方法,要求根据派生类对象的不同调用各自的sound()方法。
- 点赞
- 收藏
- 关注作者
评论(0)