关闭

23种设计模式介绍以及在Java中的实现

标签: 设计模式javajava设计模式23种设计模式java中的23种设计模式
24357人阅读 评论(7) 收藏 举报
分类:

原创不易,转载请注明出处:http://blog.csdn.net/anxpp/article/details/51224293,谢谢!

    文章比较长,读者可以通过顶端的目录选择要了解的模式,然后通过文章右边的按钮快速返回顶部重新选择一个新的模式浏览

    博主精心准备了大量的示例代码。文章尽量提供与编程相关的例子,而不是像多数其他介绍的文章一样,提供一些感觉挺滑稽的例子(那样的例子可能看完觉得写得很好,然而还是不会用...)。

    本文耗费了作者大量时间,还请亲们给个赞O(∩_∩)O~

    也可以通过CTRL+F并输入要了解的模式并跳到对应位置。

    文章中的示例源码在github上:https://github.com/anxpp/JavaDesignPattern

    文中未给出UML图,如果需要请回复说明,本人也可以画出需要的设计模式对应的UML图。


设计模式介绍及Java描述

概述

    设计模式是针对某一类问题的最优解决方案,是从许多优秀的软件系统中总结出的。

    Java中设计模式(java design patterns)通常有23种。

    模式可以分成3类:创建型、行为型和结构型。

    创建型模式

    创建型模式涉及对象的实例化,特点是不让用户代码依赖于对象的创建或排列方式,避免用户直接使用new创建对象。

    创建型模式有以下5个:

    工厂方法模式抽象工厂方法模式生成器模式原型模式单例模式

    行为型模式

   行为型模式涉及怎样合理的设计对象之间的交互通信,以及怎样合理为对象分配职责,让设计富有弹性,易维护,易复用。

    行为型模式有以下11个:

    责任链模式命令模式解释器模式迭代器模式中介者模式备忘录模式观察者模式状态模式策略模式模板方法模式访问者模式

    结构型模式

    结构型模式涉及如何组合类和对象以形成更大的结构,和类有关的结构型模式涉及如何合理使用继承机制;和对象有关的结构型模式涉及如何合理的使用对象组合机制。

    结构型模式有以下7个:

    适配器模式组合模式代理模式享元模式外观模式桥接模式装饰模式

    模式中涉及的重要角色,会在描述中(加粗字体)介绍出来。下面就逐一介绍。

1、单例模式(Singleton Pattern)

    Ensure a class only has one instance,and provide a global point of access to it.

    保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    何时使用

  •     当系统需要某个类只有一个实例的时候

    优点

  •     单例模式的类唯一实例由其本身控制,可以很好的控制用户何时访问它。

    单例模式概念很简单,而且也比较常用。

    在使用这个模式的时候,我们要考虑是否会在多线程中使用,如果不会应用于多线程,那写法就足够简单:

  1. public class SimpleSingleton {
  2. private static SimpleSingleton instance;
  3. private SimpleSingleton(){}
  4. public static SimpleSingleton getIntance(){
  5. if(instance == null)
  6. instance = new SimpleSingleton();
  7. return instance;
  8. }
  9. }

    上例就是一个简单的单例模式实现,使用了懒加载模式。但是多线程中可能会创建多个实例。下面就介绍多线程中的使用。

    如果直接将上面例子应用到多线程中,可以直接把getInstance()设置为同步的(synchronized),但是并不高效,任一之后,只能有一个线程可以调用这个方法,其余的会排队等待。

    所以整个方法做同步不是优解,那就只同步代码块就好了。这就引出了双重检验锁,即在同步块外检查一次null,然后再在同步块内检查一次。但是最终这种方式也是会有问题的,使用静态内部类是一种比较好的方式。

    单例模式使用很频繁,也很简单,但不一定都能写对,详细的写法请参考:如何正确地写出单例模式。里面详细的分析的单例模式的各种写法。

    其他模式中的示例代码,有很多时候用到了单例模式,此处就不额外添加例子了。

    这里给出一个推荐的实现方式(枚举):

  1. public enum EasySingleton{
  2. INSTANCE;
  3. }

2、工厂方法模式(Factory Method Pattern)

    别名:虚拟构造(Another Name:Virtual Constructor)。

    Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclassess.

    定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

    何时使用

  •     用户需要一个类的子类的实例,但不希望与该类的子类形成耦合
  •     用户需要一个类的子类的实例,但用户不知道该类有哪些子类可用

    优点

  •     使用工厂方法可以让用户的代码和某个特定类的子类的代码解耦
  •     工厂方法使用户不必知道它所使用的对象是怎样被创建的,只需知道该对象有哪些方法即可。

    简单工厂模式

    介绍工厂方法模式前,先介绍一下简单工厂模式,简单工厂模式也是一种工厂方法模式。

    简单工厂模式又称静态工厂方法模式。从命名上就可以看出这个模式一定很简单。它存在的目的很简单:定义一个用于创建对象的接口。

    如果一个一些对象(产品),已经确定了并不易改变和添加新的产品,那么久可以使用简单工厂模式。下面就是简单工厂的例子:

  1. //演示简单工厂
  2. public class SimpleFactory {
  3. public static void main(String args[]) throws Exception{
  4. Factory factory = new Factory();
  5. factory.produce("PRO5").run();
  6. factory.produce("PRO6").run();
  7. }
  8. }
  9. //抽象产品
  10. interface MeizuPhone{
  11. void run();
  12. }
  13. //具体产品X2
  14. class PRO5 implements MeizuPhone{
  15. @Override
  16. public void run() {
  17. System.out.println("我是一台PRO5");
  18. }
  19. }
  20. class PRO6 implements MeizuPhone{
  21. @Override
  22. public void run() {
  23. System.out.println("我是一台PRO6");
  24. }
  25. }
  26. //工厂
  27. class Factory{
  28. MeizuPhone produce(String product) throws Exception{
  29. if(product.equals("PRO5"))
  30. return new PRO5();
  31. else if(product.equals("PRO6"))
  32. return new PRO6();
  33. throw new Exception("No Such Class");
  34. }
  35. }

    很容易看出,简单工厂模式是不易维护的,如果需要添加新的产品,则整个系统都需要修改。如果我们需要添加诸如PRO7、PRO8等产品,直接在工程类中添加即可。但是如果这时候根部不知道还有什么产品,只有到子类实现时才知道,这时候就需要工厂方法模式。

    而在实际应用中,很可能产品是一个多层次的树状结构。由于简单工厂模式中只有一个工厂类来对应这些产品,所以实现起来是比较麻烦的,那么工厂方法模式正式解决这个问题的,下面就介绍工厂方法模式。

    工厂方法模式

    工厂方法模式去掉了简单工厂模式中工厂方法的静态属性,使得它可以被子类继承。这样在简单工厂模式里集中在工厂方法上的压力可以由工厂方法模式里不同的工厂子类来分担。

    针对上面的例子,如果使用工厂方法模式,即将工厂定义为一个接口,然后由具体的工厂来确定需要生成什么样的产品,为了与简单工厂比较,这里还是贴上代码:

  1. //工厂方法模式
  2. public class FactoryMethod {
  3. public static void main(String args[]){
  4. IFactory bigfactory;
  5. bigfactory = new SmallFactory();
  6. bigfactory.produce().run();
  7. bigfactory = new BigFactory();
  8. bigfactory.produce().run();
  9. }
  10. }
  11. //抽象产品
  12. interface MeizuPhone{
  13. void run();
  14. }
  15. //具体产品*2
  16. class PRO5 implements MeizuPhone{
  17. @Override
  18. public void run() {
  19. System.out.println("我是一台PRO5");
  20. }
  21. }
  22. class MX5 implements MeizuPhone{
  23. @Override
  24. public void run() {
  25. System.out.println("我是一台MX5");
  26. }
  27. }
  28. interface IFactory{//抽象的工厂
  29. MeizuPhone produce();
  30. }
  31. //工厂*2
  32. class BigFactory implements IFactory{
  33. @Override
  34. public MeizuPhone produce() {
  35. return new PRO5();
  36. }
  37. }
  38. class SmallFactory implements IFactory{
  39. @Override
  40. public MeizuPhone produce() {
  41. return new MX5();
  42. }
  43. }

    如果了解Java的集合框架,那么它就是一个很好的例子:

    Java中的Collection接口的实现都能通过iterator()方法返回一个迭代器,而不同的实现的迭代器都在该实现中以内部类的方式对Iterator接口实现的,然后通过iterator()方法返回。那么,这个iterator()方法就是一种工厂方法。

    可以看到,在这里抽象产品是Iterator接口,具体产品就是Collection接口的实现中对Iterator接口的实现,构造者是Collection接口,其提供的工厂方法就是Iterator iterator();,具体构造者就是Collection的实现。而工厂方法模式的结构,也就是由前面加粗的4部分组成。

    如果对Java容器不熟悉,下面再提供一个例子(模仿Iterator,其实顺便也介绍了Iterator):

    如果有多种数据结构要遍历,我们就需要一种用于遍历不同结构的工具,首先我们就需要为这个工具定义一个接口(抽象产品),用于描述如何来遍历:

  1. //只是需要遍历一堆数据,那么只需要2个方法就可以了
  2. public interface Iterator<T> {
  3. boolean hasNext(); //是否还有下一个元素
  4. T next(); //得到下一个元素
  5. }

    然后就是我们要遍历的目标,而这些目标此处我们暂定为列表,这就是构造者

  1. //便于介绍,不做多的操作
  2. public interface List<T> {
  3. Iterator<T> iterator(); //返回一个遍历器
  4. boolean add(T t); //添加元素到列表
  5. }

    对于List可能有多种实现方式,比如数组和链表,此处就简陋的介绍一下,而这些就是具体构造者,而里面有遍历器的具体实现(具体产品),此处以内部类的形式放到了List的实现(具体构造者)里面,也完全可以修改代码将遍历器的实现(具体产品)独立出来:

    数组的实现:

  1. package com.anxpp.designpattern.factorymethod;
  2. //方便演示而实现的简陋的数组list
  3. public class ArrayList<T> implements List<T>{
  4. private int size; //存放的元素个数,会默认初始化为0
  5. private Object[] defaultList; //使用数组存放元素
  6. private static final int defaultLength = 10;//默认长度
  7. public ArrayList(){ //默认构造函数
  8. defaultList = new Object[defaultLength];
  9. }
  10. @Override
  11. public Iterator<T> iterator() {
  12. return new MyIterator();
  13. }
  14. //添加元素
  15. @Override
  16. public boolean add(T t) {
  17. if(size<=defaultLength){
  18. defaultList[size++] = t;
  19. return true;
  20. }
  21. return false;
  22. }
  23. //遍历器(具体产品)
  24. private class MyIterator implements Iterator<T>{
  25. private int next;
  26. @Override
  27. public boolean hasNext() {
  28. return next<size;
  29. }
  30. @SuppressWarnings("unchecked")
  31. @Override
  32. public T next() {
  33. return (T) defaultList[next++];
  34. }
  35. }
  36. }

    链表实现:

  1. //方便演示而实现的简陋的单向链表list
  2. public class LinkList<T> implements List<T>{
  3. private int size; //存放的元素个数,会默认初始化为0
  4. private Node<T> first; //首节点,默认初始化为null
  5. @Override
  6. public Iterator<T> iterator() {
  7. return new MyIterator();
  8. }
  9. @Override
  10. public boolean add(T t) {
  11. if(size==0){
  12. first = new Node<T>(t,null);
  13. size++;
  14. return true;
  15. }
  16. Node<T> node = first;
  17. while(node.next!=null)
  18. node = node.next;
  19. node.next = new Node<T>(t,null);
  20. size++;
  21. return true;
  22. }
  23. //链表节点
  24. private static class Node<T>{
  25. T data;
  26. Node<T> next;
  27. Node(T data,Node<T> next){
  28. this.data = data;
  29. this.next = next;
  30. }
  31. }
  32. //遍历器
  33. private class MyIterator implements Iterator<T>{
  34. private Node<T> next; //下一个节点
  35. MyIterator(){
  36. next = first;
  37. }
  38. @Override
  39. public boolean hasNext() {
  40. return next != null;
  41. }
  42. @Override
  43. public T next() {
  44. T data = next.data;
  45. next = next.next;
  46. return data;
  47. }
  48. }
  49. }

    使用上述代码(模式的使用):

  1. package com.anxpp.designpattern.factorymethod;
  2. public class TestUse {
  3. public static void main(String args[]){
  4. //分别定义两种结构
  5. List<Integer> array = new ArrayList<Integer>();
  6. List<Integer> link = new LinkList<Integer>();
  7. //添加数据
  8. for(int i = 1;i < 8; i++){
  9. array.add(i);
  10. link.add(i);
  11. }
  12. //获得迭代器
  13. Iterator<Integer> ai = array.iterator();
  14. Iterator<Integer> li = link.iterator();
  15. //遍历并输出
  16. while(ai.hasNext())
  17. System.out.print(ai.next());
  18. System.out.println();
  19. while(li.hasNext())
  20. System.out.print(li.next());
  21. }
  22. }

    控制台会输出:

  1. 1234567
  2. 1234567

    这就是工厂方法模式,其中遍历器也算是一种迭代器设计模式,后面会介绍。我不会跟你讲什么造车,造轮船,造人,我会给出实际应用。这里只是其中一种应用的举例,当一个接口的一系列实现需要另外的对象对其进行相同操作时,我们就可以这样用:在这个接口中定义返回另外一个对象的方法(工厂方法),然后再在这个接口的实现中,返回对其操作的对象。

    上面这个例子会在迭代器模式中给出完整的实现代码

    一抽象产品类派生出多个具体产品类;一抽象工厂类派生出多个具体工厂类;每个具体工厂类只能创建一个具体产品类的实例。 即定义一个创建对象的接口(即抽象工厂类),让其子类(具体工厂类)决定实例化哪一个类(具体产品类)。“一对一”的关系。

    与简单工厂间的取舍:工厂方法模式和简单工厂模式在定义上的不同是很明显的。工厂方法模式的核心是一个抽象工厂类,而不像简单工厂模式, 把核心放在一个实类上。工厂方法模式可以允许很多实的工厂类从抽象工厂类继承下来, 从而可以在实际上成为多个简单工厂模式的综合,从而推广了简单工厂模式。 反过来讲,简单工厂模式是由工厂方法模式退化而来。设想如果我们非常确定一个系统只需要一个实的工厂类, 那么就不妨把抽象工厂类合并到实的工厂类中去。而这样一来,我们就退化到简单工厂模式了。

    可以看出工厂方法的加入,使得对象的数量成倍增长。当产品种类非常多时,会出现大量的与之对应的工厂对象,这不是我们所希望的。

    如果再分得详细一点,一个工厂可能不只是生产手机(如小米除了手机,连电饭锅都有),但有得工厂智能生成低端的产品,而大一点的工厂可能通常是生成更高端的产品。所以一个工厂是不够用了,这时,就应该使用抽象工厂来解决这个问题。

 

