Back

设计模式

设计模式原则,常见的设计模式、领域驱动模型

[TOC]

只记录常用设计模式

设计模式六大原则

  1. 单一职责原则(SRP):一个类只负责一个功能领域中的相应职责,就一个类而言,应该只有一个引起它变化的原因。
  2. 开闭原则(OCP):一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。
  3. 里氏代换原则(LSP):所有引用基类(父类)的地方必须能透明地使用其子类的对象。
  4. 依赖倒转原则(DIP):抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。如 控制反转和依赖注入
  5. 接口隔离原则(ISP):使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些它不需要的接口。
  6. 迪米特法则(LoD):一个软件实体应当尽可能少地与其他实体发生相互作用。

常见设计模式

创建型模式是将创建和使用代码解耦

结构型模式是将不同功能代码解耦

行为型模式是将不同的行为代码解耦

创建型

单例模式

数据在应用上只保持一份,解决资源访问冲突的问题,就可以使用单例模式

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
/** 1.
 * 懒汉模式,线程不安全,只有在调用方法的时候才实例化,好处是没用到该类时就不实例化,节约资源
 */
class LazyInstance {

    private static LazyInstance singleton;

    private LazyInstance() {
//        if (singleton != null)
//            throw new RuntimeException();
    }

    /** 1.1
     * 想要线程安全只需在方法上加上synchronized关键字,缺点是,多线程访问时锁的操作耗时
     */
    public static LazyInstance getInstance() {
        if (singleton == null) {
            singleton = new LazyInstance();
        }
        return singleton;
    }

}

/** 2.
 * 饿汉模式,直接实例化,线程安全,缺点是丢失了延迟实例化造成资源浪费
 */
class HungryInstance {

    private static final HungryInstance singleton = new HungryInstance();   //加不加final都可以

    public static HungryInstance getInstance() {
        return singleton;
    }

}

/** 3.
 * 双重锁,可在多线程下使用
 */
class DoubleCheckedLocking {

    /**
     * 注意变量要声明volatile,也需要两次if判断,否则可能因为指令重排序导致在多线程情况下不安全,这个比较难测试
     * singleton = new Singleton()不是原子操作,而分为了三个步骤
     * 1. 给 singleton 分配内存
     * 2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
     * 3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null了)
     * 由于有一个『instance已经不为null但是仍没有完成初始化』的中间状态,而这个时候,
     * 如果有其他线程刚好运行到第一层if (instance ==null)这里,这里读取到的instance已经不为null了,
     * 所以就直接把这个中间状态的instance拿去用了,就会产生问题。这里的关键在于线程T1对instance的写操作没有完成,
     * 线程T2就执行了读操作 **/
    private volatile static DoubleCheckedLocking singleton;

    public static DoubleCheckedLocking getInstance(){
        if (singleton == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (singleton == null) {
                    singleton = new DoubleCheckedLocking();
                }
            }
        }
        return singleton;
    }

}

/**4.
 * 静态内部类模式,利用的是JVM对静态内部类的加载机制
 * 因为静态内部类只有被调用的时候才会被初始化,相当于延时的机制,且JVM能保证只初始化一次
 * 相当与结合了懒汉模式和饿汉模式的优点吧
 */
class StaticInnerClassMode {

    private static class StaticInnerClassInstance {
        private static final StaticInnerClassMode SINGLETON = new StaticInnerClassMode();
    }

    public static StaticInnerClassMode getInstance() {
        return StaticInnerClassInstance.SINGLETON;
    }
}

/**5.
 * 枚举类创建单例,利用JVM的机制,保证只实例化一次,同时可防止反射和反序列化操作破解
 */
enum EnumMode {
    SINGLETON;
    public void method(){}
}

/**
 * 除了枚举类可防止反射和反序列化操作破解外,其他四种方法都会被反射和反序列化破解
 * 1,阻止反射破解
 * 在空构造方法里,判断singleton是否为空,如果不为空,则抛出RuntimeException,
 * 因为反射需要通过class.getInstance()调用空参构造方法实例化对象,如果此时抛出异常,则会终止程序,
 * 如果在懒汉模式里使用就会发现会抛出异常
 *
 * 2.阻止反序列化破解
 *  实现Serializable接口,定义readResolve()方法返回对象,具体原理不太清楚
 *  在反序列化的时候用readResolve()中返回的对象直接替换在反序列化过程中创建的对象
 *  private Object readResolve() throws ObjectStreamException {
 *     return instance;
 *  }
 */

