设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
一、设计模式的分类
- 创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
- 结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
另外还有并发型模式和线程池模式。
二、23种设计模式
第一部分 创建型模式
1、单例模式 (Singleton)
单例模式是一种创建型设计模式,它确保类只有一个实例,并提供了一种访问该实例的全局方法。这种模式有助于确保系统中的某些组件只有一个实例,并提供了一种方便的方法来访问该实例。
使用场景
- 需要频繁的进行创建和销毁的对象;
- 创建对象时耗时过多或耗费资源过多,但又经常用到的对象;
- 工具类对象;
- 频繁访问数据库或文件的对象。
在Java中,单例模式通常使用一个私有构造函数和一个静态getInstance方法来实现。以下是Java中单例模式的几种示例:
1.1 饿汉式(静态常量)【可用】
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { return instance; } }
1.2 饿汉式(静态代码块)【可用】
这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
public class Singleton { private static Singleton instance; static { instance = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return instance; } }
1.3 懒汉式(线程不安全)【不可用】
这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。
public class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
1.4 懒汉式(线程安全,同步方法)【不推荐用】
解决上面第三种实现方式的线程不安全问题,做个线程同步就可以了,于是就对getInstance()方法进行了线程同步。
缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。
public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
1.5 懒汉式(线程安全,同步代码块)【不可用】
由于第四种实现方式同步效率太低,所以摒弃同步方法,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。
public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { instance = new Singleton(); } } return instance; } }
1.6 双重检查【推荐使用】
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if(singleton == null),直接return实例化对象。
优点:线程安全;延迟加载;效率较高。
public class Singleton { private static Singleton instance; private Singleton() {} public static synchronized Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
1.7 静态内部类【推荐使用】
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
class InnerClass{ // 1、创建一个私有的静态内部类, private static class CreateInstance{ private static InnerClass instance = new InnerClass(); } // 2、私有化构造器,保证不能在类外部通过new构造器来构造对象 private InnerClass() { System.out.println(Thread.currentThread().getName()); } // 3、创建一个公共的静态方法,返回当前类对象实例 public static InnerClass getInstance() { return CreateInstance.instance; } }
1.8 枚举【推荐使用】
创建枚举默认就是线程安全的,不需要担心double checked locking,而且还能防止反序列化导致重新创建新的对象,枚举让JVM来帮我们保证线程安全和单一实例的问题,是JDK1.5版本后最适合用于创建单例设计模式的方法,是唯一一种不会被反射破坏单例状态的模式
class SingletonEnum{ // 1、创建一个枚举 public enum CreateInstance{ // 创建一个枚举实例 INSTANCE; // 创建一个指向SingletomEnum的变量 private SingletonEnum instance; // 2、私有化构造器,保证不能在类外部通过new构造器来构造对象 private CreateInstance() { instance = new SingletonEnum(); System.out.println(Thread.currentThread().getName()); } // 3、创建一个公共的方法,由实例调用返回单例类 public SingletonEnum getInstance() { return instance; } } }
2、工厂方法模式(Factory Method)
工厂模式是一种创建型设计模式,它提供了一种方法来创建对象,而无需将对象的创建逻辑暴露给客户端代码。这种模式有助于将代码的创建和使用分离,从而使代码更加灵活和易于维护。
public interface Shape { public void draw(); } public class Circle implements Shape { public void draw() { System.out.println("Drawing a circle"); } } public class Rectangle implements Shape { public void draw() { System.out.println("Drawing a rectangle"); } } public class ShapeFactory { public Shape createShape(String shapeType) { if (shapeType == null) { return null; } if (shapeType.equalsIgnoreCase("CIRCLE")) { return new Circle(); } else if (shapeType.equalsIgnoreCase("RECTANGLE")) { return new Rectangle(); } return null; } }
在此示例中,我们有一个Shape接口,定义了一个draw方法。我们还有两个具体的形状实现,Circle和Rectangle。最后,我们有一个ShapeFactory类,它根据传递给它的字符串参数创建不同类型的形状对象。
要使用此工厂,您可以创建一个ShapeFactory对象,并调用其createShape方法,将要创建的形状类型作为参数传递。例如:
ShapeFactory shapeFactory = new ShapeFactory(); Shape circle = shapeFactory.createShape("CIRCLE"); circle.draw(); Shape rectangle = shapeFactory.createShape("RECTANGLE"); rectangle.draw();
这将输出:
Drawing a circle
Drawing a rectangle
3、抽象工厂模式(Abstract Factory)
抽象工厂模式是一种创建型模式,是工厂模式的一种变体,它提供了一个接口来创建相关或依赖对象的系列,而不需要指定它们的具体类。当您需要创建一组相关对象,但希望将这些对象的实现与使用它们的代码分离时,此模式非常有用。
ppublic interface AbstractFactory { public Shape createShape(); public Color createColor(); } public class RoundedShapeFactory implements AbstractFactory { public Shape createShape() { return new RoundedRectangle(); } public Color createColor() { return new Red(); } } public class SquareShapeFactory implements AbstractFactory { public Shape createShape() { return new Square(); } public Color createColor() { return new Blue(); } } public interface Shape { public void draw(); } public class RoundedRectangle implements Shape { public void draw() { System.out.println("Drawing a rounded rectangle"); } } public class Square implements Shape { public void draw() { System.out.println("Drawing a square"); } } public interface Color { public void fill(); } public class Red implements Color { public void fill() { System.out.println("Filling with red"); } } public class Blue implements Color { public void fill() { System.out.println("Filling with blue"); } }
在此示例中,我们有一个AbstractFactory接口,定义了创建形状和颜色的方法。然后,我们有两个具体工厂,RoundedShapeFactory和SquareShapeFactory,它们实现了AbstractFactory接口并创建不同类型的形状和颜色。最后,我们有Shape和Color接口及其各自的实现,RoundedRectangle、Square、Red和Blue。
通过使用抽象工厂模式,我们可以创建相关对象系列,而不会将我们的代码紧密耦合到它们的具体实现中。这使我们的代码更加灵活和易于维护。
4、建造者模式(Builder)
建造者模式是一种创建型设计模式,它允许您使用相同的代码基础构建不同类型的对象。它通过将对象构建过程分解为多个较小的步骤来实现此目的,并允许您通过更改步骤的顺序或更改步骤本身来创建不同类型的对象。
在Java中,建造者模式通常包括两个主要组成部分:建造者和产品。建造者是负责构建产品的类,而产品则是最终构建的对象。以下是Java中建造者模式的示例:
public class Product { private String partA; private String partB; private String partC; public void setPartA(String partA) { this.partA = partA; } public void setPartB(String partB) { this.partB = partB; } public void setPartC(String partC) { this.partC = partC; } // other product-related methods } public interface Builder { void buildPartA(); void buildPartB(); void buildPartC(); Product getResult(); } public class ConcreteBuilder implements Builder { private Product product = new Product(); public void buildPartA() { product.setPartA("Part A"); } public void buildPartB() { product.setPartB("Part B"); } public void buildPartC() { product.setPartC("Part C"); } public Product getResult() { return product; } } public class Director { private Builder builder; public Director(Builder builder) { this.builder = builder; } public void construct() { builder.buildPartA(); builder.buildPartB(); builder.buildPartC(); } }
在此示例中,我们有一个Product类,它是最终构建的对象。我们还有一个Builder接口,定义了构建过程的关键步骤,并具有一个getResult方法,该方法返回构建后的Product对象。我们还有一个ConcreteBuilder类,它实现了Builder接口中定义的方法,以构建具体的Product对象。最后,我们有一个Director类,它负责使用Builder对象来构建最终的Product对象。Director使用Builder接口中定义的方法,以确保Product对象按照正确的顺序创建。
要使用该模式,您可以实例化一个ConcreteBuilder对象,并将其传递给Director对象。然后,您可以调用Director对象的construct方法,该方法将使用Builder对象构建Product对象。例如:
Builder builder = new ConcreteBuilder(); Director director = new Director(builder); director.construct(); Product product = builder.getResult();
这将构建一个Product对象,并将其存储在product变量中。
5、原型模式(Prototype)
原型模式是一种创建型设计模式,它允许您复制已有对象来创建新对象,而无需通过实例化类来构建新对象。这种模式通过克隆对象来实现,并提供了一种比手动创建新对象更快,更简单的方法来创建新的可变对象。
在Java中,复制对象是通过clone()实现的,先创建一个原型类:
public class Prototype implements Cloneable { public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super.clone(); return proto; } }
很简单,一个原型类,只需要实现Cloneable接口,覆写clone方法,此处clone方法可以改成任意的名称,因为Cloneable接口是个空接口,你可以任意定义实现类的方法名,如cloneA或者cloneB,因为此处的重点是super.clone()这句话,super.clone()调用的是Object的clone()方法,而在Object类中,clone()是native的,具体怎么实现,我会在另一篇文章中,关于解读Java中本地方法的调用,此处不再深究。在这儿,我将结合对象的浅复制和深复制来说一下,首先需要了解对象深、浅复制的概念:
浅复制:将一个对象复制后,基本数据类型的变量都会重新创建,而引用类型,指向的还是原对象所指向的。
深复制:将一个对象复制后,不论是基本数据类型还有引用类型,都是重新创建的。简单来说,就是深复制进行了完全彻底的复制,而浅复制不彻底。
此处,写一个深浅复制的例子:
public class Prototype implements Cloneable, Serializable { private static final long serialVersionUID = 1L; private String string; private SerializableObject obj; /* 浅复制 */ public Object clone() throws CloneNotSupportedException { Prototype proto = (Prototype) super.clone(); return proto; } /* 深复制 */ public Object deepClone() throws IOException, ClassNotFoundException { /* 写入当前对象的二进制流 */ ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); /* 读出二进制流产生的新对象 */ ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); } public String getString() { return string; } public void setString(String string) { this.string = string; } public SerializableObject getObj() { return obj; } public void setObj(SerializableObject obj) { this.obj = obj; } } class SerializableObject implements Serializable { private static final long serialVersionUID = 1L; }
要实现深复制,需要采用流的形式读入当前对象的二进制输入,再写出二进制数据对应的对象。
第二部分 结构型模式
6、适配器模式(Adapter)
7、装饰模式(Decorator)
8、代理模式(Proxy)
9、外观模式(Facade)
10、桥接模式(Bridge)
11、组合模式(Composite)
12、享元模式(Flyweight)
第三部分 行为型模式
第一类:通过父类与子类的关系进行实现。第二类:两个类之间。第三类:类的状态。第四类:通过中间类
关系图