分类: 设计模式

  • 关于设计模式:浅谈设计模式-状态模式十三

    浅谈设计模式 – 状态模式(十三)

    前言

    ​ 状态模式其实也是一个非常常见的模式,最常见的利用场景是线程的状态切换,最常应用的形式就是对于If/else进行解耦,另外这个模式能够配合 责任链模式组合搭配出各种不同的状态切换成果,能够用设计模式模仿一个简略的“工作流”。

    优缺点:

    ​ 状态模式非常明显的是用于解耦大量If/else的模式,所以他的长处非常突出,就是能够简化大量的if/else判断,然而毛病页非常显著,那就是程序的执行受限于状态,如果状态的常量十分多的状况下,仍然会呈现大量的if/else的景象,状态模式和策略模式一样一旦状况十分复杂的时候很容易造成类的收缩,当然少数状况下这种毛病简直能够疏忽,总比太多。

    ​ 对于状态模式在jdk1.8之后的lambada表达式中能够有体现,lambada实现了java的参数“办法化”,这样极大地简化了类的收缩,然而可能比拟难以了解,并且一旦简单仍然倡议应用状态来实现切换,这样更不便保护。

    状态模式的结构图:

    ​ 上面是状态模式的结构图,自身比较简单,仿佛并没有什么特地的中央,然而当咱们和“策略模式”比照之后,仿佛很容易混同,上面咱们来看下这两个结构图的比照:

    ​ 状态模式结构图:

    策略模式结构图:

    ​ 通过比照能够看到,状态和策略这两个模式是十分相似的,那么咱们应该如何辨别这两个模式呢?在平时的开发工作中,如果一个对象有很多种状态,并且这个对象在不同状态下的行为也不一样,那么咱们就能够应用状态模式来解决这个问题,然而如果你让同一事物在不同的时刻有不同的行为,能够应用策略模式触发不同的行为。打个比方,如果你想让开关呈现不同的行为,你须要设计两个状态开关,而后在事件处理的时候将逻辑散发到不同的状态实现触发,而如果你想要实现相似商品的折扣或者优惠促销,满减等等“模式”的时候更加适宜应用策略,当然如果无奈辨别也没有关系,哪个应用更为纯熟即可。

    案例:糖果机

    ​ 这是《head first设计模式》中对于状态模式案例当中的糖果机,咱们能够从上面的图中看到如果应用单纯的代码来实现上面的工作,就会呈现十分多难堪的状况,比方大量繁冗的if/else代码充斥,上面咱们来看下对于这个糖果机依照一般的形式要如何设计?

    不应用设计模式

    ​ 不应用设计模式的状况下,咱们通常的做法是定义状态常量,比方设置枚举或者间接设置final的标示位等等,咱们

    1. 首先咱们须要划分对象,糖果机和糖果,糖果机蕴含硬币的总钱数,糖果的数量等等。

      1. 定义四个状态:售罄,售出中,存在硬币,没有硬币
    2. 为了实现状态的实现,咱们须要设计相似枚举的常量来示意糖果机的状态。

      1. 状态设置为常量,而糖果机须要内置机器的状态
    3. 最初用逻辑代码和判断让糖果机外部进行工作,当然这会呈现巨多的if/else判断。

    ​ 最初咱们的代码表现形式如下,用传统的模式咱们很可能写出相似的代码:

    ​ MechanicaState:定义了糖果机的状态,当然能够作为糖果机的公有外部类定义应用,也能够设计为枚举,这里偷懒设计为一个常量类的模式。

    
    /**
     * 机器状态
     */
    public final class MechanicaState {
    
        /**
         * 售罄
         */
        public static final int SOLD_OUT = 0;
        /**
         * 存在硬币
         */
        public static final int HAS = 1;
        /**
         * 没有硬币
         */
        public static final int NOT = 2;
    
        /**
         * 售出糖果中
         */
        public static final int SOLD = 4;
    
    
    }

    ​ CandyMechaica:糖果器,蕴含了糖果外部的工作办法,能够看到有十分多冗余的If/else判断:

    /**
     * 糖果机
     */
    public class CandyMechanica {
    
        /**
         * 默认是售罄的状态
         */
        private int sold_state = SOLD_OUT;
    
        /**
         * 糖果数量
         */
        private int count = 0;
    
        public CandyMechanica(int count) throws InterruptedException {
            if(count <= 0){
                throw new InterruptedException("初始化失败");
            }else {
                sold_state = NOT;
            }
            System.out.println("欢迎光临糖果机,以后糖果机的糖果数量为:"+ count);
            this.count = count;
        }
    
    
        /**
         * 启动糖果机
         */
        public void startUp(){
            switch (sold_state){
                case NOT:
                    System.out.println("以后糖果机没有硬币,请先投入硬币");
                    break;
                case SOLD:
                    System.out.println("糖果售出中,请稍后");
                    break;
                case HAS:
                    sold_state = SOLD;
                    candySold();
                    break;
                case SOLD_OUT:
                    System.out.println("糖果已售罄");
                    break;
    
    
            }
        }
    
        /**
         * 投入硬币的操作
         */
        public void putCoin(){
            switch (sold_state){
                case NOT:
                    sold_state = HAS;
                    System.out.println("投入硬币胜利,请开启糖果机");
                    break;
                case SOLD:
                    System.out.println("糖果售出中,请勿反复投放");
                    break;
                case HAS:
                    System.out.println("以后曾经存在硬币,请勿反复投放");
                    break;
                case SOLD_OUT:
                    System.out.println("糖果已售罄,您投入的硬币将会在稍后退回");
                    break;
    
    
            }
        }
    
    
        /**
         * 售出糖果
         */
        public void candySold(){
            switch (sold_state){
                case NOT:
                    System.out.println("以后机器内没有硬币,请先投入硬币");
                    break;
                case SOLD:
                    System.out.println("糖果已售出,请取走您的糖果");
                    count--;
                    if(count == 0){
                        System.out.println("以后糖果曾经售罄");
                        sold_state = SOLD_OUT;
                    }
                    sold_state = NOT;
                    break;
                case HAS:
                    sold_state = NOT;
                    System.out.println("以后曾经存在硬币,请勿反复投放");
                    break;
    
            }
        }
    
    
    
    }
    

    ​ 最初就是对于下面的糖果机器进行简略的单元测试:

    /**
     * 单元测试
     */
    public class Main {
    
        public static void main(String[] args) throws InterruptedException {
    
            CandyMechanica candyMechanica = new CandyMechanica(5);
            candyMechanica.putCoin();
    
            candyMechanica.startUp();
            candyMechanica.startUp();
            candyMechanica.putCoin();
            candyMechanica.putCoin();
            candyMechanica.startUp();
            candyMechanica.putCoin();
            candyMechanica.startUp();
            candyMechanica.putCoin();
            candyMechanica.startUp();
            candyMechanica.putCoin();
            candyMechanica.startUp();
        }
    }/*运行后果:
    欢迎光临糖果机,以后糖果机的糖果数量为:5
    投入硬币胜利,请开启糖果机
    糖果已售出,请取走您的糖果
    以后糖果机没有硬币,请先投入硬币
    投入硬币胜利,请开启糖果机
    以后曾经存在硬币,请勿反复投放
    糖果已售出,请取走您的糖果
    投入硬币胜利,请开启糖果机
    糖果已售出,请取走您的糖果
    投入硬币胜利,请开启糖果机
    糖果已售出,请取走您的糖果
    投入硬币胜利,请开启糖果机
    糖果已售出,请取走您的糖果
    以后糖果曾经售罄
    
    Process finished with exit code 0
    
    */

    应用状态模式重构

    ​ 接着咱们应用状态模式来重构一下下面的代码,咱们重点关注CandyMechanica这个类,他的三个办法耦合了大量的if/else判断,在编写这种代码的时候不仅会使得代码非常的死板,而且很容易出错,我想没有人会喜爱写下面这样的代码,所以上面咱们应用状态模式看下如何重构:

    1. 糖果机分为四个状态,然而他们有着相似的行为:推入硬币,启动机器,推出糖果这三个办法

      1. 应用接口的模式定时状态的专用行为
    2. 咱们把下面三个状态抽取为状态的专用办法,然而context在哪里?
    3. context在这里的表现形式为糖果机,咱们应用在状态外部组合糖果机的模式实现糖果机的状态“切换”。
    4. 留神:因为应用了外部类的内置模式,所以有时候很多判断能够简化,更多的时候倡议抽出来作为独自的类。

    ​ 最初他的表现形式如下:

    CandyState:糖果机分为四个状态,然而他们有着相似的行为:推入硬币,启动机器,推出糖果这三个办法

    /**
     * 糖果状态
     */
    public interface CandyState {
    
        /**
         * 启动糖果机
         */
        void startUp();
    
        /**
         * 投入硬币
         */
        void putCoin();
    
        /**
         * 推出糖果
         */
        void candySold();
    }

    糖果机还是很简单,当然并不像是下面的模式:

    ​ CandyMechanica:重写之后的糖果机,此糖果机把所有的状态解耦并且抽取为对象的模式。

    /**
     * 状态模式重写糖果机
     */
    public class CandyMechanica implements CandyState {
    
        private int count;
        /**
         * 以后状态
         */
        private CandyState nowState;
    
        // 有硬币
        private CandyState hasState;
        // 无硬币
        private CandyState notState;
        // 售罄
        private CandyState solidOutState;
        // 售出中
        private CandyState solidState;
    
    
        public CandyMechanica(int count) throws InterruptedException {
            notState = new NotState(this);
            solidOutState = new SoldOutState(this);
            hasState = new HasState(this);
            solidState = new SoldOutState(this);
            if (count <= 0) {
                throw new InterruptedException("初始化失败");
            } else {
                nowState = notState;
            }
            this.count = count;
        }
    
        @Override
        public void startUp() {
            nowState.startUp();
        }
    
        @Override
        public void putCoin() {
            nowState.putCoin();
        }
    
        @Override
        public void candySold() {
            nowState.candySold();
        }
    
    
        /**
         *
         */
        public static class HasState implements CandyState {
    
            private CandyMechanica candyMechanica;
    
            public HasState(CandyMechanica candyMechanica) {
                this.candyMechanica = candyMechanica;
            }
    
            @Override
            public void startUp() {
                candyMechanica.nowState = candyMechanica.solidState;
                candyMechanica.candySold();
                System.out.println("糖果售出中,请稍后");
            }
    
            @Override
            public void putCoin() {
                System.out.println("以后已有糖果,请勿反复投入");
            }
    
            @Override
            public void candySold() {
                System.out.println("糖果已售罄");
            }
        }
    
    
        /**
         * 售罄状态
         */
        public static class SoldOutState implements CandyState {
    
            private CandyMechanica candyMechanica;
    
            public SoldOutState(CandyMechanica candyMechanica) {
                this.candyMechanica = candyMechanica;
            }
    
            @Override
            public void startUp() {
                System.out.println("糖果已售罄");
            }
    
            @Override
            public void putCoin() {
                System.out.println("糖果已售罄,您投入的硬币将会在稍后退回");
            }
    
            @Override
            public void candySold() {
                System.out.println("糖果已售罄");
            }
        }
    
        /**
         * 售出状态
         */
        public static class SoldState implements CandyState {
    
            private CandyMechanica candyMechanica;
    
            public SoldState(CandyMechanica candyMechanica) {
                this.candyMechanica = candyMechanica;
            }
    
            @Override
            public void startUp() {
                System.out.println("糖果售出中,请稍后");
            }
    
            @Override
            public void putCoin() {
                System.out.println("糖果售出中,请勿反复投放");
            }
    
            @Override
            public void candySold() {
                System.out.println("糖果已售出,请取走您的糖果");
                candyMechanica.count--;
                if (candyMechanica.count == 0) {
                    System.out.println("以后糖果曾经售罄");
                    candyMechanica.nowState = candyMechanica.solidOutState;
                }
                candyMechanica.nowState = candyMechanica.notState;
            }
        }
    
        /**
         * 无硬币状态
         */
        public static class NotState implements CandyState {
    
            private CandyMechanica candyMechanica;
    
            public NotState(CandyMechanica candyMechanica) {
                this.candyMechanica = candyMechanica;
            }
    
            @Override
            public void startUp() {
                System.out.println("以后糖果机没有硬币,请先投入硬币");
            }
    
            @Override
            public void putCoin() {
                candyMechanica.nowState = candyMechanica.hasState;
                System.out.println("投入硬币胜利,请开启糖果机");
            }
    
            @Override
            public void candySold() {
                System.out.println("以后机器内没有硬币,请先投入硬币");
            }
        }
    }
    

    最初是单元测试的局部:

    /**
     * 单元测试
     */
    public class Main {
    
        public static void main(String[] args) throws InterruptedException {
            CandyMechanica candyMechanica = new CandyMechanica(5);
            candyMechanica.putCoin();
            candyMechanica.putCoin();
            candyMechanica.startUp();
            candyMechanica.candySold();
            candyMechanica.startUp();
            candyMechanica.putCoin();
            candyMechanica.startUp();
            candyMechanica.putCoin();
            candyMechanica.startUp();
            candyMechanica.putCoin();
            candyMechanica.startUp();
            candyMechanica.putCoin();
            candyMechanica.startUp();
        }
    }/*运行后果:
    欢迎光临糖果机,以后糖果机的糖果数量为:5
    投入硬币胜利,请开启糖果机
    以后曾经存在硬币,请勿反复投放
    糖果已售出,请取走您的糖果
    以后机器内没有硬币,请先投入硬币
    以后糖果机没有硬币,请先投入硬币
    投入硬币胜利,请开启糖果机
    糖果已售出,请取走您的糖果
    投入硬币胜利,请开启糖果机
    糖果已售出,请取走您的糖果
    投入硬币胜利,请开启糖果机
    糖果已售出,请取走您的糖果
    投入硬币胜利,请开启糖果机
    糖果已售出,请取走您的糖果
    以后糖果曾经售罄
    */

    总结

    ​ 本文的代码比拟多,状态模式也是和策略一样,只有看一眼样例代码即可。

    ​ 上面咱们来具体总结一下状态模式的特点,应用状态模式的劣势有以下几个方面:

    • 将利用的代码解耦,利于浏览和保护。咱们能够看到,在第一种计划中,咱们应用了大量的if/else来进行逻辑的判断,将各种状态和逻辑放在一起进行解决。在咱们利用相干对象的状态比拟少的状况下可能不会有太大的问题,然而一旦对象的状态变得多了起来,这种耦合比拟深的代码保护起来几乎就是噩梦。
    • 将变动封装进具体的状态对象中,相当于将变动部分化,并且进行了封装。利于当前的保护与拓展。应用状态模式之后,咱们把相干的操作都封装进对应的状态中,如果想批改或者增加新的状态,也是很不便的。对代码的批改也比拟少,扩展性比拟好。
    • 通过组合和委托,让对象在运行的时候能够通过扭转状态来扭转本人的行为。咱们只须要将对象的状态图画进去,专一于对象的状态扭转,以及每个状态有哪些行为。这让咱们的开发变得简略一些,也不容易出错,可能保障咱们写进去的代码品质是不错的。

    写在最初

    ​ 状态模式应用频率和策略模式差不多,应用的中央还是比拟多的,也是能够疾速的简化代码的一种设计模式。

  • 关于设计模式:俺就因为把int改成Integer第2天被辞了

    本文节选自《设计模式就该这样学》之享元模式(Flyweight Pattern)

    1 故事背景

    一个程序员就因为改了生产环境上的一个办法参数,把int型改成了Integer类型,因为波及到钱,后果上线之后公司损失惨重,程序员被解雇了。信不信持续往下看。先来看一段代码:

    public static void main(String[] args) {
    
            Integer a = Integer.valueOf(100);
            Integer b = 100;
    
            Integer c = Integer.valueOf(129);
            Integer d = 129;
    
            System.out.println("a==b:" + (a==b));
            System.out.println("c==d:" + (c==d));
    }

    大家猜它的运行后果是什么?在运行完程序后,咱们才发现有些不对,失去了一个意想不到的运行后果,如下图所示。

    看到这个运行后果,有人就肯定会问,为什么是这样?之所以失去这样的后果,是因为Integer用到的享元模式。来看Integer的源码,

    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);
    
            }
    
            ...
    
    }

    再持续进入到IntegerCache的源码来看low和high的值:

    private static class IntegerCache {
      // 最小值
      static final int low = -128;
      // 最大值,反对自定义
      static final int high;
      // 缓存数组
      static final Integer cache[];
    
      static {
        // 最大值能够通过属性配置来扭转
        int h = 127;
        String integerCacheHighPropValue =
          sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        // 如果设置了对应的属性,则应用该值
        if (integerCacheHighPropValue != null) {
          try {
            int i = parseInt(integerCacheHighPropValue);
            i = Math.max(i, 127);
            // 最大数组大小为Integer.MAX_VALUE
            h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
          } catch( NumberFormatException nfe) {
            // If the property cannot be parsed into an int, ignore it.
          }
        }
        high = h;
    
        cache = new Integer[(high - low) + 1];
        int j = low;
        // 将low-high范畴内的值全副实例化并存入数组中当缓存应用
        for(int k = 0; k < cache.length; k++)
          cache[k] = new Integer(j++);
    
        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
      }
    
      private IntegerCache() {}
    }

    由上可知,Integer源码中的valueOf()办法做了一个条件判断,如果目标值在-128 – 127,则间接从缓存中取值,否则新建对象。其实,Integer第一次应用的时候就会初始化缓存,其中范畴最小值为-128,最大值默认是127。接着会把low至high中所有的数据初始化存入数据中,默认就是将-128 – 127总共256个数循环实例化存入cache数组中。精确的说应该是将这256个对象在内存中的地址存进数组中。这里又有人会问了,那为什么默认是-128 – 127,怎么不是-200 – 200或者是其余值呢?那JDK为何要这样做呢?

    在Java API 中是这样解释的:

    Returns an Integer instance representing the specified int value. If a new Integer instance is not required, this method should generally be used in preference to the constructor Integer(int), as this method is likely to yield significantly better space and time performance by caching frequently requested values. This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range

    大抵意思是:

    128~127的数据在int范畴内是应用最频繁的,为了缩小频繁创建对象带来的内存耗费,这里其实是用到了享元模式,以进步空间和工夫性能。

    JDK减少了这一默认的范畴并不是不可变,咱们在应用前能够通过设置-Djava.lang.Integer.IntegerCache.high=xxx或者设置-XX:AutoBoxCacheMax=xxx来批改缓存范畴,如下图:

    起初,我又找到一个比拟靠谱的解释:

    实际上,在Java 5中首次引入此性能时,范畴固定为-127到+127。起初在Java 6中,范畴的最大值映射到java.lang.Integer.IntegerCache.high,VM参数容许咱们设置高位数。依据咱们的利用用例,它能够灵便地调整性能。应该从-127到127抉择这个数字范畴的起因应该是什么。这被认为是宽泛应用的整数范畴。在程序中首次应用Integer必须破费额定的工夫来缓存实例。

    Java Language Specification 的原文解释如下:

    Ideally, boxing a given primitive value p, would always yield an identical reference. In practice, this may not be feasible using existing implementation techniques. The rules above are a pragmatic compromise. The final clause above requires that certain common values always be boxed into indistinguishable objects. The implementation may cache these, lazily or eagerly. For other values, this formulation disallows any assumptions about the identity of the boxed values on the programmer’s part. This would allow (but not require) sharing of some or all of these references. This ensures that in most common cases, the behavior will be the desired one, without imposing an undue performance penalty, especially on small devices. Less memory-limited implementations might, for example, cache all char and short values, as well as int and long values in the range of -32K to +32K.

    2 对于Integer和int的比拟

    \1) 因为Integer变量实际上是对一个Integer对象的援用,所以两个通过new生成的Integer变量永远是不相等的(因为new生成的是两个对象,其内存地址不同)。

    Integer i = new Integer(100);
    Integer j = new Integer(100);
    System.out.print(i == j); //false

    \2) Integer变量和int变量比拟时,只有两个变量的值是向等的,则后果为true(因为包装类Integer和根本数据类型int比拟时,java会主动拆包装为int,而后进行比拟,实际上就变为两个int变量的比拟)

    Integer i = new Integer(100);
    int j = 100;
    System.out.print(i == j); //true

    \3) 非new生成的Integer变量和new Integer()生成的变量比拟时,后果为false。(因为 ①当变量值在-128 – 127之间时,非new生成的Integer变量指向的是java常量池中的对象,而new Integer()生成的变量指向堆中新建的对象,两者在内存中的地址不同;②当变量值在-128 – 127之间时,非new生成Integer变量时,java API中最终会依照new Integer(i)进行解决(参考上面第4条),最终两个Interger的地址同样是不雷同的)

    Integer i = new Integer(100);
    Integer j = 100;
    System.out.print(i == j); //false

    \4) 对于两个非new生成的Integer对象,进行比拟时,如果两个变量的值在区间-128到127之间,则比拟后果为true,如果两个变量的值不在此区间,则比拟后果为false

    3 扩大常识

    在JDK中,这样的利用不止int,以下包装类型也都利用了享元模式,对数值做了缓存,只是缓存的范畴不一样,具体如下表所示:

    根本类型 大小 最小值 最大值 包装器类型 缓存范畴 是否反对自定义
    boolean Bloolean
    char 6bit Unicode 0 Unic ode 2(16)-1 Character 0~127
    byte 8bit -128 +127 Byte -128~127
    short 16bit -2(15) 2(15)-1 Short -128~127
    int 32bit -2(31) 2(31)-1 Integer -128~127 反对
    long 64bit -2(63) 2(63)-1 Long -128~127
    float 32bit IEEE754 IEEE754 Float
    double 64bit IEEE754 IEEE754 Double
    void Void

    大家感觉这个锅背得值不值?

    4 应用享元模式实现数据库连接池

    再举个例子,咱们常常应用的数据库连接池,因为应用Connection对象时次要性能耗费在建设连贯和敞开连贯的时候,为了进步Connection对象在调用时的性能,将Connection对象在调用前创立好并缓存起来,在用的时候间接从缓存中取值,用完后再放回去,达到资源重复利用的目标,代码如下。

    public class ConnectionPool {
    
        private Vector<Connection> pool;
    
        private String url = "jdbc:mysql://localhost:3306/test";
        private String username = "root";
        private String password = "root";
        private String driverClassName = "com.mysql.jdbc.Driver";
        private int poolSize = 100;
    
    public ConnectionPool() {
    
            pool = new Vector<Connection>(poolSize);
    
            try{
                Class.forName(driverClassName);
                for (int i = 0; i < poolSize; i++) {
                    Connection conn = DriverManager.getConnection(url,username,password);
                    pool.add(conn);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
    
        }
    
        public synchronized Connection getConnection(){
            if(pool.size() > 0){
                Connection conn = pool.get(0);
                pool.remove(conn);
                return conn;
            }
            return null;
        }
    
        public synchronized void release(Connection conn){
            pool.add(conn);
    }
    
    }

    这样的连接池,广泛利用于开源框架,能够无效晋升底层的运行性能。

  • 关于设计模式:软件设计七大原则

    开闭准则
    依赖倒置准则
    繁多职责准则
    接口隔离准则
    迪米特法令
    里氏替换准则
    合成复用准则


    代码开发遵循设计准则,使代码构造有条理且易于扩大和保护。并不需要僵硬地依照软件设计准则去实现,而是依据不同的场景,选取比拟适合的设计准则。

    开闭准则

    含意:一个软件实体,如类、模块和函数要对扩大凋谢,对批改敞开。在一个根底类上要减少性能时,不要去批改类中的代码,而是新增类继承根底类,在新增类中减少性能。

    示例:

    一般计算器反对加减乘除

    public class GeneralCalculator {
        public int add(int x, int y){
            return x + y;
        }
        public int sub(int x, int y){
            return x - y;
        }
        public int mul(int x, int y){
            return x * y;
        }
        public int div(int x, int y){
            return x / y;
        }
    }

    迷信计算器反对幂运算和一般运算

    public class ScientificCalculator extends GeneralCalculator {
        public int power(int x, int y){
            if (x == 0){
                return 0;
            }
            if (y == 0){
                return 1;
            }
            int sum = 1;
            for (int i = 0; i < y; i++) {
                sum = mul(sum, x);
            }
            return sum;
        }
    }

    类图:

    依赖倒置准则

    含意:形象不应该依赖于细节,细节该当依赖于形象。就是针对接口编程,在任何应用对象的中央都是用形象类型,比方返回类型、成员变量和办法参数等。

    示例:

    人吃水果,能够吃苹果香蕉等

    public interface IFruits {
        String getName();
    }
    
    public class Apple implements IFruits {
        public String getName() {
            return "苹果";
        }
    }
    
    public class Banana implements IFruits {
        public String getName() {
            return "香蕉";
        }
    }
    
    public class Person {
        public void eat(IFruits fruits){
            System.out.println("我正在吃" + fruits.getName());
        }
    }
    
    public class TestMain {
        public static void main(String[] args) {
            Person person = new Person();
            IFruits apple = new Apple();
            person.eat(apple);
        }
    }

    类图:

    繁多职责准则

    含意:一个类、办法或接口只实现一个性能。比方办法的命名都是见名知意,一个办法内容不要蕴含多种解决逻辑。

    示例:电商下单流程有创立订单、扣减库存、领取。这是三个模块各自独自用一个类示意一个服务,每个服务都有本人独立的性能,不把这三个性能放到同一个类中。

    public class OrderService {
        public void createOrder(){
            System.out.println("创立订单");
        }
    }
    
    public class StockService {
        public void subStock(){
            System.out.println("扣减库存");
        }
    }
    
    public class PayService {
        public void pay(){
            System.out.println("领取胜利");
        }
    }
    
    public class TestMain {
        public static void main(String[] args) {
            new OrderService().createOrder();
            new StockService().subStock();
            new PayService().pay();
        }
    }

    类图:

    接口隔离准则

    含意:建设多个专门的接口,不要建设抽象的接口。多个办法不应该在一个接口中,应该建设多个接口,搁置一种类型的办法。

    示例:有飞机、汽车、轮船三种交通工具

    接口不能定义成这样,不然每一个实现类都要实现这三个接口,导致代码的冗余

    public interface IVehicle {
        /**
         * 飞
         */
        void fly();
    
        /**
         * 行驶
         */
        void run();
    
        /**
         * 航行
         */
        void sail();
    }

    正确示例如下

    public interface IFlyVehicle {
        /**
         * 飞
         */
        void fly();
    }
    
    public interface IRunVehicle {
        /**
         * 行驶
         */
        void run();
    }
    
    public interface ISailVehicle {
        /**
         * 航行
         */
        void sail();
    }
    
    public class Plane implements IFlyVehicle {
        public void fly() {
            System.out.println("飞机航行");
        }
    }
    
    public class Car implements IRunVehicle {
        public void run() {
            System.out.println("汽车行驶");
        }
    }
    
    public class Ship implements ISailVehicle {
        public void sail() {
            System.out.println("轮船行驶");
        }
    }

    类图:

    迪米特法令

    含意:迪米特法令又叫起码晓得准则。类与类之间的利用尽量放弃间接利用,一个类中尽量少导入别的类。

    示例:司机开车送老板去机场

    谬误示例:老板类中没必要引入汽车这个类,司机引入这个类即可

    public class Car {
        public void run(){
            System.out.println("汽车行驶中");
        }
    }
    
    public class Driver {
        public void driveCar(Car car){
            car.run();
        }
    }
    
    public class Boss {
        public void goAirport(Driver driver){
            Car car = new Car();
            driver.driveCar(car);
        }
    }
    
    public class TestMain {
        public static void main(String[] args) {
            Boss boss = new Boss();
            Driver driver = new Driver();
            boss.goAirport(driver);
        }
    }

    类图:

    正确示例:

    public class Car {
        public void run(){
            System.out.println("汽车行驶中");
        }
    }
    
    public class Driver {
        public void driveCar(){
            Car car = new Car();
            car.run();
        }
    }
    
    public class Boss {
        public void goAirport(Driver driver){
            driver.driveCar();
        }
    }
    
    public class TestMain {
        public static void main(String[] args) {
            Boss boss = new Boss();
            Driver driver = new Driver();
            boss.goAirport(driver);
        }
    }

    类图:

    里氏替换准则

    含意:一个软件实体如果实用一个父类,那么也肯定要能实用于其子类,援用父类的中央,能替换成实用其子类,保障程序逻辑不变。

    子类能够实现父类的形象办法,也能够减少本人的性能,但不要笼罩非形象办法;

    示例:这边要和依赖倒置准则辨别,依赖倒置准则重视的是模块依赖形象接口;里氏替换准则重视的是子类与父类的关系。

    司机能够开车,奥托飞驰都能够,飞驰多一个车载冰箱性能

    public interface ICar {
        void run();
    }
    
    public class Alto implements ICar {
        public void run() {
            System.out.println("行驶");
        }
    }
    
    public class BenzCar implements ICar {
        public void run() {
            System.out.println("行驶");
        }
    
        private void codeStorage(){
            System.out.println("具备冰箱冷藏性能");
        }
    }
    
    public class TestMain {
        public static void main(String[] args) {
            Alto alto = new Alto();
            BenzCar benzCar = new BenzCar();
            Driver driver = new Driver();
            driver.driveCar(alto);
            driver.driveCar(benzCar);
        }
    }

    类图:

    合成复用准则

    含意:实现性能复用,采纳对象组合的形式。一个新类要应用另一个类的已有性能,能够去援用类,而不是去继承类。

    示例:同繁多职责准则,一个服务要实现下单功能,则同时引入订单服务、库存服务、领取服务,而不是去继承服务

    类图:同繁多职责准则

  • 关于设计模式:经典设计模式之动态代理

    为什么要学习设计模式?

    设计模式在很多源码框架中广泛应用,如果不理解设计模式,间接看源码必定一头雾水。那我不看源码行不行?行,除非你有其余更好的倒退方向或者始终做一个高级。不然源码是绕不过来的,你不理解它,它对你来说就是一个黑盒子。

    怎么学习设计模式?

    网上学习教程很多,大多数讲一些demo。我在学习的过程中,甚至有的把很简略的货色讲得超级简单。个别是简略类图+demo+源码+理论场景中的利用联合来学习。尽可能找到走心的老师。

    什么是动静代理?

    mybatis谁都会用,然而为啥一个定义一个接口,没有实例能够间接调用?起因是java外部主动生成了一个实例。

    怎么写?

    咱们先找到java.lang.reflect包下的Proxy对象,调用它的newProxyInstance办法;
    点进去看到,须要三个参数,别离是类加载器,类数组,以及Invocation接口;
    vm参数-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

    package disign.pattern;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 动静代理
     */
    public class DynamicProxy {
        interface DynamicProxyInterface {
            void sayHello();
        }
    
        public static void main(String[] args) {
            ClassLoader loader = DynamicProxy.class.getClassLoader();
            Class<?>[] interfaces = new Class[]{DynamicProxyInterface.class};
            DynamicProxyInterface dynamicProxyInterface = (DynamicProxyInterface) Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("sayHello");
                    return null;
                }
            });
            dynamicProxyInterface.sayHello();
        }
    }


    这图一放调用链路是不是很清晰,$Proxy0外部保护了InvocationHandler接口,最终调用还是匿名外部类实现的办法;
    从栈帧的角度看也是一样一样的;

  • 关于设计模式:浅谈设计模式-组合模式十二

    浅谈设计模式 – 组合模式(十二)

    前言

    ​ 组合模式是一种十分重要的设计模式,应用场景简直随处可见,各类菜单和目录等中央都能看到组合模式的影子,组合模式通常状况下是和树形构造相辅相成的,而树是软件设计外面十分重要的数据结构,这篇文章将介绍什么是组合模式。

    什么是组合模式

    ​ 容许你将对象组合到树形构造体现“整体局部”的构造,组合能让客户以统一的形式解决个别对象和对象组合,组合其实更像是对于对于各种独立组建的“统一性”,能够将一类类似的事物看为一个整体然而领有齐全不同的工作机制。

    介绍

    ​ 能够说将类似的物品造成一个汇合的模式就是组合模式,他能看两个类似的物品在一处进行完满的交融以及操作。当咱们须要 整体/局部的操作时候,就能够应用这种模式。

    特点

    • 组合模式考究的是整体和局部之间的关系,整体能够蕴含局部,局部能够回溯到整体,相互蕴含
    • 组合模式能够让对象构造以“树”的模式蕴含关系。少数状况能够疏忽整体和个体之前的差异

    优缺点

    长处:

    • 组合模式能够帮忙对象和组合的对象厚此薄彼的看待

    毛病:

    • 继承构造,批改抽象类违反凋谢敞开准则
    • 如果层次结构十分深,递归结构影响效率
    • 应用迭代器有可能造成并发遍历菜单的问题

    组合模式以繁多职责的准则换取透明性?

    组合模式毁坏了的繁多职责准则,组合了多个对象的办法,同时在办法外面做了多种操作,然而这样做却是能够让整个对象能够更加直观的理解整体和局部的个性,这是设计模式外面十分常见的操作。

    组合模式的结构图

    ​ 组合模式的结构图如下:

    • Component 组件:定义组件的接口,这里能够设计为抽象类,能够设计为接口,能够视为组件的“可能的公共行为”。
    • Leaf 叶子节点:用于示意原始对象,叶子节点只须要实现本人的非凡性能即可,比方菜单的菜单子项。
    • Composite 组件节点:定义组件行为,能够具备子节点。同时实现叶子节点的相干操作(继承同一个接口),能够视为一个分类的大类

    理论利用场景

    ​ 因为事实场景当中这样的设计模式构造是有树状构造转换而来的,所以组合模式的应用场景就是呈现树形构造的中央。比方:文件目录显示,多及目录出现等树形构造数据的操作。上面咱们就应用一个菜单的构造来理解一下组合模式的“模板”代码。

    实战

    模仿场景

    ​ 组合模式是为树形结构设计的一种设计模式,案例参照一个菜单的治理性能作为模仿,咱们须要拿到不同的菜单分类,在菜单的分类外面,咱们有须要拿到不同的菜单项,咱们能够由任意的菜单项进入到不同的菜单分类,同时能够进入不同的叶子节点。

    ​ 这次的代码案例是从网上找的例子:

    形象组件

    形象组件定义了组件的告诉接口,并实现了增删子组件及获取所有子组件的办法。同时重写了hashCodeequales办法(至于起因,请读者自行思考。如有疑难,请在评论区留言)。

    package com.jasongj.organization;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public abstract class Organization {
    
      private List<Organization> childOrgs = new ArrayList<Organization>();
    
      private String name;
    
      public Organization(String name) {
        this.name = name;
      }
    
      public String getName() {
        return name;
      }
    
      public void addOrg(Organization org) {
        childOrgs.add(org);
      }
    
      public void removeOrg(Organization org) {
        childOrgs.remove(org);
      }
    
      public List<Organization> getAllOrgs() {
        return childOrgs;
      }
    
      public abstract void inform(String info);
    
      @Override
      public int hashCode(){
        return this.name.hashCode();
      }
      
      @Override
      public boolean equals(Object org){
        if(!(org instanceof Organization)) {
          return false;
        }
        return this.name.equals(((Organization) org).name);
      }
    
    }

    简略组件(部门)

    简略组件在告诉办法中只负责对接管到音讯作出响应。

    package com.jasongj.organization;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Department extends Organization{
      
      public Department(String name) {
        super(name);
      }
    
      private static Logger LOGGER = LoggerFactory.getLogger(Department.class);
      
      public void inform(String info){
        LOGGER.info("{}-{}", info, getName());
      }
    
    }

    复合组件(公司)

    复合组件在本身对音讯作出响应后,还须告诉其下所有子组件

    package com.jasongj.organization;
    
    import java.util.List;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class Company extends Organization{
      
      private static Logger LOGGER = LoggerFactory.getLogger(Company.class);
      
      public Company(String name) {
        super(name);
      }
    
      public void inform(String info){
        LOGGER.info("{}-{}", info, getName());
        List<Organization> allOrgs = getAllOrgs();
        allOrgs.forEach(org -> org.inform(info+"-"));
      }
    
    }

    awt的组合模式

    ​ 组合模式因为应用了同样的接口,会让叶子节点实现一些不必要的性能,此时个别能够应用一个空对象或者应用更为激进的应用抛出异样的模式。

    ​ awt这种老掉牙的货色就不多介绍,java的gui其实就是应用了组合模式,上面是一部分的案例代码:

     //创立组件
        public MethodsTank() {
            //创立组件等
            jm = new JMenu("我的菜单(G)");
            jmb = new JMenuBar();
            jl1 = new JMenuItem("开始新游戏(F)");
            jl2 = new JMenuItem("完结游戏");
            jl3 = new JMenuItem("从新开始(R)");
            jl4 = new JMenuItem("存盘退出");
            jl5 = new JMenuItem("回到上次游戏");
    
            draw = new DrawTank();
            ses = new selectIsSallup();
    
            //设置快捷键形式
            jm.setMnemonic('G');
            jl1.setMnemonic('f');
            jl3.setMnemonic('r');
            jl4.setMnemonic('q');
            jl5.setMnemonic('w');
    
            //开启闪动线程
            new Thread(ses).start();
            //先运行开始画面
            this.addTank();
    
        }
    
    
        public void addTank() {
            //增加菜单栏目
            jm.add(jl1);
            jm.add(jl2);
            jm.add(jl3);
            jm.add(jl4);
            jm.add(jl5);
            jmb.add(jm);
    
            //运行选关界面
            this.add(ses);
    
    
            //对于子菜单增加事件
            jl1.addActionListener(this);
            jl1.setActionCommand("newgame");
            jl2.addActionListener(this);
            jl2.setActionCommand("gameexit");
            jl3.addActionListener(this);
            jl3.setActionCommand("restart");
    
    
            //设置窗体的一些根本属性
            this.setTitle("我的坦克大战");
            this.setBounds(600, 350, width, height);
            //增加菜单栏的形式
            this.setJMenuBar(jmb);
            this.setDefaultCloseOperation(this.EXIT_ON_CLOSE);
    
    
            this.setVisible(true);
    
        }

    总结

    ​ 组合模式精华在于“破而后立”,他尽管违反了设计准则,然而通过更加优雅的模式,实现了将繁多的对象由局部变为一个整体。

    ​ 而组合模式也常常和适配器模式搭配应用,本文的案例只是一个简略的套板,对于组合模式的理论使用场景其实更常见的状况是对于菜单和菜单子项的内容。

    结语

    ​ 组合模式很多状况下可能并不是非常用的上,更多的时候是和其余的设计模式搭配,组合模式咱们须要关注的是“整体-局部”的交融对立即可。

    参考资料:

    ​ 这里有一篇讲的更好的材料,在组合模式的根底上给了一个品质稍高的案例代码:

    ​ 实战组合模式「营销差异化人群发券,决策树引擎搭建场景」

    源码剖析组合模式的典型利用

    java.awt中的组合模式

    Java GUI分两种:

    • AWT(Abstract Window Toolkit):形象窗口工具集,是第一代的Java GUI组件。绘制依赖于底层的操作系统。根本的AWT库解决用户界面元素的办法是把这些元素的创立和行为委托给每个指标平台上(Windows、 Unix、 Macintosh等)的本地GUI工具进行解决。
    • Swing,不依赖于底层细节,是轻量级的组件。当初多是基于Swing来开发。

    咱们来看一个AWT的简略示例:

    留神:为了失常显示中文,须要在IDEA中的 Edit Configurations -> VM Options 中设置参数 -Dfile.encoding=GB18030

    import java.awt.*;
    import java.awt.event.WindowAdapter;
    import java.awt.event.WindowEvent;
    
    public class MyFrame extends Frame {
    
        public MyFrame(String title) {
            super(title);
        }
    
        public static void main(String[] args) {
            MyFrame frame = new MyFrame("这是一个 Frame");
    
            // 定义三个构件,增加到Frame中去
            Button button = new Button("按钮 A");
            Label label = new Label("这是一个 AWT Label!");
            TextField textField = new TextField("这是一个 AWT TextField!");
    
            frame.add(button, BorderLayout.EAST);
            frame.add(label, BorderLayout.SOUTH);
            frame.add(textField, BorderLayout.NORTH);
    
            // 定义一个 Panel,在Panel中增加三个构件,而后再把Panel增加到Frame中去
            Panel panel = new Panel();
            panel.setBackground(Color.pink);
    
            Label lable1 = new Label("用户名");
            TextField textField1 = new TextField("请输出用户名:", 20);
            Button button1 = new Button("确定");
            panel.add(lable1);
            panel.add(textField1);
            panel.add(button1);
    
            frame.add(panel, BorderLayout.CENTER);
    
            // 设置Frame的属性
            frame.setSize(500, 300);
            frame.setBackground(Color.orange);
            // 设置点击敞开事件
            frame.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    System.exit(0);
                }
            });
            frame.setVisible(true);
        }
    }
    复制代码

    运行后窗体显示如下

    咱们在Frame容器中增加了三个不同的构件 ButtonLabelTextField,还增加了一个 Panel 容器,Panel 容器中又增加了 ButtonLabelTextField 三个构件,为什么容器 FramePanel 能够增加类型不同的构件和容器呢?

    咱们先来看下AWT Component的类图

    GUI组件依据作用能够分为两种:根本组件和容器组件。

    • 根本组件又称构件,诸如按钮、文本框之类的图形界面元素。
    • 容器是一种比拟非凡的组件,能够包容其余组件,容器如窗口、对话框等。所有的容器类都是 java.awt.Container 的间接或间接子类

    容器父类 Container 的局部代码如下

    public class Container extends Component {
        /**
         * The components in this container.
         * @see #add
         * @see #getComponents
         */
        private java.util.List<Component> component = new ArrayList<>();
        
        public Component add(Component comp) {
            addImpl(comp, null, -1);
            return comp;
        }
        // 省略...
    }
    复制代码

    容器父类 Container 外部定义了一个汇合用于存储 Component 对象,而容器组件 Container 和 根本组件如 ButtonLabelTextField 等都是 Component 的子类,所以能够很分明的看到这里利用了组合模式

    Component 类中封装了组件通用的办法和属性,如图形的组件对象、大小、显示地位、前景色和背景色、边界、可见性等,因而许多组件类也就继承了 Component 类的成员办法和成员变量,相应的成员办法包含:

    &emsp;&emsp;&emsp;getComponentAt(int x, int y)
    &emsp;&emsp;&emsp;getFont()
    &emsp;&emsp;&emsp;getForeground()
    &emsp;&emsp;&emsp;getName()
    &emsp;&emsp;&emsp;getSize()
    &emsp;&emsp;&emsp;paint(Graphics g)
    &emsp;&emsp;&emsp;repaint()
    &emsp;&emsp;&emsp;update()
    &emsp;&emsp;&emsp;setVisible(boolean b)
    &emsp;&emsp;&emsp;setSize(Dimension d)
    &emsp;&emsp;&emsp;setName(String name)
    复制代码

    Java汇合中的组合模式

    HashMap 提供 putAll 的办法,能够将另一个 Map 对象放入本人的存储空间中,如果有雷同的 key 值则会笼罩之前的 key 值所对应的 value 值

    public class Test {
        public static void main(String[] args) {
            Map<String, Integer> map1 = new HashMap<String, Integer>();
            map1.put("aa", 1);
            map1.put("bb", 2);
            map1.put("cc", 3);
            System.out.println("map1: " + map1);
    
            Map<String, Integer> map2 = new LinkedMap();
            map2.put("cc", 4);
            map2.put("dd", 5);
            System.out.println("map2: " + map2);
    
            map1.putAll(map2);
            System.out.println("map1.putAll(map2): " + map1);
        }
    }
    复制代码

    输入后果

    map1: {aa=1, bb=2, cc=3}
    map2: {cc=4, dd=5}
    map1.putAll(map2): {aa=1, bb=2, cc=4, dd=5}
    复制代码

    查看 putAll 源码

        public void putAll(Map<? extends K, ? extends V> m) {
            putMapEntries(m, true);
        }
    复制代码

    putAll 接管的参数为父类 Map 类型,所以 HashMap 是一个容器类,Map 的子类为叶子类,当然如果 Map 的其余子类也实现了 putAll 办法,那么它们都既是容器类,又都是叶子类

    同理,ArrayList 中的 addAll(Collection<? extends E> c) 办法也是一个组合模式的利用,在此不做探讨

    Mybatis SqlNode中的组合模式

    MyBatis 的弱小个性之一便是它的动静SQL,其通过 if, choose, when, otherwise, trim, where, set, foreach 标签,可组合成非常灵活的SQL语句,从而进步开发人员的效率。

    来几个官网示例:

    动静SQL — IF

    <select id="findActiveBlogLike"  resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’ 
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="author != null and author.name != null">
        AND author_name like #{author.name}
      </if>
    </select>
    复制代码

    动静SQL — choose, when, otherwise

    <select id="findActiveBlogLike"  resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <choose>
        <when test="title != null">
          AND title like #{title}
        </when>
        <when test="author != null and author.name != null">
          AND author_name like #{author.name}
        </when>
        <otherwise>
          AND featured = 1
        </otherwise>
      </choose>
    </select>
    复制代码

    动静SQL — where

    <select id="findActiveBlogLike"  resultType="Blog">
      SELECT * FROM BLOG 
      <where> 
        <if test="state != null">
             state = #{state}
        </if> 
        <if test="title != null">
            AND title like #{title}
        </if>
        <if test="author != null and author.name != null">
            AND author_name like #{author.name}
        </if>
      </where>
    </select>
    复制代码

    动静SQL — foreach

    <select id="selectPostIn" resultType="domain.blog.Post">
      SELECT * FROM POST P WHERE ID in
      <foreach item="item" index="index" collection="list"
          open="(" separator="," close=")">
            #{item}
      </foreach>
    </select>
    复制代码

    Mybatis在解决动静SQL节点时,利用到了组合设计模式,Mybatis会将映射配置文件中定义的动静SQL节点、文本节点等解析成对应的 SqlNode 实现,并造成树形构造。

    SQLNode 的类图如下所示

    须要先理解 DynamicContext 类的作用:次要用于记录解析动静SQL语句之后产生的SQL语句片段,能够认为它是一个用于记录动静SQL语句解析后果的容器

    形象构件为 SqlNode 接口,源码如下

    public interface SqlNode {
      boolean apply(DynamicContext context);
    }
    复制代码

    applySQLNode 接口中定义的惟一办法,该办法会依据用户传入的实参,参数解析该SQLNode所记录的动静SQL节点,并调用 DynamicContext.appendSql() 办法将解析后的SQL片段追加到 DynamicContext.sqlBuilder 中保留,当SQL节点下所有的 SqlNode 实现解析后,咱们就能够从 DynamicContext 中获取一条动静生产的、残缺的SQL语句

    而后来看 MixedSqlNode 类的源码

    public class MixedSqlNode implements SqlNode {
      private List<SqlNode> contents;
    
      public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
      }
    
      @Override
      public boolean apply(DynamicContext context) {
        for (SqlNode sqlNode : contents) {
          sqlNode.apply(context);
        }
        return true;
      }
    }
    复制代码

    MixedSqlNode 保护了一个 List<SqlNode> 类型的列表,用于存储 SqlNode 对象,apply 办法通过 for循环 遍历 contents 并调用其中对象的 apply 办法,这里跟咱们的示例中的 Folder 类中的 print 办法十分相似,很显著 MixedSqlNode 表演了容器构件角色

    对于其余SqlNode子类的性能,略微概括如下:

    • TextSqlNode:示意蕴含 ${} 占位符的动静SQL节点,其 apply 办法会应用 GenericTokenParser 解析 ${} 占位符,并间接替换成用户给定的理论参数值
    • IfSqlNode:对应的是动静SQL节点 <If> 节点,其 apply 办法首先通过 ExpressionEvaluator.evaluateBoolean() 办法检测其 test 表达式是否为 true,而后依据 test 表达式的后果,决定是否执行其子节点的 apply() 办法
    • TrimSqlNode :会依据子节点的解析后果,增加或删除相应的前缀或后缀。
    • WhereSqlNodeSetSqlNode 都继承了 TrimSqlNode
    • ForeachSqlNode:对应 <foreach> 标签,对汇合进行迭代
    • 动静SQL中的 <choose><when><otherwise> 别离解析成 ChooseSqlNodeIfSqlNodeMixedSqlNode

    综上,SqlNode 接口有多个实现类,每个实现类对应一个动静SQL节点,其中 SqlNode 表演形象构件角色,MixedSqlNode 表演容器构件角色,其它个别是叶子构件角色

  • 关于设计模式:发现一个学习资料宝库

    最近看vertx的reactor模式介绍,翻了一下网页,链接进入软件设计模式,发现了机密,软件设计模式类型
    1.创立类型设计模式
    2.构造类型的设计模式
    3.行为类型的设计模式
    4.并发设计模式(reactor是并发设计模式的一种)

    各种设计模式的利用场景都有介绍,很全;以前买的书籍下面介绍的设计模式,还没有这里全,很多讲得没有这里业余。

    这是什么网站,不说类,懂的都懂!

  • 关于设计模式:发现一个学习资料宝库

    最近看vertx的reactor模式介绍,翻了一下网页,链接进入软件设计模式,发现了机密,软件设计模式类型
    1.创立类型设计模式
    2.构造类型的设计模式
    3.行为类型的设计模式
    4.并发设计模式(reactor是并发设计模式的一种)

    各种设计模式的利用场景都有介绍,很全;以前买的书籍下面介绍的设计模式,还没有这里全,很多讲得没有这里业余。

    这是什么网站,不说类,懂的都懂!

  • 关于设计模式:设计模式中的俄罗斯套娃装饰者Decorator模式

    俄罗斯套娃想必大家都不生疏,就是同一种玩具娃娃大的套小的,而后一层一层嵌套上来。

    在设计模式中,有一种罕用的套娃模式,叫做装璜者(Decorator)模式,又称为包装(Wrapper)模式。

    HttpServletRequest 套娃

    在 Spring 框架开发的 Web 利用中,如果应用了 Spring Security 或 Spring Session,用 Debug 模式察看一下某个申请对应的 HttpServletRequest 对象,会发现这就是一个俄罗斯套娃:

    图中能够看到咱们拿到的 HttpServletRequest 对象,外部成员中蕴含了一个 HttpServletRequest 对象,而这个外部的 HttpServletRequest 对象外部又蕴含了一个 HttpServletRequest 对象,层层蕴含,层层套娃。这就是一个典型的装璜者模式。

    咱们晓得,HttpServletRequest 是 Servlet 标准中提供的一个 interface 接口。Servlet 标准自身没有实现 HttpServletRequest 接口,HttpServletRequest 接口个别是由 Servlet 容器来实现,例如 Tomcat、Jetty。如果 Spring Security、Spring Session 等框架想要加强 HttpServletRequest 对象的性能,然而不扭转原有对象的接口,最好的方法就是应用装璜者模式。例如:

    • Spring Security 加强了 HttpServletRequest.getRemoteUser() 办法,可返回以后通过 Spring Security 框架登录用户的用户名;
    • Spring Session 加强了 HttpServletRequest.getSession() 办法,加强后的 Session 取代了 Servlet 容器的默认实现,其读写能够应用一个集中式的存储,例如 Redis,这样能够不便集群中的多个实例共享 Session。

    HttpServletRequestWrapper / ServletRequestWrapper

    javax.servlet.http 包下有个 HttpServletRequestWrapper 类[源码],继承自 ServletRequestWrapper 类[源码]。能够看到这两个类上的正文:

    This class implements the Wrapper or Decorator pattern. Methods default to calling through to the wrapped request object.

    翻译:这个类实现了装璜者模式/包装模式,办法模式会间接调用外部包装的 request 对象。

    ServletRequestWrapper 自身实现了 ServletRequest 接口,它的构造方法要求传入另一个 ServletRequest 对象,并将这个对象赋值给外部 request 对象:

    public class ServletRequestWrapper implements ServletRequest {
    
        private ServletRequest request;
    
        public ServletRequestWrapper(ServletRequest request) {
            if (request == null) {
                throw new IllegalArgumentException("Request cannot be null");   
            }
            this.request = request;
        }
        
        // ...
    }

    ServletRequestWrapperServletRequest 接口办法的实现,则是间接调用外部 request 对象对应的办法:

    public String getContentType() {
        return this.request.getContentType();
    }
    
    public ServletInputStream getInputStream() throws IOException {
        return this.request.getInputStream();
    }
    
    public String getParameter(String name) {
        return this.request.getParameter(name);
    }
    
    // ...

    以上就是一个最根本的装璜器。咱们能够间接拿来套娃:

    HttpServletRequest request = ...; // 已有的 request 对象
    HttpServletRequest requestWrapper = new HttpServletRequestWrapper(request); // 包装后的对象

    当然,下面代码没有任何意义,因为 requestWrapper 没有做任何扩大,应用 requestWrapper 对象和间接用 request 对象没有任何区别。真正的装璜者类会继承 ServletRequestWrapper 并在此基础上做加强。

    上面,咱们再看下 Spring Security 和 Spring Session 如何对 HttpServletRequest 对象进行装璜。

    Spring Security / Spring Session 中的装璜者实现

    在 Spring Security 文档 Servlet API integration 中,能够看到 Spring Security 框架对 HttpServletRequest 对象的 getRemoteUser()getUserPrincipal()isUserInRole(String) 等办法进行了加强,例如 getRemoteUser() 办法能够间接返回以后登录用户的用户名。接下来看一下 Spring Security 如何加强这些办法。

    首先,Spring Security 提供了一个过滤器 SecurityContextHolderAwareRequestFilter,对相干申请进行过滤解决。在 SecurityContextHolderAwareRequestFilter 第 149 行 联合 HttpServlet3RequestFactory 第 163 行 能够看到,这个 Filter 中创立了一个新的 Servlet3SecurityContextHolderAwareRequestWrapper 对象,这个类继承自 HttpServletRequestWrapper 类,并加强了相干办法。其父类 SecurityContextHolderAwareRequestWrapper 类[源码]中能够看到对 getRemoteUser() 办法的加强:

    public class SecurityContextHolderAwareRequestWrapper extends HttpServletRequestWrapper {
    
        @Override
        public String getRemoteUser() {
            Authentication auth = getAuthentication();
            if ((auth == null) || (auth.getPrincipal() == null)) {
                return null;
            }
            if (auth.getPrincipal() instanceof UserDetails) {
                return ((UserDetails) auth.getPrincipal()).getUsername();
            }
            if (auth instanceof AbstractAuthenticationToken) {
                return auth.getName();
            }
            return auth.getPrincipal().toString();
        }
        
        // ...
    }

    简略来讲,就是 Spring Security 通过一个 Filter 过滤相干申请,拿到原始的 HttpServletRequest 对象,通过一个继承自 HttpServletRequestWrapper 类的装璜者,加强了 getRemoteUser() 等相干办法,再将加强后的对象传给后续的业务解决,那么后续咱们在 Controller 层拿到的 HttpServletRequest 对象就能够间接应用 getRemoteUser() 等办法。

    Spring Session 实现和 Spring Security 相似,这里就不再反复介绍,有趣味能够看 SessionRepositoryFilter 源码。

    Collections 中的装璜者

    装璜者模式岂但能够加强被装璜者的性能,还能够禁用某些性能。当然,禁用实际上也是一种“加强”。

    例如,假如有一个 List,当咱们须要将这个 List 传给第三方的某个办法去读,然而因为这个第三方办法不可信,为了避免这个办法对 List 篡改,能够通过装璜器模式禁用 List 的批改办法,装璜成一个只读的 List。

    java.util.Collections 中提供了一个静态方法 unmodifiableList(List),用于将一个 List 封装为只读的 List:

    List<String> list = ...;
    List<String> unmodifiableList = Collections.unmodifiableList(list);

    通过这个办法的源码能够看到,Collections.unmodifiableList(List) 办法实际上返回了一个 UnmodifiableListUnmodifiableList 是一个典型的装璜者,其外部对 List 的读相干办法间接调用被装璜对象的对应办法,而对写相干办法做了限度,抛出 UnsupportedOperationException。上面是 UnmodifiableList 的局部源码:

    static class UnmodifiableList<E> extends UnmodifiableCollection<E>
                                      implements List<E> {
        final List<? extends E> list;
    
        UnmodifiableList(List<? extends E> list) {
            super(list);
            this.list = list;
        }
    
        public E get(int index) {
            return list.get(index);
        }
        public E set(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public void add(int index, E element) {
            throw new UnsupportedOperationException();
        }
        public E remove(int index) {
            throw new UnsupportedOperationException();
        }
        public int indexOf(Object o) {
            return list.indexOf(o);
        }
        public int lastIndexOf(Object o) {
            return list.lastIndexOf(o);
        }
        public boolean addAll(int index, Collection<? extends E> c) {
            throw new UnsupportedOperationException();
        }
        
        // ...
    }

    java.util.Collections 中还提供了其余一系列装璜者:

    • unmodifiableSet(Set)unmodifiableMap(Map) 等办法和 unmodifiableList(List) 相似,用于不同类型的汇合的装璜
    • synchronizedList(List)synchronizedSet(Set)synchronizedMap(Map) 等办法应用 synchronized 装璜 List、Set、Map 中的相干办法,返回一个线程平安的汇合
    • checkedList(List, Class)checkedSet(Set, Class)checkedMap(List, Class, Class) 等办法返回类型平安的汇合,如果插入汇合的元素类型不符合要求则会抛出异样

    InputStream 装璜者

    装璜者岂但能够加强被装璜者原有的办法,还能够减少新的办法扩大性能。

    java.io 包中,针对 InputStream 有一个根底的形象装璜者 FilterInputStream,其源码如下:

    public class FilterInputStream extends InputStream {
    
        protected volatile InputStream in;
    
        protected FilterInputStream(InputStream in) {
            this.in = in;
        }
    
        public int read() throws IOException {
            return in.read();
        }
        
        // ...
    }

    相似于下面讲到的 HttpServletRequestWrapper 类,FilterInputStream 是一个根底的装璜者,它的子类才是具体的装璜者的实现。DataInputStream 就是其中一个典型的装璜者实现。

    DataInputStream 用于从被装璜的 InputStream 对象中读取根本数据类型,它继承自 FilterInputStream,并新增了新的办法,如 readByte()readInt()readFloat() 等,这些办法是 InputStream 接口中没有的。

    除了 DataInputStream 之外,FilterInputStream 常见的子类装璜者还有:

    • BufferedInputStream 为被装璜的 InputStream 提供缓冲性能以及反对 markreset 办法
    • CipherInputStream 应用加密算法(例如 AES)对 InputStream 中的数据加密或解密
    • DeflaterInputStreamInflaterInputStream 应用 deflate 压缩算法对 InputStream 中的数据压缩或解压

    装璜者模式构造

    图片起源: https://refactoringguru.cn/de…

    上面总结一下在后面的例子中,各个类和上图中的对应关系:

    • 部件(Component)对应有 HttpServletRequestListInputStream
    • 根底装璜(Base Decorator)对应有 HttpServletRequestWrapperFilterInputStream
    • 具体装璜类(Concrete Decorators)对应有 Servlet3SecurityContextHolderAwareRequestWrapperUnmodifiableListDataInputStream

    关注我的公众号

    微信搜一搜 Java论道 关注我

  • 关于设计模式:设计模式策略模式

    策略模式

    1.定义与类型

    • 定义:定义了算法家族,别离封装起来,让他们之间能够相互转换,此模式让算法的变动不会影响到应用算法的用户
    • if…else
    • 类型:行为型

    2.实用场景

    • 零碎有很多类,而他们的区别仅仅在于他们的行为不同
    • 一个零碎须要动静地在几种算法中抉择一种

    3.长处

    • 开闭准则
    • 防止应用多种条件转移语句
    • 进步算法的保密性和安全性

    4.毛病

    • 客户端必须晓得所有的策略类,并自行决定应用哪一个策略类
    • 产生很多策略类

    5.相干设计模式

    • 策略模式 和 工厂模式
    • 策略模式 和 状态模式

    6.Coding

    ​ 策略模式加工厂模式

    • 创立策略接口Promotion
    public interface PromotionStrategy {
        void doPromotion();
    }
    • 创立一系列促销策略
    public class FanXianPromotionStrategy implements PromotionStrategy{
        @Override
        public void doPromotion() {
            System.out.println("返现促销,返回的余额寄存到用户余额中!");
        }
    }
    public class LiJianPromotionStrategy implements PromotionStrategy {
        @Override
        public void doPromotion() {
            System.out.println("立减促销,课程的价格间接减去配置的价格");
        }
    }
    public class ManJianPromotionStrategy implements PromotionStrategy{
        @Override
        public void doPromotion() {
            System.out.println("满减促销:满200减20");
        }
    }
    • 创立促销流动:用来执行促销策略的类
    public class PromotionActivity {
      
        private PromotionStrategy strategy;
      
        public PromotionActivity(PromotionStrategy strategy){
            this.strategy = strategy;
        }
    
        public void executeStrategy(){
            strategy.doPromotion();
        }
    }
    • 创立促销策略工厂
    /**
     * @program: design_pattern
     * @description: 促销策略工厂
     * @create: 2021-10-13 22:23
     **/
    public class PromotionStrategyFactory {
    
        /** 私有化结构器  */
        private PromotionStrategyFactory(){}
    
        private static  final Map<String,PromotionStrategy>  PROMOTION_STRATEGY_MAP = new HashMap<>();
    
        //初始化工厂
        static {
            PROMOTION_STRATEGY_MAP.put(PromotionType.FANXIAN,new FanXianPromotionStrategy());
            PROMOTION_STRATEGY_MAP.put(PromotionType.LIJIAN,new LiJianPromotionStrategy());
            PROMOTION_STRATEGY_MAP.put(PromotionType.MANJIAN,new ManJianPromotionStrategy());
        }
    
        /** 对外提供获取策略的办法 */
        public  static PromotionStrategy getPromotionStrategy(String promotionKey){
            PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey);
            return promotionStrategy == null?null:promotionStrategy;
        }
    
        private interface PromotionType{
             String LIJIAN = "LIJIAN";
             String MANJIAN = "MANJIAN";
             String FANXIAN = "FANXIAN";
        }
    
    }
    • 测试类
    public class Test {
        public static void main(String[] args) {
            String promotionKey = "LIJIAN";
            PromotionActivity promotionActivity = 
              new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey));
            promotionActivity.executeStrategy();
        }
    }
    • 控制台输入:

    7.总结

    • 策略模式联合工厂模式缩小了if…else代码,进步了代码的效率和可维护性
    • 代码的解耦
    • 策略类中都有独特的行为,不过这个行为的后果不一样
  • 关于设计模式:设计模式观察者模式

    //观察者
    public interface Observer {
        public void update(float temp, float humidity, float pressure);
    }
    //观察者行为
    public interface Displayment {
        public void display();
    }
    //主题
    public interface Subject {
        public void registerObserver(Observer o);
        public void removeObserver(Observer o);
        public void notifyObserver();
    }
    //观察者1
    public class CurrentConditionsDisplay implements Observer, Displayment{
        private float temp;
        private float humidity;
        private Subject weatherData;
    
        public void CurrentConditionsDisplay(Subject weatherData){
            this.weatherData = weatherData;
        }
    
        public void display() {
            System.out.println("Current conditions: " + temp + "F degrees and "+ humidity + " % humidity");
        }
    
        public void update(float temp, float humidity, float pressure) {
            this.temp = temp;
            this.humidity = humidity;
            display();
        }
    }
    //主题相干的实例
    public class WeatherData implements Subject {
        private ArrayList observers;
        private float temperature;
        private float humidity;
        private float pressure;
    
        public WeatherData(){
            this.observers = new ArrayList<Observer>();
        }
        @Override
        public void registerObserver(Observer o) {
            observers.add(o);
        }
    
        @Override
        public void removeObserver(Observer o) {
            observers.remove(o);
        }
    
        @Override
        public void notifyObserver() {
            for(int i = 0; i < observers.size(); i++ ){
                Observer observer = (Observer) observers.get(i);
                observer.update(temperature,humidity,pressure);
            }
        }
    
        public void measurementsChanged(){
            notifyObserver();
        }
    
        public void setMeasurements(float temperature, float humidity, float pressure){
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
        }
    }
    //测试
    public class WeatherDataTest extends TestCase {
    
        public void testRegisterObserver() {
            WeatherData weatherData = new WeatherData();
            weatherData.setMeasurements(23,15,175);
            CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay();
    //        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
            weatherData.registerObserver(currentConditionsDisplay);
            weatherData.notifyObserver();
            weatherData.removeObserver(currentConditionsDisplay);
            weatherData.notifyObserver();
        }
    }

    //测试后果
    Current conditions: 23.0F degrees and 15.0 % humidity