多态
大家好,这个章节我们来讲解面向对象中的多态。
简介
多态是面向对象编程中三大概念之一,它允许不同的对象对同一个消息作出不同的响应。
简单来说,多态是指同一个方法或操作符在不同的对象实例上可以有不同的行为。这意味着可以通过一个共同的接口或基类引用不同的子类对象,并根据实际的对象类型来调用相应的方法。
多态是面向对象编程中的一项重要特性,它允许同一个方法根据不同的对象做出不同的反应。简单来说,就是同一个操作对不同的对象,表现出的行为可以不一样。举个简单的例子,假设你有一个“动物”类,动物类有一个 make_sound 的方法,狗会“汪汪”叫,猫会“喵喵”叫。虽然它们都在调用make_sound 这个方法,但每种动物的反应是不同的——这就是多态。通过多态,我们可以以一种统一的方式来处理不同的对象,而且它能够让我们的代码更简洁、扩展性更强。比如我们可以通过父类的引用来操作子类的对象,而不关心它到底是哪一种具体类型,程序会根据实际的对象来调用相应的方法。
多态性在实际应用中提供了很多好处,包括:
- 简化代码:通过以相同的方式处理不同的对象,并使用统一的接口进行编程,可以降低代码的复杂性和重复性。
- 可维护性:多态可以提高代码的可维护性。当需要新增一种子类时,不需要修改已有的代码,只需要创建一个新的子类并继承父类,就能够在原有的代码基础上实现新的功能。
- 扩展性:由于多态允许在不修改已有的代码的情况下新增功能,因此可以更容易地对系统进行扩展和适应需求的变化。
多态带来了很多好处,最主要的就是让我们的代码变得更加简洁、易维护和更易扩展。首先是简化代码:我们可以通过一个共同的接口或基类引用来处理不同的对象,避免了大量的重复代码。然后是可维护性:当我们需要增加新的功能时,不需要修改已有的代码,只需要增加一个新的子类并继承父类,这就能实现新的功能。还有扩展性:随着系统需求的变化,我们可以很方便地通过继承和重写来扩展系统功能,而不需要担心现有的代码会受到影响。举个例子,假设你有一个医生类 Doctor,有一个病人类 Patient。如果你想支持中医和西医两种不同类型的医生,使用多态就能很方便地在病人类里统一管理和调用不同的治疗方法。
多态性的实现通常通过继承和方法重写来实现。在继承关系中,子类可以重写父类的方法,在父类引用子类对象时,调用的实际上是子类重写后的方法。
# 中医
class Father:
def cure(self):
print("使用中医方法进行治疗。。。")
# 西医
class Son(Father):
def cure(self):
print("使用西医方法进行治疗。。。")
# 患者
class Patient:
def need_doctor(self, doctor):
doctor.cure()
if __name__ == '__main__':
old_doctor = Father()
little_doctor = Son()
patient = Patient()
patient.need_doctor(old_doctor)
patient.need_doctor(little_doctor)
多态的实现通常通过继承和方法重写来实现。子类通过重写父类的方法,来实现不同的行为。比如,我们有一个“父亲”类 Father,它有一个 cure 方法,然后我们创建了一个“儿子”类 Son,它重写了父类的 cure 方法。虽然Father和Son类是两个不同的类,但通过多态,我们依然可以用一个统一的 need_doctor 方法来调用它们。在这个例子中,Patient类不需要知道医生具体是哪一种,只是通过 need_doctor 方法,分别调用不同医生的cure 方法,展现了多态的魅力。
鸭子类型
鸭子类型(Duck Typing)是一种动态类型的概念,它源自于走路像鸭子、叫声像鸭子、看起来像鸭子,那么它就是鸭子的观念。
在鸭子类型中,一个对象的适用性不是由它的类或接口决定,而是由它的方法和属性是否与所需的方法和属性匹配来决定。换句话说,只要一个对象具有特定方法和属性,我们就可以将其视为具有相同类型。
举个例子,如果我们需要一个能叫的对象,并且某个对象有一个名为quack的方法,那么我们可以将该对象视为一个鸭子,不管它实际上是什么类的对象。
换句话说,我们关注的是对象的行为而不是其类型。

