做网站的赚钱吗,免费网站正能量入口下载,做的网站需要买什么服务器,门店管理系统有哪些什么是多态
面向对象程序设计有三要素#xff1a;封装、继承#xff08;或组合#xff09;、多态#xff0c;前两者较好理解#xff0c;多态总让人困惑#xff0c;不知道具体有什么作用#xff0c;更不知道为什么要用多态。今天就来详细分析下什么是多态#xff0c;以…什么是多态
面向对象程序设计有三要素封装、继承或组合、多态前两者较好理解多态总让人困惑不知道具体有什么作用更不知道为什么要用多态。今天就来详细分析下什么是多态以及多态有哪些好处为什么要用多态 多态.png
多态是指同一行为作用于不同对象时可以表现出多种不同的形式和结果来。例如子类继承父类并覆盖其方法后用父类引用指向子类对象并调用该方法时实际执行的是子类的方法。
这种根据对象实际类型而非声明类型来确定执行方法的行为就是多态性的体现。多态主要通过继承和接口实现允许同一接口有多种不同的实现方式。
多态的分类 编译时多态又称静态绑定是指编译器在编译时通过检查引用类型的方法是否存在来定位到相应的类及其方法而不检查实际对象是否支持该方法。编译时多态主要体现在方法重载上即根据参数类型、数量和顺序在编译时确定要执行的方法。 运行时多态又称动态绑定是指程序在运行时根据对象的实际类型来确定调用哪个方法而不是在编译时确定。这意味着方法的具体实现取决于对象的实际类型而非其声明类型。父类引用可以指向不同的子类对象使得相同方法调用产生不同的行为结果。通过运行时确定具体执行的方法代码具有更好的扩展性和可维护性。
多态的实现方式
编译时多态方法重载Overloading
重载指在同一个类中可以有多个方法这些方法名称相同但参数列表不同参数数量或类型不同。
编译器在编译阶段就能确定具体的方法。以下是一个重载示例展示了多个同名方法但参数个数或类型不同。重载的好处是简化接口设计不需要为不同类型编写多个方法名。 // OverloadExample.java 全部源码见文档链接
/*** 重载示例同名方法参数个数或类型不同。* 编译器在编译时确定具体的调用方法。*/
class Calculator {public int add(int num1, int num2) {return num1 num2;}public int add(int... nums) {int sum 0;for (int num : nums) {sum num;}return sum;}
}运行时多态方法重写Override与转型Casting
运行时多态是在程序运行时确定实际要执行的方法。
当子类继承父类并覆盖同名方法时这称为重写。使用父类引用来声明子类对象时子类会向上转型为父类类型。调用该对象的方法时实际执行的是子类的方法而不是父类的方法。
向上转型是指使用父类引用声明子类对象使子类对象的实际类型变为父类。通过父类引用调用子类的方法使代码更加通用处理一组相关对象时无需知道它们的具体类型。
向下转型则是将父类引用转换为子类引用这需要显式进行并且在转换前需要使用 instanceof 关键字进行类型检查。 // OverrideExample.java 全部源码见文档链接
/*** 重写示例子类覆盖父类同名方法体现多态。* 子类向上转型为父类型父类强制向下转型为子类型。*/
class Shape {void draw() {System.out.println(Shape-draw);}void drawShape() {System.out.println(Shape-drawShape);}
}class Circle extends Shape {Overridevoid draw() {System.out.println(Circle-draw);}void drawCircle() {System.out.println(Circle-drawCircle);}
}class Square extends Shape {Overridevoid draw() {System.out.println(Square-draw);}void drawSquare() {System.out.println(Square-drawSquare);}
}public class OverrideExample {public static void main(String[] args) {// 用父类引用声明子类对象向上转型Shape shape1 new Circle();Shape shape2 new Square();// 子类有同名方法动态绑定到子类实质执行的是Circle.draw()体现多态shape1.draw();// 报错编译时按声明类型检查Shape类中没有drawCircle方法// shape1.drawCircle();// 执行父类方法输出 Shape-drawShapeshape1.drawShape();if (shape2 instanceof Square) {// 向下转型用子类重新声明成为子类型了Square mySquare (Square) shape2;// 输出 Square-drawmySquare.draw();// 输出 Square-drawSquaremySquare.drawSquare();// 报错。若强转为父类型则无法调用drawSquare方法// ((Shape) mySquare).drawSquare();// 继承父类输出 Shape-drawShapemySquare.drawShape();}}
}
多态三个必要条件
严格来说多态需要具备以下三个条件。重载不属于严格意义上的多态因为重载在编译阶段就确定了。我们主要探讨运行时的多态即针对某个类型的方法调用实际执行的方法取决于运行时的对象而不是声明时的类型。
继承子类继承父类或实现接口。重写子类覆盖父类的方法。父类声明子类使用父类引用来声明子类对象。 // 父类
class Animal {void makeSound() {System.out.println(Animal makes a sound);}
}// 子类继承并重写同名方法
class Dog extends Animal {Overridevoid makeSound() {System.out.println(Dog barks);}
}public class Test {public static void main(String[] args) {// 父类引用声明子类Animal myAnimal new Dog();// 运行时对象为子类故输出Dog barksmyAnimal.makeSound();}
}如何理解父类声明子类 Parent child new Child(); 解释用 Parent 类声明了一个 child 引用变量变量存于栈中并赋值为 Child 实例对象对象存于堆中。变量 child 的类型为 Parent向上转型它的值是一个 Child 类型的实例对象。 加载执行顺序编译时JVM 编译时检查类的关系和对应方法包括重载确定变量的类型并定位相关方法名称生成字节码。运行时 JVM 加载 Parent 和 Child 类。根据 Parent 和 Child 的大小分配堆内存。初始化 new Child() 并返回对象引用。分配栈内存给变量 child。将对象引用赋值给 child。 总结 编译时根据引用类型不是实例对象确定方法的名称和参数包括重载。 运行时如果子类覆盖了父类的方法则调用子类实例引用类型的方法如果没有覆盖则执行父类变量引用类型的方法。
多态的好处为什么要用多态
在面向对象设计中“开闭原则”是非常重要的一条。即系统中的类应该对扩展开放而对修改关闭。这样的代码更可维护和可扩展同时更加简洁与清晰。
延续上面的例子假设业务需要扩充更多子类我们可以通过以下步骤来体现开闭原则 新增子类根据业务需求新增符合现有类层次结构的子类例如增加AnotherChild。 继承和重写新的子类应该继承自适当的父类并根据需要重写父类的方法或添加新的方法。 不需要修改现有的代码遵循开闭原则我们不修改现有的 Parent 和 Child 类的代码。 使用多态通过父类引用来声明子类例如 Parent child new AnotherChild();这样代码中现有的逻辑不需要改变。 编译时不变性编译时确定方法调用的特性不改变仍然根据引用类型来确定方法的名称和参数子类随意增加只要覆盖父类同名方法即可。 运行时多态性运行时根据实际对象的类型来决定要执行的方法这使得代码具有良好的可扩展性和可维护性。 // 定义一个通用Animal类
class Animal {void makeSound() {System.out.println(Animal makes a sound);}
}// 定义Dog类它是动物的子类
class Dog extends Animal {Overridevoid makeSound() {System.out.println(Dog barks);}
}// 定义Cat类它是动物的子类
class Cat extends Animal {Overridevoid makeSound() {System.out.println(Cat meows);}// Cat自有方法void meow() {System.out.println(Cat is meowing...);}
}// 定义一个动物园类管理不同的动物
class Zoo {// 传入的是抽象父类或接口方便扩展void letAnimalMakeSound(Animal animal) {animal.makeSound();}
}public class AnimalExample {public static void main(String[] args) {Zoo zoo new Zoo();Animal myDog new Dog(); // 向上转型Animal myCat new Cat(); // 向上转型((Cat)myCat).meow(); // 向下强转打印自有方法// 通过多态性动物园可以使用相同的方法处理不同种类的动物zoo.letAnimalMakeSound(myDog); // 输出 Dog barkszoo.letAnimalMakeSound(myCat); // 输出 Cat meows}
}要增加新的动物如鸟类Bird只需扩展 Animal 类而无需修改现有 Zoo 类中的方法。 class Bird extends Animal {Overridevoid makeSound() {System.out.println(Bird chirps);}
}public class AnimalExample {public static void main(String[] args) {Zoo zoo new Zoo();Animal myDog new Dog(); // 向上转型Animal myCat new Cat(); // 向上转型Animal myBird new Bird(); // 向上转型// 通过多态性动物园可以使用相同的方法处理不同种类的动物zoo.letAnimalMakeSound(myDog); // 输出 Dog barkszoo.letAnimalMakeSound(myCat); // 输出 Cat meowszoo.letAnimalMakeSound(myBird); // 输出 Bird chirps}
}这种设计
允许新增 Animal 的子类保持对扩展开放无需修改依赖 Zoo 的 letAnimalMakeSound 方法实现对修改封闭。
我们的业务总在不停变化如何使得代码底层不用大概而表层又能跟随业务不停变动这就显得十分重要。通过这种方式我们在不修改现有代码的情况下可以轻松地引入新的子类并扩展系统功能同时保持现有代码的稳定性和可靠性。
其他语言如何实现多态
不同语言因为语言特性的不同在实现多态上也略有不同。不过总的概念是一致的即达到“开闭原则”的目标。
Go语言例子
在Go语言中虽然没有传统意义上的类继承、父类声明子类和方法重载但通过结构体struct和接口interface以及匿名组合等方式实现类似的功能。这样也能实现代码的组织和复用同时保持了灵活性和简洁性。 package mainimport (fmt
)// 定义一个Animal接口
type Animal interface {MakeSound()
}// 定义一个 Dog 类型
type Dog struct{}// 实现 Animal 接口的 MakeSound 方法
func (d Dog) MakeSound() {fmt.Println(Dog barks)
}// 定义一个 Cat 类型
type Cat struct{}// 实现 Animal 接口的 MakeSound 方法
func (c Cat) MakeSound() {fmt.Println(Cat meows)
}// Cat自有方法
func (c *Cat) Meow() {fmt.Println(Cat is meowing...)
}// 定义一个 Zoo 类型用于管理动物
type Zoo struct{}// 定义一个方法让动物发出声音
func (z Zoo) LetAnimalMakeSound(a Animal) {a.MakeSound()
}func main() {zoo : Zoo{}myDog : Dog{}// 接口断言var myCat Animal Cat{}// 类型断言打印自有方法(myCat.(*Cat)).Meow()// 使用多态性通过接口类型处理不同的具体类型zoo.LetAnimalMakeSound(myDog) // 输出 Dog barkszoo.LetAnimalMakeSound(myCat) // 输出 Cat meows
}当需要增加Bird类型时直接增加即可。同样无需修改Zoo类里面的LetAnimalMakeSound方法。 type Bird struct{}// 实现 Animal 接口的 MakeSound 方法
func (b Bird) MakeSound() {fmt.Println(Bird chirps)
}func main() {zoo : Zoo{}myDog : Dog{}var myCat Animal Cat{}(myCat.(*Cat)).Meow()myBird : Bird{}// 使用多态性通过接口类型处理不同的具体类型zoo.LetAnimalMakeSound(myDog) // 输出 Dog barkszoo.LetAnimalMakeSound(myCat) // 输出 Cat meowszoo.LetAnimalMakeSound(myBird) // 输出 Bird chirps
}严格的多态概念包括子类继承父类、方法重写以及父类声明子类等这些特性在Go语言中无法实现。Go语言没有class概念虽然它的struct可以包含方法看起来像class但实际上没有继承和重载的支持它们本质上仍是结构体。
Go语言摒弃了传统面向对象语言中的class和继承概念我们需要用新的视角来理解和实践面向对象编程在Go中的应用方式
JavaScript语言例子
JavaScript是一种动态弱类型的基于对象的语言其一切皆是对象。它通过对象的原型链来实现面向对象编程。尽管JavaScript具有class和继承的能力但由于缺少强类型系统因此无法实现传统意义上的多态。
当然JavaScript作为动态语言具有天然的动态性优势。这使得它在灵活性和扩展性方面更具优势。 // 定义一个通用Animal类
class Animal {makeSound() {console.log(Animal makes a sound);}
}// 定义Dog类它是动物的子类
class Dog extends Animal {makeSound() {console.log(Dog barks);}
}// 定义Cat类它是动物的子类
class Cat extends Animal {makeSound() {console.log(Cat meows);}// Cat自有函数meow() {console.log(Cat is meowing..., this);}
}// 定义一个动物园类管理不同的动物
class Zoo {// JS没有严格类型出原始数据类型外其他均是Object// 说出传入的对象只要有makeSound方法即可。letAnimalMakeSound(animal) {animal.makeSound();}
}// 测试代码
const zoo new Zoo();
// JS没有父类定义子类概念直接声明即可无需向上转型
// 通过instanceof类型判断时可得到子类和父类类型
const myDog new Dog();
const myCat new Cat();// 直接调用自有函数
myCat.meow();// 可以动态给对象设置函数并绑定对象
myDog.meow myCat.meow.bind(myDog);
myDog.meow();// 动物园可以使用相同的方法处理不同种类的动物
// 当需要增加其他动物时直接建立新的类继承Animal而无需修改Zoo。
zoo.letAnimalMakeSound(myDog); // 输出 Dog barks
zoo.letAnimalMakeSound(myCat); // 输出 Cat meows
可以看出JS要实现Java意义的多态是做不到的但JavaScript更加灵活方便声明对象无需类型还可以动态添加函数和绑定对象。
Python语言例子 # 定义一个通用Animal类
class Animal: def make_sound(self): print(Animal makes a sound) # 定义Dog类继承Animal
class Dog(Animal): name Dogdef make_sound(self): print(Dog barks) # 定义Cat类继承Animal
class Cat(Animal): name Catdef make_sound(self): print(Cat meows) # Cat自有方法 def meow(self): print(self.name is meowing...) # 定义Bird类它是动物的子类
class Bird(Animal): def make_sound(self): print(Bird chirps) # 定义管理类
class Zoo: # python与js一样为动态语言使用duck typing不需要显式声明接口def let_animal_make_sound(self, animal): animal.make_sound() # 测试代码
if __name__ __main__:zoo Zoo()# 直接创建实例Python中不需要向上转型my_dog Dog()my_cat Cat()my_bird Bird()# 直接调用自有方法my_cat.meow()# Python中可直接给对象设置方法self不会改变my_dog.meow my_cat.meowmy_dog.meow()# 动物园可以使用相同的方法处理不同种类的动物zoo.let_animal_make_sound(my_dog) # 输出 Dog barkszoo.let_animal_make_sound(my_cat) # 输出 Cat meowszoo.let_animal_make_sound(my_bird) # 输出 Bird chirpsPython是一种动态语言它使用 self 参数来引用实例无需像其他语言那样使用 new 关键字来实例化对象。Python没有严格的接口概念不需要像其他语言那样显示声明对象的接口。Python通过继承和方法重写来实现多态概念但不支持传统意义上的父类声明子类和方法重载。
因此Python在多态性上的表现与JavaScript相似都是基于动态语言特性灵活而动态通过继承和重写实现对象行为的多样性。
Java多态实例全面剖析
理解Java多态的实例可以帮助澄清其原理和执行过程。以下是一个简单而详尽的例子帮助你全面理解Java中多态的工作机制。
// PolymorphismSimple.java
// 父类A
class A {public String show(D object) {return (A and D);}public String show(A object) {return (A and A);}// 默认注释掉。可开关注释测试下// public String show(B object) {// return (A and B);// }
}// 子类B
class B extends A {public String show(B object) {return (B and B);}public String show(A object) {return (B and A);}
}// 孙子类C
class C extends B {
}// 孙子类D
class D extends B {
}// 测试验证
public class PolymorphismSimple {public static void main(String[] args) {// 父类声明自己A a new A();// 父类声明子类A ab new B();// 子类声明自己B b new B();C c new C();D d new D();// 1) A and A。b的类型是B也是B的实例A里没有show(B)方法但有show(A)方法。B的父类是A因此定位到A.show(A)。System.out.println(1) a.show(b));// 2) A and A。c的类型是C也是C的实例C继承BB继承A。A里没有show(C)方法也没有show(B)方法最后指向A.show(A)。System.out.println(2) a.show(c));// 3) A and D, d的类型是D也是D的实例D继承BB继承A。A里有show(D)方法直接定位到A.show(D)。System.out.println(3) a.show(d));// 4) B and A, ab是B的实例但用A声明即向上转型得到的类型是A运行时才能确定具体该调用哪个方法。// ab是B的实例对象但引用类型是A。类型是在编译时确定因此从类型开始定位方法。// A类中没有show(B)方法但有show(A)方法因为A是B的父类ab也是A的实例于是定位到A.show(A)方法。// 由于B是A的子类且B重写了A的show(A)A的方法被覆盖了于是定位到B.show(A)这就是动态绑定。// 虽然B中有show(B)方法但是因为ab的类型是A编译时根据类型定位到A的方法而不是B。// 以下几种可开关打开/注释代码测试下。// -// 若A里有show(A)和show(B)B里有show(B)有show(A)则编译时关联到A.show(B)因B覆盖了A.show(B)动态绑定到B.show(B)。// -// 若A里有show(A)和show(B)B里无show(B)有show(A)则编译时关联到A.show(B)因B无覆盖则直接调用A.show(B)。// -// 若A里有show(A)无show(B)B里无show(B)有show(A)则编译时关联到A.show(A)因B覆盖了A.show(A)动态绑定到B.show(A)。// -// 若A里有show(A)无show(B)B里无show(A)有show(B)则编译时关联到A.show(A)因B无覆盖则直接调用A.show(A)。// 查找顺序为编译时根据引用类型确定所属类 - 根据重载参数类型定位类型按子-父-祖逐级往上查找到类的具体方法包括继承的方法 -// 运行时实例对象覆盖覆盖只有子-父一层了引用类型的同名方法 - 定位到实例对象的方法。System.out.println(4) ab.show(b));// 5) B and A。ab是B的实例类型是A。从A类没找到show(C)方法也没找到A.show(B)方法找到A.show(A)方法。A.show(A)被B.show(A)覆盖因此调用B.show(A)。System.out.println(5) ab.show(c));// 6) A and D。A里面有show(D)的方法直接定位到。System.out.println(6) ab.show(d));// 7) B and B。B里面有show(B)的方法直接定位到。System.out.println(7) b.show(b));// 8) B and B。B没有show(c)方法但有show(B)方法。C继承自B父类型是B因此调用B.show(B)。System.out.println(8) b.show(c));// 9) A and D。B中没有show(D)方法B继承AA里有show(D), 故调用A.show(D)方法。System.out.println(9) b.show(d));// 10) B and A。父类声明子类存在向上转型。A里有show(A)被B.show(A)覆盖了因此定位到B.show(A)。System.out.println(10) ab.show(a));}
}总结
多态包括编译时多态和运行时多态。编译时多态即静态绑定通常通过方法重载实现。运行时多态则是在代码运行时确定具体调用的方法。
从Java的角度看严格意义上的多态需要满足三个条件继承、方法覆盖和父类引用子类对象。Java完全符合这些要求实现了严格意义上的多态。
尽管Go语言、Python和JavaScript不完全符合严格意义上的多态它们仍能够实现多态效果。多态的核心在于动态确定运行的方法从而使代码更加灵活、易于维护和扩展。
Go语言虽然没有继承和方法重载但仍能实现多态效果。Python和JavaScript作为动态语言没有接口和显式类型声明但由于其灵活性同样能很好地实现多态。
各语言完整示例
https://github.com/microwind/design-pattern/tree/main/programming-paradigm/oop/polymorphism
简单示例
PolymorphismSimple.javaPolymorphismSimple.gopolymorphism_simple.cPolymorphismSimple.cppPolymorphismSimple.jsPolymorphismSimple.pyPolymorphismSimple.ts