讨伐设计模式——结构型模式篇

本文是学习设计模式时做的笔记,原视频链接:

https://www.bilibili.com/video/BV1G4411c7N4

目录

适配器模式(Adapter)

使用场景

实现方式

类适配器模式

对象适配器模式

接口适配器模式

桥接模式(Bridge)

使用场景

扫描二维码关注公众号,回复: 12756994 查看本文章

实现方式

装饰者模式(Decorator)

使用场景

实现方式

组合模式(Composite)

使用场景

实现方式

外观模式(Facade)

使用场景

实现方式

享元模式(Flyweight)

使用场景

实现方式

代理模式(Proxy)

使用场景

实现方式

静态代理

动态代理


适配器模式(Adapter)

适配器模式:将某个接口转换成客户端期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类协同工作。

使用场景

美国电器的额定电压是110V,中国电器的额定电压是220V,想要在美国使用made in China的电器,需要一个适配器将110V的交流电转换为220V。

实现方式

适配器模式主要分为三类:

  • 类适配器模式
  • 对象适配器模式
  • 接口适配器模式

类适配器模式

美国交流电的电压是110V,中国电器的额定电压是220V,想要在美国使用made in China的电器,需要一个适配器将110V的交流电转换为220V。

1.创建适配器

美国交流电电压为110V:

public class Voltage110V {
    int src = 110;
    public int output110V() {
        return src;
    }
}

中国电器额定电压是220V,所以在使用适配器后的目标电压是220V:

public interface Voltage220V {
    public int output220V();
}

适配器起到调节电压的作用:

public class VoltageAdapter extends Voltage110V implements Voltage220V {
    int srcV;
    int dstV;
    @Override
    public int output220V() {
        srcV = output110V();
        dstV = srcV * 2;
        return dstV;
    }
}

2.使用适配器

创建一个榨汁机类Juicer:

public class Juicer {
    public void play(Voltage220V voltage220V) {
        if (voltage220V.output220V() == 220) {
            System.out.println("榨汁!");
        } else {
            System.out.println("不对劲!");
        }
    }
}

最后创建一个用户Client:

public class Client {
    public static void main(String[] args) {
        Juicer juicer = new Juicer();
        juicer.play(new VoltageAdapter());
    }
}

输出结果:

榨汁!

分析:

adapter类(VoltageAdapter),通过继承src类(Voltage110V),实现dst接口(Voltage220V),完成了从src到dst的适配。

这么做有两个缺点:

  • adapter类继承了src类,由于Java是单继承机制,adapter类不能再继承其它类
  • dst必须是接口,有一定局限性

对象适配器模式在类适配器模式的基础上,遵循合成复用原则,对类适配器模式进行了改进。

对象适配器模式

基本实现思路和类适配器模式相同,只是adapter类不再继承src类,而是持有src类的实例。

根据合成复用原则,在系统中尽量使用组合/聚合关系来代替继承关系。解决了适配器必须继承src类的局限性问题,也不再要求dst必须是接口。

美国交流电的电压是110V,中国电器的额定电压是220V,想要在美国使用made in China的电器,需要一个适配器将110V的交流电转换为220V。

1.创建适配器

美国交流电电压为110V:

public class Voltage110V {
    int src = 110;
    public int output110V() {
        return src;
    }
}

中国电器额定电压是220V,所以在使用适配器后的目标电压是220V:

public interface Voltage220V {
    public int output220V();
}

适配器起到调节电压的作用:

public class VoltageAdapter implements Voltage220V {
    int srcV;
    int dstV;
    private Voltage110V voltage110V;
    public VoltageAdapter(Voltage110V voltage110V) {
        this.voltage110V = voltage110V;
    }
    @Override
    public int output220V() {
        dst = 0;
        if (null != voltage110V) {
            src = voltage110V.output110V();
            dst = src * 2;
        }
        return dst;
    }
}

2.使用适配器

创建一个榨汁机类Juicer:

