Java单分派和双分派以及访问者模式的使用

文章详细介绍了Java中的静态类型与实际类型、静态绑定与动态绑定的概念,强调了this和super关键字的作用,并通过示例解释了单分派和双分派的区别。此外,文章深入探讨了访问者模式,包括其实现和优缺点,并给出了一个宠物喂食的案例来说明双分派的实现。

1.静态类型和实际类型

静态类型

变量被声明时的类型,就叫静态类型,其是静态绑定(下文解释)

实际类型

变量所引用对象的真实类型就叫实际类型。动态绑定(下文解释)

// 左边变量a1,a2就是静态类型;而右边引用的cat和dog对象就是实际类型	
Animal a1 = new Cat();
Animal a2 = new Dog();

2.静态绑定和动态绑定

  • 静态绑定:程序执行前方法就已经被绑定,即程序编译期的绑定;也就是说在程序编译过程中就知道了该方法所属于那个类。(方法重载)
  • 动态绑定:程序在运行期间,通过判断对象的类型,调用合适的方法。也就是说需要依赖于对象的实际类型才能决定方法的使用。动态绑定过程:①jvm提取对象实际类型的方法表,②jvm匹配方法签名,③使用提取的对象调用方法。
  • java中动态绑定和静态绑定的体现:
    • 动态绑定的针对范畴只是对象的方法
    • java当中的方法只有final,static,private和构造方法是静态绑定,其他为动态绑定
    • 属性为静态绑定。
  • 动态绑定和静态绑定的特点:
    • 静态绑定:可以在编译器发现程序的错误,减少系统开销,提高程序运行效率
    • 动态绑定:对方法采取动态绑定是为了实现多态,一效率为代价来实现多态。实现无需修改代码就可以对功能进行拓展
  • Java 的编译与运行
    • Java 的编译过程是将 Java 源文件编译成字节码( JVM 可执行代码,即 .class 文件)的过程,在这个过程中 Java 是不与内存打交道的,在这个过程中编译器会进行语法的分析,如果语法不正确就会报错。
    • Java 的运行过程是指 JVM(Java 虚拟机)装载字节码文件并解释执行。在这个过程才是真正的创立内存布局,执行 Java 程序。Java 字节码的执行有两种方式: ①即时编译方式:解释器先将字节编译成机器码,然后再执行该机器码;②解释执行方式:解释器通过每次解释并执行一小段代码来完成 Java 字节码程序的所有操作。

3.this关键字和super关键字

thissuper
引用变量当前对象超类
引用方法本身超类
构造器本身其他的构造器超类
代表一个对象本身对象超类对象

tips:在调用构造器时,两个关键字的使用方式相似。调用构造器的语句只能作为另一个构造器的第一条语句出现。

public class StaticAndTrendsBindingDemo {
    static class Parent {
        protected String name;
        protected int age;

        public Parent() {
            // 调用本对象中的其他构造方法
            this("parent",30);
        }

        public Parent(String name, int age) {
            this.name = name;
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public Parent setName(String name) {
            this.name = name;
            // 返回当前方法所指代的对象
            return this;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }



        public void show() {
            System.out.println("我是父类parent:"+ this.getName());
        }

        @Override
        public String toString() {
            return "Parent{" +
                    "name='" + this.name + '\'' +
                    ", age=" + this.age +
                    '}';
        }
    }

    static class Son extends Parent {
        private String gender;

        public Son() {
            // 调用父类的构造方法
            super("son",15);
        }

        public Son(String name,int age,String gender) {
            super(name, age);
            this.gender = gender;
        }

        public String getGender() {
            return gender;
        }

        public void setGender(String gender) {
            this.gender = gender;
        }

        @Override
        public void show() {
            System.out.println("我是子类:" + this.name + "父类名字为:" + super.getName());
        }
    }

    static class Binding {

        private Parent parent;

        public Binding(Parent parent) {
            this.parent = parent;
        }

        /**
         * 动态绑定 根据parent对象的实际类型进行访问show()方法
         */
        public void trendsBinding() {
            parent.show();
        }

        public void staticBinding(Parent parent) {
            System.out.println("方法参数 parent");
            parent.show();
        }

        public void staticBinding(Son son) {
            System.out.println("方法参数 son");
            son.show();
        }

        /**
         * 静态方法为编译器绑定,不能使用this对象
         * @return
         */
//        public static Binding method() {
//            return this;//编译报错
//        }
    }