3、抽象工厂方法模式(Abstract Factory Pattern)

    别名:配套(Another Name:Kit)

    Provide an interface for creating families of related or dependent objects without specifying their concrete classess.

    提供一个创建一系列或相互依赖对象的接口,而无须指定他们的具体的类。

    何时使用:

    优点: 

    上述生成魅族产品的例子中,我们只生产了手机,但是它不止有手机一种产品,可能还有其他的,比如耳机,为了还可以生成耳机,我们需要对上例进行扩展。

    我们先给出上面生成手机的例子的扩展后的抽象工厂模式代码,以比较这几种模式:

  1. //抽象工厂模式
  2. public class AbstractFactory {
  3. public static void main(String args[]){
  4. IFactory bigfactory = new BigFactory();
  5. IFactory smallfactory = new BigFactory();
  6. bigfactory.producePhone().run();
  7. bigfactory.produceHeadset().play();
  8. smallfactory.producePhone().run();
  9. smallfactory.produceHeadset().play();
  10. }
  11. }
  12. //抽象产品*2
  13. interface Headset{
  14. void play();
  15. }
  16. //抽象产品
  17. interface MeizuPhone{
  18. void run();
  19. }
  20. //具体产品*2*2
  21. class PRO5 implements MeizuPhone{
  22. @Override
  23. public void run() {
  24. System.out.println("我是一台PRO5");
  25. }
  26. }
  27. class MX5 implements MeizuPhone{
  28. @Override
  29. public void run() {
  30. System.out.println("我是一台MX5");
  31. }
  32. }
  33. class EP21 implements Headset{
  34. @Override
  35. public void play() {
  36. System.out.println("我是一副EP21");
  37. }
  38. }
  39. class EP30 implements Headset{
  40. @Override
  41. public void play() {
  42. System.out.println("我是一台EP30");
  43. }
  44. }
  45. //抽象工厂
  46. interface IFactory{
  47. MeizuPhone producePhone();
  48. Headset produceHeadset();
  49. }
  50. //具体工厂*2
  51. class BigFactory implements IFactory{
  52. @Override
  53. public MeizuPhone producePhone() {
  54. return new PRO5();
  55. }
  56. @Override
  57. public Headset produceHeadset() {
  58. return new EP30();
  59. }
  60. }
  61. //具体工厂*2
  62. class SmallFactory implements IFactory{
  63. @Override
  64. public MeizuPhone producePhone() {
  65. return new MX5();
  66. }
  67. @Override
  68. public Headset produceHeadset() {
  69. return new EP21();
  70. }
  71. }

    在抽象工厂模式中,抽象产品 (AbstractProduct) 可能是一个或多个,从而构成一个或多个产品族(Product Family)。 在只有一个产品族的情况下,抽象工厂模式实际上退化到工厂方法模式(不如上例减去耳机这种产品,就回到工厂方法模式了)。

    这样举例子其实很空洞,这里只是为了比较三种模式,给出抽象的例子才更容易看出区别。

    那么上例中实际应用就是生产迭代器的例子,这里也对齐扩展来介绍抽象工厂模式。Iterator迭代器是Collection专属的,但是现在我们希望能生产Map的迭代器,我们都知道,Map不是继承自Collection的,遍历的方式是不一样的,这就相当于2个产品族,接下来我们就要来实现它。

    为了演示我们如果实现这个同时能生产Map和Collection的迭代器,我会将例子一步步贴出来:

    首先是抽象产品,用来描述迭代器的公共接口:

  1. //抽象产品
  2. public interface IIterator<T> {
  3. boolean hasNext();
  4. Object next();
  5. }

    然后是抽象工厂,用来返回不同迭代器:

  1. //抽象工厂
  2. public interface IIteratorFactory<T> {
  3. IIterator<T> iteratorMap(Map<T,Object> m);
  4. IIterator<T> iteratorCollection(Collection<T> c);
  5. }

    接下来是具体产品。

    Collection的迭代器(具体产品):

  1. //具体产品,Collection迭代器(用到了代理模式)
  2. public class IteratorCollection<T> implements IIterator<T>{
  3. Iterator<T> iterator;
  4. public IteratorCollection(Collection<T> c){
  5. iterator = c.iterator();
  6. }
  7. @Override
  8. public boolean hasNext() {
  9. return iterator.hasNext();
  10. }
  11. @Override
  12. public T next() {
  13. return iterator.next();
  14. }
  15. }

    Map的迭代器(具体产品):

  1. //具体产品,Map迭代器(用到了代理模式)
  2. public class IteratorMap<T> implements IIterator<T>{
  3. Iterator<Map.Entry<T, Object>> iterator;
  4. public IteratorMap(Map<T,Object> m){
  5. iterator = m.entrySet().iterator();
  6. }
  7. @Override
  8. public boolean hasNext() {
  9. return iterator.hasNext();
  10. }
  11. @Override
  12. public Object next() {
  13. return iterator.next().getValue();
  14. }
  15. }

    完成具体产品设计后,我们就要实现具体的工厂了:

  1. //具体工厂
  2. public class IteratorFactory<T> implements IIteratorFactory<T>{
  3. @Override
  4. public IteratorMap<T> iteratorMap(Map<T,Object> m) {
  5. return new IteratorMap<T>(m);
  6. }
  7. @Override
  8. public IteratorCollection<T> iteratorCollection(Collection<T> c) {
  9. return new IteratorCollection<T>(c);
  10. }
  11. }

    至此,这个小框架就完成了,我们可以使用它来遍历Collection(List,Set,Queue都是集成自它)和Map: 

  1. //测试使用
  2. public class TestUse {
  3. public static void main(String args[]){
  4. IIteratorFactory<Integer> factory = new IteratorFactory<>();
  5. Collection<Integer> collection = new ArrayList<Integer>();
  6. Map<Integer, Object> map = new LinkedHashMap<>();
  7. for(int i=0;i<10;i++){
  8. collection.add(i);
  9. map.put(i, i);
  10. }
  11. IIterator<Integer> iteratorCollection = factory.iteratorCollection(collection);
  12. IIterator<Integer> iteratorMap = factory.iteratorMap(map);
  13. while(iteratorCollection.hasNext())
  14. System.out.print(iteratorCollection.next());
  15. System.out.println();
  16. while(iteratorMap.hasNext())
  17. System.out.print(iteratorMap.next());
  18. }
  19. }

    输出:

  1. 0123456789
  2. 0123456789

    实际情况下,我们可能不应该这么做,以为Collection面向一种对象的容器,Map是面向两种对象的关联容器,但是此例使用抽象工厂模式确实实现了不同容器的 统一遍历方式。

    如果一个容器持有的大量对象,他们都直接或间接集成自某一个类,使用访问者模式遍历也是一种很好的方式,具体在后面的访问者模式中会详细介绍。

    工厂模式主要就涉及上面介绍的三种:

  •     简单工厂模式是由一个具体的类去创建其他类的实例,父类是相同的,父类是具体的。
  •     工厂方法模式是有一个抽象的父类定义公共接口,子类负责生成具体的对象,这样做的目的是将类的实例化操作延迟到子类中完成。
  •     抽象工厂模式提供一个创建一系列相关或相互依赖对象的接口,而无须指定他们具体的类。它针对的是有多个产品的等级结构。而工厂方法模式针对的是一个产品的等级结构。

 