工厂模式

  • 创建型模式

  • 定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行,还有另一种工厂需要用到单例,通过在静态代码块里先初始化好对象,然后+key放到map里

  • util.Calendar、util.ResourceBundle、text.NumberFormat、nio.charset.Charset、util.EnumSet、DI容器

  • 应用场景:当有一段需要通过if-else的代码来判断初始化哪些对象的时候,就可考虑

工厂模式例子类图
工厂模式例子类图

 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
// 接口
public interface Shape{
    void draw();
}

// 实体,工厂生产的产品
public class Circle implements Shape {
   @Override
   public void draw() {
      System.out.println("circle draw");
   }
}

public class Rectangle implements Shape {
   @Override
   public void draw() {
      System.out.println("Rectangle draw");
   }
}
// 工厂
public class ShapeFactory {
    
   //使用 getShape 方法获取形状类型的对象, 生产产品的方法
   public Shape getShape(String shapeType){
      if(shapeType == null){
         return null;
      }        
      if(shapeType.equalsIgnoreCase("CIRCLE")){
         return new Circle();
      } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
         return new Rectangle();
      }
      return null;
   }
}
// 例子
public class FactoryPatternDemo {
 
   public static void main(String[] args) {
      // 创建工厂
      ShapeFactory shapeFactory = new ShapeFactory();
      //获取 Circle 的对象,并调用它的 draw 方法, 接口装载子类对象
      Shape shape1 = shapeFactory.getShape("CIRCLE");
      shape1.draw();	// circle draw
      //获取 Rectangle 的对象,并调用它的 draw 方法
      Shape shape2 = shapeFactory.getShape("RECTANGLE");
      shape2.draw();	//Rectangle draw
   }
}

抽象工厂模式

  • 与工厂模式类似,也创建型模式
  • 工厂方法是一个工厂,根据传入参数生产不同实例,而抽象工厂则加多一层工厂获取。抽象工厂属于大工厂,根据传入参数产生工厂实例,在通过这个工厂,传入参数获取对象实例

建造者模式

  • 其实就是链式调用,主要为了解决构造方法参数过多,且需要校验的情况下,如果参数过多且需要校验,使用构造方法或者set方法来实例化对象不太方便,同时,使用建造者模式还可以把对象处理成初始化后属性不可变得对象
  • 工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class ResourcePoolConfig {
  private String name;
  private int maxTotal;
  private int maxIdle;
  private int minIdle;
  private ResourcePoolConfig(Builder builder) {
    this.name = builder.name;
    this.maxTotal = builder.maxTotal;
    this.maxIdle = builder.maxIdle;
    this.minIdle = builder.minIdle;
  }
  //...省略getter方法...
  // 建议此处把builder设置为内部类
  public static class Builder {
    // default value
    private static final int DEFAULT_MAX_TOTAL = 8;
    private static final int DEFAULT_MAX_IDLE = 8;
    private static final int DEFAULT_MIN_IDLE = 0;
      
    private String name;
    private int maxTotal = DEFAULT_MAX_TOTAL;
    private int maxIdle = DEFAULT_MAX_IDLE;
    private int minIdle = DEFAULT_MIN_IDLE;
      
    public ResourcePoolConfig build() {
      // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
      if (StringUtils.isBlank(name)) {
        throw new IllegalArgumentException("...");
      }
      if (maxIdle > maxTotal) {
        throw new IllegalArgumentException("...");
      }
      if (minIdle > maxTotal || minIdle > maxIdle) {
        throw new IllegalArgumentException("...");
      }
      return new ResourcePoolConfig(this);
    }
    public Builder setName(String name) {
      if (StringUtils.isBlank(name)) {
        throw new IllegalArgumentException("...");
      }
      this.name = name;
      return this;
    }
    public Builder setMaxTotal(int maxTotal) {
      if (maxTotal <= 0) {
        throw new IllegalArgumentException("...");
      }
      this.maxTotal = maxTotal;
      return this;
    }
    public Builder setMaxIdle(int maxIdle) {
      if (maxIdle < 0) {
        throw new IllegalArgumentException("...");
      }
      this.maxIdle = maxIdle;
      return this;
    }
    public Builder setMinIdle(int minIdle) {
      if (minIdle < 0) {
        throw new IllegalArgumentException("...");
      }
      this.minIdle = minIdle;
      return this;
    }
  }
}

// 调用
public class FactoryPatternDemo {
 
   public static void main(String[] args) {
   		// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
        .setName("dbconnectionpool")
        .setMaxTotal(16)
        .setMaxIdle(10)
        .setMinIdle(12)
        .build();
   }
}

