总结下自己关于设计模式的一些思考。(我终于看穿了爱情,不过就是一圈圈圈圈烟圈圈圈圈 用来解闷消遣。。)
设计模式是对一些 常用的类的行为规范的一个最佳实践的提取。
设计模式不是架构模式,设计模式关注的是代码的可读性,拓展性。而架构关注的是 性能、稳定性、可用性
开闭原则,并不是说,我们能完全的 能够通过新增类、接口来拓展而做到不修改已有的任何组件, 而是说,把修改从某个局部 转移到 外部。
设计模式并不是说只有23种,GoF的那本书《Design Patterns》出版到现在都过去了几十年了,当时的情况,现在已经发生了天翻地覆。很多新的场景、用法 可能都是GoF没有考虑到的。所以说,我认为,如果把各种各样的,常用的不常用的设计模式统计起来,恐怕230、2300 种都不止。 但是,这23种是最最常用的, 也是最 久负盛名的。
其实每一个复杂的类,和其相关的类之间,都会存在一定的模式,有已知的,也有未命名的,有好的,也有坏的,只不过是我们是否发现了而言。
当然,不同的行业,常用到的模式又不一样。
我们看到了,这些模式,其实都很简单,实际情况,可能复杂得多。
下面,我们从 主要功能、是否能简化、适用范围、示例 等方面进行探讨。
单例模式:
适用于,全局唯一,只有一个实例对象。
但是正是由于,它要求全局唯一, 那么可能出现多线程同时访问,需要防范多线程问题。
如何对单例模式进行改造,使得系统中某个类的对象可以存在有限多个,例如两例或三例?【注:改造之后的类可称之为多例类。】
简化,无法再继续简化了,因为只有一个类啊! 方法也简单,没有复杂的调用。简化反而可能引起错误。
简单工厂模式:
(下面的factoryMethod应该为静态方法)
简化:去掉了工厂类,抽象产品同时兼具工厂功能 (下面的factoryMethod可以为静态方法):
拓展性:新增一个具体产品实现抽象产品类即可。简单,但是新增产品的时候,必须修改工厂,从而破坏了开闭原则。
讨论:Product可以为抽象类吗? 当然可以的,Product 可以是任意的东西,pojo,controller,dao,service,复杂的对象。 可以有很多的各种属性,也可以各种方法。所以,我们不限制它必须是接口。
工厂方法模式:
避免了新增产品的时候 修改工厂,新增一个产品类,则需要同时增加一个工厂类, 方便拓展(新增类,不是什么缺点)。
拓展范围:有一系列有统一规范的产品(继承、实现了Product接口)
讨论:1 Product、Factory可以为接口类吗? 应该可以的,主要看是否有公共的内容需要提取到上层,如果是,那么则需要为 抽象类,否则,就无所谓了。
2 工厂方法模式中的工厂方法能否为静态方法?为什么? 不能,因为工厂方法实际上是抽象方法,要求由子类来动态地实现,而动态性与static所声明的静态性相冲突
抽象工厂模式:
概念说明:
产品等级:有继承关系的一些列产品,通常不是一个通常生产的,AbstractProductA的子类:ConcreteProductA1、A2 就是产品等级。产品等级可以理解为 不同工厂的 brand
产品族: 同一个工厂生产的不同产品,上图的AbstractProductA、AbstractProductB就是两个产品族。显然,产品族是一个更加 宽泛的概念。
当然实际情况可能更加复杂,比如有的工厂可以生产AbstractProductA,同时自己工厂的AbstractProductA下面有很多细分的产品,比如AbstractProductA1,AbstractProductA2,有很多型号,但都是属于一个产品线。
新增具体工厂涉及到拓展已有的 产品等级(不会有新的产品族出现,这个有点难理解,可以这么说, 这个工厂没有增加新 种类的产品,而只是新增了不同brand的产品。),新增工厂方便,新增3个类即可; 但是新增产品族呢? 则破坏了开闭原则,需要修改各个工厂,同时需要新增3个类。这就是所谓的“ “开闭原则”的倾斜性 ”
简化:Factory是否可以省去? 不能,否则各ConcreteFactory 没有统一的规范,相互之间难以约束,就乱套了。然而实际情况也正是如此,所以它的适用范围不多。如果我们又一个刚刚好的类似的产品体系, 则可以考虑此模式。
适用范围:复杂的产品体系(同时包含 产品等级、产品族 两个 维度)
讨论:AbstractProduct、Factory可以为抽象类吗? 应该可以。不应做过多限制。但是Factory 作为一个高层级的 东西, 设置为 接口更好。
原型模式:
对已有对象克隆,创建一个一模一样的对象。当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。
适用范围:对于某些复杂的,消耗资源多的对象,直接创建对象的代价大,克隆代价小。什么情况下克隆的代价会比较小?比如一个对象有几十个属性,现在我需要新建一个一模一样的对象,不想使用初始值,通常我们可以先new 一个对象,然后一个个的set Xxx( old.getXxx() ),这样是完全没问题的,但是显然就影响了可读性,而且显得繁琐而且笨重, 当然我们也可以使用 BeanUtils的那一套。
如果使用Cloneable,那么只需要写一个clone方法即可,由于clone方法是native,也就是c++实现的,那么jdk底层做了优化,肯定是比一般的操作要快一些(估计是直接进行了内存拷贝操作等)。 当然, 具体有多快,是不是就比BeanUtils的那一套 还要快,我无法证明。
Prototype 就对应了java 的Cloneable。相对于BeanUtils或自己写copyXxx方法,确实是多了一些麻烦,也就是我们需要实现它,虽然 实现clone 方法通常非常简单,但是确实是破坏了开闭原则,然而,话也不能这么说,使用设计模式,当然也是有一些些的代价的,但是我们要看到其方便的一面。任何事物都有两面性,有利有弊。
原型管理器(Prototype Manager),可以把很多的Prototype 统一管理,相对于是把 Prototype 和 享元模式结合到了一起。
拓展性:虽然外面可以 对Prototype 进行继承实现拓展,但是 估计一般人也不会这么做,需要多种clone 的方式吗? 所以说Prototype 模式的重点不是拓展,而是 clone 方法的代价,是否有必要。
简化: client 是不需要考虑的,显然Prototype 还可以简化, 我们可以把Prototype,ConcretePrototype 合并, 如上所述,我们几乎不需要 多种 clone 的方式, 不需要扩展。
建造者模式:
适用范围:同一过程、标准 构建 很多很复杂产品。
简化:省去Director,可以将Director和抽象建造者Builder进行合并。当一个产品非常复杂的时候,我们需要一个Director来指挥 建造过程。当然,如果这样的产品不多,比如只有一个ConcreteBuilder,那么也没必要专门的Director,我们完全可以省去Director,甚至Builder。所以它适用于 很多很复杂产品,同时,这些产品有 很相似的 建造过程。 —— 话说回来,这样的 场景 多吗? 也不多,所以,这个模式用得少。