4、生成器模式(Builder Pattern)

    Separate the construction of a complex object from its representation so that the same construction process can create different representations.

    将一个复杂对象的构建与它的表示分离,使同样的构建过程可以创建不同的表示。

    何时使用

  •     当系统准备为用户提供一个内部结构复杂的对象,而且在构造方法中编写创建该对象的代码无法满足用户需求时,就可以使用生成器模式老构造这样的对象。
  •     当某些系统要求对象的构造过程必须独立于创建该对象的类时。

    优点

  •     生成器模式将对象的构造过程封装在具体的生成器中,用户使用不同的具体生成器就可以得到该对象的不同表示。
  •     生成器模式将对象的构造过程从创建该对象的类中分离出来,使用户无须了解该对象的具体组件。
  •     可以更加精细有效的控制对象的构造过程。生成器将对象的构造过程分解成若干步骤,这就是程序可以更加精细,有效的控制整个对象的构造。
  •     生成器模式将对象的构造过程与创建该对象类解耦,是对象的创建更加灵活有弹性。
  •     当增加新的具体的生成器是,不必修改指挥者的代码,即该模式满足开-闭原则。

    模式的重心在于分离构建算法和具体的构造实现,从而使构建算法可以重用。

    比如我们要得到一个日期,可以有不同的格式,然后我们就使用不同的生成器来实现。

    首先是这个类(产品):

  1. //产品
  2. public class MyDate {
  3. String date;
  4. }

    然后就是抽象生成器,描述生成器的行为: 

  1. //抽象生成器
  2. public interface IDateBuilder {
  3. IDateBuilder buildDate(int y,int m,int d);
  4. String date();
  5. }

    接下来是具体生成器,一个以“-”分割年月日,另一个使用空格:

  1. //具体生成器
  2. public class DateBuilder1 implements IDateBuilder{
  3. private MyDate myDate;
  4. public DateBuilder1(MyDate myDate){
  5. this.myDate = myDate;
  6. }
  7. @Override
  8. public IDateBuilder buildDate(int y, int m, int d) {
  9. myDate.date = y+"-"+m+"-"+d;
  10. return this;
  11. }
  12. @Override
  13. public String date() {
  14. return myDate.date;
  15. }
  16. }
  1. //具体生成器
  2. public class DateBuilder2 implements IDateBuilder{
  3. private MyDate myDate;
  4. public DateBuilder2(MyDate myDate){
  5. this.myDate = myDate;
  6. }
  7. @Override
  8. public IDateBuilder buildDate(int y, int m, int d) {
  9. myDate.date = y+" "+m+" "+d;
  10. return this;
  11. }
  12. @Override
  13. public String date() {
  14. return myDate.date;
  15. }
  16. }

    接下来是指挥官,向用户提供具体的生成器:

  1. //指挥者
  2. public class Derector {
  3. private IDateBuilder builder;
  4. public Derector(IDateBuilder builder){
  5. this.builder = builder;
  6. }
  7. public String getDate(int y,int m,int d){
  8. builder.buildDate(y, m, d);
  9. return builder.date();
  10. }
  11. }

    使用如下: 

  1. public class TestUse {
  2. public static void main(String args[]){
  3. MyDate date = new MyDate();
  4. IDateBuilder builder;
  5. builder = new DateBuilder1(date).buildDate(2066, 3, 5);
  6. System.out.println(builder.date());
  7. builder = new DateBuilder2(date).buildDate(2066, 3, 5);
  8. System.out.println(builder.date());
  9. }
  10. }

    输出:

  1. 2066-3-5
  2. 2066 3 5

    使用不同生成器,可以使原有产品表现得有点不一样。

    往往在实际的应用中,生成器要做的工作不会这么简单,而是相对复杂的(因为其产品一般是比较复杂的),原有构建的维护会转移到生成器的维护上。

 

5、原型模式(Prototype Pattern)

    Specify the kinds of objects to create using a prototypical instance,and create new objects by copying this prototype.

    用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

    何时使用

  •     程序需要从一个对象出发,得到若干个和其状态相同,并可独立变化其状态的对象时。
  •     当对象的创建需要独立于它的构造过程和表示时。
  •     一个类创建实例状态不是很多,那么就可以将这个类的一个实例定义为原型,那么通过该实例复制该原型得到新的实例可能比重新使用类的构造方法创建新实例更方便

    优点:

  •     当创建类的新实例的代价更大时,使用原型模式复制一个已有的实例可以提高创建新实例的效率。
  •     可以动态的保存当前对象的状态。在运行时,可以随时使用对象流保存当前对象的一个复制品。
  •     可以在运行时创建新的对象,而无须创建一系列类和集成结构。
  •     可以动态的添加、删除原型的复制品。

    原型模式要求对象实现一个可以“克隆”自身的接口,这样就可以通过复制一个实例对象本身来创建一个新的实例。这样一来,通过原型实例创建新的对象,就不再需要关心这个实例本身的类型,只要实现了克隆自身的方法,就可以通过这个方法来获取新的对象,而无须再去通过new来创建。

    例子中的抽象原型没有使用方法名clone(),其原因下面会介绍。

    简单形式的原型模式:

  1. //具体原型
  2. public class SimplePrototype implements Prototype,Cloneable {
  3. int value;
  4. //clone()实现
  5. @Override
  6. public Object cloneSelf() {
  7. SimplePrototype self = new SimplePrototype();
  8. self.value = value;
  9. return self;
  10. }
  11. //使用
  12. public static void main(String args[]){
  13. SimplePrototype simplePrototype = new SimplePrototype();
  14. simplePrototype.value = 500;
  15. SimplePrototype simplePrototypeClone = (SimplePrototype) simplePrototype.cloneSelf();
  16. System.out.println(simplePrototypeClone.value);
  17. }
  18. }
  19. //抽象原型
  20. interface Prototype{
  21. Object cloneSelf();//克隆自身的方法
  22. }
  23. //客户端使用
  24. class Client{
  25. SimplePrototype prototype;
  26. public Client(SimplePrototype prototype){
  27. this.prototype = prototype;
  28. }
  29. public Object getPrototype(){
  30. return prototype.cloneSelf();
  31. }
  32. }

    简单的原型模式就是在clone()实现时,new一个新的实例,然后为成员变量赋值后返回。

    Java的原生支持

    Java中所有类都直接或间接继承自Object类,Object类中已有clone()方法:”protected native Object clone() throws CloneNotSupportedException;“,可以看到权限是protected的,所以仅有子类可以访问这个方法,但我们可以在子类中重写这个方法,将访问权限上调到public,然后方法体里面return super.clone()。

    我们能看到这个Object方法是可能会抛出异常的,我们必须实现Cloneable接口,才可以使用这个方法,否则会抛出“java.lang.CloneNotSupportedException”的异常。这个Cloneable接口其实是空的,实现它的目的在于让JVM知道这个对象是可以可复制的,否则clone()时就会发生异常。下面看演示代码:

  1. //使用 java 自带的支持
  2. public class APITestUse {
  3. public static void main(String args[]) throws CloneNotSupportedException{
  4. MyObject myObject = new MyObject();
  5. myObject.i = 500;
  6. MyObject myObjectClone = (MyObject) myObject.clone();
  7. System.out.println(myObjectClone.i);
  8. }
  9. }
  10. //一个可以复制的对象
  11. class MyObject implements Cloneable{
  12. int i;
  13. public Object clone() throws CloneNotSupportedException{
  14. return super.clone();
  15. }
  16. }//结果会输出 500

    调用这个方法时,成员变量会自动被复制。所以如果需要使用原型模式,Java原生支持就已经很好用了。

    除了以上的原生支持,java中还有一种序列化,只需要对象实现Serializable接口。这样,我们可以将对象写入到流中,可以保存到文件,也可以通过网络发送到其他地方:

  1. //使用Serializable支持克隆
  2. public class SerializablePrototype implements Serializable{
  3. private static final long serialVersionUID = 1L;
  4. private int i;
  5. private transient int notClone;//transient关键字的成员不会被序列化
  6. public int getI() {
  7. return i;
  8. }
  9. public void setI(int i) {
  10. this.i = i;
  11. }
  12. public int getNotClone() {
  13. return notClone;
  14. }
  15. public void setNotClone(int notClone) {
  16. this.notClone = notClone;
  17. }
  18. public void writeToFile(String path) throws Exception{
  19. FileOutputStream outStream = new FileOutputStream(path);
  20. ObjectOutputStream objectOutputStream = new ObjectOutputStream(outStream);
  21. objectOutputStream.writeObject(this);
  22. outStream.close();
  23. }
  24. public SerializablePrototype ReadFromFile(String path) throws Exception{
  25. File file = new File(path);
  26. if(!file.exists())
  27. file.createNewFile();
  28. FileInputStream inStream = new FileInputStream(path);
  29. ObjectInputStream objectOutputStream = new ObjectInputStream(inStream);
  30. Object o= objectOutputStream.readObject();
  31. inStream.close();
  32. return (SerializablePrototype) o;
  33. }
  34. public static void main(String args[]) throws Exception{
  35. String path = "D:/SerializablePrototype.instance";
  36. SerializablePrototype prototype = new SerializablePrototype();
  37. prototype.setI(123);
  38. prototype.setNotClone(456);
  39. prototype.writeToFile(path);
  40. SerializablePrototype prototypeClone = new SerializablePrototype();
  41. prototypeClone = prototype.ReadFromFile(path);
  42. System.out.println(prototypeClone.getI() + " " + prototypeClone.getNotClone());
  43. }
  44. }//输出:123 0

    我们来分析上例:这个对象有3个成员变量,而其中一个是有transient关键字申明的,一个是序列化id,一个是普通变量,在main方法中,想创建了对象,并设置值,然后写入到文件,再从文件读出来,最后输出读出来的对象的变量,普通变量是可以正常输出的(序列化id也可以,只是此处没有输出来而已),而transient申明的变量为0了,那就证明这个变量没有被保存到文件中,因为这个关键字声明的变量在序列化时会被忽略,而是后来创建时被自动初始化为0了(java中类的成员变量是基本数据类型时,如果没有初值,就会被自动初始化为0)。

    额...这里是介绍模式,好像说得多了点,那原型模式就介绍到这儿了。

 

6、责任链模式(Chain of Responsibility Pattern)

    使很多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

    何时使用

  •     有许多对象可以处理用户请求,希望程序在运行期间自动确定处理用户的那个对象。
  •     希望用户不必明确指定接收者的情况下,想多个接受者的一个提交请求
  •     程序希望动态的指定可处理用户请求的对象集合

    优点

  •     低耦合
  •     可以动态的添加删除处理者或重新指派处理者的职责
  •     可以动态改变处理者之间的先后顺序

    通常来说,一个纯粹的责任链是先传给第一个处理,如果处理过了,这个请求处理就此结束,如果没有处理,再传给下一个处理者。

    比如我们有一个数学公式,有一个整数输入,要求小于0时返回绝对值,其次,小于10的时候返回他的二次幂,否则,返回他本身:

    首先我们要定义一个接口(处理者),来描述他们共有的行为:

  1. //处理者
  2. public interface Handler {
  3. int handleRequest(int n);
  4. void setNextHandler(Handler next);
  5. }

    然后是具体的处理者(3个):

  1. //第一个具体处理者,处理小于0的
  2. public class Handler1 implements Handler {
  3. private Handler next;
  4. @Override
  5. public int handleRequest(int n) {
  6. if(n<0) return -n;
  7. else{
  8. if(next==null)
  9. throw new NullPointerException("next 不能为空");
  10. return next.handleRequest(n);
  11. }
  12. }
  13. @Override
  14. public void setNextHandler(Handler next) {
  15. this.next = next;
  16. }
  17. }
  1. //第二个具体处理者,处理>=0但小于10的
  2. public class Handler2 implements Handler {
  3. private Handler next;
  4. @Override
  5. public int handleRequest(int n) {
  6. if(n<10) return n*n;
  7. else{
  8. if(next==null)
  9. throw new NullPointerException("next 不能为空");
  10. return next.handleRequest(n);
  11. }
  12. }
  13. @Override
  14. public void setNextHandler(Handler next) {
  15. this.next = next;
  16. }
  17. }
  1. //第三个具体处理者,处理>=0但小于10的
  2. public class Handler3 implements Handler {
  3. private Handler next;
  4. @Override
  5. public int handleRequest(int n) {
  6. if(n<=Integer.MAX_VALUE) return n;
  7. else{
  8. if(next==null)
  9. throw new NullPointerException("next 不能为空");
  10. return next.handleRequest(n);
  11. }
  12. }
  13. @Override
  14. public void setNextHandler(Handler next) {
  15. this.next = next;
  16. }
  17. }

    使用:

  1. public class TestUse {
  2. public static void main(String args[]){
  3. Handler h1,h2,h3;
  4. h1 = new Handler1();
  5. h2 = new Handler2();
  6. h3 = new Handler3();
  7. h1.setNextHandler(h2);
  8. h2.setNextHandler(h3);
  9. System.out.println(h1.handleRequest(-1));
  10. System.out.println(h1.handleRequest(5));
  11. System.out.println(h1.handleRequest(9999));
  12. }
  13. }

    此处责任链中的具体处理者的顺序是不能重设的,否则可能会引发错误,但更多的情况是完全可以随意更改他们的位置的,就上例中,只要把if中的条件重新设置(各自独立,不相互依赖),就可以了。

    我们写java web程序的时候,通常会编写一些过滤器(Filter),然后配置到web.xml中,这其实就是责任链模式的一种实践。而使用Log4j记录日志,配置级别的时候,也同样用到了责任链模式。

    我们使用责任链模式的时候,不一定非得某一处理者处理后就得终止请求的传递,如果有其他需求,我们依然可以继续传递这个请求到下一个具体的处理者。

 

