《大话设计模式 下》

状态模式

  早上早起工作清醒,11点又开始困,2点后工作状态良好,晚上9点后犯困。如果想让你编写这个状态的转变,你如何设计?
1、最低级的方式:在Work类中定义一个变量hour,并定义一个getState()方法,getState方法中根据hour值进行判断,并返回状态值。此种方式在getState()方法中有很多判断分支,可以说责任过大了,违背 了单一职责原则,当如果要改动某个时间段的状态时,需要对整个方法做出改动,维护出错风险很高,违背了“开放-封闭原则”。
2、定义一个抽象的类getResult,并且上午中午下午晚上的类都实现该类,并且在getResult中根据hour值进行切换,例如,上午的类中getResult方法判断hour如果小于11则输出困,否则将上下文类Context中的State更改为中午的类。
3、同是状态模式,可以在Work这一环境类中实现状态转换,可以在Work中创建多个State,MoningState 、NoonState 、EveningState 、AfterNoonState 和一个CurrentState,根据hour选择赋值给CurrentState。此种方式在一定程度上违背了“开闭原则”,但对其他状态类没有任何影响。
抽象的状态类:

public abstract class State {
	public abstract void getResult(Work w);	
}

具体的状态类,状态切换的过程在这几类中根据Work的成员hour进行判断,然后更改环境类中的状态类。

public class MoningState extends State {

	@Override
	public void getResult(Work w) {
		// TODO Auto-generated method stub
		if(w.hour <11)
			System.out.println("早上精神好!");
		else
		{
			w.setState(new NoonState());
			w.getResult();
		}
	}
}
public class NoonState extends State {
	@Override
	public void getResult(Work w) {
		// TODO Auto-generated method stub
		if(w.hour <14)
			System.out.println("中午困的很!");
		else
		{
			w.setState(new AfterNoonState());
			w.getResult();
		}
	}
}
public  class AfterNoonState extends State {
	@Override
	public void getResult(Work w) {
		// TODO Auto-generated method stub
		if(w.hour <18)
			System.out.println("下午午觉后精神好!");
		else
		{
			w.setState(new EveningState());
			w.getResult();
		}
	}
}
public class EveningState extends State {
	@Override
	public void getResult(Work w) {
		// TODO Auto-generated method stub
		if(w.hour <22)
			System.out.println("下班之前磨洋工!");
	}
}

测试:

public class Work {
	private State state = new MoningState();
	public int hour;
	public void setHour(int hour) {
		this.hour = hour;
	}
	public void setState(State state) {
		this.state = state;
	}
	public void getResult()
	{
		state.getResult(this);
	}
	public static void main(String[] args) {
		Work work = new Work();
		work.setHour(9);
		work.getResult();
		work.setHour(12);
		work.getResult();
		work.setHour(16);
		work.getResult();
		work.setHour(21);
		work.getResult();
	}
}

运行结果:
              在这里插入图片描述
  状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。
  在状态模式中引入了抽象状态类和具体状态类,它们是状态模式的核心,其结构如图3所示:
在这里插入图片描述
在状态模式结构图中包含如下几个角色:

  • Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
  • State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
  • ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
      在状态模式中,我们将对象在不同状态下的行为封装到不同的状态类中,为了让系统具有更好的灵活性和可扩展性,同时对各状态下的共有行为进行封装,我们需要对状态进行抽象,引入了抽象状态类角色,其典型代码如下所示:
abstract class State {
    //声明抽象业务方法,不同的具体状态类可以不同的实现
	public abstract void handle();
}

   在抽象状态类的子类即具体状态类中实现了在抽象状态类中声明的业务方法,不同的具体状态类可以提供完全不同的方法实现,在实际使用时,在一个状态类中可能包含多个业务方法,如果在具体状态类中某些业务方法的实现完全相同,可以将这些方法移至抽象状态类,实现代码的复用,典型的具体状态类代码如下所示

class ConcreteState extends State {
	public void handle() {
		//方法具体实现代码
	}
}

  环境类维持一个对抽象状态类的引用,通过setState()方法可以向环境类注入不同的状态对象,再在环境类的业务方法中调用状态对象的方法,典型代码如下所示:

class Context {
	private State state; //维持一个对抽象状态对象的引用
	private int value; //其他属性值,该属性值的变化可能会导致对象状态发生变化 
    //设置状态对象
	public void setState(State state) {
		this.state = state;
	} 
	public void request() {
		//其他代码
		state.handle(); //调用状态对象的业务方法
		//其他代码
	}
}

   在状态模式的使用过程中,一个对象的状态之间还可以进行相互转换,通常有两种实现状态转换的方式:
1、统一由环境类来负责状态之间的转换,此时,环境类还充当了状态管理器(State Manager)角色,在环境类的业务方法中通过对某些属性值的判断实现状态转换,还可以提供一个专门的方法用于实现属性判断和状态转换,如下代码片段所示:
  

      public void changeState() {
		//判断属性值,根据属性值进行状态转换
      if (value == 0) {
			this.setState(new ConcreteStateA());
		}
		else if (value == 1) {
			this.setState(new ConcreteStateB());
		}
        ......
	}

2、 由具体状态类来负责状态之间的转换,可以在具体状态类的业务方法中判断环境类的某些属性值再根据情况为环境类设置新的状态对象,实现状态转换,同样,也可以提供一个专门的方法来负责属性值的判断和状态转换。此时,状态类与环境类之间就将存在依赖或关联关系,因为状态类需要访问环境类中的属性值,如下代码片段所示:

      public void changeState(Context ctx) {
		//根据环境对象中的属性值进行状态转换
      if (ctx.getValue() == 1) {
			ctx.setState(new ConcreteStateB());
		}
		else if (ctx.getValue() == 2) {
			ctx.setState(new ConcreteStateC());
		}
        ......
	}

