设计模式是对大家实际工作中写的各种代码进行高层次抽象的总结,其中最出名的当属 Gang of Four (GoF) 的分类了,他们将设计模式分类为 23 种经典的模式,根据用途我们又可以分为三大类,分别为创建型模式、结构型模式和行为型模式。

有一些重要的设计原则在开篇和大家分享下,这些原则将贯通全文:

  1. 面向接口编程,而不是面向实现。这个很重要,也是优雅的、可扩展的代码的第一步,这就不需要多说了吧。

  2. 职责单一原则。每个类都应该只有一个单一的功能,并且该功能应该由这个类完全封装起来。

  3. 对修改关闭,对扩展开放。对修改关闭是说,我们辛辛苦苦加班写出来的代码,该实现的功能和该修复的 bug 都完成了,别人可不能说改就改;对扩展开放就比较好理解了,也就是说在我们写好的代码基础上,很容易实现扩展。

「创建型模式比较简单,但是会比较没有意思,结构型和行为型比较有意思。」

创建型模式

创建型模式的作用就是创建对象,说到创建一个对象,最熟悉的就是 new 一个对象,然后 set 相关属性。但是,在很多场景下,我们需要给客户端提供更加友好的创建对象的方式,尤其是那种我们定义了类,但是需要提供给其他开发者用的时候。

简单工厂模式

和名字一样简单,非常简单,直接上代码吧:


 

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;         }     } }

其中,LanZhouNoodle 和 HuangMenChicken 都继承自 Food。

简单地说,简单工厂模式通常就是这样,一个工厂类 XxxFactory,里面有一个静态方法,根据我们不同的参数,返回不同的派生自同一个父类(或实现同一接口)的实例对象。

我们强调「职责单一」原则,一个类只提供一种功能,FoodFactory 的功能就是只要负责生产各种 Food。

工厂模式

简单工厂模式很简单,如果它能满足我们的需要,我觉得就不要折腾了。之所以需要引入工厂模式,是因为我们往往需要使用两个或两个以上的工厂。

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;         }     } }

其中,ChineseFoodA、ChineseFoodB、AmericanFoodA、AmericanFoodB 都派生自 Food。

客户端调用:


 
  1.  
    public class APP {
  2.  
        public static void main(String[] args) {
  3.  
            // 先选择一个具体的工厂
  4.  
            FoodFactory factory = new ChineseFoodFactory();
  5.  
            // 由第一步的工厂产生具体的对象,不同的工厂造出不一样的对象
  6.  
            Food food = factory.makeFood("A");
  7.  
        }
  8.  
    }
 

虽然都是调用 makeFood("A")  制作 A 类食物,但是,不同的工厂生产出来的完全不一样。关注公众号[Java面试]获取更多面试资料

第一步,我们需要选取合适的工厂,然后第二步基本上和简单工厂一样。

「核心在于,我们需要在第一步选好我们需要的工厂」。比如,我们有 LogFactory 接口,实现类有 FileLogFactory 和 KafkaLogFactory,分别对应将日志写入文件和写入 Kafka 中,显然,我们客户端第一步就需要决定到底要实例化 FileLogFactory 还是 KafkaLogFactory,这将决定之后的所有的操作。

虽然简单,不过我也把所有的构件都画到一张图上,这样读者看着比较清晰:

factory-1

抽象工厂模式

当涉及到「产品族」的时候,就需要引入抽象工厂模式了。

一个经典的例子是造一台电脑。我们先不引入抽象工厂模式,看看怎么实现。

因为电脑是由许多的构件组成的,我们将 CPU 和主板进行抽象,然后 CPU 由 CPUFactory 生产,主板由 MainBoardFactory 生产,然后,我们再将 CPU 和主板搭配起来组合在一起,如下图:

factory-1

这个时候的客户端调用是这样的:


 
  1.  
    // 得到 Intel 的 CPU
  2.  
    CPUFactory cpuFactory = new IntelCPUFactory();
  3.  
    CPU cpu = intelCPUFactory.makeCPU();
  4.  
     
  5.  
    // 得到 AMD 的主板
  6.  
    MainBoardFactory mainBoardFactory = new AmdMainBoardFactory();
  7.  
    MainBoard mainBoard = mainBoardFactory.make();
  8.  
     
  9.  
    // 组装 CPU 和主板
  10.  
    Computer computer = new Computer(cpu, mainBoard);
 

单独看 CPU 工厂和主板工厂,它们分别是前面我们说的「工厂模式」。这种方式也容易扩展,因为要给电脑加硬盘的话,只需要加一个 HardDiskFactory 和相应的实现即可,不需要修改现有的工厂。

但是,这种方式有一个问题,那就是如果 「Intel 家产的 CPU 和 AMD 产的主板不能兼容使用」,那么这代码就容易出错,因为客户端并不知道它们不兼容,也就会错误地出现随意组合。

下面就是我们要说的「产品族」的概念,它代表了组成某个产品的一系列附件的集合:

abstract-factory-2

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

abstract-factory-3

这个时候,对于客户端来说,不再需要单独挑选 CPU厂商、主板厂商、硬盘厂商等,直接选择一家品牌工厂,品牌工厂会负责生产所有的东西,而且能保证肯定是兼容可用的。


 
  1.  
    public static void main(String[] args) {
  2.  
        // 第一步就要选定一个“大厂”
  3.  
        ComputerFactory cf = new AmdFactory();
  4.  
        // 从这个大厂造 CPU
  5.  
        CPU cpu = cf.makeCPU();
  6.  
        // 从这个大厂造主板
  7.  
        MainBoard board = cf.makeMainBoard();
  8.  
          // 从这个大厂造硬盘
  9.  
          HardDisk hardDisk = cf.makeHardDisk();
  10.  
     
  11.  
        // 将同一个厂子出来的 CPU、主板、硬盘组装在一起
  12.  
        Computer result = new Computer(cpu, board, hardDisk);
  13.  
    }
 

当然,抽象工厂的问题也是显而易见的,比如我们要加个显示器,就需要修改所有的工厂,给所有的工厂都加上制造显示器的方法。这有点违反了「对修改关闭,对扩展开放」这个设计原则。