设计模式(Design pattern)在软件行业一直都扮演着很重要的角色。最近感觉自己对设计模式的知识有些遗忘了,虽然以前也看了很多,但是坦白说,其实并没有怎么理解。基本还是为了应付面试。然后,在工作当中,也很少用到那么多的设计模式,大部分工作都是涉及单例模式,工厂模式或者装饰模式。慢慢地,就觉得设计模式好像离自己越来越远了,变成了一种似懂非懂的东西。于是乎就赶紧脑补一下。脑补的时候,感觉知识确实是一种温故而知新的东西。
其实,设计模式更多是代表着一种思想,就像面向对象一样。你在开发的过程中,也学已经用了很多种设计模式,只是你不知道原来这就是设计模式中某一种…也因为这样,如果你把设计模式当作一种思想思维而不是一种工具去看待的话,那么理解起来也会舒服和容易很多。由于设计模式基本面向的是OOP的语言,所以OOP的思想应该是你阅读的基础。
这里想顺便插播几句关于教育的问题。我们一直以来都批判我们国家的教育太过死板,老是填鸭式的教学。我个人认为,这些真的不能算作什么很劣质,然后外国那套很优良。我们现在回想起来,会发现,这种我们称之为填鸭式教学,其实也有它好的一面,那就是无论怎么样,你总是有一定理论知识的基础。至于往后的个人发展、学习以及工作的种种影响,并不能怪罪于这种教学方式,而是我们赋予了教育太多的使命和任务才让原本单纯传播知识的这项任务变得如此沉重不堪……之所以,插播这个,也是希望同学们不要去抱怨过去的鸭子,过去的鸭子再难吃也过去了,好好去捕捉其他鸟兽吧~
这篇关于设计模式主要还是偏向概况,并且主要是关于基本的那23种模式,不会去讨论如MVC、MVP或其他MV*等衍生的设计模式。如果你对好多基本的概念不了解还是先去看看一些详细的书籍,这样会比较好一些。好了,所有的废话和水分都说加完了,下面开始进入干货模式。
基础设计模式分为三种类型,共23种。
创建型模式:工厂方法模式、抽象工厂模式、创建者模式、原型模式、单例模式。
结构型模式:外观模式、适配器模式、代理模式、装饰模式、桥接模式、组合模式、享元模式。
行为型模式:模版方法模式、观察者模式、状态模式、策略模式、职责链模式、命令模式、访问者模式、调停者模式、备忘录模式、迭代器模式、解释器模式。
一、创建型模式 —— 该类模式研究的是如果更好地创建一个对象的问题
1,工厂方法模式(Factory Pattern) —— 设计一个专门的工厂类Factory,用来专职创建一些比较复杂的对象(就是它的初始化比较复杂,不是单纯new Constructor()那么简单)如:Bitmap bitmap = BitmapFactory.createBitmap(a,b,c);
2,抽象工厂模式(Abstract Factory Pattern) —— 该模式是基于 工厂方法模式 。设计一个抽象的工厂接口,然后再实现它产生具体的工厂类来创建那些比较复杂的对象。当我们需要工厂方法类解决复杂对象的创建问题并发现复杂对象属于一系列相关联的对象时,也即出现了对象创建的管理问题时,就可以考虑抽象工厂方法类了。
3、创建者模式(Builder Pattern)—— 当我们需要创建一个复杂的对象,并且这个复杂的对象在使用之前的组装也比较复杂的时候,形如Android的AlertDialog。那么就可以考虑使用创建者模式了。
4、原型模式(Prototype Pattern)—— 当我们需要动态创建或加载具有一定层次的对象,这些对象对外都是相对稳定的时候,原型模式就比较适合了。因为原型模式实现的基础就是java的clone,也就是复制一个对象,然后直接操作已复制的对象。
5、单例模式(Singleton Pattern)—— 该模式应该是用得比较多的模式。因为我们经常碰到一些场景就是,只能允许有一个实例的情况。最多的就是打印日志了。但是我们经常会发现一些其实本来是一种Util类型的类,却写成了单例,这是完全没有必要的。
二、结构型模式 —— 创建型模式解决了关于对象创建的问题。但是对象创建后使用的过程中,就会碰到很多对象组成以及对象与对象之间的关系管理问题。而这类问题就是结构型模式所要去解决的。
1、外观模式(Facade Pattern)—— 为子系统中的一组接口提供一个一致的界面,定义一个高层接口。该模式实现了系统之间的松耦合关系,也使得这一子系统对外的接口更加简洁和容易使用。
2、适配器模式(Adapter Pattern)—— 一个接口所产生的结果总是固定的,但是对外则总是有各种各样的结果需求。如何让一个返回单一结果类型的接口,能够满足各种调用需求(当然这个需求也是建立在该接口的原始返回数据上的),这就是适配器模式所要处理的场景,该模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
3、代理模式(Proxy Pattern)—— 该模式是为了强化一些核心类的功能。在调用目标对象的方法之前之后都可以执行特定的操作。所以,要实现代理模式,一般至少有一个abstract或interface,一个RealObject和一个ProxyRealObject,同时ProxyRealObject附带着RealObject的应用,当外部调用接口方法时,代理类会在调用真正核心类之前之后做一项操作。
4、装饰模式(Decorator Pattern)—— 在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。通过创建一个包装对象来包裹真实的对象,添加一些额外的职责。乍一看,其实挺像代理模式的。因为代理模式也可以实现这样的功能。但是大家要注意代理模式是要实现接口的。而装饰模式更多的是为了处理那些不适合通过生成子类来实现的,比如有大量独立的扩展,如果为支持每一种组合将产生大量的子类,那将会使得子类数目呈爆炸性增长。当然还有一种情况也可能是因为类的定义被隐藏,或者它是个final类不能用于生成子类。
5、桥模式(Bridge)—— 有些类型由于自身的逻辑,它可能具有多个维度的变化,如何应对这种“多维度的变化”就是桥模式所要解决的问题。比如类型A有A1,A2,A3,类型B也有B1,B2,B3,而实际使用的类型则是A*B*,也就是说如果按传统继承的方法则会有3*3=9种独立的类型。任何一个维度再加1或者再增加一个维度的变化,那么最终类型的数量就会成倍地增加而且如果后期稍有改动,维护将非常繁琐,显然就不太合理。我们试试用桥模式去分析这个问题,我们就会发现最终类型有两个维度的变化,A*和B*的变化是独立的也是确定的,没法改变,但是对外提供的是A*B*却是变化无穷的。我们应该给A和B建立一座桥,使得A和B独立变化,自由组合。这也就是桥模式形象的解释。
桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。但是在它的定义描述的是:“将抽象部分与实现部分分离,使它们都可以独立的变化。”这里要注意以下抽象部分并不是指的是java里面的单纯的interface而是形如A与B一样的抽象化的类型,这里包括接口A和B,同时也包括A* 和B*;实现部分指的是最终给外部使用的部分,即A*B*组合。
6、组合模式(Composite Pattern)—— 又称部分-整体模式,组合模式使得用户对单个对象和组合对象的使用具有一致性的操作。经典形象例子就是树形结构的文件系统。在让外部系统使用的时候,可以让他们统一对待目录与文件,进行删除增加。
7、享元模式(Flyweight Pattern)—— 该模式通俗的讲就是复用我们内存中已存在的对象,降低系统创建对象实例的性能消耗。更广义一点就是,运用共享技术有效的支持大量细粒度的对象。如果一个应用程序使用了大量相近甚至重复的对象,而这些对象造成了很大的存储开销的时候就可以考虑是否可以使用享元模式。
三、行为型模式 —— 对象的创建和结构都处理好了以后,新的问题又来了,就是对象的行为要怎么样才更加合理,更加有效率,更加协调地与各个组件完成一系列的任务。行为模式就是专门研究这些问题。因为行为的多样性,也导致了行为型模式有很多种方案。要理解背后的思想,才能灵活运用。
1、模板方法模式(Template Pattern)—— 就是定义一种模板逻辑来处理类似逻辑框架问题的设计模式。在面向对象开发过程中,我们会遇到这样的一个问题:我们知道一个算法所需的关键步骤,并确定了这些步骤的执行顺序。但是某些步骤的具体实现是未知的,或者说某些步骤的实现与具体的环境相关。比如定义一个操作顺序的抽象类,其中有个final方法RunAll会依次调用run1,run2,run3,然后子类就可以不改变一个算法的结构即可重定义该算法的某些特定步骤。该模式的核心:封装不变部分,扩展可变部分;行为由父类控制,子类实现;提供公共部分代码,便于维护。
2、观察者模式(Observer Pattern)—— 目标对象管理所有相依于它的观察者对象,并且在它自身状态改变时主动发出通知。该模式定义了对象间的一种一对多的依赖关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
3、状态模式(State Pattern)—— 该模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时,那就可以考虑使用状态模式了。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。
4、策略模式(Strategy Pattern)—— 该模式的使用场景是在项目中有多个可供选择的算法策略,客户端在其运行时根据不同需求决定使用某一具体算法策略。在实现过程为,首先定义不同的算法策略,然后客户端把算法策略作为它的一个参数。比如java中Collections.sort()方法了,它使用Comparator对象作为参数。根据Comparator接口不同实现,对象会被不同的方法排序。
5、职责链模式(Chain of Responsibility Pattern)—— 在该模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。就如同android中TouchEvent事件的处理一样,它会被一层一层传下去,如果被处理的就不往下传了。
6、命令模式(Command Pattern)—— 该模式将一个请求封装成一个对象(参数+行为),使客户的不同请求实现了参数化。大体结构是Command接口,Command*实现类,接收者Receiver(一般是个队列)。该模式是对行为进行封装的典型模式,解耦了发送者和接受者之间联系。 发送者调用一个操作,接受者接受请求执行相应的动作,因为使用Command模式解耦,发送者无需知道接受者任何接口。
7、访问者模式(Visitor Pattern)—— 封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。访问者模式的优点是增加操作很容易,因为增加操作意味着增加新的访问者。访问者模式将有关行为集中到一个访问者对象中,其改变不影响系统数据结构。其缺点就是增加新的数据结构很困难。如果数据结构不是很稳定,那访问者模式将不适用。
8、调停者模式(Mediator Pattern)—— 该模式包装了一系列对象相互作用的方式,使得这些对象不必相互明显引用。从而使它们可以较松散地耦合。当这些对象中的某些对象之间的相互作用发生改变时,不会立即影响到其他的一些对象之间的相互作用。从而保证这些相互作用可以彼此独立地变化。典型的例子——电脑的CPU、显示器、内存、声卡、硬盘、光驱有相互联系的,也有不需要联系的。而主板就相当于调停者。如果没有主板,那么一个对象就要处理好几个与之联系的对象。并且由于联系的不同,每个对象都是如此,这种系统简称“过度耦合系统”……而有了主板(调停者),每一个对象所要处理的对象就变成了单一的调停者,调停者知道如何跟所有的配件打交道,这样就简单多了。
9、备忘录模式(Memento Pattern)—— 在不破坏封装的条件下,将一个对象的状态存储起来并外部化,从而可以在将来合适的时候把这个对象还原到存储时的状态。实现方式是创建一个备忘对象的管理类,客户端只跟管理类耦合。如同Android系统中对Activity对象的状态保存一样。
10、迭代器模式(Iterator Pattern)—— 主要处理集合对象,该模式的思想是提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部结构。这也是java中用的最多的模式,Java的集合类都可实现迭代器模式,对集合里面的对象进行遍历。迭代器模式的优点是简化了复杂集合的遍历方式,封装性良好,用户只需要得到迭代器就可以遍历,而对于遍历算法则不用去关心。但是它的缺点也很明显,那就是不适合去处理比较简单的(像数组或者有序列表),使用迭代器方式去遍历简单的裂变反而较为繁琐,大如同List,我们更愿意使用foreach循环去遍历。
11、解释器模式(Interpreter Pattern)—— 该模式就是定义语言的规则,并且建立一个解释器来解释该规则的语义。如果一种特定类型的问题发生的频率足够高,那么就值得将该问题的各个实例表述为一个简单语言中的句子,自然也就需要构建一个解释器去解释这些句子来解决该类问题。解释器模式在实际的系统开发中使用的非常少,因为它会引起效率、性能以及维护等问题,通常的做法是使用其他工具甚至语言来完成这种类型的业务逻辑。
结束语:知识永远都不可能学完,但是如果你对知识进行更高层的认识而不是死记硬背,生活着任何一个点滴都能唤起你对这些知识的运用。