7、命令模式(Command Pattern)

    别名:动作,事物(Another Name:Action,Transaction)  

    Encapsulate a request as an object,thereby letting you parameterize clients with different reauests,queue or log requests,and support undoable operations.

    将一个请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

    何时使用

  •     程序需要在不同的时刻指定、排列和执行请求。
  •     程序需要提供撤销操作。
  •     程序需要支持宏操作。

    优点

  •     在命令模式中,请求者(Invoker)不直接与接受者(Receiver)交互,及请求者(Invoker)不包含接受者(Receiver)的引用,因此彻底消除了彼此间的耦合。
  •     命令模式满足“开-闭原则”。如果增加新的具体命令和该命令的接受者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有具体命令和接收者,新增加的调用者就可以使用已有的具体命令。
  •     由于请求者的请求被封装到具体的命令中,那么就可以将具体命令保存到持久化的媒介中,在需要的时候,重新执行这个具体命令。因此,使用命令模式可以记录日志。
  •     使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按一定顺序执行这些具体命令。

    一个对象有多种操作,但是我们不希望调用者(请求者)直接使用,我们就额外添加一个对象,然后让调用者通过这个对象来使用那些操作。

    比如,我们有一个类可以在磁盘上新建或是删除文件(接收者),但是我们不希望直接提供给别人(请求者)使用,所以我们就为它的各种操作创建对应的命令,下面我们用代码来实现这个需求:

    接收者,可以在磁盘删除或新建文件:

  1. //接收者
  2. public class MakeFile {
  3. //新建文件
  4. public void createFile(String name) throws IOException{
  5. File file = new File(name);
  6. file.createNewFile();
  7. }
  8. //删除文件
  9. public boolean deleteFile(String name){
  10. File file = new File(name);
  11. if(file.exists()&&file.isFile()){
  12. file.delete();
  13. return true;
  14. }
  15. return false;
  16. }
  17. }

    然后就是执行操作的接口:

  1. //命令接口
  2. public interface Command {
  3. void execute(String name) throws Exception;
  4. }

    我们需要创建具体的命令,这里就是2个,新建和删除:

  1. //新建文件命令
  2. public class CommandCreate implements Command {
  3. MakeFile makeFile;
  4. public CommandCreate(MakeFile makeFile) {
  5. this.makeFile = makeFile;
  6. }
  7. @Override
  8. public void execute(String name) throws Exception {
  9. makeFile.createFile(name);
  10. }
  11. }
  1. //删文件命令
  2. public class CommandDelete implements Command{
  3. MakeFile makeFile;
  4. public CommandDelete(MakeFile makeFile) {
  5. this.makeFile = makeFile;
  6. }
  7. @Override
  8. public void execute(String name) throws Exception {
  9. makeFile.deleteFile(name);
  10. }
  11. }

    最后就是请求者了:

  1. //请求者
  2. public class Client {
  3. Command command;
  4. public Client setCommand(Command command){
  5. this.command = command;
  6. return this;
  7. }
  8. public void executeCommand(String name) throws Exception{
  9. if(command==null)
  10. throw new Exception("命令不能为空!");
  11. command.execute(name);
  12. }
  13. }

    这样,我们就可以使用了,方式如下:

  1. public class TestUse {
  2. public static void main(String args[]) throws Exception{
  3. //接收者
  4. MakeFile makeFile = new MakeFile();
  5. //命令
  6. CommandCreate create = new CommandCreate(makeFile);
  7. CommandDelete delete = new CommandDelete(makeFile);
  8. //请求者
  9. Client client = new Client();
  10. //执行命令
  11. client.setCommand(create).executeCommand("d://test1.txt");
  12. client.setCommand(create).executeCommand("d://test2.txt");
  13. client.setCommand(delete).executeCommand("d://test2.txt");
  14. }
  15. }//执行完后在D盘会有一个test1.txt的文件,test2.txt本页创建了,不过又被删除了。。

    这里只是简单的实现,诸如CommandCreate命令的操作,如果我们需要undo的,那么就需要在命令接口中添加undo()方法并在具体命令中实现即可(将操作保存到栈里即可,undo的时候出栈并撤销操作)。

    命令模式不宜滥用,比如:使用这种模式,会多出来很多对象(命令)。

    命令模式中还有一种具体命令叫宏命令,它会包含一些其他具体命令的引用,执行宏命令可以执行这个宏命令所包含的引用的命令,概念清楚后实现起来也是容易的:

    比如输出文章的命令,有中文输出命令、英文输出命令和宏命令,宏命令包含了其他两个命令的引用(可以使用列表保存这些命令),如果执行宏命令,宏命令会一次执行它所包含引用的其他命令(迭代命令列表并执行即可)。

 

8、解释器模式(Interpreter Patterm)

    Given a language,define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

    给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

    何时使用

  •     当有一个简单的语言需要解释执行,并且可以将该语言的每一个规则表示为一个类时,就可以使用解释器模式。

    优点

  •     将每一个语法规则表示成一个类,方便与实现简单的语言。
  •     由于使用类表示语法的规则,可以较容易改变或扩展语言的行为。
  •     通过在类结构中加入新的方法,可以在解释的同时增加新的行为。

    概念其实很简单。在有些问题上,我们可能希望自定定义简单的语言来描述,然后我们自己能解释它。

    解释器模式一般包括四种角色:

  •     抽象表达式:该角色为一个接口,负责定义抽象的解释操作。
  •     终结符表达式:实现抽象表达式接口的类。
  •     非终结表达式:也是实现抽象表达式的类。
  •     上下文(Context):包含解释器之外的一些全局信息。

    使用该模式设计程序一般需要三个步骤:

  •     解析语句中的动作标记。
  •     将标记规约为动作。
  •     执行动作。

    这种模式一般会应用到一些特殊的问题上,使用这种模式一般需要了解形式语言中的基本知识。js内核就是一个强大的解释器。

    简单的解释器模式,我们需要解释出来表达式的信息即可;而更深一层的,我们需要把表达式中的内容,翻译成我们程序运行的一部分来执行。

    本初不提供例子,理解概念即可。有需求的时候再深入学习。如果确实需要举例,请在回复中说明,我会更新文章并添加一些内容。

 