原型模式

  • 其实就是拷贝,把对于那些创建成本较大(比如创建要进行复杂计算、IO读取等)且同一类但不同对象得对象,利用原有对象进行拷贝,来达到创建新对象的目的
  • 常见方式有序列化再反序列化,或者递归遍历对象里的字段进行创建和赋值,深拷贝或者浅拷贝,这里要注意浅拷贝和深拷贝问题

结构型

代理模式

  • 结构型模式

  • 通过代理类扩展被代理类的能力,代理类和被代理类实现同一接口,重写其中的方法,在代理类中传入被代理类的实例,在两者相同的方法中,调用被代理类的该方法,同时可以处理其他逻辑,达到扩展的能力

  • 1、和适配器模式的区别:适配器模式主要是两个代表不同维度的接口,它们的实现通过组合的方式扩展原来的功能,而代理模式是代理类和被代理类实现相同的接口,通过代理类调用被代理类相同的方法来达到对代理类补充的作用。

    2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。

  • 下面的例子属于静态代理,其他代理请看动态代理和CGLIB代理

代理模式例子
代理模式例子

 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
// 代理类和被代理类的接口
public interface Image {
   void display();
}
// 被代理类
public class RealImage implements Image {
   private String fileName;
   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }
   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   }
   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}
// 代理类
public class ProxyImage implements Image{
   private RealImage realImage;
   private String fileName;
    
   public ProxyImage(String fileName){
      this.fileName = fileName;
   } 
   public ProxyImage(RealImage realImage){
      this.realImage = realImage;
   }
   @Override
   public void display() {
      if(realImage == null){
         // 可以选择在这里延迟加载 或者 在构造方法的时候加载 被代理类
         realImage = new RealImage(fileName);
      }
      // 在调用被代理类同名方法前后做其他操作
      realImage.display();
   }
}
// 使用时
public class ProxyPatternDemo {
    
   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg"); 
      // 图像将从磁盘加载
      image.display(); 		// Loading test_10mb.jpg /n Displaying test_10mb.jpg
      System.out.println("");
      // 图像不需要从磁盘加载
      image.display();  	// Displaying test_10mb.jpg
   }
}

装饰器模式

  • 结构型模式

  • 装饰类向被装饰类添加新功能,同时又不改变其结构,作为现被装饰类的包装,继承的一种代替,主要解决多层次继承的问题

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

  • 跟代理模式很像,本质上代码差别不大,只是意图不太一样。

    区别:装饰器可以一层一层装饰,每次装饰可以增强或扩展被装饰者的功能,功能是相关的,是对功能的增强。外部是知道具体的被装饰者的(对装饰器传入被装饰对象),然后不断通过装饰达到对原有功能的增强。

    代理模式是一层,代理类控制被代理类,控制被代理对象的访问,外部并不关心被代理的对象是谁(被代理类是在代理类内部进行构造,不从外部传入),只知道通过代理对象可以实现对被代理对象的功能补充,是对代理对象的功能补充,直接加强

  • 最典型的例子就是IO类了,每个io类都继承了in/outputStream(带了默认实现),同时又持有in/outputStream,调用的本质还是在调持有的in/outputStream方法的基础上进行增强

    题外话,为啥BufferedInputStream不直接继承InputStream,而是继承FileInputStream?

    原因是如果BufferInputStream继承并同时持有inputStream,由于BufferedInputStream只对部分方法增加buffer功能,对那些不需要增强的方法的调用就需要显式调用持有的inputStream,而如果先通过FileInputStream继承并组合InputStream,并调用持有的inputStream的默认实现,就不用写这部分多余的代码了

装饰模式例子
装饰模式例子

接口Shape表示形状,它的实现类是圆Circle类,统一的抽象装饰类ShapeDecorator,带有Shape类的引用,其 实现的装饰类RedShapeDecorator通过传入具体的形状实例,来对共同的方法做增强

 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
