一图速记:23种设计模式之单例模式、工厂方法模式

设计模式是软件工程中解决特定问题的经典方案模板,如同建筑大师手中的蓝图,能指导我们构建出更灵活、可维护的代码结构。本文将用最直观的图解方式,带你快速掌握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);
}

抽象工厂的问题也是显而易见的,如:需要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法,着实违反了对修改关闭、对扩展开放的设计原则。

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注