适配器模式

  笔记本电脑的工作电压是20V,而我国的家庭用电是220V,如何让20V的笔记本电脑能够在220V的电压下工作?答案是引入一个电源适配器(AC Adapter)。与电源适配器相似,在适配器模式中引入了一个被称为适配器(Adapter)的包装类,而它所包装的对象称为适配者(Adaptee),即被适配的类。适配器的实现就是把客户类的请求转化为对适配者的相应接口的调用。也就是说:当客户类调用适配器的方法时,在适配器类的内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类。因此,适配器让那些由于接口不兼容而不能交互的类可以一起工作。
  适配器模式(Adapter Pattern):将一个接口转换成客户希望的另一个接口,使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。在对象适配器模式中,适配器与适配者之间是关联关系;在类适配器模式中,适配器与适配者之间是继承(或实现)关系在实际开发中,对象适配器的使用频率更高,对象适配器模式结构如图9-3所示:
      在这里插入图片描述
在对象适配器模式结构图中包含如下几个角色:

  • Target(目标抽象类):目标抽象类定义客户所需接口,可以是一个抽象类或接口,也可以是具体类。
  • Adapter(适配器类):适配器可以调用另一个接口,作为一个转换器,对Adaptee和Target进行适配,适配器类是适配器模式的核心,在对象适配器中,它通过继承Target并关联一个Adaptee对象使二者产生联系。
  • Adaptee(适配者类):适配者即被适配的角色,它定义了一个已经存在的接口,这个接口需要适配,适配者类一般是一个具体类,包含了客户希望使用的业务方法,在某些情况下可能没有适配者类的源代码。

   根据对象适配器模式结构图,在对象适配器中,客户端需要调用request()方法,而适配者类Adaptee没有该方法,但是它所提供的specificRequest()方法却是客户端所需要的。为了使客户端能够使用适配者类,需要提供一个包装类Adapter,即适配器类。这个包装类包装了一个适配者的实例,从而将客户端与适配者衔接起来,在适配器的request()方法中调用适配者的specificRequest()方法。因为适配器类与适配者类是关联关系(也可称之为委派关系),所以这种适配器模式称为对象适配器模式。典型的对象适配器代码如下所示:

class Adapter extends Target {
	private Adaptee adaptee; //维持一个对适配者对象的引用
	
	public Adapter(Adaptee adaptee) {
		this.adaptee=adaptee;
	}	
	public void request() {
		adaptee.specificRequest(); //转发调用
	}
}

备忘录模式(软件中的月光宝盒)

  可以在软件中实现后悔机制的设计模式——备忘录模式,它是软件中的“后悔药”,是软件中的“月光宝盒”。
Sunny软件公司欲开发一款可以运行在Android平台的触摸式中国象棋软件,由于考虑到有些用户是“菜鸟”,经常不小心走错棋;还有些用户因为不习惯使用手指在手机屏幕上拖动棋子,常常出现操作失误,因此该中国象棋软件要提供“悔棋”功能,用户走错棋或操作失误后可恢复到前一个步骤。如图21-1所示:
             在这里插入图片描述
在实现撤销时,首先必须保存软件系统的历史状态,当用户需要取消错误操作并且返回到某个历史状态时,可以取出事先保存的历史状态来覆盖当前状态。如图21-2所示:
        在这里插入图片描述
  备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。备忘录模式的核心是备忘录类以及用于管理备忘录的负责人类的设计,其结构如图21-3所示:
         在这里插入图片描述
在备忘录模式结构图中包含如下几个角色:

  • Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
  • Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
  • Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

   理解备忘录模式并不难,但关键在于如何设计备忘录类和负责人类。由于在备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录,特别是不允许其他对象来修改备忘录。
下面我们通过简单的示例代码来说明如何使用Java语言实现备忘录模式:
在使用备忘录模式时,首先应该存在一个原发器类Originator,在真实业务中,原发器类是一个具体的业务类,它包含一些用于存储成员数据的属性,典型代码如下所示:

package dp.memento;
public class Originator {
    private String state; 
    public Originator(){} 
  // 创建一个备忘录对象
    public Memento createMemento() {
    return new Memento(this);
    } 
  // 根据备忘录对象恢复原发器状态
    public void restoreMemento(Memento m) {
     state = m.state;
    } 
    public void setState(String state) {
        this.state=state;
    } 
    public String getState() {
        return this.state;
    }
}

  对于备忘录类Memento而言,它通常提供了与原发器相对应的属性(可以是全部,也可以是部分)用于存储原发器的状态,典型的备忘录类设计代码如下:
  

//备忘录类,默认可见性,包内可见
class Memento {
    private String state; 
    public Memento(Originator o) {
    state = o.getState();
    } 
    public void setState(String state) {
        this.state=state;
    } 
    public String getState() {
        return this.state;
    }
}

  在设计备忘录类时需要考虑其封装性,除了Originator类,不允许其他类来调用备忘录类Memento的构造函数与相关方法,如果不考虑封装性,允许其他类调用setState()等方法,将导致在备忘录中保存的历史状态发生改变,通过撤销操作所恢复的状态就不再是真实的历史状态,备忘录模式也就失去了本身的意义
  在使用Java语言实现备忘录模式时,一般通过将Memento类与Originator类定义在同一个包(package)中来实现封装,在Java语言中可使用默认访问标识符来定义Memento类,即保证其包内可见。只有Originator类可以对Memento进行访问,而限制了其他类对Memento的访问。在 Memento中保存了Originator的state值,如果Originator中的state值改变之后需撤销,可以通过调用它的restoreMemento()方法进行恢复。
  对于负责人类Caretaker,它用于保存备忘录对象,并提供getMemento()方法用于向客户端返回一个备忘录对象,原发器通过使用这个备忘录对象可以回到某个历史状态。典型的负责人类的实现代码如下:

package dp.memento;
public class Caretaker {
	private Memento memento; 
	public Memento getMemento() {
		return memento;
	} 
	public void setMemento(Memento memento) {
		this.memento=memento;
	}
}

迭代器模式

  迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。当你需要访问一个聚集对象时,而且不管这些对象是什么都需要遍历时,你就需要考虑使用迭代器模式
  例如,售票员发现有3个人没有买票,则需要遍历车厢中的每一个人,当然遍历的方式可以有多种,但是无论是谁都需要查一次。
  迭代器模式(Iterator Pattern):提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。迭代器模式是一种对象行为型模式。
        在这里插入图片描述
