设计模式是代码开发人员总结归纳出的代码设计经验,使用合理的设计模式能够提高代码的可拓展性和可维护性。
本文主要介绍了设计模式开山鼻祖经典书籍《Design Patterns》描写的23种设计模式,不过正如GoF所说“We don’t consider this collection of design patterns complete and static; it’s more a recording of our current thoughts on design.(我们并不认为这些设计模式是完整并且一成不变的,他更多的只是对我们当前设计思想的记录。)”设计模式远不止23种,并且也并非是一种固定的定式。此书出版于1994年,在长久的发展中自然出现了许多变化和进步,其中有些经典模式也已不再常用,但从其中23种设计模式仍能学到某些思想。
总而言之,创造力是自己的,设计模式是通过经验总结给予的一种方向的指引。
1. 设计模式分类:
GoF将设计模式分为三类,分别为创造型设计模式、结构型设计模式、行为型设计模式,本文将对其一一列举。
2. 创建型模式(Creational)
创建型设计模式关注点在于如何创建对象,创建型模式用于解耦对象的实例化过程,主要特点是将对象的创建与使用分离。
1. 工厂方法(Factory Method)
工厂方法将创建对象和使用对象相分离,并且在使用时引用的是工厂和产品的抽象产品。
//工厂接口
public interface Factory {
//接口中定义的变量默认为public static final
Factory factory = new FactoryImpl();
//java8后接口里可以声明静态方法,并可以实现,修饰符默认为public
static Factory getFactory() {
return factory;
}
//获取产品
Product getProduct();
}
//工厂接口的实现
public class FactoryImpl implements Factory {
@Override
public Product getProduct() {
return new ProductImpl();
}
}
//产品接口
public interface Product {
void say();
}
//产品接口的实现
public class ProductImpl implements Product{
public void say() {
System.out.println("hello factory method")
}
}
//具体使用
public class MainTest {
public static void main(String[] args) {
Factory factory = Factory.getFactory(); //获取工厂类
Product product = factory.getProduct(); //通过工厂获取产品
product.say(); //输出结果:hello factory method
}
}
2. 抽象工厂(Abstract factory)
抽象工厂和工厂方法稍微有些不同,抽象工厂中有多个产品需要创建,因此会有多个实际工厂,每个工厂又能创建多个实际产品。
就好比一台手机需要cpu、内存、屏幕等许多组件,其中的组件可能是由a厂生产,也可能是由b厂生产。
//假定有两个工厂A和B,先定义抽象工厂接口和产品接口
public interface AbstractFactory {
FirstProduct getFirstProduct(); //获取第一个产品
SecondProduct getSecondProduct(); //获取第二个产品
}
public interface FirstProduct {
void sayHi();
}
public interface SecondProduct {
void sayHello;
}
//定义第一个工厂和第一个工厂生成的产品
public class FactoryA implements AbstractFactory {
public FirstProduct getFirstProduct() {
return new FirstProductA();
}
public SecondProduct getSecondProduct() {
return new SecondProductA();
}
}
public class FirstProductA implements FirstProduct {
public void sayHi() {
System.out.println("hi,A product");
}
}
public class SecondProductA implements SecondProduct {
public void sayHello {
System.out.println("hello,A product")
}
}
//定义第二个工厂与第一个工厂工厂代码类似,只需把A换为B,方便起见简写
public class FactoryB implements AbstractFactory {...}
public class FirstProductB implements FirstProduct {...}
public class SecondProductB implements SecondProduc {...}
//具体使用
public class MainTest {
public static void main(String[] args) {
Factory factory = new FactoryA(); //使用A工厂
FirstProduct firstProduct = factory.getFirstProduct(); //通过A工厂获取产品1
SecondProduct secondProduct = factory.getSecondProduct(); //通过A工厂获取产品2
firstProduct.sayHi(); //输出结果:hi,A product
secondProduct.sayHello(); //输出结果:hello,A product
//若用B工厂只需把 Factory factory = new FactoryA() 改为 new FactoryB(),下文省略
...
...
}
}
此外如果在AbstractFactory
中创建工厂,则具体的工厂都封装屏蔽了。
public interface AbstractFactory {
//调用方法时通过传参进行工厂选择
static AbstractFactory getFactory(String factoryName) {
switch(factoryName) {
case "A":
return new FactoryA();
case "B":
return new FactoryB();
default:
throw new IllegalArgumentException();
}
}
FirstProduct getFirstProduct(); //获取第一个产品
SecondProduct getSecondProduct(); //获取第二个产品
}
//以下省略……
3. 建造者(Builder)
建造者模式也称为创建者模式,也有人叫生成器、构造器,主要是创建对象时对象参数比较多,通过每一个组件的拼接最终组合成一个完整的对象。
以下将通过lombok的注解来演示。(使用lombok可以简化很多代码,关于lombok的内容请自行搜索)
建造者模式的应用:
//声明一个“人”类,以下注解皆为lombok中的注解。
@Builder //此注解为你的类生成相对略微复杂的构建器API
@Getter //代替get方法
@Setter //代替setter方法
@ToString //重写toString方法
public class Person {
private String name;
private int age;
private String gender;
private String phoneNumber;
}
//使用
public class MainTest {
public static void main(String[] args) {
Person p = Person.builder().name("Tom").age("123").gender("male").build();
//输出结果:Person(name=Tom, age=123, gender=male, phoneNumber=null)
System.out.println(p);
p.setPhoneNumber("123456");
输出结果:Person(name=Tom, age=123, gender=male, phoneNumber=123456)
System.out.println(p);
}
}
4. 原型(Prototype)
原型模式是通过现有的一个原型来进行创建,主要用来创建重复的对象。如果当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
例如Arrays.copyof()
方法实际就是一种原型模式。
//原型
String[] original = {"qqq","www","eee"}
String[] copy = Arrays.copyof(original,original.length);
对于普通类而言,如要实现原型拷贝需要实现Cloneable
接口,然后通过Object类的clone()方法进行克隆。
5. 单例(Singleton)
单例模式保证了一个类仅有一个实例,并提供一个访问它的全局访问点。
通过如下方式可以实现一个简单的单例模式。
public class SingleObject {
//创建SingleObject的一个对象
private static SingleObject instance = new SingleObject();
//使用private修饰构造函数,避免该类被实例化
private SingleObject(){}
//提够一个获取唯一对象的方法
public static SingleObject getInstance() {
return instance
}
}
以上这种方式成为饿汉式,在类加载的时候便会实例化,也因此他是线程安全的。
除此之外还有一种懒汉式加载方式:
public class SingleObject {
private static SingleObject instance;
//private构造方法保证外部无法实例化
private SingleObject();
public static SingleObject getInstance() {
//访问该方法时,若instance未实例化则进行实例化
if(instance == null) {
instance = new SingleObject();
}
return instance;
}
}
懒汉式在多线程中是错误的,因为在竞争条件下会创造出多个实例,此时需要对整个方法进行加锁。
//对方法进行加锁,被锁的对象是当前类对象。该方式会严重影响并发性能。
public static synchronized SingleObject getInstance() {
//访问该方法时,若instance未实例化则进行实例化
if(instance == null) {
instance = new SingleObject();
}
return instance;
}
另一种实现Singleton的方式是利用Java的enum
,因为Java保证枚举类的每个枚举都是单例,所以我们只需要编写一个只有一个枚举的类即可。
此外有一种双重校验的锁的方式:
public class Singleton { private static Singleton instance; private Singleton(){} public static Singleton getInstance() { //控制阻塞条件 if(instance == null) { synchronized(Singleton.class){ //控制实例创建对象 if(instance == null) { instance = new Singleton(); } } } return instance; } }
上述方式看似很完美,但由于JVM即时编译器中存在指令重排序的优化,导致可能会产生某些错误(即DCL失效问题)。
此时需要将
instance
变量声明称volatile
,此时使用volatile并非为了使其可见,而是禁止指定重排序优化。此外可采取静态内部类的方式,外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化instance,故而不占内存。该方式不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
public class Singleton{ private static class SingletonHolder { private static Singleton instance = new Singleton(); } private Singleton() {} public static Singleton getInstance() { return SingletonHolder.instance; } }