public class Juicer {
    public void play(Voltage220V voltage220V) {
        if (voltage220V.output220V() == 220) {
            System.out.println("榨汁!");
        } else {
            System.out.println("不对劲!");
        }
    }
}

最后创建一个用户Client:

public class Client {
    public static void main(String[] args) {
        Juicer juicer = new Juicer();
        juicer.play(new VoltageAdapter(new Voltage110V()));
    }
}

输出结果:

榨汁!

接口适配器模式

使用接口适配器模式的场景是:想要使用一个接口中的一部分(而非全部的)抽象方法。

某个接口提供了多个抽象方法,当不需要全部实现接口提供的方法时,可以先设计一个抽象类实现该接口,并为该接口中每个方法提供一个默认实现(空方法),该抽象类的子类可有选择地重写需要用到的方法来实现需求。

1.创建一个接口

public interface Interface4 {
    public void method1();
    public void method2();
    public void method3();
    public void method4();
}

2.创建接口适配器(抽象类)

实现接口,并为接口中每个方法提供默认实现:

public abstract class AbsAdapter implements Interface4 {
    @Override
    public void method1() {
    }
    @Override
    public void method2() {
    }
    @Override
    public void method3() {
    }
    @Override
    public void method4() {
    }
}

3.(Client)使用接口适配器

使用匿名内部类,重写接口适配器中需要用到的方法:

public class Client {
    public static void main(String[] args) {
        AbsAdapter absAdapter = new AbsAdapter() {
            @Override
            public void method1() {
                System.out.println("使用了method1方法");
            }
        };
        absAdapter.method1();
    }
}

输出结果:

使用了method1方法

桥接模式(Bridge)

桥接模式:把抽象与实现解耦,使得二者可以独立变化。解耦是通过桥接结构实现的。

使用场景

从多个角度分析问题,问题在每个角度都可能发生变化。

举一个例子,一个男性(Man)按不同的年龄阶段,我们可以将他归为男孩(Boy)、成年男子(Adult)、老爷爷(OldMan),在不同的时间,他可能有不同的情绪(Mood),比如开心(Happy)、生气(Angry)、难过(Sad)。

我们在路上遇到一个陌生男子,我们可以从他的年龄和情绪两个角度来形容对他的印象:

“我刚才在路上走着,遇到了一个看起来很难过的男孩。”

“我刚才在路上走着,遇到了一个看起来很开心的老爷爷。”

桥接模式强调每个角度的变化:下一个遇到的陌生男子可能是开心的、生气的、难过的,这和他的年龄段关系不大(大概吧…);下一个遇到的陌生男子可能是男孩、成年男子、老爷爷,这和他的情绪关系也不大。

实现方式

桥接模式原理类图:

Client:用户,桥接类的调用者。

Abstraction:桥接类,是一个抽象类。

RefinedAbstraction:桥接类的子类。

Implementor:行为实现类的接口。

ConcreteImplementorA/B:具体的行为实现类。

从UML图可以看出:Abstraction和Implementor之间是聚合关系,也就是调用和被调用的关系。

举一个例子,一个男性(Man)按不同的年龄阶段,我们可以将他归为男孩(Boy)、成年男子(Adult)、老爷爷(OldMan),在不同的时间,他可能有不同的情绪(Mood),比如开心(Happy)、生气(Angry)、难过(Sad)。

我们在路上遇到一个陌生男子,我们可以从他的年龄和情绪两个角度来形容对他的印象:

“我刚才在路上走着,遇到了一个看起来很难过的男孩。”

“我刚才在路上走着,遇到了一个看起来很开心的老爷爷。”

1.一个男性(Man)和情绪(Mood)之间的关系是聚合,所以我们先写部分(Mood)再写整体(Man)

public interface Mood {
    void look();
}
public class Happy implements Mood{
    @Override
    public void look() {
        System.out.print("看起来很开心的");
    }
}
public class Angry implements Mood {
    @Override
    public void look() {
        System.out.print("看起来很生气的");
    }
}
public class Sad implements Mood {
    @Override
    public void look() {
        System.out.print("看起来很难过的");
    }
}

这部分很容易理解。