    public static void main(String[] args) {
        Parent parent = new Parent();
        Parent son = new Son();

        Binding binding = new Binding(son);
        binding.staticBinding(parent);
        binding.staticBinding(son);
        /**
         方法参数 parent
         我是父类parent:parent
         方法参数 parent
         我是子类:son父类名字为:son

         结果分析:由上可知,binding执行方法staticBinding只与传入参数的静态类型有关,都会调用方法staticBinding(Parent parent),
         执行对象的show()方法时,与参数的实际类型有关,即父类调用父类,子类调用子类
         */
        System.out.println("================");
        binding.trendsBinding();
        /**
         我是子类:son父类名字为:son
         */
    }
}

4.单分派和双分派

Java是一种支持双分派的单分派语言。分派是指确定要执行哪个对象的哪个方法。可分为单分派、双分派和多分派,其中Java不支持多分派。

单分派

定义:调用哪个对象(多态)的方法,在运行期确定,调用对象的哪个方法,编译期确定。

单分派根据方法参数的静态类型发生,方法重载就是单分派

双分派

定义:调用哪个对象的方法,在运行期(多态)确定;调用对象的哪个方法(方法重载),在运行期确定。

发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法的重写支持动态分派。也就是说即使是方法重载也可以确定要调用的方法,而不是根据参数的静态类型来调用。

如果要实现双分派的机制那么就要用到访问者模式。访问者模式属于行为型设计模式。

5.访问者模式

5.1 概述

定义:

封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。

5.2 结构

访问者模式包含以下主要角色:

  • 抽象访问者(Visitor)角色:定义了对每一个元素(Element)访问的行为,它的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
  • 具体访问者(ConcreteVisitor)角色:给出对每一个元素类访问时所产生的具体行为。
  • 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每一个元素都要可以被访问者访问。
  • 具体元素(ConcreteElement)角色: 提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法。
  • 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或者复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。

5.3 案例实现

【例】给宠物喂食

现在养宠物的人特别多,我们就以这个为例,当然宠物还分为狗,猫等,要给宠物喂食的话,主人可以喂,其他人也可以喂食。

  • 访问者角色:给宠物喂食的人
  • 具体访问者角色:主人、其他人
  • 抽象元素角色:动物抽象类
  • 具体元素角色:宠物狗、宠物猫
  • 结构对象角色:主人家

类图如下:
在这里插入图片描述

代码如下:

访问者角色:

/**
 * 抽象访问者类:定义对每个元素的访问行为,他的参数就是可以访问的元素,他的方法个数
 * 从理论上来说是和具体元素个数(Element元素的实现类)是一样的,访问者模式要求元素个数不能改变
 */
public interface Person {
    void feed(Cat cat);
    void feed(Dog dog);
}

/**
 * 具体访问者类:主人
 */
public class Owner implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("主人喂猫");
    }
    @Override
    public void feed(Dog dog) {
        System.out.println("主人喂狗");
    }
}

/**
 * 具体访问者类:其他人
 */
public class Somebody implements Person {
    @Override
    public void feed(Cat cat) {
        System.out.println("其他人喂猫");
    }
    @Override
    public void feed(Dog dog) {
        System.out.println("其他人喂狗");
    }
}

被访问者角色(具体元素):

/**
 * 抽象元素角色,可以被访问者访问,每个元素都要被访问者访问
 */
public interface Animal {
    /**
     * 接受访问者访问的方法
     * @param person
     */
    void accept(Person person);
}

/**
 * 具体元素类
 */
public class Dog implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);// this动态绑定机制 即this的类型是由运行时的当前对象决定的,因此,重载的方法确定无法在编译器实现,反而要被延迟到了运行时,这就是java双分派实现的原理。
        System.out.println("狗吃到了食物。。。");
    }
}

public class Cat implements Animal {
    @Override
    public void accept(Person person) {
        person.feed(this);
        System.out.println("猫吃到了食物。。。");
    }
}

对象结构角色和测试类:

/**
 * 对象结构角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体可以理解为是一个具有容器性质或者复合对象特性的类
 * ,他会含有一组元素,并且可以迭代这些元素,共访问者访问
 */
public class Home {
    private List<Animal>  animalList = new ArrayList<>();
    public void action(Person person) {
        for (Animal animal : animalList) {
            animal.accept(person);
        }
    }
    public void add(Animal animal) {
        this.animalList.add(animal);
    }
}

/**
 * 访问者模式
 */
public class Client {
    public static void main(String[] args) {
        Home home = new Home();
        home.add(new Cat());
        home.add(new Dog());
        Person person = new Owner();
        home.action(person);
        person = new Somebody();
        home.action(person);
    }
}

结果:在这里插入图片描述

从上述代码来看,访问者模式实现了双分派,其原理是基于Java中this关键字的运行时绑定即后期绑定。

5.4 优缺点

1,优点:

  • 扩展性好

    在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。

  • 复用性好,灵活度高

    通过访问者来定义整个对象结构通用的功能,从而提高复用程度。

  • 分离无关行为,符合单一职责原则

    通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。

2,缺点:

  • 对象结构变化很困难

    在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。

  • 违反了依赖倒置原则

    访问者模式依赖了具体类,而没有依赖抽象类。

5.5 使用场景

  • 对象结构相对稳定,但其操作算法经常变化的程序。

  • 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值