在迭代器模式结构图中包含如下几个角色:

  • Iterator(抽象迭代器):它定义了访问和遍历元素的接口,声明了用于遍历数据元素的方法,例如:用于获取第一个元素的first()方法,用于访问下一个元素的next()方法,用于判断是否还有下一个元素的hasNext()方法,用于获取当前元素的currentItem()方法等,在具体迭代器中将实现这些方法。
  • ConcreteIterator(具体迭代器):它实现了抽象迭代器接口,完成对聚合对象的遍历,同时在具体迭代器中通过游标来记录在聚合对象中所处的当前位置,在具体实现时,游标通常是一个表示位置的非负整数
  • Aggregate(抽象聚合类):它用于存储和管理元素对象,声明一个createIterator()方法用于创建一个迭代器对象,充当抽象迭代器工厂角色。
  • ConcreteAggregate(具体聚合类):它实现了在抽象聚合类中声明的createIterator()方法,该方法返回一个与该具体聚合类对应的具体迭代器ConcreteIterator实例。

  在迭代器模式中,提供了一个外部的迭代器来对聚合对象进行访问和遍历,迭代器定义了一个访问该聚合元素的接口,并且可以跟踪当前遍历的元素,了解哪些元素已经遍历过而哪些没有。迭代器的引入,将使得对一个复杂聚合对象的操作变得简单。
   下面我们结合代码来对迭代器模式的结构进行进一步分析。在迭代器模式中应用了工厂方法模式,抽象迭代器对应于抽象产品角色,具体迭代器对应于具体产品角色,抽象聚合类对应于抽象工厂角色,具体聚合类对应于具体工厂角色。
在抽象迭代器中声明了用于遍历聚合对象中所存储元素的方法,典型代码如下所示:

interface Iterator {
	public void first(); //将游标指向第一个元素
	public void next(); //将游标指向下一个元素
	public boolean hasNext(); //判断是否存在下一个元素
	public Object currentItem(); //获取游标指向的当前元素
}

  在具体迭代器中将实现抽象迭代器声明的遍历数据的方法,如下代码所示:

class ConcreteIterator implements Iterator {
	private ConcreteAggregate objects; //维持一个对具体聚合对象的引用,以便于访问存储在聚合对象中的数据
	private int cursor; //定义一个游标,用于记录当前访问位置
	public ConcreteIterator(ConcreteAggregate objects) {
		this.objects=objects;
	} 
	public void first() {  ......  }		
	public void next() {  ......  } 
	public boolean hasNext() {  ......  }	
	public Object currentItem() {  ......  }
}

   需要注意的是抽象迭代器接口的设计非常重要,一方面需要充分满足各种遍历操作的要求,尽量为各种遍历方法都提供声明,另一方面又不能包含太多方法,接口中方法太多将给子类的实现带来麻烦。因此,可以考虑使用抽象类来设计抽象迭代器,在抽象类中为每一个方法提供一个空的默认实现。如果需要在具体迭代器中为聚合对象增加全新的遍历操作,则必须修改抽象迭代器和具体迭代器的源代码,这将违反“开闭原则”,因此在设计时要考虑全面,避免之后修改接口。
  聚合类用于存储数据并负责创建迭代器对象,最简单的抽象聚合类代码如下所示:

interface Aggregate {
	Iterator createIterator();
}

  具体聚合类作为抽象聚合类的子类,一方面负责存储数据,另一方面实现了在抽象聚合类中声明的工厂方法createIterator(),用于返回一个与该具体聚合类对应的具体迭代器对象,代码如下所示:

class ConcreteAggregate implements Aggregate {	
    ......	
    public Iterator createIterator() {
	return new ConcreteIterator(this);
    }
	......
}

以查票为例:
聚合类:

public abstract class Aggreate<E> {
	public abstract Iterator CreateIterator();
}
public class ConcreteAggreate<E> extends Aggreate {
	public ArrayList<E> items = new ArrayList<>();	
	@Override
	public Iterator CreateIterator() {
		// TODO Auto-generated method stub
		return new ConcreateIterator(this);
	}
	public int count()
	{
		return items.size();
	}
	public ConcreteAggreate(E [] array)
	{
		for(E i:array)
			items.add(i);
		
	}
}

迭代器抽象类与具体实现:

abstract class Iterator {
	public abstract Object First();
	public abstract Object Next();
	public abstract boolean IsDone();
	public abstract Object CurrentIttem();
}
public class ConcreateIterator extends Iterator{
	private ConcreteAggreate aggreate;
	private int current = 0;
	
	public ConcreateIterator(ConcreteAggreate aggreate) {
		super();
		this.aggreate = aggreate;
	}

	@Override
	public Object First() {
		// TODO Auto-generated method stub
		return aggreate.items.get(0);
	}
	@Override
	public Object Next() {
		// TODO Auto-generated method stub
		current ++;
		if(current < aggreate.items.size())
		return aggreate.items.get(current);
		else
			return null;
	}
	@Override
	public boolean IsDone() {
		// TODO Auto-generated method stub
		return current >= aggreate.count()?true:false;
	}
	@Override
	public Object CurrentIttem() {
		// TODO Auto-generated method stub
		return aggreate.items.get(current);
	}
}

测试代码:

public class AggreateMode {
	public static void main(String[] args) {
		String [] persons = {"小偷","michael","MrRight"};
		ConcreteAggreate<String> bus = new ConcreteAggreate<String>(persons);
		Iterator iterator= bus.CreateIterator();
		while(!iterator.IsDone())
		{
			System.out.println(iterator.CurrentIttem()+"  买票!");
			iterator.Next();
		}
	}	
}

                  在这里插入图片描述