9、迭代器模式

    别名:游标(Another Name:Cursor)

    提供一种方法顺序访问一个聚合对象中的各个元素,而由不需要暴露该对象的内部细节。

    何时使用

  •     让用户访问集合汇总的对象而不想暴露这个集合的实现时
  •     对不同集合提供一个统一的遍历接口时

    优点

  •     用户使用迭代器访问集合中的对象而不需要知道这个集合的具体实现
  •     可以同时使用多个迭代器遍历一个集合

    通常容器提供的迭代器时可以高速遍历它本身的,而使用其本身的机制(如LinkedList中使用get(i)方法遍历)遍历性能可能并不好。

    其实这个在工厂方法模式给出的例子就足够解释这个模式的使用了,如需看具体代码实现,请移步工厂方法模式中的例子查看。

    其中主要的角色是集合具体集合迭代器具体迭代器

    迭代器其实很简单,下面我们就继续工厂方法模式中的例子,将它完善一下:

    稍微增强的集合接口:

  1. //集合接口
  2. public interface MyList<T> {
  3. MyIterator<T> iterator(); //返回一个遍历器
  4. boolean add(T t); //添加元素到列表
  5. T get(int index); //得到元素
  6. T remove(); //删除最后一个元素
  7. boolean remove(T element); //删除指定元素
  8. T remove(int index); //删除指定位置元素
  9. boolean set(int index,T element); //修改指定位置值
  10. int size();
  11. }

    容量可以自动增长的数组实现的集合:

  1. public class MyArrayList<T> implements MyList<T>{
  2. private int size; //存放的元素个数,会默认初始化为0
  3. private Object[] defaultList; //使用数组存放元素
  4. private static final int defaultLength = 10;//默认长度
  5. public MyArrayList(){ //默认构造函数
  6. defaultList = new Object[defaultLength];
  7. }
  8. @Override
  9. public MyIterator<T> iterator() {
  10. return new Iterator();
  11. }
  12. //大小自动增长
  13. private void ensureCapacity(int capacity){
  14. int nowLength = defaultList.length;
  15. if(capacity >= nowLength){
  16. nowLength = nowLength + (nowLength>>1);
  17. if(nowLength<0)//溢出
  18. nowLength = Integer.MAX_VALUE;
  19. defaultList = Arrays.copyOf(defaultList, nowLength);
  20. }
  21. }
  22. //添加元素
  23. @Override
  24. public boolean add(T t) {
  25. ensureCapacity(size+1);
  26. defaultList[size++] = t;
  27. return true;
  28. }
  29. //获取元素
  30. @SuppressWarnings("unchecked")
  31. @Override
  32. public T get(int index) {
  33. if(index<0 || index>=size) return null;
  34. return (T) defaultList[index];
  35. }
  36. @Override
  37. public T remove() {
  38. return remove(size-1);
  39. }
  40. @SuppressWarnings("unchecked")
  41. @Override
  42. public T remove(int index) {
  43. if(index<0||index>=size) return null;
  44. T element = (T) defaultList[index];
  45. if(index != size-1)
  46. System.arraycopy(defaultList, index+1, defaultList, index,size-index-1);
  47. size--;
  48. return element;
  49. }
  50. @Override
  51. public boolean remove(T element) {
  52. if(element==null){
  53. for(int i = 0 ; i<size;i++)
  54. if(defaultList[i]==null){
  55. System.arraycopy(defaultList, i+1, defaultList, i,size-i-1);
  56. size--;
  57. return true;
  58. }
  59. }
  60. else{
  61. for(int i = 0 ; i<size;i++)
  62. if(defaultList[i].equals(element)){
  63. System.arraycopy(defaultList, i+1, defaultList, i,size-i-1);
  64. size--;
  65. return true;
  66. }
  67. }
  68. return false;
  69. }
  70. @Override
  71. public boolean set(int index,T element) {
  72. if(index<0||index>=size) return false;
  73. defaultList[index] = element;
  74. return true;
  75. }
  76. @Override
  77. public int size() {
  78. return size;
  79. }
  80. //迭代器
  81. private class Iterator implements MyIterator<T>{
  82. private int next;
  83. @Override
  84. public boolean hasNext() {
  85. return next<size;
  86. }
  87. @SuppressWarnings("unchecked")
  88. @Override
  89. public T next() {
  90. return (T) defaultList[next++];
  91. }
  92. @Override
  93. public T remove() {
  94. // TODO Auto-generated method stub
  95. return null;
  96. }
  97. }
  98. }

    链表实现的集合:

  1. public class MyLinkedList<T> implements MyList<T>{
  2. private int size; //存放的元素个数,会默认初始化为0
  3. private Node<T> first; //首节点,默认初始化为null
  4. @Override
  5. public MyIterator<T> iterator() {
  6. return new Iterator();
  7. }
  8. @Override
  9. public boolean add(T t) {
  10. if(size==0){
  11. first = new Node<T>(t,null);
  12. size++;
  13. return true;
  14. }
  15. Node<T> node = first;
  16. while(node.next!=null)
  17. node = node.next;
  18. node.next = new Node<T>(t,null);
  19. size++;
  20. return true;
  21. }
  22. @Override
  23. public T get(int index) {
  24. Node<T> node = first;
  25. while(--index>=0)
  26. node = node.next;
  27. return node.data;
  28. }
  29. @Override
  30. public T remove() {
  31. return remove(size-1);
  32. }
  33. @Override
  34. public T remove(int index) {
  35. if(index<0||index>=size) return null;
  36. Node<T> node = first;
  37. while(--index>0)
  38. node = node.next;
  39. T element = node.next.data;
  40. node.next = node.next.next;
  41. size--;
  42. return element;
  43. }
  44. @Override
  45. public boolean remove(T element) {
  46. if(element == null){
  47. if(first.data==null){
  48. first = first.next;
  49. size--;
  50. return true;
  51. }
  52. Node<T> node = first;
  53. do{
  54. if(node.next.data==null){
  55. node.next = node.next.next;
  56. size--;
  57. return true;
  58. }
  59. node = node.next;
  60. }
  61. while(node.next!=null);
  62. }
  63. else{
  64. if(first.data.equals(element)){
  65. first = first.next;
  66. size--;
  67. return true;
  68. }
  69. Node<T> node = first;
  70. do{
  71. if(node.next.data.equals(element)){
  72. node.next = node.next.next;
  73. size--;
  74. return true;
  75. }
  76. node = node.next;
  77. }
  78. while(node.next!=null);
  79. }
  80. return false;
  81. }
  82. @Override
  83. public boolean set(int index, T element) {
  84. if(index<0||index>=size) return false;
  85. Node<T> node = first;
  86. while(--index>0)
  87. node = node.next;
  88. node.data = element;
  89. return true;
  90. }
  91. @Override
  92. public int size() {
  93. return size;
  94. }
  95. //链表节点
  96. private static class Node<T>{
  97. T data;
  98. Node<T> next;
  99. Node(T data,Node<T> next){
  100. this.data = data;
  101. this.next = next;
  102. }
  103. }
  104. //遍历器
  105. private class Iterator implements MyIterator<T>{
  106. private Node<T> next; //下一个节点
  107. Iterator(){
  108. next = first;
  109. }
  110. @Override
  111. public boolean hasNext() {
  112. return next!=null;
  113. }
  114. @Override
  115. public T next() {
  116. T data = next.data;
  117. next = next.next;
  118. return data;
  119. }
  120. @Override
  121. public T remove() {
  122. // TODO Auto-generated method stub
  123. return null;
  124. }
  125. }
  126. }

    迭代器接口:

  1. public interface MyIterator<T> {
  2. boolean hasNext(); //是否还有下一个元素
  3. T next(); //得到下一个元素
  4. T remove();
  5. }

    具体的迭代器就是集合具体实现里面的迭代器内部类,下面是使用 :

  1. public class TestUse {
  2. public static void main(String args[]){
  3. //分别定义两种结构
  4. MyList<String> array = new MyArrayList<String>();
  5. MyList<String> link = new MyLinkedList<String>();
  6. //添加数据
  7. for(int i = 1;i < 15; i++){
  8. array.add(i+"");
  9. link.add(i+"");
  10. }
  11. //数组操作
  12. System.out.println(array.remove());
  13. System.out.println(array.get(5));
  14. System.out.println(array.remove(5));
  15. System.out.println(array.get(5));
  16. System.out.println(array.remove("7"));
  17. System.out.println(array.set(0, "00"));
  18. //使用迭代器
  19. MyIterator<String> ai = array.iterator();
  20. while(ai.hasNext())
  21. System.out.print(ai.next()+" ");
  22. System.out.println();
  23. System.out.println(link.remove());
  24. System.out.println(link.get(5));
  25. System.out.println(link.remove(5));
  26. System.out.println(link.get(5));
  27. System.out.println(link.remove("7"));
  28. System.out.println(link.set(0, "00"));
  29. //使用迭代器
  30. MyIterator<String> li = link.iterator();
  31. while(li.hasNext())
  32. System.out.print(li.next()+" ");
  33. System.out.println();
  34. System.out.println("a size=" + array.size());
  35. System.out.println("l size=" + link.size());
  36. }
  37. }

    输出:

  1. 14
  2. 6
  3. 6
  4. 7
  5. true
  6. true
  7. 00 2 3 4 5 8 9 10 11 12 13
  8. 14
  9. 6
  10. 6
  11. 7
  12. true
  13. true
  14. 00 2 3 4 5 8 9 10 11 12 13
  15. a size=11
  16. l size=11

    这里的迭代器就是典型的迭代器模式的实现(不过此处的迭代器没有实现remove()方法,查找集合的remove()方法实现也简单),介绍得有点多了,把集合都给介绍了...

 

10、中介者模式(Mediator Pattern)

    用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显示的相互引用,从而使其耦合松散,而且可以独立的改变他们之前的交互。

    何时使用

    优点

    两个类直接关联,是很好实现的,但如果不希望两个类直接发生交互,那么就需要使用中介者模式了。

    比如有两个类,他们都是做持久化的,一个负责将数据写入文件,一个负责将数据写入数据库。他们谁先接收到数据是不确定的,但是要确保其中一个接收到数据后,另外一个也必须完成这些数据的持久化。如果我们直接将两个类关联在一起,互相调用是可以实现的,但不利于后期扩展或维护(比如再添加一个持久化组建,则原有的组建可能都需要修改),此时,若添加一个中介者,来协调他们,事儿就好办多了:

    数据持久化的接口:

  1. //同事(接口)
  2. public interface IPersistent {
  3. void getData(Object data);
  4. void getData(Object data,Midiator midiator);
  5. void saveData();
  6. }

    分别实现持久化到文件和持久化到数据库的组件(具体同事):

  1. //具体同事
  2. public class PersistentFile implements IPersistent{
  3. private Object data;
  4. @Override
  5. public void getData(Object data, Midiator midiator) {
  6. getData(data);
  7. midiator.notifyOther(this, data);
  8. }
  9. @Override
  10. public void saveData() {
  11. System.out.println(data + " 已保存到文件");
  12. }
  13. @Override
  14. public void getData(Object data) {
  15. this.data = data;
  16. saveData();
  17. }
  18. }
  1. //具体同事
  2. public class PersistentDB implements IPersistent{
  3. private Object data;
  4. @Override
  5. public void getData(Object data, Midiator midiator) {
  6. getData(data);
  7. midiator.notifyOther(this, data);
  8. }
  9. @Override
  10. public void saveData() {
  11. System.out.println(data + " 已保存到数据库");
  12. }
  13. @Override
  14. public void getData(Object data) {
  15. this.data = data;
  16. saveData();
  17. }
  18. }

    中介者:

  1. //具体中介者
  2. public class Midiator {
  3. PersistentDB persistentDB;//此处可以使用List来存放所有的同事
  4. PersistentFile persistentFile;
  5. public Midiator setPersistentDB(PersistentDB persistentDB) {
  6. this.persistentDB = persistentDB;
  7. return this;
  8. }
  9. public Midiator setPersistentFile(PersistentFile persistentFile) {
  10. this.persistentFile = persistentFile;
  11. return this;
  12. }
  13. public void notifyOther(IPersistent persistent,Object data){
  14. if(persistent instanceof PersistentDB)//如果同事都放在List中,此处遍历即可
  15. persistentFile.getData(data);
  16. if(persistent instanceof PersistentFile)
  17. persistentDB.getData(data);
  18. }
  19. }

    使用:

  1. public class TestUse {
  2. public static void main(String args[]){
  3. Object data = "数据";
  4. PersistentDB persistentDB = new PersistentDB();
  5. PersistentFile persistentFile = new PersistentFile();
  6. Midiator midiator = new Midiator();
  7. midiator.setPersistentDB(persistentDB).setPersistentFile(persistentFile);
  8. persistentDB.getData(data, midiator);
  9. persistentFile.getData(data, midiator);
  10. }
  11. }//输出(省略了换行符):数据 已保存到数据库数据 已保存到文件数据 已保存到文件数据 已保存到数据库

    就上例,如果还有许多的持久化组件(具体同事),可以在中介者中使用一个List来存放他们的引用,set的时候就添加。在通知其他同事时,遍历这个List,除了参数本身这个同事,其他的依次通知,即可实现。

    中介者消除了同事与同事间直接的关联。

 