// 接口,作为被装饰类的接口
public interface Shape {
   void draw();
}
// 实现类,作为被装饰类
public class Circle implements Shape { 
   @Override
   public void draw() {
      System.out.println("Shape: Circle");
   }
}
// 接口的抽象装饰类,对非增强方法带有默认实现
public abstract class ShapeDecorator implements Shape {
   protected Shape decoratedShape;
   // 每一个装饰器都需要一个被装饰引用,因此需要一个抽象父类
   public ShapeDecorator(Shape decoratedShape){
      this.decoratedShape = decoratedShape;
   } 
   // 装饰器类单独使用的默认方法
   public void draw(){
      decoratedShape.draw();
   }  
}
// 装饰类,装饰成红色
public class RedShapeDecorator extends ShapeDecorator {
   public RedShapeDecorator(Shape decoratedShape) {
      super(decoratedShape);     
   } 
   @Override
   public void draw() {
      decoratedShape.draw();         
      setRedBorder(decoratedShape);
   }
   private void setRedBorder(Shape decoratedShape){
      System.out.println("Border Color: Red");
   }
}
// 装饰类,加深颜色
public class DarkRedShapeDecorator extends ShapeDecorator {
   public DarkRedShapeDecorator(Shape decoratedShape) {
      super(decoratedShape);     
   } 
   @Override
   public void draw() {
      decoratedShape.draw();         
      setDarkRedBorder(decoratedShape);
   }
   private void setDarkRedBorder(Shape decoratedShape){
      System.out.println("Border Color: DarkRed");
   }
}
// 调用
public class DecoratorPatternDemo {
   public static void main(String[] args) {
 
      Shape circle = new Circle(); 
      Shape redCircle = new RedShapeDecorator(circle);
      Shape darkRedCircle = new DarkRedShapeDecorator(redCircle);
       
      circle.draw();		// Shape: Circle
      redCircle.draw();		// Border Color: Red
      darkRedCircle.draw(); // Border Color: DarkRed

   }
}

适配器模式

  • 结构型模式
  • 接口适配器使得实现了不同接口的类可以通过适配器的选择而工作,主要是规避接口不兼容的问题,本质是使用一组类和接口充当适配器,包在被适配的类和接口上,具体又是实现或组合
  • 典型例子:Arrays#asList(),Collections#list(),Collections#enumeration()

适配器模式
适配器模式

有两个接口AdvancedMediaPlayer和MediaPlayer,它们都有不同的作用,但它们的作用又很相似,AdvancedMediaPlayer可以播放vlc格式或者mp4格式,而MediaPlayer只能单纯的播放,多接口适配

如果想要让MediaPlayer能播放不同格式的音乐,就需要适配了,适配器实现MediaPlayer接口,根据传入的参数来判断需要实例化哪种播放器,并在播放方法里执行相应播放器的播放方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// AdvancedMediaPlayer接口
public interface AdvancedMediaPlayer { 
   public void playVlc(String fileName);
   public void playMp4(String fileName);
}
// AdvancedMediaPlayer实现类 VlcPlayer
public class VlcPlayer implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      System.out.println("Playing vlc file. Name: "+ fileName);      
   } 
   @Override
   public void playMp4(String fileName) {
      //什么也不做
   }
}
// AdvancedMediaPlayer实现类 Mp4Player
public class Mp4Player implements AdvancedMediaPlayer{
   @Override
   public void playVlc(String fileName) {
      //什么也不做
   }
   @Override
   public void playMp4(String fileName) {
      System.out.println("Playing mp4 file. Name: "+ fileName);      
   }
}

// MediaPlayer接口
public interface MediaPlayer {
   public void play(String audioType, String fileName);
}
// MediaPlayer适配器,实现了从 AdvancedMediaPlayer接口 到 MediaPlayer接口 的转换
public class MediaAdapter implements MediaPlayer {
 
   AdvancedMediaPlayer advancedMusicPlayer;
 
   public MediaAdapter(String audioType){
      if(audioType.equalsIgnoreCase("vlc") ){
         advancedMusicPlayer = new VlcPlayer();       
      } else if (audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer = new Mp4Player();
      }  
   }
 
   @Override
   public void play(String audioType, String fileName) {
      if(audioType.equalsIgnoreCase("vlc")){
         advancedMusicPlayer.playVlc(fileName);
      }else if(audioType.equalsIgnoreCase("mp4")){
         advancedMusicPlayer.playMp4(fileName);
      }
   }
}

// 适配器使用类,从而不用关心 被适配接口 的具体实现类
public class AudioPlayer implements MediaPlayer {
   MediaAdapter mediaAdapter; 
 
   @Override
   public void play(String audioType, String fileName) {    
      //播放 mp3 音乐文件的内置支持
      if(audioType.equalsIgnoreCase("mp3")){
         System.out.println("Playing mp3 file. Name: "+ fileName);         
      } 
      //mediaAdapter 提供了播放其他文件格式的支持
      else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){
         mediaAdapter = new MediaAdapter(audioType);
         mediaAdapter.play(audioType, fileName);
      }
      else{
         System.out.println("Invalid media. "+
            audioType + " format not supported");
      }
   }   
}