2.写抽象类Man和它的子类Boy、Adult、OldMan

public abstract class Man {
    //Mood接口和Man类之间为聚合关系
    private Mood mood;
    //通过构造函数传入Mood对象
    public Man(Mood mood) {
        this.mood = mood;
    }
    //根据传入的不同的Mood对象决定调用哪个Mood接口实现类中的look()
    protected void look() {
        this.mood.look();
    }
}
public class Boy extends Man {
    public Boy(Mood mood) {
        super(mood);
    }
    public void look() {
        super.look();
        System.out.println("小男孩");//我们说一个Boy类对象是“小男孩”是符合逻辑的
    }
}
public class Adult extends Man {
    public Adult(Mood mood) {
        super(mood);
    }
    public void look() {
        super.look();
        System.out.println("成年男子");
    }
}
public class OldMan extends Man {
    public OldMan(Mood mood) {
        super(mood);
    }
    public void look() {
        super.look();
        System.out.println("老爷爷");
    }
}

3.最后写我们Client

public class Client {
    public static void main(String[] args) {
        System.out.print("我刚才在路上走着,遇到了一个");
        Man man1 = new Boy(new Happy());
        man1.look();
        System.out.print("然后遇到一个");
        Man man2 = new Adult(new Angry());
        man2.look();
        System.out.print("后来又遇到一个");
        Man man3 = new OldMan(new Sad());
        man3.look();
    }
}

输出结果:

我刚才在路上走着,遇到了一个看起来很开心的小男孩
然后遇到一个看起来很生气的成年男子
后来又遇到一个看起来很难过的老爷爷

装饰者模式(Decorator)

装饰者模式:为一个现有的对象添加新的功能,同时又不改变其结构。

装饰者模式是继承的一个替代模式,可以动态扩展一个实现类的功能。

装饰类和被装饰类可以独立发展,不会相互耦合。

使用场景

代替继承。

实现方式

一个女孩(Girl)要去约会(Date)。

Beauty(美人)是Girl(女孩)的扩展类。

1.“一个女孩要去约会”——女孩类Girl要实现约会接口Date

public interface Date {
    void date();
}
public class Girl implements Date {
    @Override
    public void date() {
        System.out.println("去约会");
    }
}

2.创建实现了Date接口的抽象装饰类DateDecorator

public abstract class DateDecorator implements Date {
    protected Date decorator;
    public DateDecorator(Date date) {
        this.decorator = date;
    }
    public void date() {
        decorator.date();
    }
}

3.创建扩展了DateDecorator类的实体装饰类Beauty

public class Beauty extends DateDecorator {
    public Beauty(Date date) {
        super(date);
    }
    @Override
    public void date() {
        clean(decorator);
        makeUp(decorator);
        decorator.date();
    }
    private void clean(Date date) {
        System.out.print("洗漱,");
    }
    private void makeUp(Date date) {
        System.out.print("化妆,");
    }
}

4.测试

public class Test {
    public static void main(String[] args) {
        Date girl = new Girl();
        DateDecorator beauty = new Beauty(new Girl());
        System.out.println("普通女孩:");
        girl.date();
        System.out.println("美人:");
        beauty.date();
    }
}

输出结果:

普通女孩:
去约会
美人:
洗漱,化妆,去约会

我的理解,面向对象思想的重点在于对自然界中关系的描述,“一个女孩去约会”,“女孩”是类,“一个女孩”是女孩类的实例;“约会”是具体的事件,用接口来描述,“去约会”是“约会”这件事中的一个行为,用接口中的方法来描述;女孩类实现约会接口,表示所有的女孩都可以完成“去约会”这件事。

如果说装饰者模式是继承关系的替代模式,我们先要考虑:继承描述了自然界中的什么关系?

我们在刚开始学习继承的时候经常用的一个例子是:平行四边形是特殊的四边形——平行四边形类是四边形类的子类:平行四边形类继承四边形类,平行四边形满足四边形的所有性质。可见,继承描述了自然界中的泛化关系。

