9.面向对象之多态

未匹配的标注

一:多态详解

什么是多态

封装,继承与多态是面向对象的三大基本特征,其中多态更是面向对象的灵魂。

那么,什么是多态?

多态首先是建立在继承的基础之上的。简单来说,多态就是:用父类的引用指向子类的对象,其既可以表现出子类独有的状态(通过 override 父类的方法);也可以表现出父类的状态。

来看示例程序:

public class Polymorphic {
    public static void main(String[] args) {
        // 父类的引用指向子类的对象
        Animal cat = new Cat();
        cat.speak(); // 重写父类的speak方法 表现出了子类独有的状态
        cat.sayHello();// 直接调用父类的sayHello方法,表现出了父类的状态
    }
}
class Animal {
    public void speak(){
        System.out.println("我是一个动物");
    }
    public void sayHello(){
        System.out.println("hello");
    }
}
class Cat extends Animal{
    @Override
    public void speak() {
        System.out.println("我是一只猫");
    }
}

该程序输出的结果为:

我是一只猫
hello

静态绑定与动态绑定

我们先来说明下什么是方法的参数,什么是方法的接收者,举个栗子:

obj.f(param);

对于上述的方法调用,obj 是调用方法的对象,也叫做方法的接受者;param 则是方法的参数。

多态遵循一条规则:方法的参数为静态绑定,方法接收者为动态绑定。换句话说,多态只对方法的接收者生效;再换句话说,多态只选择方法接收者的类型,不选择参数的类型。

我们来看示例程序:

ParamBase

public class ParamBase {
}

ParamSub

public class ParamSub extends ParamBase{
}

Base

public class Base {
    public void print(ParamBase param) {
        System.out.println("I am Base,the param is ParamBase.");
    }

    public void print(ParamSub param) {
        System.out.println("I am Base,the param is ParamSub.");
    }
}

Sub

public class Sub extends Base {
    @Override
    public void print(ParamBase param) {
        System.out.println("I am Sub,the param is ParamBase.");
    }

    @Override
    public void print(ParamSub param) {
        System.out.println("I am Sub,the param is ParamSub.");
    }
}

Main

public class Main {
    public static void main(String[] args) {
        Base obj = new Sub();
        ParamBase param = new ParamSub();
        obj.print(param);
    }
}

该程序运行的结果为:

I am Sub,the param is ParamBase.

解释:

obj.print(param); obj 为方法的接收者,也就是动态绑定,它实际上是一个 Sub 类的对象;

param 是方法的参数,也就是静态绑定,传入到方法中的参数类型为 ParamBase。

所以,该程序运行后会输出:I am Sub,the param is ParamBase.

编译时的多态性与运行时的多态性

多态分为编译时的多态性和运行时的多态性,并且静态方法不具有多态性

  • 多态中成员变量的访问特点:编译时看左边,运行时看左边

示例程序:

public class Polymorphic {
    public static void main(String[] args) {
        // 父类的引用指向子类的对象
        Animal cat = new Cat();
        System.out.println(cat.name);
    }
}
class Animal {
    String name = "animal";
}
class Cat extends Animal{
    String name = "cat";
}

程序输出结果为:

animal

解释:

在程序编译时期,首先 JVM 会看向 Animal cat = new Cat(); 这句等号左边的父类引用是否有该变量(name)的定义,如果有则编译成功,如果没有则编译失败;在程序运行时期,对于成员变量,JVM 仍然会看左边的所属类型,获取的是引用父类的变量结果 。

  • 多态中成员方法调用的特点:编译看左边,运行看右边

示例程序:

public class Polymorphic {
    public static void main(String[] args) {
        // 父类的引用指向子类的对象
        Animal cat = new Cat();
        cat.speak(); // 重写父类的speak方法 表现出了子类独有的状态
    }
}
class Animal {
    public void speak(){
        System.out.println("我是一个动物");
    }
}
class Cat extends Animal{
    @Override
    public void speak() {
        System.out.println("我是一只猫");
    }
}

程序输出结果:

我是一只猫

解释:

在程序编译期,首先JVM 会看向 Animal cat = new Cat(); 等号左边所属类型是否有该方法,如果有则编译成功,如果没有则编译失败;在程序运行时,则是要看等号右边的对象是如何实现该方法的,最终输出为右边对象对这个方法重写后的结果。

多态总结

  • 实例方法默认是多态的

  • 在运行时根据 this 来决定调用哪个方法

  • 静态方法没有多态

  • 参数静态绑定,接受者动态绑定

二:设计模式实战:策略模式

策略模式案例:打折策略

我们有一个打折策略的系统:

public class PriceCalculator {
    public static int calculatePrice(String discountStrategy, int price, User user) {
        switch (discountStrategy) {
            case "NoDiscount":
                return price;
            case "Discount95":
                return (int) (price * 0.95);
            case "OnlyVip": {
                if (user.isVip()) {
                    return (int) (price * 0.95);
                } else {
                    return price;
                }
            }
            default:
                throw new IllegalStateException("Should not be here!");
        }
    }
}

该打折系统中共有三种策略,无折扣,全场 95 折,以及 VIP 用户的折扣。该代码的最大问题是,如果我们要新增

打折方案,就需要新增一个 case,如果后面陆陆续续增加几十种打折策略,就会使我们的代码变得冗长。

并且,我们具体的策略应该和具体的业务代码分开,这样才能降低代码之间的耦合,使得我们的代码变得易于维护。

我们可以使用策略模式来改进我们的代码:

DiscountStrategy

public class DiscountStrategy {
    public int discount(int price, User user) {
        throw new UnsupportedOperationException();
    }
}

NoDiscountStrategy

public class NoDiscountStrategy extends DiscountStrategy {
    // NoDiscountStrategy 不打折

    @Override
    public int discount(int price, User user) {
        return price;
    }
}

Discount95Strategy

public class Discount95Strategy extends DiscountStrategy {
    @Override
    public int discount(int price, User user) {
        return (int) (price * 0.95);
    }
}

OnlyVipDisountStrategy

public class OnlyVipDiscountStrategy extends DiscountStrategy {

    // OnlyVipDiscountStrategy 只有VIP打95折,其他人保持原价

    @Override
    public int discount(int price, User user) {
        if (user.isVip()) {
            return (int) (price * 0.95);
        } else {
            return price;
        }
    }
}

PriceCalculator

public class PriceCalculator {
    public static int calculatePrice(DiscountStrategy strategy, int price, User user) {
        return strategy.discount(price, user);
    }

    public static void main(String[] args) {
        calculatePrice(new NoDiscountStrategy(), 80, User.vip("Li Lei"));
        calculatePrice(new OnlyVipDiscountStrategy(), 80, User.vip("Jack"));
    }
}

我们看到,PriceCalculator 中的代码变得非常简洁,我们可以直接添加一个 DiscountStrategy 的子类来新增一个策略,并且我们代码中的业务逻辑和打折策略完全分开,实现了代码的低耦合。

策略模式得益于 Java 多态的好处。

JDK线程池中的策略:ThreadPoolExecutor

Java 为多线程场景提供了线程池 ThreadPoolExecutor,下面是一个线程池的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

该构造方法中就使用了策略模式,RejectedExecutionHandler 对应了可以传入不同的拒绝策略:

  • AbortPolicy

  • DiscardPolicy

  • DiscardOldestPolicy

  • CallerRunsPolicy

这几种策略(类)都实现了 RejectedExecutionHandler 接口,这也是 JDK 中策略模式的实践。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
讨论数量: 0
发起讨论 只看当前版本


暂无话题~