设计模式是软件工程中解决特定问题的经典方案模板,如同建筑大师手中的蓝图,能指导我们构建出更灵活、可维护的代码结构。本文将用最直观的图解方式,带你快速掌握23种设计模式的精髓,助你从“代码搬运工”迈向“软件设计师”,从此告别代码臃肿。
1、23种设计模式,速度口诀
创建型 (5种):单抽元件厂
单 (单例) – 抽 (抽象工厂) – 原 (原型) – 建 (建造者) – 厂 (工厂方法)
结构型 (7种):外侨组员戴配饰
外 (外观) – 侨 (桥接) – 组 (组合) – 员 (享元) – 戴 (装饰器) – 配 (适配器) – 饰 (代理)
行为型 (11种):观摩迭策,责令解放,戒忘台
观 (观察者) – 摩 (备忘录) – 迭 (迭代器) – 策 (策略)
责 (责任链) – 令 (命令) – 解 (解释器) – 放 (访问者)
戒 (中介者) – 忘 (状态) – 台 (模板方法)
总结:单抽元件厂,外侨组员戴配饰,观摩迭策,责令解放,戒忘台
2、图示单例模式和工厂方法模式

单例模式用得最多,错的也最多;
3、单例模式
3.1 实现代码
// 饿汉式,最简单的
public class Singleton {
// 首先,将 new Singleton() 堵死
private Singleton() {};
// 创建私有静态实例,意味着这个类第一次使用的时候就会进行创建
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
// 瞎写一个静态方法。这里想说的是,如果我们只是要调用 Singleton.getDate(...),
// 本来是不想要生成 Singleton 实例的,不过没办法,已经生成了
public static Date getDate(String mode) {return new Date();}
}
// 很多人都能说出饿汉式的缺点,如:您定义了一个单例的类,不需要其实例,可是你却把一个或几个会用到的静态方法塞到这个类中;
// 懒汉式,最容易出错
public class Singleton {
// 首先,也是先堵死 new Singleton() 这条路
private Singleton() {}
// 和饿汉模式相比,这边不需要先实例化出来,注意这里的 volatile,它是必须的
private static volatile Singleton instance = null;
public static Singleton getInstance() {
if (instance == null) {
// 加锁
synchronized (Singleton.class) {
// 这一次判断也是必须的,不然会有并发问题
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
// 这里是双重检查,指的是两次检查instance是否为null;volatile在这里是需要的,希望能引起读者的关注;很多人不知道该怎么写,直接就在getInstance()方法签名上加上synchronized,这样性能太差
3.2 嵌套类最经典,推荐以后使用
public class Singleton3 {
private Singleton3() {}
// 主要是使用了 嵌套类可以访问外部类的静态属性和静态方法 的特性
private static class Holder {
private static Singleton3 instance = new Singleton3();
}
public static Singleton3 getInstance() {
return Holder.instance;
}
}
// 大多数开发者会把嵌套类说成是静态内部类,严格来说:内部类和嵌套类是不一样的,它们能访问的外部类权限也是不一样的
4、工厂方法模式
4.1 简单工厂模式
public class FoodFactory {
public static Food makeFood(String name) {
if (name.equals("noodle")) {
Food noodle = new LanZhouNoodle();
noodle.addSpicy("more");
return noodle;
} else if (name.equals("chicken")) {
Food chicken = new HuangMenChicken();
chicken.addCondiment("potato");
return chicken;
} else {
return null;
}
}
}
简单说:简单工厂模式通常就是这样,一个工厂类 XXFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。即:强调职责单一,一个类只提供一种功能,FoodFactory的功能就是只要负责生产各种Food。
4.2 工厂模式
public interface FoodFactory {
Food makeFood(String name);
}
public class ChineseFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new ChineseFoodA();
} else if (name.equals("B")) {
return new ChineseFoodB();
} else {
return null;
}
}
}
public class AmericanFoodFactory implements FoodFactory {
@Override
public Food makeFood(String name) {
if (name.equals("A")) {
return new AmericanFoodA();
} else if (name.equals("B")) {
return new AmericanFoodB();
} else {
return null;
}
}
}
客户端调用:
public class APP {
public static void main(String[] args) {
// 先选择一个具体的工厂
FoodFactory factory = new ChineseFoodFactory();
// 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
Food food = factory.makeFood("A");
}
}
// 虽然都是调用makeFood("A")制作食物,但是不同的工厂生产出来的完全不一样。
首先,需要选取合适的工厂,接下来就和简单工厂一样。
核心在于,我们需要在第一步选好我们需要的工厂。比如:我们有LogFactory接口,实现类有FileLogFactory和kafkaLogFactory,分别对应奖日志写入kafka中,显然,我们客户端第一步就需要决定到底要实例化FileLogFactory还是KafkaLKogFactory,这将决定之后的所有的操作。看下图:

4.3 抽象工厂模式
当涉及到产品族的时候,需要引入抽像工厂模式;如果造一台电脑,如果不引入抽象工厂模式,看看如何实现:
电脑是由很多的构件组成的,将CPU和主板进行不抽象,然后CPU由PUFactory生产,主板由MainBoardFactory生产,之后,再将PU和主板搭配起来组合在一起。
// 得到 Intel 的 CPU
CPUFactory cpuFactory = new IntelCPUFactory();
CPU cpu = intelCPUFactory.makeCPU();
// 得到 AMD 的主板
MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
MainBoard mainBoard = mainBoardFactory.make();
// 组装 CPU 和主板
Computer computer = new Computer(cpu, mainBoard);
单独看 CPU 工厂和主板工厂,它们分别是前面我们说的工厂模式。这种方式也容易扩展,因为要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可,不需要修改现有的工厂。
但是,这种方式有一个问题,那就是如果 Intel 家产的 CPU 和 AMD 产的主板不能兼容使用,那么这代码就容易出错,因为客户端并不知道它们不兼容,也就会错误地出现随意组合。
所谓的产品族,其实是组成某个产品的一系列附件的集合:

当涉及到这种产品族的问题的时候,就需要抽象工厂模式来支持了。不再定义CPU工厂、主板工厂、硬盘工厂、显示屏工厂等等,直接定义电脑工厂即可,每个电脑工厂负责生产所有的设备,这样就能保证肯定不存在兼容问题。

此时,对于客户端来说,不再需要单独接待CPU厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。
public static void main(String[] args) {
// 第一步就要选定一个“大厂”
ComputerFactory cf = new AmdFactory();
// 从这个大厂造 CPU
CPU cpu = cf.makeCPU();
// 从这个大厂造主板
MainBoard board = cf.makeMainBoard();
// 从这个大厂造硬盘
HardDisk hardDisk = cf.makeHardDisk();
// 将同一个厂子出来的 CPU、主板、硬盘组装在一起
Computer result = new Computer(cpu, board, hardDisk);
}
抽象工厂的问题也是显而易见的,如:需要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法,着实违反了对修改关闭、对扩展开放的设计原则。