11、备忘录模式(Memento Pattern)

    别名:标记(Another Name:Token)

    Without violating encapsulation,captrue and externalize an object' orifianl state so that the object can be restored to this state later.

    在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存该状态,这样就可以将该对象恢复到之前保存的状态。

    何时使用

  •     必须保存一个对象在某一时刻的全部或部分状态,以便在需要时恢复该对象先前的状态。
  •     一个对象不想通过提供public权限的,诸如getXXX()的方法让其他对象得到自己IDE内部状态。

    优点

  •     备忘录模式使用备忘录可以吧原先者的内部状态全部保存起来,使是有很“亲密”的对象可以访问备忘录中的数据。
  •     备忘录模式强调了类设计单一责任的原则,即将状态的刻画和保存分开。

    备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。 备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。

    备忘录模式中有三种角色:

  •     备忘录(Memento)角色:将发起人(Originator)对象的内战状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
  •     发起人(Originator)角色:创建一个含有当前的内部状态的备忘录对象。使用备忘录对象存储其内部状态。
  •     负责人(Caretaker)角色:负责保存备忘录对象。不检查备忘录对象的内容。

    先看一个简单的实现方式:

  1. //简单的备忘录模式
  2. public class SimpleMemento {
  3. public static void main(String[] args) throws Exception {
  4. Originator originator = new Originator(); //发起人,要被保存的对象,也是他创建要保存的信息的
  5. Caretaker caretaker = new Caretaker(); //辅助保存的对象
  6. originator.setState("stateOne"); //设置状态
  7. caretaker.saveMemento(originator.createMemento()); //保存状态
  8. originator.setState("stateTwo"); //修改状态
  9. originator.recoverMemento(caretaker.recoverMemento()); //恢复状态
  10. }
  11. }
  12. //发起人
  13. class Originator {
  14. private String state;
  15. public Memento createMemento(){
  16. return new Memento(state);
  17. }
  18. public void recoverMemento(Memento memento){
  19. this.state = memento.getState();
  20. }
  21. public String getState() {
  22. return state;
  23. }
  24. public void setState(String state) {
  25. this.state = state;
  26. }
  27. }
  28. //备忘录
  29. class Memento {
  30. private String state;
  31. public Memento(String state){
  32. this.state = state;
  33. }
  34. public String getState() {
  35. return state;
  36. }
  37. public void setState(String state) {
  38. this.state = state;
  39. }
  40. }
  41. //负责人
  42. class Caretaker {
  43. private Memento memento;
  44. public Memento recoverMemento() throws Exception{
  45. if(memento==null)
  46. throw new Exception("没有保存的状态");
  47. return this.memento;//恢复状态
  48. }
  49. public void saveMemento(Memento memento){
  50. this.memento = memento;//保存状态
  51. }
  52. }

    备忘录角色对任何对象都提供一个接口,备忘录角色的内部所存储的状态就对所有对象公开,因此是破坏封装性的。

    按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘录角色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。

    如果上例中,我们把备忘录以发起人的私有内部类的方式实现的话,那它就只能被发起人访问了,这正好就符合备忘录模式的要求,但是我们的负责人是需要存放备忘录的引用的,于是,我们提供一个公共的接口,他是空的,我们用备忘录实现它,主要就是利用其中的类型信息,具体实现如下:

  1. //备忘录模式
  2. public class BlackMemento {
  3. public static void main(String[] args) {
  4. BlankOriginator originator = new BlankOriginator(); //发起人
  5. BlackCaretaker caretaker = new BlackCaretaker(); //负责人
  6. originator.setState("stateOne"); //设置状态
  7. caretaker.saveMemento(originator.createMemento()); //保存信息
  8. originator.setState("stateTwo"); //修改状态
  9. originator.recoverMemento(caretaker.recoverMemento());//恢复状态
  10. }
  11. }
  12. interface MementoIF {}
  13. //发起人
  14. class BlankOriginator {
  15. private String state;
  16. public String getState() {
  17. return state;
  18. }
  19. public void setState(String state) {
  20. this.state = state;
  21. }
  22. public MementoIF createMemento(){
  23. return new Memento(state);
  24. }
  25. public void recoverMemento(MementoIF memento){
  26. this.setState(((Memento)memento).getState());
  27. }
  28. //以内部类实现备忘录角色
  29. private class Memento implements MementoIF{
  30. private String state;
  31. private Memento(String state){
  32. this.state = state;
  33. }
  34. private String getState() {
  35. return state;
  36. }
  37. }
  38. }
  39. //负责人
  40. class BlackCaretaker {
  41. private MementoIF memento;
  42. public MementoIF recoverMemento(){
  43. return memento;
  44. }
  45. public void saveMemento(MementoIF memento){
  46. this.memento = memento;
  47. }
  48. }

    上面两个例子,演示的都是保存一个状态(不是指一个成员,而是只存了最近一次状态),即一个检查点,但是实际应用中,状态往往不止存储一次,我们将上面储存状态的变量改为一个栈(或队列,主要看需求)即可。比如:BlackCaretaker中的private MementoIF memento;改为LinkedList<MementoIF> mementos 实现,保存的时候压栈(入队),恢复的时候出栈(出队)。具体实现都已经描述很清楚了,代码就不贴了(文章本来就太长了)。

    针对上例,如果发起人和负责人我们并不介意他们必须是独立的,就可以把他们融合到一起,实现就会更佳简单,代码也简洁:

  1. //自述历史备忘录
  2. public class MementoSelf {
  3. public static void main(String[] args) {
  4. OriginatorCaretaker originatorCaretaker = new OriginatorCaretaker();//发起人,同时为负责人
  5. originatorCaretaker.changeState("stateOne"); //改变状态
  6. IMemento memento = originatorCaretaker.createMemento(); //保存状态
  7. originatorCaretaker.changeState("stateTwo"); //改变状态
  8. originatorCaretaker.recoverMemento(memento); //恢复状态
  9. }
  10. }
  11. interface IMemento {}
  12. //发起人兼负责人
  13. class OriginatorCaretaker {
  14. public String state;
  15. public void changeState(String state){
  16. this.state = state;
  17. }
  18. //创造快照
  19. public Memento createMemento(){
  20. return new Memento(this);
  21. }
  22. //恢复状态
  23. public void recoverMemento(IMemento memento){
  24. Memento m = (Memento)memento;
  25. changeState(m.state);
  26. }
  27. //内部类实现备忘录
  28. private class Memento implements IMemento{
  29. private String state;
  30. private Memento(OriginatorCaretaker originatorCaretaker){
  31. this.state = originatorCaretaker.state;
  32. }
  33. }
  34. }

    上例演示仅保存一个检查点。下面再给出一个实际的例子:

    我们有个程序,供用户编辑文本,用户做出修改后,可以保存文本,保存修改后,可以依次恢复到保存前的多个状态中的一个,如果恢复后用户没有修改,还可以取消恢复(重做),下面就演示整个程序。

    这个程序为了保证功能相对完整,写作演示可能有点长了:

  1. //文本编辑器
  2. public class TextEditor {
  3. public static void main(String[] args) {
  4. //使用这个文本编辑器
  5. MyTextEditor editor = new MyTextEditor("这里是初始文本,可能为文件中读取的值。");
  6. System.out.println("开始修改文本:");
  7. editor.append("添加文字1");
  8. editor.delWords(); //删除最后一个
  9. // editor.delWords(2); //删除最后2个 这两个方法是没有问题的,这里避免控制台输出太多,取消这两次修改
  10. // editor.delWords(1,5); //删除前面5个
  11. System.out.println("开始恢复:");
  12. for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
  13. System.out.println("开始重做:");
  14. for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
  15. System.out.println("再次恢复:");
  16. for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
  17. System.out.println("再次重做:");
  18. for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
  19. System.out.println("再次恢复:");
  20. for(int i=0;i<10;i++) editor.recoverMemento();//恢复大于实际修改的次数不会出错,只会将文本设为o初始化状态
  21. editor.append("添加文字2");
  22. System.out.println("再次重做:");
  23. for(int i=0;i<10;i++) editor.redo(); //重做大于实际恢复的次数不会出错,只会将文本设为最后状态
  24. }
  25. }
  26. interface IMemento {}
  27. //发起人兼负责人
  28. class MyTextEditor {
  29. public StringBuffer text;
  30. private LinkedList<IMemento> mementos; //保存快照
  31. private LinkedList<IMemento> undos; //保存撤销的操作
  32. public MyTextEditor(){
  33. this("");
  34. }
  35. public MyTextEditor(String defaultStr){
  36. text = new StringBuffer(defaultStr);
  37. mementos = new LinkedList<IMemento>();
  38. undos = new LinkedList<IMemento>();
  39. print();
  40. }
  41. public void clearHistory(){
  42. mementos.clear();
  43. undos.clear();
  44. }
  45. public void append(String appendStr){
  46. if(appendStr==null||appendStr.length()==0) return;
  47. createMemento();
  48. text.append(appendStr);
  49. print();
  50. undos.clear();
  51. }
  52. //删除最后一个
  53. public void delWords(){
  54. delWords(1);
  55. }
  56. //删除最后n个
  57. public void delWords(int n){
  58. if(n<1||n>text.length()) return;
  59. delWords(text.length()-n+1,text.length());
  60. }
  61. //删除中间start到end的字符,第一个文字为第一个(而不是0)
  62. public void delWords(int start,int end){
  63. if(start<1 || end>text.length()+1) return;
  64. createMemento();
  65. text = text.delete(start-1, end);
  66. print();
  67. }
  68. public void reset(String text){
  69. this.text = new StringBuffer(text);
  70. }
  71. //新的快照
  72. public void createMemento(){
  73. mementos.push(new Memento(this));
  74. }
  75. //恢复状态
  76. public boolean recoverMemento(){
  77. Memento memento = (Memento) mementos.poll();
  78. if(memento==null) return false;
  79. undos.push(new Memento(this));
  80. reset(memento.state);
  81. print();
  82. return true;
  83. }
  84. //redo,redo的操作也可以恢复!
  85. public boolean redo(){
  86. Memento memento = (Memento) undos.poll();
  87. if(memento==null) return false;
  88. createMemento();
  89. reset(memento.state);
  90. print();
  91. return true;
  92. }
  93. //内部类实现备忘录
  94. private class Memento implements IMemento{
  95. private String state;
  96. private Memento(MyTextEditor editor){
  97. this.state = editor.text.toString();
  98. }
  99. }
  100. void print(){
  101. System.out.println("当前文本:" + text);
  102. }
  103. }

    控制台输出:

  1. 当前文本:这里是初始文本,可能为文件中读取的值。
  2. 开始修改文本:
  3. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
  4. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字
  5. 开始恢复:
  6. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
  7. 当前文本:这里是初始文本,可能为文件中读取的值。
  8. 开始重做:
  9. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
  10. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字
  11. 再次恢复:
  12. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
  13. 当前文本:这里是初始文本,可能为文件中读取的值。
  14. 再次重做:
  15. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
  16. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字
  17. 再次恢复:
  18. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字1
  19. 当前文本:这里是初始文本,可能为文件中读取的值。
  20. 当前文本:这里是初始文本,可能为文件中读取的值。添加文字2
  21. 再次重做:

    可以看到功能都是正确的,最后的重做因为在恢复后有修改发生,所以重做是无效的(目前我们所用的编辑器都是这种策略)。多次的恢复和重做是没有问题的。

    该例子就是备忘录模式典型的例子。

 