// 调用
public class AdapterPatternDemo {
   public static void main(String[] args) {
      AudioPlayer audioPlayer = new AudioPlayer();
 
      audioPlayer.play("mp3", "beyond the horizon.mp3");	
      // Playing mp3 file. Name: beyond the horizon.mp3
       
      audioPlayer.play("mp4", "alone.mp4");
      // Playing mp4 file. Name: alone.mp4
       
      audioPlayer.play("vlc", "far far away.vlc");
      // Playing vlc file. Name: far far away.vlc
       
      audioPlayer.play("avi", "mind me.avi");
       // Invalid media. avi format not supported
   }
}

其他例子,以下例子来自极客时间-设计模式之美,都是单接口适配

1、封装外部sdk接口

 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
public class CD { //这个类来自外部sdk,我们无权修改它的代码
  public static void staticFunction1() { //... }
  public void uglyNamingFunction2() { //... }
  public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... }
  public void lowPerformanceFunction4() { //... }
}

/ 使用适配器模式进行重构
public class ITarget {
  void function1();
  void function2();
  void fucntion3(ParamsWrapperDefinition paramsWrapper);
  void function4();
  //...
}
// 注意:适配器类的命名不一定非得末尾带Adaptor
public class CDAdaptor extends CD implements ITarget {
  //...
  public void function1() {
     super.staticFunction1();
  }
  
  public void function2() {
    super.uglyNamingFucntion2();
  }
  
  public void function3(ParamsWrapperDefinition paramsWrapper) {
     super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...);
  }
  
  public void function4() {
    //...reimplement it...
  }
}

2、统一多个类的接口

 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
public class ASensitiveWordsFilter { // A敏感词过滤系统提供的接口
  //text是原始文本,函数输出用***替换敏感词之后的文本
  public String filterSexyWords(String text) {
    // ...
  }
}
public class BSensitiveWordsFilter  { // B敏感词过滤系统提供的接口
  public String filter(String text) {
    //...
  }
}
public class CSensitiveWordsFilter { // C敏感词过滤系统提供的接口
  public String filter(String text, String mask) {
    //...
  }
}
// 未使用适配器模式之前的代码:代码的可测试性、扩展性不好
public class RiskManagement {
  private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter();
  private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter();
  private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter();
  
  public String filterSensitiveWords(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = bFilter.filter(maskedText);
    maskedText = cFilter.filter(maskedText, "***");
    return maskedText;
  }
}
// 使用适配器模式进行改造
public interface ISensitiveWordsFilter { // 统一接口定义
  String filter(String text);
}
public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
  private ASensitiveWordsFilter aFilter;
  public String filter(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    return maskedText;
  }
}
//...省略BSensitiveWordsFilterAdaptor、CSensitiveWordsFilterAdaptor...
// 扩展性更好,更加符合开闭原则,如果添加一个新的敏感词过滤系统,
// 这个类完全不需要改动;而且基于接口而非实现编程,代码的可测试性更好。
public class RiskManagement { 
  private List<ISensitiveWordsFilter> filters = new ArrayList<>();
 
  public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
    filters.add(filter);
  }
  
  public String filterSensitiveWords(String text) {
    String maskedText = text;
    for (ISensitiveWordsFilter filter : filters) {
      maskedText = filter.filter(maskedText);
    }
    return maskedText;
  }
}

3、替换依赖的外部系统

 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
/ 外部系统A
public interface IA {
  //...
  void fa();
}
public class A implements IA {
  //...
  public void fa() { //... }
}
// 在我们的项目中,外部系统A的使用示例
public class Demo {
  private IA a;
  public Demo(IA a) {
    this.a = a;
  }
  //...
}
Demo d = new Demo(new A());
// 将外部系统A替换成外部系统B
public class BAdaptor implemnts IA {
  private B b;
  public BAdaptor(B b) {
    this.b= b;
  }
  public void fa() {
    //...
    b.fb();
  }
}
// 借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动,
// 只需要将BAdaptor如下注入到Demo即可。
Demo d = new Demo(new BAdaptor(new B()));

桥接模式

