本章将会针对面向对象编程进行讲述,思路适用于所有的面向对象编程语言。
首先要理解什么样的编程方式才被成为面向对象,以及面向对象和面向过程的区别:
举个例子,现在我想把大象关进冰箱,你来用代码实现整个过程,写代码的时候,你会写:博主把大象关进冰箱共分为三步,第一步博主打开冰箱门,第二步博主把大象塞进冰箱,第三步博主关闭了冰箱门。这就是面向过程的编程思路。
那么怎样用面向对象的思维实现把大象关进冰箱?
首先分析整个过程的所有实体:有博主、大象和冰箱三个实体,这三个实体分别属于不同的类:博主是人类,大象是动物类,冰箱是家电类或者干脆就叫冰箱类(因为冰箱有很多不同的类型)。这里说的类是只每个实际存在的个体所属的类别,每个实际的个体就是这个类的实例。在面向对象编程中,我们首先需要把类定义出来,然后用类去构造对象(对象指的是实际存在的具体的个体,例如博主是人类的一个个体,我家的冰箱也是冰箱类的一个对象,被我关进冰箱的那头大象是大象类的一个具体的对象)。
这个教程不会对面向对象编程进行非常深入的讲解,读者只需要掌握如何定义类、如何使用类构造对象、理解python类的定义中成员函数和成员变量的定义和引用就足够了。
此外,作为基础知识的补充,读者要理解面向对象的基本要素:
- 封装:通过设置属性和函数为私有(private)增加访问权限实现封装
- 继承:把类分为父类和子类,子类继承父类的全部属性和方法(函数)。注意python是支持多重继承的,即一个子类可以继承多个父类;Java不支持多重继承。
- 多态:指同一个函数的调用,由于对象不同而可能产生不同的行为。多态仅仅局限在方法(函数)的多态,方法可以理解为动作或行为;属性是没有多态的。多态存在的前提是继承和子类重写父类的方法。在面向对象语言中,父类可以有多个子类,这些父类和子类通常拥有类似的行为,例如所有国家的人类都能说话和吃东西,但是说的语言不同,饮食习惯也不同,中国人说中文吃火锅,德国人说德语吃土豆,这就构成了多态——即:属于同一类的不同对象拥有多种形态。
*注意:由于python是动态语言,对于数据类型的要求不高,因此我并没有铺开来讲。而在java这种强类型静态语言中,这三大特性极为重要,数据类型一定要匹配
如果有读者需要深入学习面向对象,可以参考如下教程:刘江的博客教程
现在开始我的正文:
1 定义类和类的属性(成员变量)、成员函数
现在,根据你的分析,向美国的扩张已经完成了,团队面临着更好地管理单个播客集的任务。我们现在进行系统化管理。
- 请创建一个podcast类,其属性为episode、title、length、moderator和adverts。
- 前四个属性在创建新对象时被初始化。默认情况下,广告被初始化为0。
- 除了属性之外,最初应该只有一个方法
display
,通过display
方法,要输出单个播客对象的所有信息的摘要(即输出Podcast类的对象的属性)。
class Podcast:
def __init__(self, episode, title, length, moderator):
# 属性的初始化,注意有默认值的属性不需要写在构造函数的参数表中,构造对象时默认值会自动生成
self.Episode = episode
self.Title = title
self.Length = length
self.Modetator = moderator
self.Adverts = 0
# 定义成员函数,参数表只有self,self表示Podcast对象,该对象拥有全部属性
def display(self):
print(f'Podcast episode Nr.: {self.Episode} with moderator {self.Modetator}\n')
print(f'Theme of this podcast episode: {self.Title}\n')
print(f'Length of this podcast episode: {self.Length}\n')
print(f'Advertising blocks currently sold: {self.Adverts}')
*注意:定义类的时候,第一个参数按照惯例是self,用于表示这个类的对象,类似Java的this。在定义成员函数时,只要涉及调用类的属性,就必须通过self来调用。此外,无论定义类还是成员函数,都要考虑清楚参数表的内容
现在尝试构造一个podcast类,并调用其成员函数。
newPodcast = Podcast(23, 'Traveling Salesman Problems', 70, 'Square Pants Bob')
newPodcast.display()
*注意:在我们构造类的时候,构造函数__init__()
会自动调用,而当我们成功构造出这个类的对象后,我们才可以用这个对象调用该对象所属类的成员函数
2 丰富类的定义,增加新的成员函数
面向对象编程的一个很大的优势在于,当我们希望给所有对象增加新的行为或功能时,只需要修改类的代码。也就是说类相当于对象的模板。
上一小节只是把播客类的框架搭建好了,现在给这个类增加新的功能:
- 初始化后,对象的属性不应该再能从外部控制。——访问限制,封装,私有属性
- 除了现有的方法外,还要整合如下的方法
方法
setAdverts
将3分钟的广告块添加到播客的长度中。增加多少广告取决于传递的参数值(adverts)。广告的数量被存储在相应的属性(Adverts)中。在一集播客中加入的广告时间不得超过三次。方法 "getAdverts "输出当前的播客中广告的数量。
用
cut
方法可以缩短一个播客。削减多少分钟取决于所传递的参数。然而,缩短后的播客时长不能低于30分钟。如果缩短后播客时长小于30分钟的限度,应该出现相应的提示信息。请按照规范扩展Podcast类,然后执行相应的代码。
class Podcast:
def __init__(self, episode, title, length, moderator):
# 属性名前面加两条下划线表示私有属性,后期调用这些私有属性的时候也要相应地加上下划线
self.__Episode = episode
self.__Title = title
self.__Length = length
self.__Modetator = moderator
self.__Adverts = 0
# 定义成员函数,参数表只有self,self表示Podcast对象,该对象拥有全部属性
def display(self):
print(f'Podcast episode Nr.: {self.__Episode} with moderator {self.__Modetator}\n')
print(f'Theme of this podcast episode: {self.__Title}\n')
print(f'Length of this podcast episode: {self.__Length}\n')
print(f'Advertising blocks currently sold: {self.__Adverts}')
def setAdverts(self, adverts):
if self.__Adverts + adverts > 3:
print(f'Attention: Only a maximum of {3 - self.__Adverts} adverts may be added.\n')
else:
self.__Adverts += adverts
self.__Length += 3 * adverts
def getAdverts(self):
return self.__Adverts
def cut(self, minutes):
if self.__Length - minutes >= 30:
self.__Length -= minutes
else:
print("Attention: This episode cannot be shortened any further. It is currently " +str(self.__Length) + " minutes long.\n")
*注意:这里需要注意的点是,成员函数需要先进行判断,然后如果满足条件则进行赋值操作,如果不满足则进行print,输出提醒
现在再进行一次实例化,依然创建海绵宝宝讲TSP的播客对象,看看这次的功能有没有增加:
newPodcast = Podcast(23, 'Traveling Salesman Problems', 70, 'Square Pants Bob')
newPodcast.display()
newPodcast.setAdverts(2) # 这条语句执行之前,还没有加入广告,因此这个setAdverts无论如何都会执行
newPodcast.setAdverts(2) # 这一行执行前已经有2条广告了,无法再插入两条,因此会输出一个提醒
newPodcast.cut(45)
newPodcast.cut(10)
newPodcast.getAdverts()
*注意:只要涉及到使用函数,则必须注意函数名的写法和该函数要求的参数表。看参数表的时候尤其需要注意数据类型(现在我们见到的数据类型都是int,str这样的基本类型,事实上我们自己定义的类也是数据类型,也就是说函数的参数可以是我们自定义的类的对象)
*注意:成员函数的参数表总是包含self这个参数,在调用成员函数的时候不需要任何self变量,因此我们调用成员函数时只需要填写除了self之外的参数,例如getAdverts(self)
,在调用的时候括号内不用写任何参数
3 继承:子类和父类
定义子类时,要用到下面的语法:
class 子类名(父类名):
子类包含了父类所有的属性和方法,但是子类又未必拥有父类的所有属性和方法,这就需要用到重写或覆盖的概念,以播客类为例,
作为最后一步,将引入一个SpecialPodcast类,除了现有的属性外,它还应该包含一个specialguest。然而,由于嘉宾的确认总是在相对较短的时间内进行的,因此可以使用changeGuest
方法再次改变specialguest。
请使用继承创建该子类,并且该类中包括附加属性和新方法。
class SpecialPodcast(Podcast):
def __init__(self, episode, title, length, moderator, specialguest: str):
super().__init__(episode, title, length, moderator)
self.__SpecialGuest = specialguest
def display(self):
# 调用父类display函数
super().display()
# 父类并不能完全满足需求,则增加一条属于子类的输出语句
print(f"Specialguest der Podcastfolge ist: {self.SpecialGuest}")
@property # 装饰器,decorator,用于将方法变成属性来调用
def SpecialGuest(self):
return self.__SpecialGuest
def changeGuest(self, newGuest):
if type(newGuest) != str:
pass
else:
self.__SpecialGuest = newGuest
return self.__SpecialGuest
sp = SpecialPodcast(1, "Vehicle Routing Problem", 65, "M. Ipler", "Dr. Best")
sp.changeGuest("Dr. Secondbest")
sp.display()
sp.changeGuest(2)
4 super().init()
本小节引用自红鲤鱼与彩虹
init()一般用来创建对象的实例变量,或一次性操作,super()用于调用父类的方法,可用来解决多重继承问题,直白的说super().init(),就是继承父类的init方法,同样可以使用super()点 其他方法名,去继承其他方法。
测试一、我们尝试下面代码,没有super(A, self).init()时调用A的父类Root的属性和方法(方法里不对Root数据进行二次操作)
class Root(object):
def __init__(self):
self.x= '这是属性'
def fun(self):
#print(self.x)
print('这是方法')
class A(Root):
def __init__(self):
print('实例化时执行')
test = A() #实例化类
test.fun() #调用方法
test.x #调用属性
可以看到此时父类的方法继承成功,可以使用,但是父类的属性却未继承,并不能用
*这里父类属性不能被继承,是因为父类的构造函数是私有的,父类私有的东西只能在父类中调用(x属性是公有的)
测试二、我们尝试下面代码,没有super(A,self).init()时调用A的父类Root的属性和方法(方法里对Root数据进行二次操作)
class Root(object):
def __init__(self):
self.x= '这是属性'
def fun(self):
print(self.x)
print('这是方法')
class A(Root):
def __init__(self):
print('实例化时执行')
test = A() #实例化类
test.fun() #调用方法
test.x #调用属性
可以看到此时报错和测试一相似,果然,还是不能用父类的属性
*注意:这里父类fun函数中调用了父类自己的属性,子类本应该继承了这个函数,但是实际上子类并不能使用父类的属性
测试三、我们尝试下面代码,加入super(A, self).init()时调用A的父类Root的属性和方法(方法里对Root数据进行二次操作)
class Root(object):
def __init__(self):
self.x = '这是属性'
def fun(self):
print(self.x)
print('这是方法')
class A(Root):
def __init__(self):
super(A,self).__init__()
print('实例化时执行')
test = A() # 实例化类
test.fun() # 调用方法
test.x # 调用属性
此时A已经成功继承了父类的属性,所以super().init()的作用也就显而易见了,就是执行父类的构造函数,使得我们能够调用父类的属性。