单例模式(饿汉模式与双重检查锁定的懒加载模式)

  为了确保对象的唯一性,我们可以通过单例模式来实现,这就是单例模式的动机所在。举个大家都熟知的例子——Windows任务管理器,无论我们启动任务管理多少次,Windows系统始终只能弹出一个任务管理器窗口,也就是说在一个Windows系统中,任务管理器存在唯一性。
  下面我们来模拟实现Windows任务管理器,假设任务管理器的类名为TaskManager,在TaskManager类中包含了大量的成员方法,例如构造函数TaskManager(),显示进程的方法displayProcesses(),显示服务的方法displayServices()等,该类的示意代码如下:

class TaskManager
{
     public TaskManager() {……} //初始化窗口
     public void displayProcesses()  {……} //显示进程
     public void  displayServices() {……} //显示服务
}

为了实现Windows任务管理器的唯一性,我们通过如下三步来对该类进行重构:
   (1) 由于每次使用new关键字来实例化TaskManager类时都将产生一个新对象,为了确保TaskManager实例的唯一性,我们需要禁止类的外部直接使用new来创建对象,因此需要将TaskManager的构造函数的可见性改为private,如下代码所示:

private TaskManager() {……}

  (2) 将构造函数改为private修饰后该如何创建对象呢?不要着急,虽然类的外部无法再使用new来创建对象,但是在TaskManager的内部还是可以创建的,可见性只对类外有效。因此,我们可以在TaskManager中创建并保存这个唯一实例。为了让外界可以访问这个唯一实例,需要在TaskManager中定义一个静态的TaskManager类型的私有成员变量,如下代码所示:

private static TaskManager tm = null;

   (3) 为了保证成员变量的封装性,我们将TaskManager类型的tm对象的可见性设置为private,但外界该如何使用该成员变量并何时实例化该成员变量呢?答案是增加一个公有的静态方法,如下代码所示:

public static TaskManager getInstance()
{
    if (tm == null)
    {
        tm = new TaskManager();
    }
    return tm;
}

   在getInstance()方法中首先判断tm对象是否存在,如果不存在(即tm == null),则使用new关键字创建一个新的TaskManager类型的tm对象,再返回新创建的tm对象;否则直接返回已有的tm对象。

   需要注意的是getInstance()方法的修饰符,首先它应该是一个public方法,以便供外界其他对象使用,其次它使用了static关键字,即它是一个静态方法,在类外可以直接通过类名来访问,而无须创建TaskManager对象,事实上在类外也无法创建TaskManager对象,因为构造函数是私有的。

   以上的单例在单线程时还可以确保返回的是单例,若多个线程进行判断,发现都为null,然后得到创建的实例并不唯一
例如:软件公司承接了一个服务器负载均衡(Load Balance)软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量分发到服务器集群中的多台设备上进行并发处理,提高系统的整体处理能力,缩短响应时间。由于集群中的服务器需要动态删减,且客户端请求需要统一分发,因此需要确保负载均衡器的唯一性,只能有一个负载均衡器来负责服务器的管理和请求的分发,否则将会带来服务器状态的不一致以及请求分配冲突等问题。如何确保负载均衡器的唯一性是该软件成功的关键。
   若使用单例模式来设计该负载均衡器,结构图如图3-3所示:
                  在这里插入图片描述
  在图3-3中,将负载均衡器LoadBalancer设计为单例类,其中包含一个存储服务器信息的集合serverList,每次在serverList中随机选择一台服务器来响应客户端的请求。

import java.util.*; 
//负载均衡器LoadBalancer:单例类,真实环境下该类将非常复杂,包括大量初始化的工作和业务方法,考虑到代码的可读性和易理解性,只列出部分与模式相关的核心代码
class LoadBalancer {
	//私有静态成员变量,存储唯一实例
	private static LoadBalancer instance = null;
	//服务器集合
	private List serverList = null;	
	//私有构造函数
	private LoadBalancer() {
		serverList = new ArrayList();
	}	
	//公有静态成员方法,返回唯一实例
	public static LoadBalancer getLoadBalancer() {
		if (instance == null) {
			instance = new LoadBalancer();
		}
		return instance;
	}	
	//增加服务器
	public void addServer(String server) {
		serverList.add(server);
	}	
	//删除服务器
	public void removeServer(String server) {
		serverList.remove(server);
	}	
	//使用Random类随机获取服务器
	public String getServer() {
		Random random = new Random();
		int i = random.nextInt(serverList.size());
		return (String)serverList.get(i);
	}
}

  但是在实际使用中出现了一个非常严重的问题,当负载均衡器在启动过程中用户再次启动该负载均衡器时,系统无任何异常,但当客户端提交请求时出现请求分发失败,通过仔细分析发现原来系统中还是存在多个负载均衡器对象,导致分发时目标服务器不一致,从而产生冲突。为什么会这样呢?Sunny公司开发人员百思不得其解。
  现在我们对负载均衡器的实现代码进行再次分析,当第一次调用getLoadBalancer()方法创建并启动负载均衡器时,instance对象为null值,因此系统将执行代码instance= new LoadBalancer(),在此过程中,由于要对LoadBalancer进行大量初始化工作,需要一段时间来创建LoadBalancer对象。而在此时,如果再一次调用getLoadBalancer()方法(通常发生在多线程环境中),由于instance尚未创建成功,仍为null值,判断条件(instance== null)为真值,因此代码instance= new LoadBalancer()将再次执行,导致最终创建了多个instance对象,这违背了单例模式的初衷,也导致系统运行发生错误。
  如何解决该问题?我们至少有两种解决方案,在正式介绍这两种解决方案之前,先介绍一下单例类的两种不同实现方式,饿汉式单例类和懒汉式单例类
   饿汉式单例类是实现起来最简单的单例类,在定义静态变量的时候实例化单例类。,饿汉式单例类结构图如图3-4所示:
            在这里插入图片描述
   从图3-4中可以看出,由于在定义静态变量的时候实例化单例类,因此在类加载的时候就已经创建了单例对象,代码如下所示: 当类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。如果使用饿汉式单例来实现负载均衡器LoadBalancer类的设计,则不会出现创建多个单例对象的情况,可确保单例对象的唯一性。