  • 结构型模式
  • 抽象与实现分离,桥接接口类和抽象类,实现解耦,这里其实并不一定是要抽象类,抽象类只是代表一组现实的抽象,即一个类存在两个(或多个)独立变化的维度,通过组合的方式,让这两个(或多个)维度可以独立进行扩展
  • 典型例子:JDBC,仅修改Class.forName(“com.mysql.jdbc.Driver”),即可把驱动换成别的数据库

JDBC的做法是提供Driver接口,数据库厂商实现该接口提供不同的数据库能力,并调用DriverManager进行注册,后续通过DriverManager获取connection并进行CRUD操作,都由DriverManager委派给具体的Driver做

桥接模式例子
桥接模式例子

桥接接口类和抽象类,接口类实现上色,抽象类实现形状,在抽象类中引入接口,通过不同的组合实现不同的功能

 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
// 桥接实现接口, 连接 画 和 上色
public interface DrawAPI {
   public void drawCircle(int radius, int x, int y);
}
// 不同实现类,红色
public class RedCircle implements DrawAPI {
   @Override
   public void drawCircle(int radius, int x, int y) {
      System.out.println("Drawing Circle[ color: red, radius: "
         + radius +", x: " + x +", "+ y +"]");
   }
}

// 使用 DrawAPI 接口创建抽象类 Shape
public abstract class Shape {
   protected DrawAPI drawAPI;
   protected Shape(DrawAPI drawAPI){
      this.drawAPI = drawAPI;
   }
   public abstract void draw();  
}
// Shape不同得实现类
public class Circle extends Shape {
   private int x, y, radius;
    
   public Circle(int x, int y, int radius, DrawAPI drawAPI) {
      super(drawAPI);
      this.x = x;  
      this.y = y;  
      this.radius = radius;
   } 
   public void draw() {
      drawAPI.drawCircle(radius,x,y);
   }
}
// 调用
public class BridgePatternDemo {
   public static void main(String[] args) {
      Shape redCircle = new Circle(100,100, 10, new RedCircle());
      redCircle.draw();	// Drawing Circle[ color: red, radius: 10, x: 100, 100]
   }
}

代理模式、桥接模式、装饰器模式、适配器模式的区别

**代理模式:**代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

**桥接模式:**桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

**装饰器模式:**装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

**适配器模式:**适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

其实这几种设计模式在代码层面上是很相似的,本质只是设计意图的不同,应对的场景不同

门面(外观)模式

  • 结构型模式
  • 本质是对接口的组合,比如有子系统或者子模块提供了b、c、d接口,都是些职责比较单一的接口,可以在上层提供一个大而全的接口,使用到了b、c、d接口提供的功能,简化了调用者的调用关系处理

组合模式

  • 结构型模式

  • 本质是多叉树,根节点和子节点继承或实现同一个接口,以方便递归处理树形结构的数据

  • 主要处理树形结构数据

享元模式

  • 结构型模式
  • 享元,即共享单元,一般是通过复用不可变对象达到节省内存的作用,通过map + 工厂模式来达到复用的目的,但是对于GC并不友好,如果该对象并不常用,也可以使用弱引用或软引用相关的hashMap来存储,方便垃圾回收
  • 典型例子:java中的Integer类,对于多个-128~127的包装类型对象,底层的内存地址是同一个,Integer类里有个IntegerCache内部类,相当于享元对象的工厂,缓存着-128~127之间的数据,类似的,如 Long、 Short、 Byte包装类型也用到了这种方法;还有String类的字符串常量池,会缓存string字面量,String类也提供了intern方法方便我们将字符串存入常量池

行为型

责任链模式

  • 行为型模式
  • 每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推,即:将所有接受者连成一条链,请求沿着这条链传递,直到有对象处理
  • 典型例子:servlet filter、spring interceptor、logger

责任链模式例子
责任链模式例子

这是模拟日志级别打印的例子:日志抽象类规定每个结点的日志等级和需要重写的方法,参数传递处理的方法,可以看成一个链表上结点的抽象;具体的类实现该抽象类重写共同方法当成每一个结点,最后将这些结点连成链即可

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 创建抽象的记录器类
public abstract class AbstractLogger {
 
   protected int level;
 
   // 责任链中的下一个元素
   protected AbstractLogger nextLogger;
 
   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }
   // 判断是否由当前结点处理或者交由下个结点处理
   public void logMessage(int level, String message){
      // 根据leve判断
      if(this.level <= level){
         write(message);
      }
      if(nextLogger != null){
         nextLogger.logMessage(level, message);
      }
   } 
   abstract protected void write(String message);
}
// 日志等级
public class LoggerLevel {
   public static final int INFO = 1;
   public static final int DEBUG = 2;
   public static final int ERROR = 3;
}

// 记录类的实现类,链上的结点
public class ConsoleLogger extends AbstractLogger { 
   public ConsoleLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("Standard Console::Logger: " + message);
   }
}
public class FileLogger extends AbstractLogger {
   public FileLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}
public class ErrorLogger extends AbstractLogger {
   public ErrorLogger(int level){
      this.level = level;
   }
   @Override
   protected void write(String message) {    
      System.out.println("Error Console::Logger: " + message);
   }
}
// 创建链
public class ChainPatternDemo {
   