“鸭子类型”是面向对象中的一个很有趣的概念。它的意思就是,如果一个对象走路像鸭子、叫声像鸭子,那么它就可以被认为是鸭子——也就是说,我们不关心一个对象的类型到底是什么,只关心它有没有我们需要的行为。举个例子,我们有一个需要“叫”的对象,只要对象有一个quack()的方法,无论它是鸭子、鸟还是其他动物,我们都可以把它当作鸭子来使用。
鸭子类型在动态语言中特别常见,比如 Python。在 Python 中,不需要显式地继承或实现接口,只要一个对象具有必需的方法和属性,它就可以被认为是某种类型。这使得 Python 具有灵活性和简洁性,可以更自由地处理不同类型的对象。
鸭子类型通常是动态语言的特性,相比于静态类型语言,它在编译时没有类型检查。这意味着无法在编译阶段对类型不匹配或缺失方法和属性进行检测,可能会导致运行时错误。
# 中医
class Father:
def cure(self):
print("使用中医方法进行治疗。。。")
# 西医
class Son(Father):
def cure(self):
print("使用西医方法进行治疗。。。")
# 兽医
class AnimalDoctor:
def cure(self):
print("使用兽医方法进行治疗。。。")
# 患者
class Patient:
def need_doctor(self, doctor):
doctor.cure()
if __name__ == '__main__':
old_doctor = Father()
little_doctor = Son()
animal_doctor = AnimalDoctor()
patient = Patient()
patient.need_doctor(old_doctor)
patient.need_doctor(little_doctor)
patient.need_doctor(animal_doctor)
在Python中,这个特性非常常见,因为Python是一种动态语言。你不需要显式地声明一个对象实现了某个接口,只要它有你需要的方法,就可以视为这个类型。在这个例子里,虽然Father、Son和AnimalDoctor是完全不同的类,但它们都可以作为医生来治疗病人。这里没有显式的接口继承,也没有类型检查,只要它们都有cure()方法就行了。
类型检查
Python 中提供了 isinstance() 和 issubclass() 两个函数,用来对数据进行检查判断。
虽然鸭子类型让Python的类型检查更为灵活,但有时我们还是需要确认某个对象是否具有特定的类型。Python提供了isinstance()和issubclass()两个函数,来帮助我们进行类型检查。
isinstance()
Python 中使用 isinstance() 来检查一个实例的类型。
格式: isinstance(obj, type)
判断 obj 对象是否是 Type 指定类型或其父类类型的实例。
print(isinstance(little_doctor, Father))
print(isinstance(little_doctor, Son))
print(isinstance(little_doctor, AnimalDoctor))
isinstance()函数用来判断一个对象是否是某个类或其子类的实例。它的格式是:isinstance(obj, type),返回True表示obj是type类型或者是type的子类的实例。通过isinstance(),我们可以在调用方法之前,确保对象类型是正确的。
前面示例的代码可以进行优化:
# 患者
class Patient:
def need_doctor(self, doctor):
if isinstance(doctor, Father):
doctor.cure()
else:
print("此大夫医疗方法不适用病人。。。")
你也可以在需要的时候优化前面的代码,加入类型检查:判断doctor是否是Father或其子类的实例。
issubclass()
Python 中还可以使用 issubclass() 来检查类的继承关系。
格式: issubclass(Type1, Type2)
判断 Type1 是否是 Type2 的子类。
print(issubclass(Father, Father))
print(issubclass(Son, Father))
print(issubclass(AnimalDoctor, Father))
issubclass()函数用于检查一个类是否是另一个类的子类。它的格式是:issubclass(Type1, Type2),如果Type1是Type2的子类,返回True,否则返回False。如果你想在程序中检查某个对象是否属于某个类型或其子类时,issubclass()非常有用。
前面的示例也可优化为:
其中,__class__ 是一个魔法属性,用来获取当前实例对象的类。
class Patient:
def need_doctor(self, doctor):
if issubclass(doctor.__class__, Father):
doctor.cure()
else:
print("此大夫医疗方法不适用病人。。。")
你也可以像之前一样,优化代码。在 need_doctor 方法中,先使用issubclass方法判断一下 doctor 实例对应的类,是不是 Father 的子类。其中,class 是一个魔法属性,用来获取当前实例对象的类。
总结
- 多态
- 鸭子类型
- 类型检查
总结一下,今天讲了三个关键概念:多态、鸭子类型和类型检查。多态是指同一个方法或操作符在不同的对象实例上可以有不同的行为。鸭子类型强调的是“行为”,只要对象具有所需的属性和方法,我们就可以使用它,而不需要关心它的具体类型。类型检查提供了isinstance()和issubclass()函数,帮助我们确认对象是否符合预期的类型和继承关系。通过这两个工具,你可以写出更灵活、可扩展的代码,也能在适当的地方做类型检查,避免潜在的错误。