class EagerSingleton { 
    private static final EagerSingleton instance = new EagerSingleton(); 
    private EagerSingleton() { } 
    public static EagerSingleton getInstance() {
        return instance; 
    }   
}

懒汉式单例类与线程锁定: 除了饿汉式单例,还有一种经典的懒汉式单例,也就是前面的负载均衡器LoadBalancer类的实现方式。懒汉式单例类结构图如图3-5所示:
            在这里插入图片描述
   从图3-5中可以看出,懒汉式单例在第一次调用getInstance()方法时实例化,在类加载时并不自行实例化,这种技术又称为延迟加载(Lazy Load)技术,即需要的时候再加载实例,为了避免多个线程同时调用getInstance()方法,我们可以使用关键字synchronized,代码如下所示:

class LazySingleton { 
    private static LazySingleton instance = null;  
    private LazySingleton() { } 
    synchronized public static LazySingleton getInstance() { 
        if (instance == null) {
            instance = new LazySingleton(); 
        }
        return instance; 
    }
}

   该懒汉式单例类在getInstance()方法前面增加了关键字synchronized进行线程锁,以处理多个线程同时访问的问题。但是,上述代码虽然解决了线程安全问题,但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。如何既解决线程安全问题又不影响系统性能呢?我们继续对懒汉式单例进行改进。事实上,我们无须对整个getInstance()方法进行锁定,只需对其中的代码“instance = new LazySingleton();”进行锁定即可。因此getInstance()方法可以进行如下改进:

public static LazySingleton getInstance() { 
    if (instance == null) {
        synchronized (LazySingleton.class) {
            instance = new LazySingleton(); 
        }
    }
    return instance; 
}

问题貌似得以解决,事实并非如此。如果使用以上代码来实现单例,还是会存在单例对象不唯一。原因如下:
  假如在某一瞬间线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance == null的判断。由于实现了synchronized加锁机制,线程A进入synchronized锁定的代码中执行实例创建代码,线程B处于排队等待状态,必须等待线程A执行完毕后才可以进入synchronized锁定代码。但当A执行完毕时,线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个单例对象,违背单例模式的设计思想,因此需要进行进一步改进,在synchronized中再进行一次(instance == null)判断,这种方式称为双重检查锁定(Double-Check Locking)。使用双重检查锁定实现的懒汉式单例类完整代码如下所示:

class LazySingleton { 
    private volatile static LazySingleton instance = null; 
 
    private LazySingleton() { } 
 
    public static LazySingleton getInstance() { 
        //第一重判断
        if (instance == null) {
            //锁定代码块
            synchronized (LazySingleton.class) {
                //第二重判断
                if (instance == null) {
                    instance = new LazySingleton(); //创建单例实例
                }
            }
        }
        return instance; 
    }
}

   需要注意的是,如果使用双重检查锁定来实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
   饿汉式单例类在类被加载时就将自己实例化,它的优点在于无须考虑多线程访问问题,可以确保实例的唯一性;从调用速度和反应时间角度来讲,由于单例对象一开始就得以创建,因此要优于懒汉式单例。但是无论系统在运行时是否需要使用该单例对象,由于在类加载时该对象就需要创建,因此从资源利用效率角度来讲,饿汉式单例不及懒汉式单例,而且在系统加载时由于需要创建饿汉式单例对象,加载时间可能会比较长。
   懒汉式单例类在第一次使用时创建,无须一直占用系统资源,实现了延迟加载,但是必须处理好多个线程同时访问的问题,特别是当单例类作为资源控制器,在实例化时必然涉及资源初始化,而资源初始化很有可能耗费大量时间,这意味着出现多线程同时首次引用此类的机率变得较大,需要通过双重检查锁定等机制进行控制,这将导致系统性能受到一定影响。

命令模式

  路边的烤肉摊老板,即当服务员又当烧烤师傅,当顾客多的时候,显然容易出错,这在编程中常说的就是“紧耦合”,为此,如果此时增加一个服务员,你需要任何通过服务员来转交传递给烧烤师傅执行你要的Command,这就是命令模式的思想。
定义:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。
类型:行为类模式
类图:
    在这里插入图片描述
顾名思义,命令模式就是对命令的封装,首先来看一下命令模式类图中的基本结构:

  • Command类:是一个抽象类,类中对需要执行的命令进行声明,一般来说要对外公布一个execute方法用来执行命令。

  • ConcreteCommand类:Command类的实现类,对抽象类中声明的方法进行实现。

  • Client类:最终的客户端调用类。

  • Invoker类:调用者,负责调用命令。

  • Receiver类:接收者,负责接收命令并且执行命令。

   所谓对命令的封装,说白了,无非就是把一系列的操作写到一个方法中,然后供客户端调用就行了,反映到类图上,只需要一个ConcreteCommand类和Client类就可以完成对命令的封装,即使再进一步,为了增加灵活性,可以再增加一个Command类进行适当地抽象,这个调用者和接收者到底是什么作用呢?
   其实大家可以换一个角度去想:假如仅仅是简单地把一些操作封装起来作为一条命令供别人调用,怎么能称为一种模式呢?命令模式作为一种行为类模式,首先要做到低耦合,耦合度低了才能提高灵活性,而加入调用者和接收者两个角色的目的也正是为此。命令模式的通用代码如下:

class Invoker {
	private Command command;
	public void setCommand(Command command) {
		this.command = command;
	}
	public void action(){
		this.command.execute();
	}
}
 
abstract class Command {
	public abstract void execute();
}
 
class ConcreteCommand extends Command {
	private Receiver receiver;
	public ConcreteCommand(Receiver receiver){
		this.receiver = receiver;
	}
	public void execute() {
		this.receiver.doSomething();
	}
}
 
class Receiver {
	public void doSomething(){
		System.out.println("接受者-业务逻辑处理");
	}
}
 
public class Client {
	public static void main(String[] args){
		Receiver receiver = new Receiver();
		Command command = new ConcreteCommand(receiver);
		//客户端直接执行具体命令方式(此方式与类图相符)
		command.execute();
 
		//客户端通过调用者来执行命令
		Invoker invoker = new Invoker();
		invoker.setCommand(command);
		invoker.action();
	}
}