   private static AbstractLogger getChainOfLoggers(){
      // 创建每一个结点,设定每个结点的level
      AbstractLogger errorLogger = new ErrorLogger(LoggerLevel.ERROR);
      AbstractLogger fileLogger = new FileLogger(LoggerLevel.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(LoggerLevel.INFO);
 	  // 形成链
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
 	  // 返回头结点
      return errorLogger;  
   }
 
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();
      // 每个结点根据创建来的参数来判断是否执行或者交由下一个结点
      loggerChain.logMessage(LoggerLevel.INFO, "This is an information.");
      // 输出: Standard Console::Logger: This is an information.
      loggerChain.logMessage(LoggerLevel.DEBUG, "This is an debug level information");
      // 输出:
      // File::Logger: This is an debug level information.
	  // Standard Console::Logger: This is an debug level information.
      loggerChain.logMessage(LoggerLevel.ERROR, "This is an error information.");
      // 输出:
	  // Error Console::Logger: This is an error information.
	  // File::Logger: This is an error information.
	  // Standard Console::Logger: This is an error information.
   }
}

迭代器模式

  • 行为型模式
  • 用于顺序访问集合对象的元素,不需要知道集合对象的底层表示,不会暴露该对象的内部表示,迭代器内部使用游标记录当前位置信息,每个迭代器独享游标信息,这样当我们创建不同的迭代器对不同的容器进行遍历的时候就不会互相影响
  • 针对复杂的数据结构,比如树、图的遍历,使用迭代器模式会更加有效
  • 对于迭代过程中通过对容器增加或删除元素会有问题, java使用fail-fast机制,即遍历每个元素的时候会比较modCount的值和调用迭代器时的expectedModCount的值比较来实现fail-fast,达到报错的目的。此外,如果想要删除,需要通过迭代器来删除才可以,java中使用lastRet变量来记录上一个游标,以保证在删除当前元素后,游标能正确指向。

迭代器模式例子
迭代器模式例子

 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
// 迭代器接口
public interface Iterator {
   public boolean hasNext();
   public Object next();
}
// 集合接口
public interface Container {
   public Iterator getIterator();
}
// 包含有迭代器的集合
public class NameRepository implements Container {
   public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};
   @Override
   public Iterator getIterator() {
      return new NameIterator();
   }
   // 内部类
   private class NameIterator implements Iterator {
      int index;
      @Override
      public boolean hasNext() {
         if(index < names.length){
            return true;
         }
         return false;
      }
      @Override
      public Object next() {
         if(this.hasNext()){
            return names[index++];
         }
         return null;
      }     
   }
}
// 调用
public class IteratorPatternDemo {
   public static void main(String[] args) {
       
      NameRepository namesRepository = new NameRepository(); // 创建集合
      for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
         String name = (String)iter.next();
         System.out.println("Name : " + name);
      }  
   }
}

访问者模式

  • 行为型
  • 允许一个或者多个操作应用到一组对象上,解耦操作和对象本身,比如对于多种文件类型,可以使用多种不同的执行器作用在不同的文件上

备忘录模式(快照)

  • 行为型

  • 一般栈来保存副本,入栈的元素都会叠加上一个元素的内容,以此来实现顺序撤销和恢复功能

  • 主要是用来防丢失、撤销、恢复

状态模式

  • 行为型
  • 状态 -> 事件 -> 动作 -> 状态改变 或者 状态 -> 事件 -> 状态改变 -> 动作
  • 分支逻辑法 或者 查表法 或 通过将分支判断抽成类来实现上述 当状态接收到事件后进行业务逻辑动作后改变状态

观察者模式

  • 行为型

  • 在一对多关系中,当一个对象被修改时,则会自动通知它的依赖对象

  • 典型例子:消息队列的发布/订阅模型

  • 硬要说的话,观察者模式和发布订阅模式还是有一定差别的

    观察者是当被观察者有状态发生改变时,通知观察者;而发布订阅是发布者把消息丢到消息队列,消息队列根据消息发给对应的订阅者,区别就是有没有第三方存在,消息的双方知不知道彼此的存在

    发布订阅:

观察者模式例子
观察者模式例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 被观察者,当状态发生改变时,通知观察者
public class Subject {
   // 观察者队列
   private List<Observer> observers = new ArrayList<Observer>();
   private int state;	// 状态
 
   public int getState() {
      return state;
   }
   // 状态改变时通知观察者
   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }
   // 绑定观察者
   public void attach(Observer observer){
      observers.add(observer);      
   }
   // 通知观察者,执行观察者方法
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }  
}