所有的鸟类都是动物——鸟类是动物类的子类。

在女孩约会的例子中有两个与继承有关的问题:

  1. 装饰者模式替代了哪一段继承关系?
  2. 从面向对象的角度来看,装饰者类描述了什么?

1.装饰者模式替代了哪一段继承关系?

替代了“美人类继承女孩类”的关系。从自然界的角度看,我们可以认为“所有的美人都是女孩”。

代码:Beauty beauty = new Beauty(new Girl())。美人类构造方法中传入了一个女孩类对象,表示beauty(美人类的一个实例)是女孩。

看到这里,我们就能get到装饰者模式的优点了:

如果我们不认为“所有的美人都是女孩”——也会存在一些美人是男孩,我们可以创建男孩类Boy并让它实现约会接口,通过向美人类构造方法中传入男孩类对象,例如Beauty Yanzu = new Beauty(new Boy()),可以表示Yanzu(美人类的一个实例)是男孩。

总结为,装饰者模式使美人类和女孩类的耦合度很低。

2.从面向对象的角度来看,装饰者类描述了什么?

装饰类实现了约会接口,说明装饰类的对象是能够完成约会事件的某人。

美人类实际上继承了抽象的装饰类,说明“所有的美人都是装饰类的对象”。

装饰类是对所有其它实现约会接口的类的总结,不管是男孩还是女孩,就算后边再来个外星人类实现约会接口,通通都是约会人。

我们用非常不正确但很直观的表达方式来理解:

  • 美人类可以继承女孩类,女孩类继承装饰类。
  • 美人类可以继承男孩类,男孩类继承装饰类。
  • 美人类可以继承外星人类,外星人类继承装饰类。

所以我们就懂了,装饰类对象就是约会人。

以上分析,如有误欢迎指出。

组合模式(Composite)

组合模式:创建对象组的树形结构,依据树形结构来表示对象间“整体-部分”的层次关系。

使用场景

一所大学(University)可能设有多个学院(College),每个学院可能设有多个系(Department)。

郑州大学设有机械工程学院和外语学院。

机械工程学院设有机械工程和工业设计两个系。

外语学院设有英语、日语、俄语、德语四个系。

使用组合模式可以很方便地对郑州大学的院系关系进行管理。

实现方式

我们很容易想到创建University类,College类继承University类,然后Department类继承College类。

根据里氏替换原则,我们可以创建一个公共的基类Organization,使University、College、Department类都继承这个基类,采用聚合关系代替继承。

1.创建公共基类Organization

