• 经典伴读_GOF设计模式_结构型模式


    经典伴读系列文章,想写的不是读书笔记,也不是读后感,自己的理解加上实际项目中运用,让大家4,5天读懂这本书
    在这里插入图片描述
    预备知识:

    结构型模式

    GOF中23种设计模式从用途上分为三类,第二类是结构型模式,描述的是如何组合类和对象以获得更大的结构。

    Adapter适配器

    将一个类的接口转换成客户希望的另外一个接口。 使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

    什么是适配?就是消除差异。如在已有的订单服务中,添加多平台订单来源(淘宝,京东),而淘宝,京东的订单结构肯定和已有的订单结构不同,这时候需要使用适配器模式。
    在这里插入图片描述

    	//已有系统订单
    	public static class MyOrder {
            private String orderId; //订单编号
            private Date createTime; //创建时间
            ...
    
    	//淘宝订单,字段意义相同,名称不同,需要适配
        public static class TBOrder {
            private String oid; //订单编号
            private Date ct; //创建时间
            ...
    
    	//已有系统订单服务
        interface MyOrderService {
            void saveOrder(MyOrder myOrder); //保存订单
        }
        public static class MyOrderServiceImpl implements MyOrderService{
            @Override
            public void saveOrder(MyOrder myOrder) {
                System.out.println("保存订单" + myOrder);
            }
        }
    
        //淘宝订单服务
        interface TaobaoOrderService {
            void saveTBOrder(TBOrder tbOrder); //保存订单
        }
        //通过继承的方式适配
        public static class OrderServiceAdapter
                extends MyOrderServiceImpl implements TaobaoOrderService {
            @Override
            public void saveTBOrder(TBOrder tbOrder) {
                MyOrder myOrder = new MyOrder();
                myOrder.setOrderId(tbOrder.getOid());
                myOrder.setCreateTime(tbOrder.getCt());
                saveOrder(myOrder);
            }
        }
    
    	public static void main(String[] args) {
            //测试订单
            TBOrder tbOrder = new TBOrder();
            tbOrder.setOid("1001");
            tbOrder.setCt(new Date());
    
            TaobaoOrderService taobaoOrderService = new OrderServiceAdapter();
            taobaoOrderService.saveTBOrder(tbOrder);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    注意:对照类图,我们已有的订单服务MyOrderServiceImpl称为Adaptee,需要适配的目标接口TaobaoOrderService称为Target,中间的OrderServiceAdapter就是Adapter。

     //通过继承的方式适配
     public static class OrderServiceAdapter
                extends MyOrderServiceImpl implements TaobaoOrderService 
    
    • 1
    • 2
    • 3

    适配器模式除了通过继承实现,还可以使用对象组合的方式,前者称为类适配器,后者称为对象适配器。java只能继承一个类,因此少用继承,多用对象组合。如一个Adapter对应多个Adaptee的情况。

    	//通过对象组合的方式适配
        public static class OrderServiceAdapter2 implements TaobaoOrderService {
            private MyOrderService myOrderService;
    
            public OrderServiceAdapter2() {
                this.myOrderService = new MyOrderServiceImpl(); //实际项目中由Spring注入
            }
    
            @Override
            public void saveTBOrder(TBOrder tbOrder) {
                MyOrder myOrder = new MyOrder();
                myOrder.setOrderId(tbOrder.getOid());
                myOrder.setCreateTime(tbOrder.getCt());
                myOrderService.saveOrder(myOrder);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    JDK中最常见的适配器是InputStreamReader,OutputStreamWriter,将字节流适配为字符流,它们的适配目标Target都不是接口,而是类Reader和Writer,因此只能通过对象组合实现适配。

    Bridge桥接

    将抽象部分与它的实现部分分离,使它们都可以独立地变化

    Bridge桥接模式,平时业务中很少能见到,因为这不只是一种模式,无法直接套用,而是一种设计系统的思路和规则。
    先来看下GOF中桥接的例子(有修改,保留原意),开发一个C端的GUI的界面库,包含两种窗口,带标题的窗口TitleWindow,和不带标题的窗口NoTitleWindow,每种窗口在Windows X(简称X系统)和 Linux(简称L系统)中都可以正常使用。要设计一套这样的界面库,初版设计可能是这样:
    在这里插入图片描述

    我们发现如果要开发10种窗口,那么至少需要20种类(每一种窗口都要匹配两个系统),这时就需要重新设计了,首要任务就是把和系统直接相关的代码(画线、画文字)抽离出来独立成实现类WindowImp。接着将画窗口操作根据系统实现类WindowImp中已有的方法进一步拆分,如画标题和画矩形框,封装到Window层。要求WIndow子类对WindowImp无感,也就是窗口子类根本不知道需要系统匹配这件事。
    在这里插入图片描述

    	//依赖系统的具体实现,可以是接口也可以是抽象类
        interface WindowImp {
            void drawLineByDev();
            void drawTextByDev();
        }
        public static class XWindowImpl implements WindowImp {
            @Override
            public void drawLineByDev() {
                System.out.println("WindowX系统画直线");
            }
    
            @Override
            public void drawTextByDev() {
                System.out.println("WindowX系统画文字");
            }
        }
        public static class LWindowImpl implements WindowImp {
            @Override
            public void drawLineByDev() {
                System.out.println("Linux系统画直线");
            }
    
            @Override
            public void drawTextByDev() {
                System.out.println("Linux系统画文字");
            }
        }
        
    	//将所有和WindowImpl的操作封装在Window层
        //Window子类不能感知WindowImp
        public static class Window {
            private WindowImp imp;
    
            public Window(WindowImp imp) {
                this.imp = imp;
            }
    
            public void drawText() {
                imp.drawTextByDev();
            }
    
            public void drawRect() {
                //一个矩形由四条线组成
                imp.drawLineByDev();
                imp.drawLineByDev();
                imp.drawLineByDev();
                imp.drawLineByDev();
            }
        }
        public static class TitleWindow extends Window {
            public TitleWindow(WindowImp imp) {
                super(imp);
            }
            public void drawWindow() {
                drawText(); //画标题
                drawRect();
            }
        }
        public static class NoTitleWindow extends Window{
            public NoTitleWindow(WindowImp imp) {
                super(imp);
            }
            public void drawWindow() {
                drawRect();
            }
        }
        
    	public static void main(String[] args) {
            //调用方决定使用哪个个系统的API
            TitleWindow titleWindow = new TitleWindow(new XWindowImpl());
            titleWindow.drawWindow();
            NoTitleWindow noTitleWindow = new NoTitleWindow(new LWindowImpl());
            noTitleWindow.drawWindow();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    将具体的系统实现从应用代码中分离,这才是GOF初衷,减少所需类的数量只是附带效果。很明显桥接模式对场景要求比较严苛,开发人员需要有较高的抽象能力,更多的应该出现在框架代码中。

    Composite组合

    将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。

    组合模式并不是“多用组合少用继承”中的组合,更像是android中的View和ViewGroup的关系,一个布局中可以放入一个文本,也可以继续放一个子布局,一直往下,就是一个树形结构,当我们需要渲染这棵树时,只需要将根节点渲染即可。这类明显带有递归色彩的场景,为了不必区分子节点类型,可以使用组合模式。
    在这里插入图片描述
    实际web项目中,组合模式最常见的场景是菜单展示。菜单本身是一个树状结构,节点类型是目录或叶子(带链接)。但在数据库中却是二维表形式存储,取出则是列表,此时需要使用递归构造树,并使用组合模式渲染树。(这里渲染的结果是xml格式,也可以是json等)
    在这里插入图片描述
    (1)按照组合模式构建树节点DTO

    	public static class MenuItem {
            protected int id;
            protected String name;
            protected int pid;
            
            public MenuItem(int id, String name, int pid) {
                this.id = id;
                this.name = name;
                this.pid = pid;
            }
    		//渲染
            public String print() {
                return "";
            }
        }
    	//叶子节点
        public static class MenuLeaf extends MenuItem{
            private String url;
    
            public MenuLeaf(int id, String name, int pid, String url) {
                super(id, name, pid);
                this.url = url;
            }
    
            @Override
            public String print() {
                StringBuilder builder = new StringBuilder();
                builder.append("");
                builder.append("").append(id).append("");
                builder.append("").append(name).append("");
                builder.append("").append(pid).append("");
                builder.append("").append(url).append("");
                builder.append("");
                return builder.toString();
            }
        }
    
        //目录节点
        public static class MenuDirectory extends MenuItem {
            private List<MenuItem> children = new ArrayList<>();
    
            public MenuDirectory(int id, String name, int pid) {
                super(id, name, pid);
            }
            public void addItem(MenuItem menuItem) {
                children.add(menuItem);
            }
            public void removeItem(MenuItem menuItem) {
                children.remove(menuItem);
            }
    
            @Override
            public String print() {
                StringBuilder builder = new StringBuilder();
                builder.append("");
                builder.append("").append(id).append("");
                builder.append("").append(name).append("");
                builder.append("").append(pid).append("");
                builder.append("");
                for (MenuItem menuItem : children) {
                    builder.append(menuItem.print());
                }
                builder.append("");
                builder.append("");
                return builder.toString();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    (2)递归构建树

        public static MenuItem buildTree(MenuItem current, List<MenuItem> menuItems) {
            if (current instanceof MenuDirectory) {
                menuItems.stream()
                        .filter(item -> item.pid == current.id)
                        .forEach(item -> {
                            ((MenuDirectory) current).addItem(buildTree(item, menuItems));
                        });
            }
            return current;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    (3)从数据库中查询菜单表测试数据,构建树并渲染。

        //数据库中菜单表PO
        public static class MenuItemPO {
            private int id;
            private int type; //0目录,1菜单
            private String name;
            private int pid;
            private String url;
    
    		public MenuItemPO(int id, int type, String name, int pid, String url) {
                this.id = id;
                this.type = type;
                this.name = name;
                this.pid = pid;
                this.url = url;
            }
            ......
        
        //模拟数据库中菜单数据
        public static List<MenuItemPO> getMenuItems() {
            List<MenuItemPO> menuItems = new ArrayList<>();
            menuItems.add(new MenuItemPO(1, 0, "目录1", 0, ""));
            menuItems.add(new MenuItemPO(2, 1, "菜单项a", 1, "http://菜单项a"));
            menuItems.add(new MenuItemPO(3, 1, "菜单项b", 1, "http://菜单项b"));
            menuItems.add(new MenuItemPO(4, 0, "目录2", 1, ""));
            menuItems.add(new MenuItemPO(5, 1, "菜单项c", 4, "http://菜单项c"));
            menuItems.add(new MenuItemPO(6, 1, "菜单项d", 0, "http://菜单项d"));
            return menuItems;
        }
    
    	//测试
    	public static void main(String[] args) {
            List<MenuItemPO> menuItemPOS = getMenuItems(); //获取数据库中菜单数据
            List<MenuItem> menuItems = menuItemPOS.stream() //PO转DTO
                    .map(po -> po.getType() == 0 ?
                    new MenuDirectory(
                            po.getId(),
                            po.getName(),
                            po.getPid()) :
                    new MenuLeaf(
                            po.getId(),
                            po.getName(),
                            po.getPid(),
                            po.getUrl())).collect(Collectors.toList());
            MenuDirectory root = new MenuDirectory(0, "root", -1); //根节点
            root = (MenuDirectory) buildTree(root, menuItems); //构建树
            String str = root.print(); //渲染树
            System.out.println(str);
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    输出的xml格式化:
    在这里插入图片描述
    另外,有的文章中说文件和文件夹也是一种组合模式,没错,但java中的File类不是,它只是文件路径的抽象。An abstract representation of file and directory pathnames.

    Decorator装饰器

    动态地给一个对象添加一些额外的职责。就增加功能来说, Decorator模式相比生成子类更为灵活。

    GOF想要给TextView添加滚动条,字体变粗。优先想到的肯定是使用继承,即ScrollTextView和BorderTextView,但这种方式明显不够灵活,如果既要滚动条又要字体变粗,是不是得再来一个ScrollBorderTextView。又或者加下划线,变粗加下划线,加边框两次等等,当需要给已有的类添加功能时,除了继承,还可以将额外功能做成壳,想要哪个套哪个,这就是更加灵活的装饰器模式。
    在这里插入图片描述

    	//View,TextView省略.....
    	
    	//抽象装饰器
    	public static class Decorator extends View {
            protected View component;
    
            public Decorator(View component) {
                this.component = component;
            }
    
            @Override
            public void draw() {
                component.draw();
            }
        }
    
    	//具体装饰器
        public static class BorderDecorator extends Decorator {
            private int borderWidth;
    
            public BorderDecorator(View component, int borderWidth) {
                super(component);
                this.borderWidth = borderWidth;
            }
    
            private void drawBorder(int width) {
                System.out.println("drawBorder, width=" + width);
            }
    
            @Override
            public void draw() {
                super.draw(); //先渲染文字
                drawBorder(borderWidth); //在渲染边框
            }
        }
        
    	public static void main(String[] args) {
            BorderDecorator borderDecorator = 
                    new BorderDecorator(new TextView("hello"), 2);
            borderDecorator.draw();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    实际使用时,不可能像上面这样"工整",可能没有抽象Decorator,但只要是在造壳子,都可以认为是装饰器模式,如JDK中BufferedReader:

    BufferedReader br = new BufferedReader(
                        new InputStreamReader(
                                new FileInputStream(
                                        "/Users/flyzing/Downloads/data.txt")));
    
    • 1
    • 2
    • 3
    • 4

    看到这里嵌套了三层从内到外,InputStreamReader嵌套FileInputStream使用的是对象适配器模式,将字节流适配到字符流,BufferedReader嵌套InputStreamReader使用的是装饰器模式,给字符流增加缓冲。
    在这里插入图片描述

    Facade外观

    为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

    外观模式是最少知识原则的应用,是常见的设计准则,即尽量减少外部依赖,没有固定形式,这里说几种常见场景,如:
    (1)系统内部分层,一个Service封装了多个Dao或其他Service,外部调用时只知道外层Service,那么外层Service就是Facade。
    (2)系统与系统之间通信,所有提供给外部系统的接口封装到Api类中,所有调用外部系统的接口先封装到Client类中,再给系统内部调用。那么Api类是对外Facade,Client类是对内Facade。
    (3)当做一些工具代码时,如代码生成器等,无论内部逻辑多么复杂,多少配置,有多少类,外部都只会提供一个生成类,其中有多种带参数的生成方法 ,那么外部生成类就是Facade。

    GOF认为一个子系统只需要一个Facade对象,并且是单例。Facade模式初衷是用于系统与系统之间的交互,这么看上面的第2个场景应该是最符合。

    Flyweight享元

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

    网上有些文章这样解释享元,享元就是共享对象,用一个HashMap预先存储对象,需要时从中获取。不不不,享元并不简单。
    (首先是共享模式的引入,这里做简化,觉得字多的同学可以跳过第1段)
    1、GOF又从文档编辑器入手,要创建一个文档编辑器,其中最多的不是行,不是列,而是每个字符,每个字符都可以拥有不同的格式,这就意味着每个字符都是一个对象。写一篇1万字符的文章就同时有1万个字对象在内存中。这可不是好兆头。为了减少对象数量,将字中状态加以区分,可以共享的状态,如文本(只有26种,别抬杠,GOF肯定不是指汉字),以及不能共享的状态,如字体、颜色、大小等各种格式。将可以共享的状态单独变为享元对象(Flyweight),以共享状态为key存放到池中。将不能共享的状态单独变为上下文对象(Context),以字符索引为key构建一个BTree,不是每个字符都有格式,这棵格式树不会太大。渲染字符时,根据字符文本(共享状态)从池中取出享元对象,然后再从格式树中检索出非共享状态,加在一起就可以渲染出一个字符。

    2、原型模式可以减少类的数量,享元模式可以减少对象的数量。当我们有大量相似对象存在内存中时,可以使用享元模式。共享模式的关键是区分享元对象的内部状态和外部状态。
    (1)内部状态intrinsicState,是可以共享的信息,并且不可变。如字符的文本,无论有多少字符的文章(英文),字符的文本只有26个。
    (2)外部状态extrinsicState,是不可以共享的信息,随着外部环境改变而改变,如字符的格式,随着字符索引的变化,可能有不同的大小、颜色、字体。
    (3)内部状态随着享元对象一起存入池中(如HashMap),外部状态需要客户端自己存储,
    在这里插入图片描述

    	interface Flyweight {
            void operation(String extrinsicState);
        }
    
        public static class ConcreteFlyWeight implements Flyweight{
            private final String intrinsincSate; //内部状态初始化后不能修改
    
            public ConcreteFlyWeight(String intrinsincSate) {
                this.intrinsincSate = intrinsincSate;
            }
    
            @Override
            public void operation(String extrinsicState) { //外部状态由客户端传入,可以改变
                System.out.println("内部状态:" + intrinsincSate + ",外部状态:" + extrinsicState);
            }
        }
    
        public static class FlyWeightFactory {
            private final static Map<String, Flyweight> POOL = new HashMap<>();
    
            public static Flyweight getFlyWeight(String intrinsincSate) {
                Flyweight flyweight = POOL.get(intrinsincSate);
                if (flyweight == null){
                    synchronized (POOL) { //放入池中,需要加锁
                        flyweight = POOL.get(intrinsincSate);
                        if (flyweight == null) {
                            flyweight = new ConcreteFlyWeight(intrinsincSate);
                        }
                        POOL.put(intrinsincSate, flyweight);
                    }
                }
                return flyweight;
            }
        }
    
        public static void main(String[] args) {
            Flyweight a = FlyWeightFactory.getFlyWeight("a");
            a.operation("字体=宋体,颜色=红色,大小=24");
            System.out.println(a);
    
            a = FlyWeightFactory.getFlyWeight("a");
            a.operation("字体=黑体,颜色=黑色,大小=12");
            System.out.println(a);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    输出:
    内部状态:a,外部状态:字体=宋体,颜色=红色,大小=24
    com.example.learn.ConcreteFlyWeight@27973e9b
    内部状态:a,外部状态:字体=黑体,颜色=黑色,大小=12
    com.example.learn.ConcreteFlyWeight@27973e9b

    由此可以看出对象和内部状态都没变,外部状态却不同,这就是享元模式。

    3、实际项目中的享元可以简单得多,如JDK中的享元Integer。Integer自动装箱时调用的是Integer.valueOf(int i)方法,默认情况下当i在-128到127之间时,返回值从IntegerCache中取,否则创建新的Integer对象。

    public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试代码

    	public static void main(String[] args) {
            Integer a = 127;
            Integer b = 127;
            System.out.println(a == b); //true
    
            a = 128;
            b = 128;
            System.out.println(a == b); //false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    类加载时,预先在IntegerCache加入的-128到127个Integer就是享元对象。因此,比较Integer等封装类型时不要使用==。

    Proxy代理

    为其他对象提供一种代理以控制对这个对象的访问。

    GOF还是从文档编辑器入手,当我们打开一个包含数个大图片的长文档时,为了能够迅速展开页面,我们需要先将图片按照文件长宽预占位置,等到滚动到需要图片渲染的位置,再加载图片。当需要控制某个对象的方法调用时,需要使用代理模式。
    在这里插入图片描述

    	interface View {
            void draw(); //渲染
        }
    
        //模拟图片
        public static class Image {
            private String fileName;
            public Image(String fileName) {
                this.fileName = fileName;
            }
        }
    
        public static class ImageView implements View {
            private Image imageImpl;
    
            public ImageView(String fileName) {
                imageImpl = load(fileName); //创建ImageView时加载图片
            }
    
            @Override
            public void draw() {
                System.out.println("draw " + imageImpl);
            }
            //从磁盘加载图片
            private Image load(String fileName) {
                System.out.println("load " + fileName);
                return new Image(fileName);
            }
        }
    
        //代理
        public static class ImageViewProxy implements View {
            private ImageView imageView;
            private String fileName;
    
            public ImageViewProxy(String fileName) {
                this.fileName = fileName; //创建代理时不加载图片
            }
    
            @Override
            public synchronized void draw() {
                if (imageView == null) { //渲染时加载图片
                    imageView = new ImageView(fileName);
                }
                imageView.draw();
            }
        }
    
        public static void main(String[] args) {
            View imageProxy = new ImageViewProxy("BigImage.jpg");
            imageProxy.draw();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    是否加载图片,什么时间加载图片都是代理类控制,这就是代理模式。实际项目中代理模式体现形式比较单一,都是在调用某个对象方法前后加点东西,如AOP,过滤器,拦截器等。
    在这里插入图片描述

    	interface Subject {
            void request();
        }
    
        public static class RealSubject implements Subject {
            @Override
            public void request() {
                System.out.println("RealSubject.request()");
            }
        }
    
        public static class Proxy implements Subject{
            private RealSubject realSubject;
    
            public Proxy(RealSubject realSubject) {
                this.realSubject = realSubject;
            }
    
            @Override
            public void request() {
                System.out.println("判断是否有权限访问");
                realSubject.request();
                System.out.println("记录访问日志");
            }
        }
    
        public static void main(String[] args) {
            //Proxy类代替RealSubject
            Subject proxy = new Proxy(new RealSubject());
            proxy.request();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    上例中为每一个RealSubject创建一个Proxy类的方式称为静态代理,如果不止一个类需要访问控制时,需要使用动态代理,如需要对所有Service类添加事前的权限判断和事后的日志记录。动态代理JDK和CGLIB中都有API支持,这已经不是设计模式的范畴。
    最后说回代理模式,它的代码形式和装饰器模式基本一致,只能依靠用途区分,添加新功能就是装饰器模式,需要对方法访问控制就是代理模式。

    设计模式重意不重形,未完待续

  • 相关阅读:
    汽车屏类产品(四):仪表Cluster
    Java面向对象编程
    SpringBoot使用spring.config.import多种方式导入配置文件
    我的sql没问题为什么还是这么慢|MySQL加锁规则
    POI入门
    k8s相关的概念
    学习并了解MQ消息队列
    单机多卡、多机多卡的艺术
    C++基础——引用讲解1
    Linux安装与卸载MySql
  • 原文地址:https://blog.csdn.net/flyzing/article/details/126051433