中介者模式(中介者类来协调多个类从而降低耦合)

  在QQ群没出现之前,你如果想发送给多人一份文件,那么需要逐个发送,同样 在有些软件中,某些类/对象之间的相互调用关系错综复杂,类似QQ用户之间的关系,此时,我们特别需要一个类似“QQ群”一样的中间类来协调这些类/对象之间的复杂关系,以降低系统的耦合度。有一个设计模式正为此而诞生,它就是将要介绍的中介者模式。
   Sunny软件公司欲开发一套CRM系统,其中包含一个客户信息管理模块,所设计的“客户信息管理窗口”界面效果图如图20-2所示:
            在这里插入图片描述
   Sunny公司开发人员通过分析发现,在图20-2中,界面组件之间存在较为复杂的交互关系:如果删除一个客户,要在客户列表(List)中删掉对应的项,客户选择组合框(ComboBox)中客户名称也将减少一个;如果增加一个客户信息,客户列表中需增加一个客户,且组合框中也将增加一项。 Sunny公司开发人员对组件之间的交互关系进行了分析,结果如下:
   (1) 当用户单击“增加”按钮、“删除”按钮、“修改”按钮或“查询”按钮时,界面左侧的“客户选择组合框”、“客户列表”以及界面中的文本框将产生响应。
   (2) 当用户通过“客户选择组合框”选中某个客户姓名时,“客户列表”和文本框将产生响应。
   (3) 当用户通过“客户列表”选中某个客户姓名时,“客户选择组合框”和文本框将产生响应。
于是,Sunny公司开发人员根据组件之间的交互关系绘制了如图20-3所示初始类图:
         在这里插入图片描述
我们不难发现该设计方案存在如下问题:
   (1) 系统结构复杂且耦合度高:每一个界面组件都与多个其他组件之间产生相互关联和调用,若一个界面组件对象发生变化,需要跟踪与之有关联的其他所有组件并进行处理,系统组件之间呈现一种较为复杂的网状结构,组件之间的耦合度高。
   (2) 组件的可重用性差:由于每一个组件和其他组件之间都具有很强的关联,若没有其他组件的支持,一个组件很难被另一个系统或模块重用,这些组件表现出来更像一个不可分割的整体,而在实际使用时,我们往往需要每一个组件都能够单独重用,而不是重用一个由多个组件组成的复杂结构。
   (3) 系统的可扩展性差:如果在上述系统中增加一个新的组件类,则必须修改与之交互的其他组件类的源代码,将导致多个类的源代码需要修改,同样,如果要删除一个组件也存在类似的问题,这违反了“开闭原则”,可扩展性和灵活性欠佳。
   由于存在上述问题,Sunny公司开发人员不得不对原有系统进行重构,那如何重构呢?大家想到了“迪米特法则”,引入一个“第三者”来降低现有系统中类之间的耦合度。由这个“第三者”来封装并协调原有组件两两之间复杂的引用关系,使之成为一个松耦合的系统,这个“第三者”又称为“中介者”,中介者模式因此而得名。
   如果在一个系统中对象之间的联系呈现为网状结构,如图20-4所示。对象之间存在大量的多对多联系,将导致系统非常复杂,这些对象既会影响别的对象,也会被别的对象所影响,这些对象称为同事对象,它们之间通过彼此的相互作用实现系统的行为。在网状结构中,几乎每个对象都需要与其他对象发生相互作用,而这种相互作用表现为一个对象与另外一个对象的直接耦合,这将导致一个过度耦合的系统。
               在这里插入图片描述
  中介者模式可以使对象之间的关系数量急剧减少,通过引入中介者对象,可以将系统的网状结构变成以中介者为中心的星形结构,如图20-5所示。在这个星形结构中,同事对象不再直接与另一个对象联系,它通过中介者对象与另一个对象发生相互作用。中介者对象的存在保证了对象结构上的稳定,也就是说,系统的结构不会因为新对象的引入带来大量的修改工作。
              在这里插入图片描述
  中介者模式(Mediator Pattern):用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式
  在中介者模式中,我们引入了用于协调其他对象/类之间相互调用的中介者类,为了让系统具有更好的灵活性和可扩展性,通常还提供了抽象中介者,其结构图如图20-6所示:
在这里插入图片描述
在中介者模式结构图中包含如下几个角色:

  • Mediator(抽象中介者):它定义一个接口,该接口用于与各同事对象之间进行通信。
  • ConcreteMediator(具体中介者):它是抽象中介者的子类,通过协调各个同事对象来实现协作行为,它维持了对各个同事对象的引用。
  • Colleague(抽象同事类):它定义各个同事类公有的方法,并声明了一些抽象方法来供子类实现,同时它维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。
  • ConcreteColleague(具体同事类):它是抽象同事类的子类;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中声明的抽象方法。

中介者模式的核心在于中介者类的引入,在中介者模式中,中介者类承担了两方面的职责:
(1) 中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,可通过中介者来实现间接调用。该中转作用属于中介者在结构上的支持。
(2) 协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致的和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。
在中介者模式中,典型的抽象中介者类代码如下所示:

abstract class Mediator {
	protected ArrayList<Colleague> colleagues; //用于存储同事对象
 
    //注册方法,用于增加同事对象
	public void register(Colleague colleague) {
		colleagues.add(colleague);
	}
 
	//声明抽象的业务方法
	public abstract void operation();
}

   在抽象中介者中可以定义一个同事类的集合,用于存储同事对象并提供注册方法,同时声明了具体中介者类所具有的方法。在具体中介者类中将实现这些抽象方法,典型的具体中介者类代码如下所示:
  

class ConcreteMediator extends Mediator {
    //实现业务方法,封装同事之间的调用
	public void operation() {
		......
		((Colleague)(colleagues.get(0))).method1(); //通过中介者调用同事类的方法
		......
	}
}

