面向对象的三大基本原则之一 ——多态。多态赋予了你的程序无与伦比的强大功能,是面向对象的灵魂!
面向对象的三大特征:
- 封装
- 继承
- 多态
1. 什么是多态?
这不是多态:
Person p1 = new Person();
下面是多态!Man 和 Woman 都是 Person 的子类:
Person p2 = new Man();
Person p3 = new Woman();
这就体现了对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)。
也因为一个子类类型的对象永远也是一个父类类型的对象,因此,当声明一个父类型时,总可以传递一个子类型对象。
多态性可以理解为一个事务的多种形态,=
赋值的右边有多种形态的对象来满足左边的需求。
2. 多态的使用
有了对象的多态性以后,在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类后的方法。
- 实例方法默认是多态的,在运行时根据
this
来决定调用哪个方法,而静态方法没有多态; - 参数是静态绑定,接收者是动态绑定;
- 多态只对方法的接收者生效;
- 多态只选择接收者的类型, 不选择参数的类型;
- 例如:HashSet.addAll()
- 静态方法的调用是通过在编译器静态绑定的,而实例方法的调用是在运行时动态绑定的
- 多态是运行时行为
假设子类分别覆盖了父类的eat()
和walk()
:
Person p2 = new Man();
p2.eat();
p2.walk();
那么上面这样写是ok的,在编译期,这两个方法指的仍是父类Person
的实例方法,运行时,子类对象接收到了方法调用的消息(同时可能还有方法的参数),其作为接收者要进行响应,发现子类进行了重写,于是动态调用重写后的方法。
接着这个思路,如果对p2
调用只有子类Man
中才存在的实例方法,在编译阶段会报错,因为编译时认为p2
属于赋值操作左边的类型,认为是Person
类型,而Person
并没有定义站着尿尿
这个方法。
p2.站着尿尿();
结论:
- 编译时,看左边;运行时,看右边。
- 多态的使用前提:
- 类的继承关系
- 方法的重写
3. 虚拟方法调用(多态的情况下)
子类中重写了父类方法后,在多态情况下,将此时父类的方法称为虚拟方法,在运行时,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的,所以比较虚。
在编译时是父类类型,运行时成了对子类类型上的方法调用,这个过程也称为动态绑定
。
借用一张尚硅谷免费资源中的PPT:
多态
4. 设计模式实战:策略模式
策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
使用了工厂方法的User
(注意这个类不是策略模式必须的,或者说无关):
private String name;
private boolean vip;
private User(String name, boolean vip) {
this.name = name;
this.vip = vip;
}
public static User vip(String name) {
return new User(name, true);
}
public static User dios(String name) {
return new User(name, false);
}
public String getName() { return name; }
public boolean isVip() { return vip; }
}
接下来属于策略模式。
一个需要调用这组算法策略的使用者PriceCalculator
:
public class PriceCalculator {
// 使用策略模式实现三个策略:
// NoDiscountStrategy 不打折
// Discount95Strategy 全场95折
// OnlyVipDiscountStrategy 只有VIP打95折,其他人保持原价
public static int calculatePrice(DiscountStrategy strategy, int price, User user) {
return strategy.discount(price, user); // 实际执行的是子类重写后的方法
}
public static void main(String[] args) {
User user = User.dios("wangpeng");
System.out.println("用户名:" + user.getName());
// 使用什么策略就传入什么策略
int price = calculatePrice(new Discount95Strategy(), 10000, user);
System.out.println("实际价格:" + price);
}
}
策略的父类DiscountStrategy
:
public class DiscountStrategy {
// 虽然正常情况下这个基类中的策略都会被某个具体策略给Override,但我认为目的有二:
// 1. 被子类覆盖从而实现多态调用
// 2. 用来兜底,抛出个异常
public int discount(int price, User user) {
throw new UnsupportedOperationException();
}
}
没有折扣的NoDiscountStrategy
:
public class NoDiscountStrategy extends DiscountStrategy {
@Override
public int discount(int price, User user) {
return price;
}
}
打95折的Discount95Strategy
:
public class Discount95Strategy extends DiscountStrategy {
@Override
public int discount(int price, User user) {
return (int)(price * 0.95);
}
}
Vip独享的打折策略OnlyVipDiscountStrategy
:
public class OnlyVipDiscountStrategy extends DiscountStrategy {
@Override
public int discount(int price, User user) {
if (user.isVip()) {
return (int) (price * 0.95);
} else {
return price;
}
}
}
策略模式和模板方法模式有些类似,二者的界限在于:父类中是否包含“骨架”逻辑
策略模式的父类中几乎没有代码(大多数情况是个抽象方法),但是模版方法模式的父类中有一个“模板方法”,包含很多“骨架”逻辑代码。
网友评论