public abstract class Organization {
    private String name;//名称
    private String description;//描述
    public Organization(String name, String description) {
        this.name = name;
        this.description = description;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getDescription() {
        return description;
    }
    public void setDescription(String description) {
        this.description = description;
    }
    //考虑到系不需要add方法和remove方法,所以这里默认实现而非使用抽象方法
    protected void add(Organization organization) {
    }
    protected void remove(Organization organization) {
    }
    protected abstract void print();
}

2.创建University、College、Department

import java.util.ArrayList;
import java.util.List;

public class University extends Organization {
    List<Organization> organizations = new ArrayList<Organization>();
    public University(String name, String description) {
        super(name, description);
    }
    @Override
    public String getName() {
        return super.getName();
    }
    @Override
    public String getDescription() {
        return super.getDescription();
    }
    @Override
    protected void add(Organization organization) {
        organizations.add(organization);
    }
    @Override
    protected void remove(Organization organization) {
        organizations.remove(organization);
    }
    @Override
    protected void print() {
        System.out.println("--------" + getName() + "--------");
        for (Organization organization : organizations) {
            organization.print();
        }
    }
}
import java.util.ArrayList;
import java.util.List;

public class College extends Organization {
    List<Organization> organizations = new ArrayList<Organization>();
    public College(String name, String description) {
        super(name, description);
    }
    @Override
    public String getName() {
        return super.getName();
    }
    @Override
    public String getDescription() {
        return super.getDescription();
    }
    @Override
    protected void add(Organization organization) {
        organizations.add(organization);
    }
    @Override
    protected void remove(Organization organization) {
        organizations.remove(organization);
    }
    @Override
    protected void print() {
        System.out.println("----" + getName() + "----");
        for (Organization organization : organizations) {
            organization.print();
        }
    }
}
public class Department extends Organization {
    public Department(String name, String description) {
        super(name, description);
    }
    @Override
    public String getName() {
        return super.getName();
    }
    @Override
    public String getDescription() {
        return super.getDescription();
    }
    @Override
    protected void print() {
        System.out.print(getName());
        System.out.println(": " + getDescription());
    }
}

3.测试

public class Client {
    public static void main(String[] args) {
        Organization zzu = new University("郑州大学", "某不知名高校");
        Organization mechanicalCollege = new College("机械工程学院", "盛产帅哥");
        Organization languagesCollege = new College("外语学院", "盛产美女");
        mechanicalCollege.add(new Department("机械工程", "百分之八十的人数,百分之二十的女生"));
        mechanicalCollege.add(new Department("工业设计", "百分之二十的人数,百分之八十的女生"));
        languagesCollege.add(new Department("英语", "Hello"));
        languagesCollege.add(new Department("日语", "こんにちは"));
        languagesCollege.add(new Department("俄语", "Здравствыйте"));
        languagesCollege.add(new Department("德语", "Hallo!"));

        zzu.print();
        mechanicalCollege.print();
        languagesCollege.print();
    }
}

输出结果:

--------郑州大学--------
----机械工程学院----
机械工程: 百分之八十的人数,百分之二十的女生
工业设计: 百分之二十的人数,百分之八十的女生
----外语学院----
英语: Hello
日语: こんにちは
俄语: Здравствыйте
德语: Hallo!

外观模式(Facade)

外观模式:为子系统中的一组接口提供一个一致的界面,再定义了一个高层接口来访问这个界面。

使用场景

简化调用。

实现方式

小贝(Bei)要去上学,她的嫂子湘玉(Xiangyu)请求保镖老白(Bai)、书童秀才(Lv)、丫鬟小郭(Guo)陪同。

1.老白、秀才、小郭各司其职

(因为世界上只有一个老白、一个秀才、一个小郭,所以使用单例模式)

public class Bai {
    private final static Bai bai = new Bai();
    private Bai() {
    }
    public static Bai getBai() {
        return bai;
    }
    public void work() {
        System.out.println("白:像不像,像不像,行者悟空?");
    }
}
public class Lv {
    private final static Lv lv = new Lv();
    private Lv() {
    }
    public static Lv getLv() {
        return lv;
    }
    public void work() {
        System.out.println("吕:我看起来不会太老吧?");
    }
}
public class Guo {
    private final static Guo guo = new Guo();
    private Guo() {
    }
    public static Guo getGuo() {
        return guo;
    }
    public void work() {
        System.out.println("郭:我怎么觉得有点别扭啊?");
    }
}

2.上学去

如果没有湘玉,小贝要做的是:

public class Bei {
    public static void main(String[] args) {
        Guo guo = Bai.getBai();
        guo.work();        
        Lv lv = Lv.getLv();
        lv.work();
        Bai bai = Guo.getGuo();
        bai.work();
        System.out.println("贝:嫂子我去上学了!");
    }
}

输出结果:

郭:我怎么觉得有点别扭啊?
吕:我看起来不会太老吧?
白:像不像,像不像,行者悟空?
贝:嫂子我去上学了!

如果湘玉帮小贝打点:

public class Xiangyu {
    public Bai bai;
    public Lv lv;
    public Guo guo;
    public Xiangyu(Bai bai, Lv lv, Guo guo) {
        this.bai = bai;
        this.lv = lv;
        this.guo = guo;
    }
    public void request() {
        System.out.println("湘玉:来嘛来嘛!");
        guo.work();
        lv.work();
        bai.work();
        System.out.println("湘玉:美滴很美滴很!");
    }
}

小贝只需要:

public class Bei {
    public static void main(String[] args) {
        Xiangyu xiangyu = new Xiangyu(Bai.getBai(), Lv.getLv(), Guo.getGuo());
        xiangyu.request();
        System.out.println("贝:嫂子我去上学了!");
    }
}

输出结果:

湘玉:来嘛来嘛!
郭:我怎么觉得有点别扭啊?
吕:我看起来不会太老吧?
白:像不像,像不像,行者悟空?
湘玉:美滴很美滴很!
贝:嫂子我去上学了!

享元模式(Flyweight)

享元模式:运用共享技术有效地支持大量细粒度的对象。

使用场景

享元模式经典的应用场景是池技术。String常量池、数据库连接池、缓冲池等等。

实现方式

享元模式将对象的信息分为两个部分:内部状态和外部状态。

内部状态指对象共享出来的信息,存储在享元对象内部且不会随环境的改变而改变。

外部状态指对象得以依赖的一个标记,是随环境改变而改变的、不可共享的状态。

在一场围棋对局中,可能有两三百个棋子对象产生,如果用享元模式来处理棋子,棋子对象就可以减少到只有两个实例。

棋子的颜色是内部状态,棋子的坐标是外部状态。

Flyweight:抽象的享元角色,是产品的抽象类。同时定义出对象的外部状态和内部状态的接口或实现。

ConcreteFlyweight:具体的享元角色,是具体的产品类。实现抽象享元角色定义的相关业务。

UnsharedConcreteFlyweight:不可共享的角色,一般不会出现在享元工厂中。

FlyweightFactory:享元工厂类,用于构建池的容器(集合),并提供从池中获取对象的方法。

在一场围棋对局中:

棋笥(Box):FlyweightFactory

围棋棋子(GoPieces):Flyweight

每颗棋子(Piece):ConcreteFlyweight

棋子坐标(Coordinate):UnsharedConcreteFlyweight

吃瓜群众(Client):Client

1.围棋棋子(GoPieces)

每颗棋子可以在棋盘上的某个坐标(coordinate)被放置(place):

public abstract class GoPieces {
    public abstract void place(Coordinate coordinate);//需先创建Coordinate类
}

可以看到,GoPieces类中聚合了Coordinate类,我们认为棋子的坐标是棋子的外部状态。

2.棋子坐标(Coordinate)

棋子的坐标包括横坐标abscissa和纵坐标ordinate,补充构造方法和getter and setter:

public class Coordinate {
    private int abscissa;//横坐标
    private int ordinate;//纵坐标
    public Coordinate(int abscissa, int ordinate) {
        this.abscissa = abscissa;
        this.ordinate = ordinate;
    }
    public int getAbscissa() {
        return abscissa;
    }
    public void setAbscissa(int abscissa) {
        this.abscissa = abscissa;
    }
    public int getOrdinate() {
        return ordinate;
    }
    public void setOrdinate(int ordinate) {
        this.ordinate = ordinate;
    }
}

3.每颗棋子(Piece)

棋子的内部状态包括棋子的颜色color,Piece继承了GoPieces类,每颗棋子都可以在棋盘上的某个坐标(coordinate)被放置(place):

public class Piece extends GoPieces {
    private String color = "";
    private String coordinate = "";
    public Piece(String color) {
        this.color = color;
    }
    @Override
    public void place(Coordinate coordinate) {
        this.coordinate = "(" + coordinate.getAbscissa() + ", " + coordinate.getOrdinate() + "),";
        System.out.println(this.coordinate + color + "棋落子");
    }
}

4.棋笥(Box)

享元模式中,围棋棋子的内部状态被共享:

import java.util.HashMap;

public class Box {
    //集合,充当池
    private HashMap<String, Piece> pool = new HashMap<>();
    //根据颜色,返回一个棋子,如果没有就创建,并放到池中
    public GoPieces getPieceColor(String color) {
        if (!pool.containsKey(color)) {
            pool.put(color, new Piece(color));
        }
        return (GoPieces) pool.get(color);
    }
}

5.吃瓜群众(Client)

围观一场围棋对局:

public class Client {
    public static void main(String[] args) {
        Box box = new Box();
        GoPieces blackPiece = box.getPieceColor("黑");
        GoPieces whitePiece = box.getPieceColor("白");
        System.out.println("执黑先行");
        blackPiece.place(new Coordinate(17, 4));
        whitePiece.place(new Coordinate(4, 3));
        blackPiece.place(new Coordinate(16, 17));
        whitePiece.place(new Coordinate(15, 3));
        blackPiece.place(new Coordinate(3, 16));
        whitePiece.place(new Coordinate(17, 15));
        blackPiece.place(new Coordinate(16, 5));
        System.out.println("……");
    }
}

输出结果:

执黑先行
(17, 4),黑棋落子
(4, 3),白棋落子
(16, 17),黑棋落子
(15, 3),白棋落子
(3, 16),黑棋落子
(17, 15),白棋落子
(16, 5),黑棋落子
……

代理模式(Proxy)

代理模式:为一个对象提供一个替身,以控制对这个对象的访问。

使用场景

直接访问某个对象,可能会产生一些问题时,我们可以控制对这个对象的访问。

比如要访问的对象创建开销很大、或者某些操作需要安全控制、或者需要进程外的访问等等。

实现方式

代理模式有不同的形式,主要包括静态代理和动态代理。

静态代理

使用静态代理时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。

数学老师(MathTeacher)生病了,体育老师(PETeacher)来给他代课(Teach)。

1.数学老师授课

public interface Teach {
    void teach();
}
public class MathTeacher implements Teach {
    @Override
    public void teach() {
        System.out.println("这节课讲的是微积分");
    }
}

2.体育老师代课

public class PETeacher implements Teach {
    private Teach teacher;
    public PETeacher(Teach teacher) {
        this.teacher = teacher;
    }
    @Override
    public void teach() {
        System.out.println("体育老师:这节课由我来带大家学习");
        teacher.teach();
        System.out.println("同学们学的很好");
    }
}

3.学生听课

public class Student {
    public static void main(String[] args) {
        MathTeacher mathTeacher = new MathTeacher();
        PETeacher peTeacher = new PETeacher(mathTeacher);
        peTeacher.teach();
    }
}

输出结果:

体育老师:这节课由我来带大家学习
这节课讲的是微积分
同学们学的很好

静态代理优缺点:

  • 优点:在不修改目标对象的前提下,通过代理对象对目标功能进行了扩展
  • 缺点:代理对象需要与目标对象实现一样的接口,所以会产生很多代理类,一旦接口增加方法,目标对象和所有代理对象都需要进行维护

动态代理

动态代理以调用JDK中API的方式,动态地在内存中构建代理对象。

代理类所在包:java.lang.reflect.Proxy

调用Proxy类中的newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法,即可获得代理对象。

newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法利用了Java反射机制,它的三个参数分别为:

ClassLoader loader:指定当前目标对象使用的类加载器

Class<?>[] interfaces:目标对象实现的接口类型,使用泛型可以确认其类型

InvocationHandler h:事件处理器

数学老师(MathTeacher)生病了,组织(ProxyFactory)派人来给他代课(Teach)。

1.数学老师授课

public interface Teach {
    void teach();
}
public class MathTeacher implements Teach {
    @Override
    public void teach() {
        System.out.println("这节课讲的是微积分");
    }
}

2.生成代课老师(看起来很复杂,但写法比较固定)

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactory {
    private Object target;
    public ProxyFactory(Object target) {
        this.target = target;
    }
    public Object getProxyInstance() {
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object retrunValue = method.invoke(target, args);
                return retrunValue;
            }
        });
    }
}

3.吃瓜学生

public class Student {
    public static void main(String[] args) {
        Teach target = new MathTeacher();
        Teach proxyTeacher = (Teach) new ProxyFactory(target).getProxyInstance();
        proxyTeacher.teach();
    }
}

输出结果:

这节课讲的是微积分

加油!

猜你喜欢

转载自blog.csdn.net/qq_42082161/article/details/112289652
今日推荐