// 观察者抽象类
public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}
// 观察者实现类
public class BinaryObserver extends Observer{ 
   // 绑定被观察者
   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
   // 执行观察者类
   @Override
   public void update() {
      System.out.println("Binary String:" + Integer.toBinaryString(subject.getState() )); 
   }
}
// 观察者实现类
public class OctalObserver extends Observer{
   // 绑定被观察者
   public OctalObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
   // 执行观察者类
   @Override
   public void update() {
     System.out.println("Octal String:" + Integer.toOctalString(subject.getState())); 
   }
}

// 调用
public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();
      new OctalObserver(subject);
      new BinaryObserver(subject);
 
      System.out.println("First state change: 15");   
      subject.setState(15);		// Octal String: 17 /n Binary String: 1111
      System.out.println("Second state change: 10");  
      subject.setState(10);		//Octal String: 12 /n Binary String: 1010
   }
}

模板方法

  • 行为型
  • 一个抽象类抽取一些通用方法合并成新方法并用final修饰作为模板,它的子类继承此抽象类,通过重写通用方法,来实现不一样的模板方法
  • 典型例子:JUC包里的AQS和其子类、util.Collections#sort()、InputStream#skip()、InputStream#read()、servlet、junit
  • 模板方法与回调的区别:回调的作用与模板方法类似,都是通过自由替换某个方法来实现不同的功能,但回调基于组合关系,而模板方法基于继承关系,同步回调类似模板方法,异步回调类似观察者模式

模板方法例子
模板方法例子

 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
// 抽象模板类
public abstract class Game {
   // 给子类重写
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();
    
   //模板
   public final void play(){
      //初始化游戏
      initialize();
      //开始游戏
      startPlay();
      //结束游戏
      endPlay();
   }
}
// 子类继承并重写
public class Cricket extends Game {
   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }
   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }
   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}
// 调用
public class TemplatePatternDemo {
   public static void main(String[] args) {
      Game game = new Cricket();
      game.play();
	  // Cricket Game Initialized! Start playing.
	  // Cricket Game Started. Enjoy the game!
	  // Cricket Game Finished!    
   }
}

策略模式

  • 行为型
  • 通过定义一个通用策略接口 + 一组策略的实现,由调用者在运行时动态选择某一策略完成业务逻辑,常用搭配是 策略 + 工厂模式,根据不同类型选择不同策略
  • 常用的场景是使用策略模式来避免膨胀的分支判断,缺点是策略类会变多,另外,使用查表法也可避免膨胀的分支判断

命令模式

  • 行为型
  • 将函数方法封装成对象,类似c语言的函数指针,数据驱动,将事件和数据封装成对象(命令),最后执行,有点像策略模式,但是命令模式更侧重将行为请求者和实现者解耦

解释器模式

  • 行为型
  • 针对某种语言制定对应的语法,在代码中的体现是,对某种模式的解析抽成方法,降低代码复杂度
  • 典型例子:后缀表达式的解析、sql解析、正则表达式解析

中介模式

  • 行为型
  • 引入中间层,将原来多对多的关系转换为一对多的关系,解耦对象间的关系,类似观察者模式,区别在于观察者模式中,数据的流向、对象间的关系是单向的,观察者就是观察者、被观察者就是被观察者,不会轻易改变,而中介模式是由一个中间对象来处理各个对象间的关系,类似数据总线
  • 典型例子:聊天室

对于EventBus,个人认为它不是中介模式,硬要说话,处理事件的中心类,也算是中介,但是主要是发送事件的对象和处理事件的对象并没有很直接的关系,事件处理者仅关心接收到的是什么事件,并不关心事件是由谁发出的

领域驱动模型 DDD

贫血模型,面向过程,业务逻辑和实体分开,实体仅声明需要的属性,业务逻辑的处理发生在service类,实体只用来存数据,比如各种vo、bo、do

领域驱动模型是一种面向对象的模型、充血模型,更多是用在微服务、业务复杂的场景

具体表现在 业务逻辑的处理是发生在实体类里,而不是发生在service类里,service类只是对各种vo、bo、do转换成需要的领域对象,对它们进行调度,执行它们的方法来完成业务,service甚至可以不感知领域对象的属性变化,与controller和dao层打交道,解耦流程性代码和业务代码,负责非功能性或者与第三方系统交互的工作,比如分布式事务、邮件、消息、rpc调用等,使得领域对象方法和属性复用性提高,业务更加内聚

设计模式六大原则

菜鸟|设计模式

Built with Hugo
Theme Stack designed by Jimmy