12、观察者模式(Observer Pattern)

    别名: 依赖,发布/订阅(Another Name: Dependents, Publish/Subscribe)

    定义对象间的一种一对多的依赖关系,当一个对象状态发生改变时,所有依赖它的对象都得到通知并被自动更新。

    何时使用

  •     当一个对象的数据更新时,需要通知其他对象,而又不希望和被通知的对象形成紧耦合时

    优点

    ...

    比如我们有个天气服务(主题),然后有多个使用它的客户端(观察者),包括android和iphone端app的服务(观察者),那么就可以使用这么模式。

    我们需要一种结构存放天气信息(注意,省略了get、set方法!):

  1. //天气的消息实体
  2. public class WeatherInfo {
  3. private long time;
  4. private String weather;
  5. public WeatherInfo(long time,String weather){
  6. this.time = time;
  7. this.weather = weather;
  8. }
  9. @Override
  10. public boolean equals(Object obj) {
  11. WeatherInfo info = (WeatherInfo) obj;
  12. return info.time==this.time&&info.weather.equals(this.weather);
  13. }
  14. }

    然后我们定义天气服务的接口(主题),以表示它应实现哪些功能:

  1. //主题
  2. public interface IWeatherService {
  3. void addClient(Client client); //添加观察者
  4. boolean deleteClient(Client client);//删除观察者
  5. void notifyClients(); //通知
  6. void updateWeather(WeatherInfo info);//主题内容更新
  7. }

    接着就是客户端的接口描述:

  1. //观察者
  2. public interface Client {
  3. void getWeather(WeatherInfo info);
  4. }

    然后实现具体的天气服务,这里同样用到了单例模式:

  1. //具体主题
  2. public enum WeatherService implements IWeatherService{
  3. instance;
  4. private LinkedList<WeatherInfo> weatherInfos = new LinkedList<WeatherInfo>();
  5. private LinkedHashSet<Client> clients = new LinkedHashSet<Client>(); //存放观察者
  6. //添加观察者
  7. @Override
  8. public void addClient(Client client) {
  9. clients.add(client);
  10. }
  11. //删除观察者
  12. @Override
  13. public boolean deleteClient(Client client) {
  14. return clients.remove(client);
  15. }
  16. //通知观察者
  17. @Override
  18. public void notifyClients() {
  19. Iterator<Client> iterator = clients.iterator();
  20. while(iterator.hasNext()){
  21. iterator.next().getWeather(weatherInfos.peekFirst());
  22. }
  23. }
  24. //更新天气
  25. @Override
  26. public void updateWeather(WeatherInfo info) {
  27. if(weatherInfos.size()>0)
  28. if(weatherInfos.peekFirst().equals(info)) return;
  29. weatherInfos.push(info);
  30. if(clients.size()==0) return;
  31. notifyClients();
  32. }
  33. }

    最后就是具体的客户端(观察者,此处给出两个):

  1. public class ClientAndroidServer implements Client {
  2. private static String name = "安卓服务";
  3. private WeatherInfo info;
  4. @Override
  5. public void getWeather(WeatherInfo info) {
  6. this.info = info;
  7. dealMsg();
  8. }
  9. private void dealMsg(){
  10. System.out.println(name + "收到最新天气:time="+info.getTime()+"msg="+info.getWeather()+"。马上开始推送消息...");
  11. }
  12. }
  1. public class ClientIphoneServer implements Client {
  2. private static String name = "苹果服务";
  3. private WeatherInfo info;
  4. @Override
  5. public void getWeather(WeatherInfo info) {
  6. this.info = info;
  7. dealMsg();
  8. }
  9. private void dealMsg(){
  10. System.out.println(name + "收到最新天气:time="+info.getTime()+"msg="+info.getWeather()+"。马上开始推送消息...");
  11. }
  12. }

    好,现在就可以直接使用了:

  1. public class TestUse {
  2. public static void main(String args[]){
  3. //创建主题
  4. WeatherService service = WeatherService.instance;
  5. //添加观察者
  6. service.addClient(new ClientAndroidServer());
  7. service.addClient(new ClientIphoneServer());
  8. //更新主题
  9. service.updateWeather(new WeatherInfo(System.currentTimeMillis(), "多云"));
  10. service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24, "多云转晴"));
  11. service.updateWeather(new WeatherInfo(System.currentTimeMillis()+1000*60*60*24*2, "晴"));
  12. }
  13. }

    运行后,控制台有如下输出:

  1. 安卓服务收到最新天气:time=1461246047007msg=多云。马上开始推送消息...
  2. 苹果服务收到最新天气:time=1461246047007msg=多云。马上开始推送消息...
  3. 安卓服务收到最新天气:time=1461332447007msg=多云转晴。马上开始推送消息...
  4. 苹果服务收到最新天气:time=1461332447007msg=多云转晴。马上开始推送消息...
  5. 安卓服务收到最新天气:time=1461418847007msg=晴。马上开始推送消息...
  6. 苹果服务收到最新天气:time=1461418847007msg=晴。马上开始推送消息...

    可以看出,观察者模式是一对多的。而本例是将更新的内容整个推给客户端。

    而观察者模式中的数据有推和拉的区别,上例是推。

    推的方式会将主题更改的内容全部直接推给客户端,拉的方式就是主题的数据更新后,不直接将数据推给客户端,而是先推送一个通知并提供对应的方法供客户端拉取数据。

    如果上例中,天气服务每半小时更新(半点和整点推消息),还有一个客户端,不需要特别即时的天气消息,只取整点的消息,那么我们就可以使用拉的方式,数据更新后,给客户端推送一个标志,客户端自己按需取得数据(天气服务需要提供这样一个接口)。这就是拉。

    java.util包中也提供了观察者模式的支持,因为java程序设计中使用比较广泛。有一个Observable类(相当于这里的具体主题)和一个Observer接口(相当于这里的主题接口):

  1. public interface Observer {
  2. void update(Observable o, Object arg);
  3. }
  1. public class Observable {
  2. private boolean changed = false;
  3. private Vector<Observer> obs;
  4. public Observable() { obs = new Vector<>();}
  5. public synchronized void addObserver(Observer o) {
  6. if (o == null) throw new NullPointerException();
  7. if (!obs.contains(o)) { obs.addElement(o); }
  8. }
  9. public synchronized void deleteObserver(Observer o) { obs.removeElement(o); }
  10. public void notifyObservers() { notifyObservers(null); }
  11. public void notifyObservers(Object arg) {
  12. Object[] arrLocal;
  13. synchronized (this) {
  14. if (!changed) return;
  15. arrLocal = obs.toArray();
  16. clearChanged();
  17. }
  18. for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);
  19. }
  20. public synchronized void deleteObservers() { obs.removeAllElements(); }
  21. protected synchronized void setChanged() { changed = true; }
  22. protected synchronized void clearChanged() { changed = false; }
  23. public synchronized boolean hasChanged() { return changed; }
  24. public synchronized int countObservers() { return obs.size(); }
  25. }

    其实跟上面的例子大体差不多,如果有这方面的需求,也可以直接使用Java的API。 但可以看到里面还在使用Vector(已过时),这其实是不推荐的,我们可以自己实现观察者模式,如果是多线程中,我们也可以自己实现同步。

 

13、状态模式(State Pattern)

    别名:状态对象(Another Name:Objects for States)

    Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.

    允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

    何时使用

  •     一个对象的行为依赖于它的状态,并且它必须在运行时根据状态改变它的行为 。
  •     需要编写大量的条件分支语句来决定一个操作的行为,而且这些条件恰好表示对象的一种状态。

    优点

  •     使用一个类封装对象的一种状态,很容易增加新的状态
  •     在状态模式中,环境(Context)中不必出现大量的条件判断语句。环境(Context)实例所呈现的状态变得更加清晰、容易理解。
  •     使用状态模式可以让用户程序很方便地切换环境(Context)实例的状态。
  •     使用状态模式不会让环境(Context)中的实例中出现内部状态不一致的情况。
  •     当状态对象没有实例变量时,环境(Context)的各个实例可以共享一个状态对象。

    用一句话来表述,状态模式把所研究的对象的行为包装在不同的状态对象里,每一个状态对象都属于一个抽象状态类的一个子类。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

    可能这段时间老是在想数据库相关的事儿,所以一想例子就就想到这方面来了...不过,这样大家也能更好的对比设计模式之间的差异,下本例还是与这方面相关的。

    设想我们有一个程序,要保存数据的,按照数据(这里以String举例)的大小,使用不同的方式保存。如果数据很小,我们将其保存到Redis(缓存数据库)中,如果数据库不太小也不太大,我们将其保存到mysql中,如果数据非常大,我们直接将其写入到文件中。数据的大小就是一种状态,很适合使用状态模式:

    环境

  1. //环境(Context)
  2. public class SaveDataController {
  3. private ISaveData saveData;
  4. public void save(String data){
  5. //为了演示,此处的大的数据其实也是很小的
  6. if(data.length()<1<<2)
  7. saveData = SaveSmallData.instance;
  8. else if(data.length()<1<<4)
  9. saveData = SaveMiddleData.instance;
  10. else
  11. saveData = SaveBigData.instance;
  12. saveData.save(data);
  13. }
  14. }

    抽象状态

  1. //抽象状态
  2. public interface ISaveData {
  3. void save(Object data);
  4. }

    具体状态(每个具体状态都使用到了单例模式):

  1. //具体状态
  2. public enum SaveSmallData implements ISaveData{
  3. instance;
  4. @Override
  5. public void save(Object data) {
  6. System.out.println("保存到Redis:" + data);
  7. }
  8. }
  1. //具体状态
  2. public enum SaveMiddleData implements ISaveData{
  3. instance;
  4. @Override
  5. public void save(Object data) {
  6. System.out.println("保存到Mysql:" + data);
  7. }
  8. }
  1. //具体状态
  2. public enum SaveBigData implements ISaveData{
  3. instance;
  4. @Override
  5. public void save(Object data) {
  6. System.out.println("保存到文件:" + data);
  7. }
  8. }

    使用:

  1. public class TestUse {
  2. public static void main(String args[]){
  3. String smallData = "小数据";
  4. String middleData = "介于小数据和大数据之间的数据";
  5. String bifgData = "这里就假定这是一个很大很大很大的数据";
  6. SaveDataController saveDataController = new SaveDataController();
  7. saveDataController.save(smallData);
  8. saveDataController.save(middleData);
  9. saveDataController.save(bifgData);
  10. }
  11. }

    输出:

  1. 保存到Redis:小数据
  2. 保存到Mysql:介于小数据和大数据之间的数据
  3. 保存到文件:这里就假定这是一个很大很大很大的数据

    可以看到,我们对三种数据都使用了同一个对象的相同方法,但是行为是不同的,因为他们的状态不一样。

    上面例子的状态更改是自动的,也可以添加setState()方法,手动切换状态,并在执行的方法体中不在自动判断状态。不过自动判断的,更智能一些,而手动切换状态的,可控性更好。

 

14、策略模式(Strategy Pattern)

    定义一系列算法,把他们一个个封装起来,并且使他们可相互替换。本模式使得算法可独立于其他客户端而变化。

    何时使用

    优点

    策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。下面就以一个示意性的实现讲解策略模式实例的结构。

   策略模式中包括三种角色:

  •     策略(Strategy):一个接口,定义了若干个算法(抽象方法)。
  •     具体策略(ConcreteStrategy):策略的实现。
  •     上下文/环境(Context):依赖于策略接口的类。

    策略模式的重心不是如何实现算法,而是如何组织、调用这些算法,从而让程序结构更灵活,具有更好的维护性和扩展性。

    策略模式一个很大的特点就是各个策略算法的平等性。对于一系列具体的策略算法,大家的地位是完全一样的,正因为这个平等性,才能实现算法之间可以相互替换。所有的策略算法在实现上也是相互独立的,相互之间是没有依赖的。所以可以这样描述这一系列策略算法:策略算法是相同行为的不同实现。

    运行期间,策略模式在每一个时刻只能使用一个具体的策略实现对象,虽然可以动态地在不同的策略实现中切换,但是同时只能使用一个。

    经常见到的是,所有的具体策略类都有一些公有的行为。这时候,就应当把这些公有的行为放到共同的抽象策略角色Strategy类里面。当然这时候抽象策略角色必须要用Java抽象类实现,而不能使用接口。 这其实也是典型的将代码向继承等级结构的上方集中的标准做法。

    上次我们使用状态模式将数据按不同状态保存到不同地方,这里,我们使用策略模式来实现通过不同的策略来选择数据的保存方式。

    首先是抽象的数据保持类(策略):

  1. //策略
  2. public interface ISaveData {
  3. void save(Object data);
  4. }

    然后是具体的数据保存类,三个(具体策略):

  1. public class SaveToRedis implements ISaveData {
  2. @Override
  3. public void save(Object data) {
  4. System.out.println("数据:" + data + " 保存到Redis");
  5. }
  6. }
  1. //具体策略
  2. public class SaveToFile implements ISaveData {
  3. @Override
  4. public void save(Object data) {
  5. System.out.println("数据:" + data + " 保存到文件");
  6. }
  7. }
  1. //具体策略
  2. public class SaveToMysql implements ISaveData {
  3. @Override
  4. public void save(Object data) {
  5. System.out.println("数据:" + data + " 保存到Mysql");
  6. }
  7. }

    最后是客户端(环境Context):

  1. //环境
  2. public class SaveClient {
  3. private ISaveData saveData;
  4. public SaveClient(ISaveData saveData){
  5. this.saveData = saveData;
  6. }
  7. public void setSaveData(ISaveData saveData){
  8. this.saveData = saveData;
  9. }
  10. public void save(Object data){
  11. saveData.save(data);
  12. }
  13. }

    使用:

  1. public class TestUse {
  2. public static void main(String args[]){
  3. Object data = "数据";
  4. ISaveData saveData = new SaveToRedis();
  5. SaveClient client = new SaveClient(saveData);
  6. client.save(data);
  7. client.setSaveData(new SaveToFile());
  8. client.save(data);
  9. }
  10. }

    这里数据的保存就是根据使用的时候设置的策略来决定。

    使用策略模式可以避免使用多重条件(if-else)语句。多重条件语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重条件语句里面,比使用继承的办法还要原始和落后。

    客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道算法或行为的情况。由于策略模式把每个具体的策略实现都单独封装成为类,如果备选的策略很多的话,那么对象的数目就会很可观。

 