在具体中介者类中将调用同事类的方法,调用时可以增加一些自己的业务代码对调用进行控制。 在抽象同事类中维持了一个抽象中介者的引用,用于调用中介者的方法,典型的抽象同事类代码如下所示:

abstract class Colleague {
  protected Mediator mediator; //维持一个抽象中介者的引用
  
  public Colleague(Mediator mediator) {
  	this.mediator=mediator;
  }
  
  public abstract void method1(); //声明自身方法,处理自己的行为
  
  //定义依赖方法,与中介者进行通信
  public void method2() {
  	mediator.operation();
  }
}

  在抽象同事类中声明了同事类的抽象方法,而在具体同事类中将实现这些方法,典型的具体同事类代码如下所示:

class ConcreteColleague extends Colleague {
	public ConcreteColleague(Mediator mediator) {
		super(mediator);
	}	
    //实现自身方法
	public void method1() {
		......
	}
}

  在具体同事类ConcreteColleague中实现了在抽象同事类中声明的方法,其中方法method1()是同事类的自身方法(Self-Method),用于处理自己的行为,而方法method2()是依赖方法(Depend-Method),用于调用在中介者中定义的方法,依赖中介者来完成相应的行为,例如调用另一个同事类的相关方法。

责任链模式

  责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任
击鼓传花是一种热闹而又紧张的饮酒游戏。在酒宴上宾客依次坐定位置,由一人击鼓,击鼓的地方与传花的地方是分开的,以示公正。开始击鼓时,花束就开始依次传递,鼓声一落,如果花束在某人手中,则该人就得饮酒。比如说,贾母、贾赦、贾政、贾宝玉和贾环是五个参加击鼓传花游戏的传花者,他们组成一个环链。击鼓者将花传给贾母,开始传花游戏。花由贾母传给贾赦,由贾赦传给贾政,由贾政传给贾宝玉,又贾宝玉传给贾环,由贾环传回给贾母,如此往复,如下图所示。当鼓声停止时,手中有花的人就得执行酒令。击鼓传花便是责任链模式的应用。责任链可能是一条直线、一个环链或者一个树结构的一部分
      在这里插入图片描述
责任链模式代码实现:这里以请假的流程为例,用责任链模式来实现,首先这里定义一个请假信息的对象
  

/**
 * 请假的基本信息
 */
public class LeaveRequest {
    private String empName;//请假人
    private int leaveDays;//请假天数
    private String reason;//请假理由   
    public LeaveRequest(String empName, int leaveDays, String reason) {
        super();
        this.empName = empName;
        this.leaveDays = leaveDays;
        this.reason = reason;
    }
    public String getEmpName() {
        return empName;
    }
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    public int getLeaveDays() {
        return leaveDays;
    }
    public void setLeaveDays(int leaveDays) {
        this.leaveDays = leaveDays;
    }
    public String getReason() {
        return reason;
    }
    public void setReason(String reason) {
        this.reason = reason;
    }  
}

然后定义一个抽象类,来处理各个请求之间的关系。也就是UML图中的Handler部分

/**
 * 抽象类:管理责任链上的对象处理的抽象类
 */
public abstract class Leader {
    protected String name;
    protected Leader nextLeader;//下一个继承者
    public Leader(String name) {
        super();
        this.name = name;
    }
    //设置责任链上的下一个继承者
    public void setNextLeader(Leader nextLeader) {
        this.nextLeader = nextLeader;
    }
    //处理请求的抽象方法
    public abstract void handleRequest(LeaveRequest leader);
}

接下来就可以开始定义处理请求的具体对象了,比如处理请假信息的:主任,经理,总经理等等。这些对象都必须继承抽象类,来处理请求。主任对象:处理小于等于3天的假期。经理对象:处理大于3天,小于等于10天的假期。总经理对象:处理大于等于10天,小于30天的请假信息

//主任
public class Director extends Leader {
    public Director(String name) {
        super(name);
    }
    /**
     * 责任链上对象对请求的具体处理
     */
    @Override
    public void handleRequest(LeaveRequest request) {
        if (request.getLeaveDays()<=3) {
            System.out.println("请假人:"+request.getEmpName()+",天数:"+request.getLeaveDays()+",理由:"+request.getReason());
            System.out.println("审批人:"+this.name+" 主任,审批通过!");
        }else{
            if (this.nextLeader != null ) {//如果有下一个继承者
                //让下一个继承者处理请求
                this.nextLeader.handleRequest(request);
            }
        }
    }
}
//经理
public class Manager extends Leader {
    public Manager(String name) {
        super(name);
    }
    /**
     * 责任链上对象对请求的具体处理
     */
    @Override
    public void handleRequest(LeaveRequest request) {
        if (request.getLeaveDays()<=10) {
            System.out.println("请假人:"+request.getEmpName()+",天数:"+request.getLeaveDays()+",理由:"+request.getReason());
            System.out.println("审批人:"+this.name+" 经理,审批通过!");
        }else{
            if (this.nextLeader != null ) {//如果有下一个继承者
                //让下一个继承者处理请求
                this.nextLeader.handleRequest(request);
            }
        }
    }
}
//总经理
public class GeneralManager extends Leader {
    public GeneralManager(String name) {
        super(name);
    }
    /**
     * 责任链上对象对请求的具体处理
     */
    @Override
    public void handleRequest(LeaveRequest request) {
        if (request.getLeaveDays()<=30) {
            System.out.println("请假人:"+request.getEmpName()+",天数:"+request.getLeaveDays()+",理由:"+request.getReason());
            System.out.println("审批人:"+this.name+" 总经理,审批通过!");
        }else{
//          if (this.nextLeader != null ) {//如果有下一个继承者
//              //让下一个继承者处理请求
//              this.nextLeader.handleRequest(leader);
//          }
            //总经理上面没人了,所以不往下发送请求。
            System.out.println("请假申请,最终不通过!最终审批人:"+this.name+"  总经理");
        }
    }
}

重要代码都写完了,下面开始测试:
  

