• Java设计模式(三)结构性设计模式


    四 结构性设计模式

    4.1 外观模式

    0ae926eca828fc06ad5958e40f42031c_facade.png

    • 门面模式(Facade)可能是最简单的结构型设计模式,它能将多个不同的子系统接口封装起来,并对外提供统一的高层接口,使复杂的子系统变得更易使用。
    • 外观模式也叫门面模式,主要解决的是降低调用方的使用接口的复杂逻辑组合。这样调用方与实际的接口提供方提供方提供了一个中间层,用于包装逻辑提供API接口。
    • 有些时候外观模式也被用在中间件层,对服务中的通用性复杂逻辑进行中间件层包装,让使用方可以只关心业务开发。
    • 总之,无论门面内部如何错综复杂,从门面外部看来总是一目了然,使用起来也很简单。

    4.1.1 代码实现

    菜贩

    package com.shu;
    
    /**
     * @description: 蔬菜类
     * @author: shu
     * @createDate: 2022/9/10 17:35
     * @version: 1.0
     */
    public class VegVendor {
    
        /**
         * 供应
         */
        public void supply(){
            System.out.println("菜贩供应蔬菜给饭馆");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    厨师

    package com.shu;
    
    /**
     * @description: 厨房厨师
     * @author: shu
     * @createDate: 2022/9/10 17:36
     * @version: 1.0
     */
    public class Helper {
    
        /**
         * 烹饪菜
         */
        public void cook(){
            System.out.println("厨师正在烹饪菜");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    服务员

    package com.shu;
    
    /**
     * @description: 服务员
     * @author: shu
     * @createDate: 2022/9/10 17:38
     * @version: 1.0
     */
    public class Waiter {
    
    
        /**
         * 上菜
         */
        public void service(){
            System.out.println("服务员上菜");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    清洁

    package com.shu;
    
    /**
     * @description: 清洁工
     * @author: shu
     * @createDate: 2022/9/10 17:39
     * @version: 1.0
     */
    public class Clear {
    
        public void clearRoom(){
            System.out.println("清洁工打扫房间");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    饭馆

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/10 17:40
     * @version: 1.0
     */
    public class RestaurantFace {
        private VegVendor vegVendor;
        private Helper helper;
        private Waiter waiter;
        private Clear clear;
    
    
        public RestaurantFace() {
            vegVendor=new VegVendor();
            helper=new Helper();
            waiter=new Waiter();
            clear=new Clear();
        }
    
    
        /**
         * 对外提供接口,完成一些复杂动作
         */
        public void order(){
            vegVendor.supply();
            helper.cook();
            waiter.service();
            clear.clearRoom();
        }
    
    
    }
    
    
    • 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

    测试

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/10 17:42
     * @version: 1.0
     */
    public class Client {
        public static void main(String[] args) {
            RestaurantFace face = new RestaurantFace();
            face.order();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    image.png
    外观门面类内部封装了大量的子系统资源,如蔬菜商、厨师、服务员、洗碗工,并于构造方法中依次对各个子系统进行了初始化操作,也就是说餐厅在开门前需要提前准备好这些资源,进行依次调度。

    4.1.2 外观模式的结构

    epub_39201628_58.png

    • Facade(外观门面):封装了多个子系统,并将它们整合起来对外提供统一的访问接口。
    • SubSystemA、SubSystemB、SubSystemC(子系统A、子系统B、子系统C):隐藏于门面中的子系统,数量任意,且对外部不可见。对应本章例程中的蔬菜商类、厨师类、服务员类等。
    • Client(客户端):门面系统的使用方,只访问门面提供的接口。对客户端这种“门外汉”来说,直接使用子系统是复杂而烦琐的,门面则充当了包装类的角色,对子系统进行整合,再对外暴露统一接口,使其结构内繁外简,最终达到资源共享、简化操作的目的。从另一方面讲,门面模式也降低了客户端与子系统之间的依赖度,高内聚才能低耦合。

    4.1.3 mybatis中的外观模式

    ec4b3d0e2578ddeb63e8a88b539a84ad_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMzM5MjEw,size_16,color_FFFFFF,t_70.png
    由上面的类图可以看出,client只需要调用Configuration的newMetaObject(Object object)方法就可以得到一个MetaObject对象,而具体的对象是怎么生成与client无关,下面我们可以看一下Configuration的部分源码分析。

    //Configuration 类:
    public class Configuration {
    	protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
    	protected ObjectFactory objectFactory = new DefaultObjectFactory();
      	protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
    
    	 public MetaObject newMetaObject(Object object) {
        	return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
        }
    }
    
    //MetaObject类
    public class MetaObject {
    	private Object originalObject;
    	private ObjectWrapper objectWrapper;
    	private ObjectFactory objectFactory;
    	private ObjectWrapperFactory objectWrapperFactory;
    	private ReflectorFactory reflectorFactory;
    
    	public static MetaObject forObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        	if (object == null) {
        		return SystemMetaObject.NULL_META_OBJECT;
        	} else {
          		return new MetaObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
        	}
     	}
    
    	private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
        	this.originalObject = object;
        	this.objectFactory = objectFactory;
        	this.objectWrapperFactory = objectWrapperFactory;
       		this.reflectorFactory = reflectorFactory;
    
        	if (object instanceof ObjectWrapper) {
          		this.objectWrapper = (ObjectWrapper) object;
        	} else if (objectWrapperFactory.hasWrapperFor(object)) {
          		this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
        	} else if (object instanceof Map) {
          		this.objectWrapper = new MapWrapper(this, (Map) object);
        	} else if (object instanceof Collection) {
         		this.objectWrapper = new CollectionWrapper(this, (Collection) object);
        	} else {
          		this.objectWrapper = new BeanWrapper(this, object);
      		}
    	}
    }
    
    
    • 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

    4.2 组合模式(重点理解)

    7525460b3a59a935cc9d1a3b242b926d_composite.png

    • 组合模式又叫部分整体模式,它创建了对象组的属性结构,将对象组合成树状结构以表示“整体-部分”的层次关系。
    • 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
    • 这种类型的设计模式属于结构型模式。
    • 组合模式使得用户对单个对象和嘴和对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象。

    4.2.1 营销决策树案例

    8a8622029fd61e95f147d82f362be66e_itstack-demo-design-8-02.png
    详细参考:https://bugstack.cn/md/develop/design-pattern/2020-06-08-%E9%87%8D%E5%AD%A6%20Java%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8A%E5%AE%9E%E6%88%98%E7%BB%84%E5%90%88%E6%A8%A1%E5%BC%8F%E3%80%8B.html

    4.2.2 组合模式的结构

    e2d18bac1fcdbe3546c10cea52a69db2_structure-zh.png
    优点

    • 你可以利用多态和递归机制更方便地使用复杂树结构。
    • 开闭原则。 无需更改现有代码, 你就可以在应用中添加新元素, 使其成为对象树的一部分。

    缺点

    • 对于功能差异较大的类, 提供公共接口或许会有困难。 在特定情况下, 你需要过度一般化组件接口, 使其变得令人难以理解。

    4.2.3 代码中是组合模式

    java的集合类-HashMap

     static class Node<K,V> implements Map.Entry<K,V> {
            final int hash;
            final K key;
            V value;
            Node<K,V> next;
    
            Node(int hash, K key, V value, Node<K,V> next) {
                this.hash = hash;
                this.key = key;
                this.value = value;
                this.next = next;
            }
    
            public final K getKey()        { return key; }
            public final V getValue()      { return value; }
            public final String toString() { return key + "=" + value; }
    
            public final int hashCode() {
                return Objects.hashCode(key) ^ Objects.hashCode(value);
            }
    
            public final V setValue(V newValue) {
                V oldValue = value;
                value = newValue;
                return oldValue;
            }
    
            public final boolean equals(Object o) {
                if (o == this)
                    return true;
                if (o instanceof Map.Entry) {
                    Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                    if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                        return true;
                }
                return false;
            }
        }
    
    
    
    
    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable {
    
        private static final long serialVersionUID = 362498820763181265L;
    
     	public V put(K key, V value) {
            return putVal(hash(key), key, value, false, true);
        }
    
    	/**
         * Copies all of the mappings from the specified map to this map.
         * These mappings will replace any mappings that this map had for
         * any of the keys currently in the specified map.
         *
         * @param m mappings to be stored in this map
         * @throws NullPointerException if the specified map is null
         */
        public void putAll(Map<? extends K, ? extends V> m) {
            putMapEntries(m, true);
        }
    }
    
    
    
    
    • 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

    4.3 装饰器模式

    545d1ba2e22beb21c43b32ecab4cabb7_decorator.png

    • 装饰器模式(Decorator)能够在运行时动态地为原始对象增加一些额外的功能,使其变得更加强大。
    • 从某种程度上讲,装饰器非常类似于“继承”,它们都是为了增强原始对象的功能,区别在于方式的不同,后者是在编译时(compile-time)静态地通过对原始类的继承完成,而前者则是在程序运行时(run-time)通过对原始对象动态地“包装”完成,是对类实例(对象)“装饰”的结果。
    • 穿衣服是使用装饰的一个例子。 觉得冷时, 你可以穿一件毛衣。 如果穿毛衣还觉得冷, 你可以再套上一件夹克。 如果遇到下雨, 你还可以再穿一件雨衣。 所有这些衣物都 “扩展” 了你的基本行为, 但它们并不是你的一部分, 如果你不再需要某件衣物, 可以方便地随时脱掉。

    4.3.1 案列代码

    人们总是惊叹女生们魔法师一般的化妆技巧,可以从素面朝天变成花容月貌(如图9-2所示),化妆前后简直判若两人,这正是装饰器的粉饰效果在发挥作用。
    化妆接口

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 11:46
     * @version: 1.0
     */
    public interface Showable {
    
        // 展示
        public void  show();
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    素颜

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 11:47
     * @version: 1.0
     */
    public class Girl implements Showable{
    
    
        @Override
        public void show() {
            System.out.println("女生的素颜");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    抽象类

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 11:48
     * @version: 1.0
     */
    public abstract class Decorator implements Showable{
    
    
        private Showable showable;
    
        public Decorator(Showable showable) {
            this.showable = showable;
        }
    
    
        @Override
        public void show() {
            showable.show();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    粉底

    package com.shu;
    
    /**
     * @description: 粉底
     * @author: shu
     * @createDate: 2022/9/11 12:06
     * @version: 1.0
     */
    public class FoundationMakeup extends Decorator {
    
        public FoundationMakeup(Showable showable) {
            super(showable);
        }
    
        @Override
        public void show() {
            System.out.println("打粉底了");
            super.show();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    口红

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 12:07
     * @version: 1.0
     */
    public class Lipstick extends Decorator{
    
        public Lipstick(Showable showable) {
            super(showable);
        }
    
    
        @Override
        public void show() {
            System.out.println("涂口红");
            super.show();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    测试

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 11:49
     * @version: 1.0
     */
    public class Client {
        public static void main(String[] args) {
           new Lipstick(new FoundationMakeup(new Girl())).show();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 可以看到我们在不改变原来类的基础上,对类进行增强,进行包装,实现更多的功能。
    • 装饰器模式最终的目的就在于“装饰”对象,其中装饰器抽象类扮演着至关重要的角色,它实现了组件的通用接口,并且使自身抽象化以迫使子类继承,使装饰器固定特性的延续与多态化成为可能。
    • 在我看来有点无限套娃。

    4.3.2 装饰模式结构

    c21261487a96f4ad2cbadd96896e1969_structure.png
    优点

    • 你无需创建新子类即可扩展对象的行为。
    • 你可以在运行时添加或删除对象的功能。
    • 你可以用多个装饰封装对象来组合几种行为。
    • 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。

    缺点

    • 在封装器栈中删除特定封装器比较困难。
    • 实现行为不受装饰栈顺序影响的装饰比较困难。
    • 各层的初始化配置代码看上去可能会很糟糕。

    4.3.3 代码中的使用

    java.io中的使用
    在Java开发工具包(Java Development Kit,JDK)里就有大量应用,例如“java.io”包里一系列的流处理类InputStream、FileInputStream、BufferedInputStream、ZipInputStream等。

    InputStream inputStream = new BufferedInputStream(new FileInputStream(""));
    
    • 1

    4.4 适配器模式

    73a6314b0e66eb6fe66e63cd6448dfc7_adapter-zh.png

    • 适配器模式(Adapter)通常也被称为转换器,顾名思义,它一定是进行适应与匹配工作的物件。
    • 当一个对象或类的接口不能匹配用户所期待的接口时,适配器就充当中间转换的角色,以达到兼容用户接口的目的,同时适配器也实现了客户端与接口的解耦,提高了组件的可复用性。、
    • 适配器在我们生活中非常常见,如内存卡转换器、手机充电器、各种USB接口适配器等,再如我们上网用的调制解调器,它能够进行数模转换,让互联网服务提供商(ISP)与用户之间的网络接口互相适配与兼容,最终使两端进行正常通信。

    4.4.1 代码实现

    三相接口

    package com.shu;
    
    /**
     * @description: 三相插孔接口
     * @author: shu
     * @createDate: 2022/9/11 17:30
     * @version: 1.0
     */
    public interface TriplePin {
        /**
         * 参数分别为火线,零线,地线
         * @param l
         * @param n
         * @param e
         */
        void electrify(int l,int n, int e);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    两相插孔接口

    package com.shu;
    
    /**
     * @description: 两相插孔接口
     * @author: shu
     * @createDate: 2022/9/11 17:33
     * @version: 1.0
     */
    public interface DualPin {
        /**
         * 参数分别为零线与火线
         * @param l
         * @param n
         */
        void electrify(int l,int n);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    电视

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 17:35
     * @version: 1.0
     */
    public class TV implements DualPin{
        /**
         * 参数分别为火线,零线
         *
         * @param l
         * @param n
    
         */
        @Override
        public void electrify(int l, int n) {
            System.out.println("接通火线,零线");
            System.out.println("电视开机");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    测试

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 17:37
     * @version: 1.0
     */
    public class Client {
        public static void main(String[] args) {
            // 接口不兼容
           TriplePin triplePin= new TV();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    修改创建适配器

    package com.shu;
    
    /**
     * @description: 插座适配器
     * @author: shu
     * @createDate: 2022/9/11 17:40
     * @version: 1.0
     */
    public class Adapter implements TriplePin{
    
        private DualPin dualPin;
    
        public Adapter(DualPin dualPin) {
            this.dualPin = dualPin;
        }
    
    
        /**
         * 参数分别为火线,零线,地线
         *
         * @param l
         * @param n
         * @param e
         */
        @Override
        public void electrify(int l, int n, int e) {
            dualPin.electrify(l, n);
        }
    }
    
    
    • 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

    测试

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 17:37
     * @version: 1.0
     */
    public class Client {
        public static void main(String[] args) {
            DualPin dualPin = new TV();
            Adapter adapter = new Adapter(dualPin);
            adapter.electrify(1,0,-1);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    最后,我们直接调用三插通电方法给电视机供电,表面上看我们使用的是三插通电标准,而实际上是用两插标准为电视机供电(只使用了火线与零线),最终电视机顺利开启,两插标准的电视机与三相插孔接口成功得以适配。

    4.4.2 适配器模式的结构

    81b3e506ee814f160f3aa4348ced817d_structure-class-adapter_id=e1c60240508146ed3b98ac562cc8e510.png
    优点

    • _单一职责原则_你可以将接口或数据转换代码从程序主要业务逻辑中分离。
    • 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

    缺点

    • 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。

    4.4.3 框架中的使用

    Mbatis 日志收集分析

    • Java开发中经常用到的日志框架有很多,Log4j、Log4j2、slf4j等等,Mybatis定义了一套统一的日志接口供上层使用,并为上述常用的日志框架提供了相应的适配器
    • 在Mybatis的日志模块中就是使用了适配器模式。Mybatis内部在使用日志模块时,使用了其内部接口 org.apache.ibatis.logging.Log,但是常用的日志框架的对外接口各不相同,Mybatis为了复用和集成这些第三方日志组件,在其日志模块中,提供了多种Adapter,将这些第三方日志组件对外接口适配成org.apache.ibatis.logging.Log,这样Myabtis 就可以通过Log接口调用第三方日志了

    日志接口

    public interface Log {
    
      boolean isDebugEnabled();
    
      boolean isTraceEnabled();
    
      void error(String s, Throwable e);
    
      void error(String s);
    
      void debug(String s);
    
      void trace(String s);
    
      void warn(String s);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    实现类

    public class Log4jImpl implements Log {
    
      private static final String FQCN = Log4jImpl.class.getName();
    
      private final Logger log;
    
      public Log4jImpl(String clazz) {
        log = Logger.getLogger(clazz);
      }
    
      @Override
      public boolean isDebugEnabled() {
        return log.isDebugEnabled();
      }
    
      @Override
      public boolean isTraceEnabled() {
        return log.isTraceEnabled();
      }
    
      @Override
      public void error(String s, Throwable e) {
        log.log(FQCN, Level.ERROR, s, e);
      }
    
      @Override
      public void error(String s) {
        log.log(FQCN, Level.ERROR, s, null);
      }
    
      @Override
      public void debug(String s) {
        log.log(FQCN, Level.DEBUG, s, null);
      }
    
      @Override
      public void trace(String s) {
        log.log(FQCN, Level.TRACE, s, null);
      }
    
      @Override
      public void warn(String s) {
        log.log(FQCN, Level.WARN, s, null);
      }
    
    }
    
    
    • 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

    4.5 享元模式

    197be40f50d8ad7853abafad7ea45c00_flyweight-zh_id=3454f49363769767c6ff3500cf9f4889.png

    • 享元模式是一种结构型设计模式, 它摒弃了在每个对象中保存所有数据的方式, 通过共享多个对象所共有的相同状态, 让你能在有限的内存容量中载入更多对象。
    • 享元”则是共享元件的意思。享元模式的英文flyweight是轻量级的意思,这就意味着享元模式能使程序变得更加轻量化。当系统存在大量的对象,并且这些对象又具有相同的内部状态时,我们就可以用享元模式共享相同的元件对象,以避免对象泛滥造成资源浪费。

    4.5.1 代码实现

    我们利用享元模式,加载游戏中的地图
    绘图接口

    package com.shu;
    
    /**
     * @description: 绘图接口
     * @author: shu
     * @createDate: 2022/9/11 18:15
     * @version: 1.0
     */
    public interface Drawable {
        /**
         * 绘制坐标
         * @param x
         * @param y
         */
        void draw(int x,int y);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    分别绘制河流,草地,道路,房屋

    package com.shu;
    
    /**
     * @description: 河流地图
     * @author: shu
     * @createDate: 2022/9/11 18:16
     * @version: 1.0
     */
    public class River implements Drawable{
    
        private String images;
    
        public River() {
            this.images = "河流";
            System.out.println("从磁盘加载......");
        }
    
        /**
         * 绘制坐标
         *
         * @param x
         * @param y
         */
        @Override
        public void draw(int x, int y) {
            System.out.println("在坐标:"+x+"  "+y+"绘制"+images);
        }
    }
    
    
    • 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
    package com.shu;
    
    /**
     * @description: 草地地图
     * @author: shu
     * @createDate: 2022/9/11 18:22
     * @version: 1.0
     */
    public class Grass implements Drawable{
    
    
        private String images;
    
        public Grass() {
            this.images = "草地";
            System.out.println("从磁盘加载......");
        }
    
    
        /**
         * 绘制坐标
         *
         * @param x
         * @param y
         */
        @Override
        public void draw(int x, int y) {
            System.out.println("在坐标:"+x+"  "+y+"绘制"+images);
        }
    }
    
    
    • 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
    package com.shu;
    
    /**
     * @description: 道路
     * @author: shu
     * @createDate: 2022/9/11 18:23
     * @version: 1.0
     */
    public class Road implements Drawable{
    
    
        private String images;
    
        public Road() {
            this.images = "道路";
            System.out.println("从磁盘加载......");
        }
    
    
        /**
         * 绘制坐标
         *
         * @param x
         * @param y
         */
        @Override
        public void draw(int x, int y) {
            System.out.println("在坐标:"+x+"  "+y+"绘制"+images);
        }
    }
    
    
    • 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
    package com.shu;
    
    /**
     * @description: 房屋地图
     * @author: shu
     * @createDate: 2022/9/11 18:25
     * @version: 1.0
     */
    public class House implements Drawable{
    
    
        private String images;
    
        public House() {
            this.images = "房屋";
            System.out.println("从磁盘加载......");
        }
    
        /**
         * 绘制坐标
         *
         * @param x
         * @param y
         */
        @Override
        public void draw(int x, int y) {
    
        }
    }
    
    
    • 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

    地图工厂

    package com.shu;
    
    /**
     * @description: 地图工厂
     * @author: shu
     * @createDate: 2022/9/11 18:26
     * @version: 1.0
     */
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class TileFactory {
    
        private Map<String, Drawable> images;//图库
    
        public TileFactory() {
            images = new HashMap<String, Drawable>();
        }
    
        public Drawable getDrawable(String image) {
            //缓存池里如果没有图件,则实例化并放入缓存池
            if (!images.containsKey(image)) {
                switch (image) {
                    case "河流":
                        images.put(image, new River());
                        break;
                    case "草地":
                        images.put(image, new Grass());
                        break;
                    case "道路":
                        images.put(image, new Road());
                        break;
                    case "房屋":
                        images.put(image, new House());
                }
            }
    
            //至此,缓存池里必然有图件,直接取得并返回
            return images.get(image);
        }
    
    }
    
    
    • 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

    测试

    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/11 18:27
     * @version: 1.0
     */
    public class Client {
    
        public static void main(String[] args) {
            //先实例化图件工厂
            TileFactory factory = new TileFactory();
    
            //随便绘制一列为例
            factory.getDrawable("河流").draw(10, 10);
            factory.getDrawable("河流").draw(10, 20);
            factory.getDrawable("道路").draw(10, 30);
            factory.getDrawable("草地").draw(10, 40);
            factory.getDrawable("草地").draw(10, 50);
            factory.getDrawable("草地").draw(10, 60);
            factory.getDrawable("草地").draw(10, 70);
            factory.getDrawable("草地").draw(10, 80);
            factory.getDrawable("道路").draw(10, 90);
            factory.getDrawable("道路").draw(10, 100);
    
            //绘制完地板后接着在顶层绘制房屋
            factory.getDrawable("房屋").draw(10, 10);
            factory.getDrawable("房屋").draw(10, 50);
    
    
        }
    
    }
    
    
    • 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

    我们抛弃了利用“new”关键字随意制造对象的方法,改用这个图件工厂类来构建并共享图件元,外部需要什么图件直接向图件工厂索要即可。此外,图件工厂类返回的图件实例也不再包含坐标信息这个属性了,而是将其作为绘图方法的参数即时传入。

    4.5.2 享元模式的结构

    877e796f7d1ca3d67ae7ad99fa28ef4c_structure_id=c1e7e1748f957a4792822f902bc1d420.png
    优点

    • 如果程序中有很多相似对象, 那么你将可以节省大量内存。

    缺点

    • 你可能需要牺牲执行速度来换取内存, 因为他人每次调用享元方法时都需要重新计算部分情景数据。
    • 代码会变得更加复杂。 团队中的新成员总是会问: “为什么要像这样拆分一个实体的状态?”。

    4.5.3 代码中的实现

    平常在使用Integer类的时候。你是否思考过用valueOf还是用new创建Integer对象。看完源码就会发现在valueOf这个方法中它会先判断传进去的值是否在IntegerCache中,如果不在就创建新的对象,在就直接返回缓存池里的对象。这个valueOf方法就用到享元模式。它将-128到127的Integer对象先在缓存池里创建好,等我们需要的时候直接返回即可。所以在-128到127中的数值我们用valueOf创建会比new更快。如果你还想继续往里钻研,可以去看看IntegerCache如何实现。(面试题)

    public final class Integer extends Number implements Comparable<Integer> {
        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
        private final int value;
        public Integer(int value) {
            this.value = value;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    4.6 代理模式

    220b5d3e0622f1eae09dac2433d76db1_proxy_id=efece4647fb11e3f7539291796327666.png

    • 代理模式(Proxy),顾名思义,有代表打理的意思。某些情况下,当客户端不能或不适合直接访问目标业务对象时,业务对象可以通过代理把自己的业务托管起来,使客户端间接地通过代理进行业务访问。
    • 代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。

    4.6.1 代码实现

    epub_39201628_91.png

    package com.shu;
    
    /**
    * @description: 上网接口
    * @author: shu
    * @createDate: 2022/9/17 14:19
    * @version: 1.0
    */
    public interface Internet {
    
        /**
    * 连通上网
    * @param url
    */
        void  httpAccess(String url);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    package com.shu;
    
    /**
     * @description: 真正上网的对象
     * @author: shu
     * @createDate: 2022/9/17 14:21
     * @version: 1.0
     */
    public class Model implements Internet{
    
        public Model(String passWord) throws Exception {
            if(!"123456".equals(passWord)){
                throw new Exception("密码错误!!!");
            }
            System.out.println("拨号成功!!!");
        }
    
    
        /**
         * 连通上网
         *
         * @param url
         */
        @Override
        public void httpAccess(String url) {
            System.out.println("正在上网"+url);
        }
    }
    
    
    • 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
    package com.shu;
    
    /**
     * @description: 代理对象
     * @author: shu
     * @createDate: 2022/9/17 14:25
     * @version: 1.0
     */
    
    import java.util.Arrays;
    import java.util.List;
    
    public class RouterProxy implements Internet {
    
    
        private Internet modem;//被代理对象
        private List<String> blackList = Arrays.asList("电影", "游戏", "音乐", "小说");
    
    
    
        public RouterProxy() throws Exception {
            this.modem = new Model("123456");//实例化被代理类
        }
    
    
        @Override
        public void httpAccess(String url) {//实现互联网访问接口方法
            for (String keyword : blackList) {//遍历黑名单
                if (url.contains(keyword)) {//是否包含黑名单中的字眼
                    System.out.println("禁止访问:" + url);
                    return;
                }
            }
            modem.httpAccess(url);//转发请求至“猫”以访问互联网
        }
    
    }
    
    • 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
    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/17 14:27
     * @version: 1.0
     */
    public class Client {
    
        public static void main(String[] args) throws Exception {
            Internet proxy = new RouterProxy();//实例化的是代理
            proxy.httpAccess("http://www.电影.com");
            proxy.httpAccess("http://www.游戏.com");
            proxy.httpAccess("ftp://www.学习.com/java");
            proxy.httpAccess("http://www.工作.com");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    屏幕截图 2022-09-17 143105.png

    • 这是代理模式区别于装饰器模式的一种体现。虽然二者的理念与实现有点类似,但装饰器模式往往更加关注为其他对象增加功能,让客户端更加灵活地进行组件搭配。
    • 而代理模式更强调的则是一种对访问的管控,甚至是将被代理对象完全封装而隐藏起来,使其对客户端完全透明。读者大可不必被概念所束缚,属于哪种模式并不重要,最适合系统需求的设计就是最好的设计。

    4.6.2 动态代理

    记住重要的一点,为其他对象提供一个代理以控制对某个对象的访问。代理类主要负责为委托了(真实对象)预处理消息、过滤消息、传递消息给委托类,代理类不现实具体服务,而是利用委托类来完成服务,并将执行结果封装处理。
    在我们的现实生活中其实有很多这样的案例,不如商品经销商与厂家的关系,出租房的人与中介。

    • 子类继承同一个接口
    package com.shu.agency;
    
    /**
     * @author shu
     * @version 1.0
     * @date 2022/7/20 16:00
     * @describe 代理类和委托代理类都要实现的接口
     */
    public interface Sell {
        /**
         * 销售产品
         */
        void market();
    
        /**
         * 生成产品
         */
        void add();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 厂家
    package com.shu.agency;
    
    /**
     * @author shu
     * @version 1.0
     * @date 2022/7/20 16:03
     * @describe 生产厂家
     */
    public class Production implements Sell{
    
        @Override
        public void market() {
            System.out.println("生产厂家销售产品了哦!!!");
        }
    
        @Override
        public void add() {
            System.out.println("生产厂家销售产品了哦!!!");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 经销商
    package com.shu.agency;
    
    /**
     * @author shu
     * @version 1.0
     * @date 2022/7/20 16:05
     * @describe 商家代销产品
     */
    public class Merchant implements Sell{
    
        Sell production;
    
        public Merchant(Production production) {
            this.production = production;
        }
    
        @Override
        public void market() {
            production.market();
        }
    
        @Override
        public void add() {
            production.add();
        }
    }
    
    
    • 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
    • 在这我们可以看出被委托类持有委托类的引用,在实际中调用还是委托者的方法。
    • 这里我们也可以看到类之间的解耦,不用修改被委托的类就可以做额外处理,但是缺点就是我们需要指定被代理的类。
    • 由字面意思可知,在程序运行期间生成的代理方法,我们称为动态代理,注意是在Java代码中动态生成。
    • 在使用动态代理时,我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler接口。
    /**
     * 调用处理程序
     */
    public interface InvocationHandler { 
        Object invoke(Object proxy, Method method, Object[] args); 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 从InvocationHandler这个名称我们就可以知道,实现了这个接口的中介类用做“调用处理器”。
    • 当我们调用代理类对象的方法时,这个“调用”会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用。
    • 这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。

    定义中介类

    package com.shu.agency;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    /**
     * @author shu
     * @version 1.0
     * @date 2022/7/20 19:33
     * @describe
     */
    public class DynamicProxy implements InvocationHandler {
        //obj为委托类对象;
    
        private Object obj;
    
    
        public DynamicProxy(Object obj) {
            this.obj = obj;
        }
    
        /**
         * 通过反射来执行真正的方法
         * @param proxy
         * @param method
         * @param args
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object result = method.invoke(obj, args);
            return result;
        }
    }
    
    
    • 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

    测试

    package com.shu.agency;
    
    import java.lang.reflect.Proxy;
    
    /**
     * @author shu
     * @version 1.0
     * @date 2022/7/20 19:35
     * @describe
     */
    public class ProxyTest {
        public static void main(String[] args) {
            //创建中介类实例
            DynamicProxy inter = new DynamicProxy(new Production());
            //加上这句将会产生一个$Proxy0.class文件,这个文件即为动态生成的代理类文件
            System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
            //获取代理类实例sell
            Sell sell = (Sell)(Proxy.newProxyInstance(Sell.class.getClassLoader(), new Class[] {Sell.class}, inter));
            sell.market();
            sell.add();
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4.6.3 动态代理的结构

    c9f4ceb65151a3b55c6756451d44dd22_structure.png
    优点

    • 你可以在客户端毫无察觉的情况下控制服务对象。
    • 如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理。
    • 即使服务对象还未准备好或不存在, 代理也可以正常工作。
    • 开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理。

    缺点

    • 代码可能会变得复杂, 因为需要新建许多类。
    • 服务响应可能会延迟。

    4.6.4 框架中的实现

    • 动态代理MapperProxy:请参考,https://www.yuque.com/docs/share/dca739b5-7f1f-4593-9149-c9215e34da6b?# 《Mybatis源码分析》
    • 如Spring的面向切面编程技术AOP,我们只需要定义好一个切面类@Aspect并声明其切入点@Pointcut(),以及被切入的代码块,框架就会自动帮我们生成代理并切入目标。我们最常用到的就是给多个类方法前后动态加入写日志,此外还有为业务类加上数据库事务控制(业务代码开始前先切入“事务开始”,执行过后再切入“事务提交”,如果抛异常被捕获则执行“事务回滚”),如此就不必为每个业务类都写这些重复代码了,整个系统对数据库的访问都得到了事务管控,开发效率得到了提升。

    4.7 桥接模式

    c506887dd8540ffea0a90d210406b18d_bridge.png

    • 桥接模式是一种结构型设计模式, 可将一个大类或一系列紧密相关的类拆分为抽象和实现两个独立的层次结构, 从而能在开发时分别使用。
    • 桥接模式(Bridge)能将抽象与实现分离,使二者可以各自单独变化而不受对方约束,使用时再将它们组合起来,就像架设桥梁一样连接它们的功能,如此降低了抽象与实现这两个可变维度的耦合度,以保证系统的可扩展性。

    4.7.1 代码实现

    package com.shu;
    
    /**
     * @description: 画笔
     * @author: shu
     * @createDate: 2022/9/17 14:51
     * @version: 1.0
     */
    public abstract class Pen {
    
        protected Ruler ruler;
    
        public Pen(Ruler ruler) {
            this.ruler = ruler;
        }
    
        
        public abstract void draw();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/17 14:54
     * @version: 1.0
     */
    public interface Ruler {
    
        /**
         * 规则
         */
        public void regularize();
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/17 14:52
     * @version: 1.0
     */
    public class BlackPen extends Pen{
        public BlackPen(Ruler ruler) {
            super(ruler);
        }
    
        @Override
        public void draw() {
            System.out.println("黑");
            ruler.regularize();
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/17 14:58
     * @version: 1.0
     */
    public class WhitePen extends Pen {
        public WhitePen(Ruler ruler) {
            super(ruler);
        }
    
        @Override
        public void draw() {
            System.out.println("白");
            ruler.regularize();
        }
    
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/17 14:57
     * @version: 1.0
     */
    public class CircleRuler implements Ruler{
        /**
         * 规则
         */
        @Override
        public void regularize() {
            System.out.println("圆形");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/17 14:56
     * @version: 1.0
     */
    public class SquareRuler implements Ruler{
        /**
         * 规则
         */
        @Override
        public void regularize() {
            System.out.println("正方形");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/17 14:56
     * @version: 1.0
     */
    public class TriangleRuler implements Ruler{
        /**
         * 规则
         */
        @Override
        public void regularize() {
            System.out.println("三角形");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    package com.shu;
    
    /**
     * @description:
     * @author: shu
     * @createDate: 2022/9/17 14:59
     * @version: 1.0
     */
    public class Client {
    
        public static void main(String args[]) {
    
            //白色画笔对应的所有形状
            new WhitePen(new CircleRuler()).draw();
            new WhitePen(new SquareRuler()).draw();
            new WhitePen(new TriangleRuler()).draw();
    
            //黑色画笔对应的所有形状
            new BlackPen(new CircleRuler()).draw();
            new BlackPen(new SquareRuler()).draw();
            new BlackPen(new TriangleRuler()).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

    屏幕截图 2022-09-17 150443.png

    4.7.2 桥接模式的结构

    ff2830144a119d5223c9455c465431f6_structure-zh.png
    优点

    • 你可以创建与平台无关的类和程序。
    • 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。
    • 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。

    缺点

    • 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。
    • 对高内聚的类使用该模式可能会让代码更加复杂。
  • 相关阅读:
    SpringBoot缓存使用方式@EnableCaching、@Cacheable
    回溯leetcode.1774
    Tomat的默认servlet(DefaultServlet)
    《网络协议》01. 基本概念
    Jmeter系列(4) 线程属性详解
    用node开发微信群聊机器人第②章
    QDockWidget重新实现的事件处理
    自动化搜索ARX密码差分特征的方法
    Hadoop高可用环境搭建-HDFSNameNode高可用搭建、Yarn高可用搭建
    EhLib VCL v11.0.021 for Delphi Crack
  • 原文地址:https://blog.csdn.net/weixin_44451022/article/details/126907349