15、模板方法模式(Template Method Pattern)

    Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

    定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

    何时使用

  •     设计者需要给出一个算法的固定步骤,并将某些步骤的具体实现留给子类来实现。
  •     需要对代码进行重构,将各个子类公共行为提取出来集中到一个共同的父类中以避免代码重复。

    优点

  •     可以通过在抽象摸吧能定义模板方法给出成熟的算法步骤,同时又不限制步骤的细节,具体模板实现算法细节不会改变整个算法的骨架。
  •     在抽象模板模式中,可以通过钩子方法对某些步骤进行挂钩,具体模板通过钩子可以选择算法骨架中的某些步骤。

    模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。 模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method);而将这些基本方法汇总起来的方法叫做模板方法(template method),这个设计模式的名字就是从此而来。

    例如,我们有这样的操作:首先得到一些数据,然后计算这些数据,最后再输出数据,至于这些操作如何实现(当然,一些方法也可以提前实现),我们没有要求,但是这些操作的先后逻辑我们已经确定好了,子类不能改变:

    抽象模板

  1. //抽象模板
  2. public abstract class AbstractTemplate {
  3. Object data;
  4. //这个就是模板方法
  5. void dealData(){
  6. getData();
  7. calcData();
  8. printData();
  9. }
  10. //下面是普通方法,可能已经实现,也可能需要子类实现
  11. abstract void getData();
  12. abstract void calcData();
  13. void printData(){
  14. System.out.println(data);
  15. }
  16. }

    具体模板

  1. //具体模板
  2. public class Template extends AbstractTemplate {
  3. @Override
  4. void getData() {
  5. data = "data";
  6. }
  7. @Override
  8. void calcData() {
  9. data = (String)data+data;
  10. }
  11. }

    使用:

  1. public class TestUse {
  2. public static void main(String args[]){
  3. Template template = new Template();
  4. template.dealData();
  5. }
  6. }

    模板方法也比较简单,但是非常常用,如Android中Activity中生命周期的一些方法,都会被按序调用,也用到了这种设计模式。同样的,我们通常会使用一些封装好的http请求库,里面的实现要我们自己写,但是逻辑都已经规定好了,不也是模板方法模式么。总之,模板方法模式使用是非常广泛的。

 

16、访问者模式(Visitor Pattern)

    Represent an opration to be performed on the elements of an object structure.Visitor lets you define a new operation without changing the classes of the elements on which it oprates.

    表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。

    何时使用

  •     一个对象结构中,比如某个集合中,包含很多对象,想对集合中的对象增加一些新的操作。
  •     需要对集合中的对象进行很多不同的并且不相关的操作,而不想修改对象的类,就可以使用访问者模式。访问者模式可以在Visitor类中集中定义一些关于集合中对象的操作。

    优点

  •     可以在不改变一个集合中的元素的类的情况下,增加新的施加于该元素上的新操作。
  •     可以将集合中各个元素的某些操作集中到访问者中,不仅便于集合的维护,也有利于集合中元素的复用。

    访问者模式的目的是封装一些施加于某种数据结构元素之上的操作。一旦这些操作需要修改的话,接受这个操作的数据结构则可以保持不变。

    这个模式可能稍微难理解一点,希望读者一点一点读下去,碰到不清楚的先跳过,看完例子再回过头来基本就清楚是怎么回事了。

    在介绍访问者模式前,先介绍一下分派的概念。

    变量被声明时的类型叫做变量的静态类型(Static Type),而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type),如:

  1. List<String> list = new ArrayList<String>();

    这个list变量的静态类型是List,而它的实际类型是ArrayList。根据对象的类型而对方法进行的选择,就是分派(Dispatch)。分派又分为两种:静态分派和动态分派。

    静态分派(Static Dispatch)发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。

    动态分派(Dynamic Dispatch)发生在运行时期,动态分派动态地置换掉某个方法。

    看一个例子:

  1. public class Dispatch {
  2. void print(FatherClass c){
  3. System.out.print("父类");
  4. }
  5. void print(ChildClass c){
  6. System.out.print("子类");
  7. }
  8. public static void main(String args[]){
  9. FatherClass child = new ChildClass();
  10. new Dispatch().print(child);
  11. child.print();
  12. }
  13. }
  14. class FatherClass{
  15. void print(){
  16. System.out.println("父类");
  17. }
  18. }
  19. class ChildClass extends FatherClass{
  20. void print(){
  21. System.out.print("子类");
  22. }
  23. }//输出:父类子类

    可以看到,重载的分派是根据静态类型进行的。

    java的方法重写是根据实际类型来的(动态分派),编译器编译时并不知道其真实类型,而是运行时动态决定的。

    一个对象又叫做它所包含的方法的接收者,java中的动态分派,要调用哪一个方法,是由这个对象的真实类型决定的。

    如果能够根据参数和接收者来动态的决定调用某个方法,这就是动态的多分派语言,如果可以根据这两种方式来动态的决定方法调用,就是动态双重分派,但前面已经说了,java中重载是根据静态类型进行的,所以java只能动态的根据接收者来进行方法调用,即java是动态单分派语言,如果要实现双重分派,就必须通过设计模式来完成。

    OK,讲到重点了,访问者模式正是实现双重分派的模式。java中通过两次方法调用来实现两次分派。

    既然重载不能完成动态分派,我们就添加一个Visitor:

  1. public class MultiDispatch {
  2. public static void main(String args[]){
  3. Child child = new Child();
  4. child.print();
  5. child.print(new Vistor());
  6. }
  7. }
  8. class Father{
  9. void print(){
  10. System.out.println("父类");
  11. }
  12. }
  13. class Child extends Father{
  14. void print(){
  15. System.out.print("子类");
  16. }
  17. void print(Vistor c){
  18. c.print(this);
  19. }
  20. }
  21. class Vistor {
  22. public void print(Child child){
  23. child.print();
  24. }
  25. }//输出:子类子类

    这样,动态双重分派也算是完成了(通过调用一个其它对象的方法,传入自己,在其他类的这个方法中再通过传入的这个参数调用自己。如果还是不清楚,请继续看下面的例子)。那么这个有什么用呢?下面继续解释。

    比如我们有个app需要接收用户的反馈,用户由会员和普通用户,因为反馈太多,并不是所有反馈都会被记录到有效反馈表中,是否记录为有效通常不是用户说了算,而是有我们自己定。

    本例中的用户就是抽象元素

  1. //抽象元素
  2. public interface User {
  3. void accept(Visitor visitor);
  4. }

    具体的用户就是具体元素,本例有两个:

  1. //普通用户,具体元素
  2. public class UserOrdinary implements User{
  3. String estimation;
  4. public UserOrdinary(String estimation){
  5. this.estimation = estimation;
  6. }
  7. @Override
  8. public void accept(Visitor visitor) {
  9. visitor.visit(this);//这个就是重点,第一次分派是调用accept()方法时根据接收者的实际类型来调用的,第二次分派就是通过visitor.visit(this),传入静态类型,然后再visit()方法中反过来调用this本身的方法。
  10. }
  11. String getEstimation(){
  12. return estimation;
  13. }
  14. }
  1. //VIP用户,具体元素
  2. public class UserVIP implements User{
  3. String estimation;
  4. public UserVIP(String estimation){
  5. this.estimation = estimation;
  6. }
  7. @Override
  8. public void accept(Visitor visitor) {
  9. visitor.visit(this);
  10. }
  11. String getEstimation(){
  12. return estimation;
  13. }
  14. }

    抽象访问者

  1. //抽象访问者
  2. public interface Visitor {
  3. void visit(UserVIP user);
  4. void visit(UserOrdinary user);
  5. }

    具体访问者,检查反馈是否记录到有效反馈中:

  1. //具体访问者
  2. public class APPOwner implements Visitor{
  3. @Override
  4. public void visit(UserVIP user) {
  5. String estimation = user.getEstimation();
  6. if(estimation.length()>5)
  7. System.out.println("记录一条有效反馈:" + estimation);
  8. }
  9. @Override
  10. public void visit(UserOrdinary user) {
  11. String estimation = user.getEstimation();
  12. if(estimation.length()>10)
  13. System.out.println("记录一条有效反馈:" + estimation);
  14. }
  15. }

    使用:

  1. public class TestUse {
  2. public static void main(String args[]){
  3. Visitor appOwner = new APPOwner();
  4. ArrayList<User> users = new ArrayList<User>();
  5. users.add(new UserOrdinary("普通用户短反馈"));
  6. users.add(new UserOrdinary("这是一个普通用户的比较长的反馈"));
  7. users.add(new UserVIP("VIP用户的短反馈"));
  8. users.add(new UserVIP("VIP用户的比较长的反馈反馈"));
  9. Iterator<User> iterator = users.iterator();
  10. while(iterator.hasNext()){
  11. iterator.next().accept(appOwner);
  12. }
  13. }
  14. }

    输出:

  1. 记录一条有效反馈:这是一个普通用户的比较长的反馈
  2. 记录一条有效反馈:VIP用户的短反馈
  3. 记录一条有效反馈:VIP用户的比较长的反馈反馈

    其中ArrayList就是结构中的对象结构,我们在列表中存放了实际类型不同的元素,然后访问的时候,不需要“visitor instanceof SomeClass”这种语句一样能完成遍历,因为在具体元素中,我们通过visit()方法,以参数this传递给访问者,访问者中是多个方法的重载,是静态分派的,恰好此时编译器是很清楚这个this的类型的,它的静态类型就是这个具体元素本身,然后在访问者(Visitor)的对应的方法中,我们再通过传过来的这个参数(某一个具体元素)来调用这个元素中的方法得到数据。

    在回到前面的双重分派,我们知道,动态多分派的语言是很容易做到双重分派这一点的,可以通过接收者和参数(实际类型)来判断方法的调用,但是,Java是动态单分派的,只能通过接收者来动态分派,但是现在我们也清楚了,使用访问者模式,通过两次方法调用,我们依然实现了双重分派。

 

17、适配器模式(Adapter Pattern)

    将一个类的接口转换成客户希望的另外一个接口。该模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

    何时使用

  •     一个程序想使用已经存在的类,但是该类所实现的接口和当前程序所使用的接口不一致时。

    优点

  •     目标与被适配者解耦
  •     满足开-闭原则

    原理就是保留现有的类所提供的服务,修改其接口,从而达到客户端的期望。

    再者,适配器也分对象适配器和类适配器,先看下面代码再来解释。

    举个例子,你有一个播放器,只能播放MP3格式的音乐,但是现在需要它能播放flac格式的,我们不能直接使用这个播放器,但可以添加一个适配器来解决这个问题:

    原来的播放器(被适配者):

  1. //被适配者
  2. class Adaptee{
  3. void playMp3(Object src){
  4. System.out.println("播放MP3:" + src);
  5. }
  6. }

    用户像使用的功能(目标):

  1. //目标,也就是用户所希望使用的
  2. interface Target{
  3. void playFlac(Object src);
  4. }

    对象适配器

  1. //对象适配器
  2. public class ObjectAdapter implements Target{
  3. private Adaptee adaptee;
  4. public ObjectAdapter(){
  5. super();
  6. adaptee = new Adaptee();
  7. }
  8. @Override
  9. public void playFlac(Object src) {
  10. //可能需要对src作处理
  11. adaptee.playMp3(src);
  12. }
  13. }

    类适配器

  1. //类适配器
  2. public class ClassAdapter extends Adaptee implements Target {
  3. @Override
  4. public void playFlac(Object src) {
  5. //可能需要对src作处理
  6. playMp3(src);
  7. }
  8. }

    使用:

  1. public class TestUse {
  2. public static void main(String args[]){