对设计模式中工厂模式的理解和感悟

253 阅读8分钟

回想刚开始学习设计模式时,被23种设计模式冲昏头脑,很想学好,但是越看越晕。工作几年后,回头在看,略有些心得,因此做个小总结,也希望能带给后来的小伙伴一点点启发。作者水平有限,文中若有错误,敬请谅解。

一、设计模式之道

  很喜欢一些大佬们对知识的分类法,他们会把知识分成“道法术器”(也是源自道家的思想)。所谓“道”,我理解指的就是根源和初心了,它的特点就是高度概括,包罗万象;所谓“法”,我理解就是运行的法度、制度、规章,它的特点就是保证系统的正确运转;所谓“术”,就是一些做事的方法,它的特点就是有条理,有步骤;所谓“器”,则是一些辅助工具了。
我在这里特意提这一点,是因为我们学习任何事物,总脱不开从道开始(自上而下)还是从术开始(自下而上),而在设计模式的学习中,我认为就应该是自上而下的。现在网络上很多文章,上来就是一通介绍,对设计模式一个一个的介绍,有的讲的详细的还会举些“鸭子的类型”、“工厂生产”的例子等等,看的时候热热闹闹地,实际中你回到现实,看着手里项目里面一坨坨几千行代码的类,是不是还是感觉无从下手?我觉得问题就是出在过于强调术了,看介绍很清晰,实际使用就很容易沦为滥用、不敢用、用错了的地步。实际上,整个设计模式的核心,也就是设计模式之道,我认为就是一个字 --- “拆”。大道至简,其实不论是分布式系统、分库分表、微服务等等,说到最后就是个“拆”字,无非就是怎么拆,怎么保证拆了以后能维护的问题。那么“拆”背后反映的是什么呢?是实现拆分的要付出工作量,也是系统角色多了以后的复杂性。很多文章介绍了“分布式”、“微服务”、“设计模式”等等概念以及它们的优点,却很少提及它们的缺陷和问题,实际上前人早就总结过:“软件开发没有银弹”,一切事物都是有其优点和缺点的。比如一个运行稳定地老系统,也没什么新需求了,硬要套一些设计模式上去,进行代码的改造,大概率是要出问题的;或者新系统上来就“面向接口”编程,每个类都搞个对应的接口,这开发效率估计也是不能接受。这也就是为什么我要说设计模式的学习不同于一般的学习路径,需要从“道”开始。也就是抓住事物的本源,而不是把设计模式生搬硬套上去。
再谈谈设计模式的“法”,大家也听得多了,就是“七大原则”、“高内聚,低耦合”等,乍一听云里雾里的,其实也就是告诉你怎么拆比较好,因为是前人总结的经验,所以一般按照这个指导来做就没什么问题。比如说哪天你突然搞出一个23种模式之外的新设计模式,“法”可以让你知道这个新开创的模式靠不靠谱,是不是符合规范的。
最后谈谈设计模式的“术”,也就是总结的23种不同的设计模式了,大部分设计模式的关键,不在于类图、示例代码等,而在于它的提出是为了解决什么问题。“术”是很有局限性的,它的出现可能就是为了解决某一个具体问题,我们带着这个问题去看这个设计模式,可能理解起来就相对简单了。
设计模式概念图   以上这个图就说明了,大部分设计模式,纵向来看,是引入了一个新角色,用来解决某个问题;横向来看,是拆分成类和接口,也就是依赖倒置原则。但是也可以看出,应用设计模式,会导致代码和文件数量激增,甚至有可能降低代码的可读性。

二、工厂模式的理解和感悟

  带着以上的思想再来看工厂模式,就会发现三种工厂模式不用记忆,它们只不过是递进关系,因为解决的问题越来越复杂,但是本质的思想是不变的。这里我既不画类图,也不写示例代码,从“问题、解决方法”两层分类来介绍工厂模式。

简单工厂模式
  • 问题:需要分离创建代码和类代码
    这个场景太常见了,我们想使用某个类,但不想实例化(new)它。可能是实例化太麻烦,也可能是有很多类似的类,想在统一的地方实例化。一般我们比较常用一些依赖注入框架来做,但如果不用框架,我们也可以用工厂模式来实现。
  • 解决方法:设计一个工厂类
    这个工厂类提供创建代码,它最后可以返回实例化后的产品类,也可以返回产品类对应的接口,返回类还是接口其实都不影响,看你需不需要隐藏类的实现细节,因为这个模式的关键就是工厂这个概念。
工厂方法模式
  • 问题:单个工厂不够用
    可能你使用以上的模式发现,这个工厂内部生产着十多种不同的类,每修改一种或者增加一种就需要改代码,这时候又需要拿出“拆”字诀。
  • 解决方法:为工厂类增加抽象层
    定义一个父类或者接口,然后根据不同的产品实现拆分成不同的子工厂,它们同时实现了同一个接口。这样系统内部的类就又多了,但是庞大的单工厂被拆分成多个小工厂,利于维护。
抽象工厂模式
  • 问题:我有一系列复杂的类,相互之间还有关联,而且我想用工厂模式
    这个问题也是最难的问题,我在这里简单的画个图表达一下。 image 以日常经常碰到的加密解密为例,假设我写好了四个类,AES256\RSA256表示秘钥长度256位,加密算法是AES\RSA的加解密专用工具类;AES512\RSA512类似,就是长度上升了而已。如图我们分成了两大类,但实际上它们还有一种关联关系,就是秘钥长度的关联关系。现在我想利用工厂模式去创建类,完成先AES加密,再通过RSA签名的需求。
  • 解决方法:
    1. 简单工厂模式行不行?
      当然可以,我设计个AES工厂,根据传入的参数key_len返回是256还是512长度;再设计个RSA工厂,根据传入的参数key_len返回是256还是512长度。就是用的时候有点繁琐,先初始化AES工厂,再初始化RSA工厂。要是以后来个再用RC4算法加密一遍的需求,还得加个RC4工厂。
    2. 那工厂方法模式行不行?
      当然也可以,但是不解决任何问题,还更加复杂了。
    3. 抽象工厂模式
      再思考一下需求:AES加密 -> RSA签名。最好能通过一个工厂类完成这个需求,那就可以选取秘钥长度这个维度:
          class 256Factory {
              newAES()
              newRSA()
          }
          class 512Factory {
              newAES()
              newRSA()
          }
          
          func main(){
              Factory f = new 256Factory()
              f.newAES().Encrypt()
              f.newRSA().Sign()
          }
      
      如果还需要再用RC4算法加密,直接在Factory中添加·new·方法即可。 所以抽象工厂方法解决的问题一是比较复杂的类,二是类之间相互有关联。但其实本质思想还是简单工厂模式的思想。同时也可以看出,一个问题可以有多个解决方案,最牛逼的方案一定会比较复杂的,但适不适合得看具体情况。
感悟

  写了这么多,特意没有画类图,就是感觉并不是一定要按照类图或者示例代码来实现,才叫XX模式,否则岂不是落了用“术”的下乘。主要看看提出这个模式是为了解决什么问题,只要能解决问题且不违背软件设计的七大原则即可。除了文中举例的工厂模式,其它的设计模式思想也是大同小异,还是那句话,学习设计模式应该从掌握“道”入手,也不应该学的过早,否则容易为了设计模式而设计模式。

三、推荐阅读

设计模式介绍(我认为讲的最详细的)