public static void main(String[] args) {
    //构建各个领导人
    Leader a = new Director("张三");//主任
    Leader b = new Manager("李四");//经理
    Leader c = new GeneralManager("王五");//总经理
    //设置各个责任链上的关系
    a.setNextLeader(b);//主任的下一个审批人为经理
    b.setNextLeader(c);//经理的下一个审批人为总经理
      
    //开始请假
    LeaveRequest request = new LeaveRequest("小明", 3, "旅游");
    a.handleRequest(request);//小明提交了请假申请给主任
}

开发中常见场景:

1、Java的异常机制就是一个责任链模式,一个try可以对应多个cathc。如果某一个catch不匹配,则跳到下一个catch中

2、JavaScript语言中的事件的冒泡和捕获机制

3、Servlet开发中,过滤器的链式处理

4、Struts2中,拦截器的调用也是典型的责任链模式

责任链的好处:

​1、接受者和发送者都没有对方的明确信息,且链中的对象也并不知道链的结构,结果是责任链可简化对象的相互连接,它们仅需保持一个指向其后继者的引用,而不需要保持它所有的候选继承者,大大的降低了耦合度。请求者不用管具体哪个对象会处理,反正该请求肯定会被处理就行了
2、可以随时增加或者修改处理一个请求的结构,增加了给对象指派职责的灵活性

亨元模式(单纯享元模式和复合享元模式)

  在JAVA语言中,String类型就是使用了享元模式。String对象是final类型,对象一旦创建就不可改变。在JAVA中字符串常量都是存在常量池中的,JAVA会确保一个字符串常量在常量池中只有一个拷贝。String a=“abc”,其中"abc"就是一个字符串常量。

public class Test {
    public static void main(String[] args) {    
        String a = "abc";
        String b = "abc";
        System.out.println(a==b);       
    }
}

  上面的例子中结果为:true ,这就说明a和b两个引用都指向了常量池中的同一个字符串常量"abc"。这样的设计避免了在创建N多相同对象时所产生的不必要的大量的资源消耗。
  享元模式采用一个共享来避免大量拥有相同内容对象的开销。这种开销最常见、最直观的就是内存的损耗。享元对象能做到共享的关键是区分内蕴状态(Internal State)和外蕴状态(External State)。 一个内蕴状态是存储在享元对象内部的,并且是不会随环境的改变而有所不同。因此,一个享元可以具有内蕴状态并可以共享。一个外蕴状态是随环境的改变而改变的、不可以共享的。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态不可以影响享元对象的内蕴状态,它们是相互独立的。 享元模式可以分成单纯享元模式和复合享元模式两种形式。单纯享元模在单纯的享元模式中,所有的享元对象都是可以共享的。
  核心总结,可以共享的对象,也就是说返回的同一类型的对象其实是同一实例,当客户端要求生成一个对象时,工厂会检测是否存在此对象的实例,如果存在那么直接返回此对象实例,如果不存在就创建一个并保存起来,这点有些单例模式的意思。通常工厂类会有一个集合类型的成员变量来用以保存对象,如hashtable,vector等。在java中,数据库连接池,线程池等即是用享元模式的应用
      在这里插入图片描述
 单纯享元模式所涉及到的角色如下:

  • 抽象享元(Flyweight)角色 :给出一个抽象接口,以规定出所有具体享元角色需要实现的方法。
  • 具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定出的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。
  • 享元工厂(FlyweightFactory)角色本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象

抽象享元角色类

public interface Flyweight {
    //一个示意性方法,参数state是外蕴状态
    public void operation(String state);
}

    具体享元角色类ConcreteFlyweight有一个内蕴状态,在本例中一个Character类型的intrinsicState属性代表,它的值应当在享元对象被创建时赋予。所有的内蕴状态在对象创建之后,就不会再改变了。
  如果一个享元对象有外蕴状态的话,所有的外部状态都必须存储在客户端,在使用享元对象时,再由客户端传入享元对象。这里只有一个外蕴状态,operation()方法的参数state就是由外部传入的外蕴状态。

public class ConcreteFlyweight implements Flyweight {
    private Character intrinsicState = null;
    /**
     * 构造函数,内蕴状态作为参数传入
     * @param state
     */
    public ConcreteFlyweight(Character state){
        this.intrinsicState = state;
    }    
    /**
     * 外蕴状态作为参数传入方法中,改变方法的行为,
     * 但是并不改变对象的内蕴状态。
     */
    @Override
    public void operation(String state) {
        // TODO Auto-generated method stub
        System.out.println("Intrinsic State = " + this.intrinsicState);
        System.out.println("Extrinsic State = " + state);
    }
}

  享元工厂角色类,必须指出的是,客户端不可以直接将具体享元类实例化,而必须通过一个工厂对象,利用一个factory()方法得到享元对象。一般而言,享元工厂对象在整个系统中只有一个,因此也可以使用单例模式
  当客户端需要单纯享元对象的时候,需要调用享元工厂的factory()方法,并传入所需的单纯享元对象的内蕴状态,由工厂方法产生所需要的享元对象。

public class FlyweightFactory {
    private Map<Character,Flyweight> files = new HashMap<Character,Flyweight>();    
    public Flyweight factory(Character state){
        //先从缓存中查找对象
        Flyweight fly = files.get(state);
        if(fly == null){
            //如果对象不存在则创建一个新的Flyweight对象
            fly = new ConcreteFlyweight(state);
            //把这个新的Flyweight对象添加到缓存中
            files.put(state, fly);
        }
        return fly;
    }
}

客户端类:

public class Client {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        FlyweightFactory factory = new FlyweightFactory();
        Flyweight fly = factory.factory(new Character('a'));
        fly.operation("First Call");
        
        fly = factory.factory(new Character('b'));
        fly.operation("Second Call");
        
        fly = factory.factory(new Character('a'));
        fly.operation("Third Call");
    }
}

  虽然客户端申请了三个享元对象,但是实际创建的享元对象只有两个,这就是共享的含义。运行结果如下:
                    在这里插入图片描述

解释器模式

待补充。

组合模式

待补充。

访问者模式

待补充。

猜你喜欢

转载自blog.csdn.net/weixin_41262453/article/details/88654373