23种设计模式(下)

目录

    • 十一、享元模式(Flyweight)
    • 十二、门面模式(Façade)
    • 十三、代理模式(Proxy)
    • 十四、适配器模式(Adapter)
    • 十五、中介者模式(Mediator)
    • 十六、状态模式(State)
    • 十七、备忘录模式(Memento)
    • 十八、组合模式(Composite)
    • 十九、迭代器模式(Iterator)
    • 二十、职责链模式(Chain of Resposibility)
    • 二十一、命令模式(Command)
    • 二十二、访问者模式(Visteor)
    • 二十三、解析器模式(Interpreter)
    • 什么时候不用模式

十一、享元模式(Flyweight)

动机(Motivation)

  • 在软件系统采用纯粹对象方案的问题在于大量细粒度的对象会很快充斥在系统中,从而带来很高的运行时代价——主要指内存需求方面的代价。
  • 如何在避免大量细粒度对象问题的同时,让外部客户程序仍然能够透明地使用面向对象的方式来进行操作?

模式定义

  • 运行共享技术有效地支持大量细粒度的对象。

要点总结

  • 面向对象很好地解决了抽象性的问题,但是作为运行机器中的程序实体,我们需要考虑对象的代价问题,Flyweight主要解决面向对象的代价问题,一般不触及面向对象的抽象性问题。
  • Flyweight采用对象共享的做法来降低系统中对象的个数,从而降低细粒度对象给系统带来的压力。如果对象被共享,我们就要保证对象的变化不会对其他使用者产生影响,所以在具体实现方面,要注意对象状态的处理,一般设为只读。如果系统中存在大量可以被共享的对象,可以考虑使用享元模式。
  • 对象的数量太大从而导致对象内存开销加大——什么样的数量才算大?这需要我们仔细的根据具体应用情况进行评估,而不能凭空臆断。

结构图

23种设计模式(下)_第1张图片
代码示例

//享元接口
interface IFlyweight{
	void doSomething();
}
//具体享元
class Flyweight implements IFlyweight{
	private String value;
	
	public Flyweight(String value){
		this.value = value;
	}
	@Override
	public void doSomething() {
		System.out.println(value);
	}
}
//享元工厂
class FlyweightFactory{
	HashMap<String, IFlyweight> flyweights = new HashMap<String, IFlyweight>();
	IFlyweight getFlyweight(String value){
		IFlyweight flyweight = flyweights.get(value);
		if(flyweight == null){
			flyweight = new Flyweight(value);
			flyweights.put(value, flyweight);
		}
		return flyweight;
	}
	public int size(){
		return flyweights.size();
	}
}
  • 测试主类
//简单的享元模式
public class SimpleFlyweight {
	public static void main(String args[]){
		FlyweightFactory factory = new FlyweightFactory();
		IFlyweight flyweight1,flyweight2,flyweight3,flyweight4;
		flyweight1 = factory.getFlyweight("value1");
		flyweight2 = factory.getFlyweight("value1");
		flyweight3 = factory.getFlyweight("value1");
		flyweight4 = factory.getFlyweight("value2");
		flyweight1.doSomething();
		flyweight2.doSomething();
		flyweight3.doSomething();
		flyweight4.doSomething();
		System.out.println(factory.size());
	}
}

十二、门面模式(Façade)

动机(Motivation)

  • 客户和组件中各种复杂的子系统有过多的耦合,但是子系统可能一直在变化。
  • 如何简化外部客户程序和系统间的交互接口?如何解耦?

模式定义

  • 为子系统中的一组接口提供一个一致(稳定)的界面,Façade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。

要点总结

  • 从客户程序角度来看,Façade模式简化了整个组件系统的接口,对于组件内部与外部的客户程序来说, 达到了一种”解耦“的效果——内部子系统的任何变化不会影响到Façade接口的变化。这种设计没有一种具体的代码结构,但是从一定高度的抽象上看满足这样一种模式。其实这有点像设计一个函数,参数是不变的,但是具体如何实现却各有所差。
  • Façade设计模式更注重架构的层次去看整个系统,而不是单个类的层次。Façade很多时候是一种架构设计模式。
  • Façade设计模式并非一个集装箱,可以任意地放进任何多个对象。Façade模式组件中的内部应该是”相互耦合关系比较大的一系列组件“,而不是一个简单的功能集合。

结构图
23种设计模式(下)_第2张图片
代码示例

示例代码很简单,这里不做过多说明,其中ProductSalesman 类就是门面模式中的门面(Facade)

  • 子系统内容
//计算优惠
public class Discount {
	int getDiscount(String discountCode){
		return Math.abs(discountCode.hashCode())%3;
	}
}
//计费子系统
public class FinalPrice {
	ProductPrice productPrice;
	Postage postage;
	Discount discount;
	public FinalPrice(){
		productPrice = new ProductPrice();
		postage = new Postage();
		discount = new Discount();
	}
	int getFinalPrice(String product,String addr,String discountCode){
		return productPrice.getPrice(product)+postage.getPostage(addr)-discount.getDiscount(discountCode);
	}
}
//计算邮费
public class Postage {
	int getPostage(String addr){
		return Math.abs(addr.hashCode())%20+6;//模拟邮费计算
	}
}
//获取商品价格
public class ProductPrice {
	int getPrice(String product){
		return Math.abs(product.hashCode());//模拟获取商品价格
	}
}
import java.util.Random;
//库存子系统
public class Stock {
	boolean hasStock(String product){
		return new Random().nextInt(Math.abs(product.hashCode()))>0;//模拟是否还有库存
	}
}
  • 门面
//外观
public class ProductSalesman {
	instance;
	Stock stock = new Stock();
	FinalPrice finalPrice = new FinalPrice();
	Object buySomething(String product,String addr,String discountCode){
		if(!stock.hasStock(product))
			return "库存不足";
		int price = finalPrice.getFinalPrice(product, addr, discountCode);
		return "订单信息:" + product + "-" + addr + "-"  + discountCode + "-"  + price;
	}
}
  • 测试主类
public class TestUse {
	public static void main(String args[]){
		Object info = ProductSalesman.instance.buySomething("银河飞船", "地球", "K1234523");
		System.out.println(info);
	}
}

十三、代理模式(Proxy)

动机(Motivation)

  • 在面向对象系统中,有些对象由于某种原因(比如对象创建的开销很大,或者某些操作需要安全控制,或者需要进程外的访问等), 直接访问会给使用者、或者系统结构带来很多麻烦。
  • 如何在不失去透明操作对象的同事来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方式。

模式定义

  • 为其他对象提供一种代理以控制(隔离,使用接口)对这对象的访问。

要点总结

  • Proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。

结构图
23种设计模式(下)_第3张图片
代码示例

代理模式是我们非常熟悉的一种模式,代码实现也非常简单。被代理对象和代理对象采用同一个接口。

  • 被代理的对象
//抽象对象
public interface AbstractObject {
	void method1();
	int method2();
	void method3();
}
//具体对象
public class TargetObject implements AbstractObject {
	@Override
	public void method1() {
		System.out.println("具体对象的方法1");
	}
	@Override
	public int method2() {
		System.out.println("具体对象的方法2");
		return 0;
	}
	@Override
	public void method3() {
		System.out.println("具体对象的方法3");
	}
}
  • 代理对象
//代理对象
public class ProxyObject implements AbstractObject {
	AbstractObject object = new TargetObject();
	@Override
	public void method1() {
		object.method1();
	}
	@Override
	public int method2() {
		return object.method2();
	}
	@Override
	public void method3() {
		System.out.println("调用目标对象前的操作");
		object.method3();
		System.out.println("调用目标对象后的操作");
	}
}
  • 测试主类
public class TestUse {
	public static void main(String args[]){
		AbstractObject obj = new ProxyObject();
        obj.method1();
        obj.method2();
        obj.method3();
	}
}

十四、适配器模式(Adapter)

动机(Motivation)

  • 由于应用环境的变化,常常需要将”一些现存的对象“放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足。
    如何应对这些”迁移的变化“?

模式定义

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

要点总结

  • 在遗留代码复用、类库迁移等方面有用,不过从中可以看出可以使用适配器的前提是代码功能确实是可以复用的。

结构图
Target是一端,Adaptee是一端,他们通过Adapter适配起来。
23种设计模式(下)_第4张图片
代码示例

我们希望将原来的Adaptee转换为适配Target接口,这种适配功能的完成可以有两种实现方式:类适配器和对象适配器。但是我们更加推荐使用对象适配器,因为这样会比较灵活一点,我可以给对象适配器传入任何满足接口的类,而类适配器就必须和一个具体的类绑定,实际上类适配器也很少被使用。

  • 被适配者和目标接口
//老的接口
interface IAdaptee{
	void playMp3(Object src);
}
//目标,也就是用户所希望使用的
interface Target{
	void playFlac(Object src);
}
//被适配者
class Adaptee implements IAdaptee{
	void playMp3(Object src){
		System.out.println("播放MP3:" + src);
	}
}
  • 两种适配器实现的适配方式
//类适配器
public class ClassAdapter extends Adaptee implements Target {
	@Override
	public void playFlac(Object src) {
		//可能需要对src作处理
		playMp3(src);
	}
}
//对象适配器
public class ObjectAdapter implements Target{
	private IAdaptee adaptee;
	public ObjectAdapter(IAdaptee adaptee){
		this.adaptee = adaptee;
	}
	@Override
	public void playFlac(Object src) {
		//可能需要对src作处理
		adaptee.playMp3(src);
	}
}
  • 测试主类
public class TestUse {
	public static void main(String args[]){
		Adaptee adaptee = new Adaptee();
		adaptee.playMp3("mp3");
		Target target = new ClassAdapter();
		target.playFlac("flac");
		target = new ObjectAdapter(new Adaptee());
		target.playFlac("flac");
	}
}

十五、中介者模式(Mediator)

动机(Motivation)

  • 多个对象相互关联的情况,对象之间常常会维持一种复杂的引用关系,如果遇到一些需求的更改,这种直接的引用关系将面临不断的变化。
  • 在这种情况下,可以使用一种”中介对象“来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。

模式定义

  • 用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各对象不需要显式的相互引用(编译时依赖->运行时依赖), 从而使其耦合松散(管理变化),并且可以独立地改变它们之间的交互。

要点总结

  • 将多个对象间复杂的关联关系解耦
  • Facade模式是解耦系统间(单向)的对象关联关系;Mediator模式是解耦系统内各个对象之间(双向)的关联关系。

结构图
23种设计模式(下)_第5张图片
23种设计模式(下)_第6张图片
代码示例
在代码中无论是PersistentDB还是PersistentFilevoid getData(Object data,Midiator midiator);操作触发后,都会触发另一个的void getData(Object data);的发生,如果不希望触发另一个的发生可以直接使用void getData(Object data);如果没有中介者,可能我就需要在类中直接使用另一个对象。

  • 中介者
//具体中介者
public class Midiator {
	PersistentDB persistentDB;
	PersistentFile persistentFile;
	public Midiator setPersistentDB(PersistentDB persistentDB) {
		this.persistentDB = persistentDB;
		return this;
	}
	public Midiator setPersistentFile(PersistentFile persistentFile) {
		this.persistentFile = persistentFile;
		return this;
	}
	public void notifyOther(IPersistent persistent,Object data){
		if(persistent instanceof PersistentDB)
			persistentFile.getData(data);
		if(persistent instanceof PersistentFile)
			persistentDB.getData(data);
	}
}
  • 相互关联的对象
//同事(接口)
public interface IPersistent {
	void getData(Object data);
	void getData(Object data,Midiator midiator);
	void saveData();
}
//具体同事
public class PersistentDB implements IPersistent{
	private Object data;
	@Override
	public void getData(Object data, Midiator midiator) {
		getData(data);
		midiator.notifyOther(this, data);
	}
	@Override
	public void saveData() {
		System.out.println(data + " 已保存到数据库");
	}
	@Override
	public void getData(Object data) {
		this.data = data;
		saveData();
	}
}
//具体同事
public class PersistentFile implements IPersistent{
	private Object data;
	@Override
	public void getData(Object data, Midiator midiator) {
		getData(data);
		midiator.notifyOther(this, data);
	}
	@Override
	public void saveData() {
		System.out.println(data + " 已保存到文件");
	}
	@Override
	public void getData(Object data) {
		this.data = data;
		saveData();
	}
}
  • 测试主类
public class TestUse {
	public static void main(String args[]){
		Object data = "数据";
		PersistentDB persistentDB = new PersistentDB();
		PersistentFile persistentFile = new PersistentFile();
		Midiator midiator = new Midiator();
		midiator.setPersistentDB(persistentDB).setPersistentFile(persistentFile);
		persistentDB.getData(data, midiator);
		persistentFile.getData(data, midiator);
	}
}

十六、状态模式(State)

动机(Motivation)

  • 对象状态如果改变,其行为也会随之而发生变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。
  • 如何在运行时根据对象的状态来透明地改变对象的行为?

模式定义

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

要点总结

  • State模式将所有与一个特定状态相关的行为都放入一个State的子对象中,在对象状态切换时,切换相应的对象; 但同时维持State的接口,这样实现了具体操作与状态转换之间的解耦。
  • 转换是原子性的
  • 与Strategy模式(策略模式)类似。

结构图
23种设计模式(下)_第7张图片
代码示例

在代码中我们根据需要存储的不同数据的大小来确定使用哪种保存方式,但是对外暴露的保存数据接口(saveDataController.save())是一样的。

  • 不同状态的处理函数
//抽象状态
public interface ISaveData {
	void save(Object data);
}
//具体状态
public enum SaveBigData implements ISaveData{
	instance;
	@Override
	public void save(Object data) {
		System.out.println("保存到文件:" + data);
	}
}
//具体状态
public enum SaveMiddleData implements ISaveData{
	instance;
	@Override
	public void save(Object data) {
		System.out.println("保存到Mysql:" + data);
	}
}
//具体状态
public enum SaveSmallData implements ISaveData{
	instance;
	@Override
	public void save(Object data) {
		System.out.println("保存到Redis:" + data);
	}
}
  • 状态控制环境(对用户暴露的使用接口)。
//环境(Context)
public class SaveDataController {
	private ISaveData saveData;
	public void save(String data){
		//为了演示,此处的大的数据其实也是很小的
		if(data.length()<1<<2)
			saveData = SaveSmallData.instance;
		else if(data.length()<1<<4)
			saveData = SaveMiddleData.instance;
		else
			saveData = SaveBigData.instance;
		saveData.save(data);
	}
}
  • 测试主类
public class TestUse {
	public static void main(String args[]){
		String smallData = "小数据";
		String middleData = "介于小数据和大数据之间的数据";
		String bigData = "这里就假定这是一个很大很大很大的数据";
		SaveDataController saveDataController = new SaveDataController();
		saveDataController.save(smallData);
		saveDataController.save(middleData);
		saveDataController.save(bigData);
	}
}

十七、备忘录模式(Memento)

动机(Motivation)

  • 某些对象的状态转换过程中,可能由于某中需要,要求程序能够回溯到对象之前处于某个点的状态。 如果使用一些公开接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
  • 如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性、

模式定义

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

要点总结

  • 备忘录存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。
  • 有些过时

结构图
23种设计模式(下)_第8张图片

代码示例

备忘录模式使用三个类 Memento、Originator 和 CareTaker。Memento 包含了要被恢复的对象的状态。Originator 创建并在 Memento 对象中存储状态。Caretaker 对象负责从 Memento 中恢复对象的状态。在这里我们将对象的状态简化为了只有一个字符串来描述。

public class Memento {
   private String state;

   public Memento(String state){
      this.state = state;
   }

   public String getState(){
      return state;
   }
}
public class Originator {
   private String state;

   public void setState(String state){
      this.state = state;
   }

   public String getState(){
      return state;
   }

   public Memento saveStateToMemento(){
      return new Memento(state);
   }

   public void getStateFromMemento(Memento Memento){
      state = Memento.getState();
   }
}
import java.util.ArrayList;
import java.util.List;

public class CareTaker {
   private List<Memento> mementoList = new ArrayList<Memento>();

   public void add(Memento state){
      mementoList.add(state);
   }

   public Memento get(int index){
      return mementoList.get(index);
   }
}
public class MementoPatternDemo {
   public static void main(String[] args) {
      Originator originator = new Originator();
      CareTaker careTaker = new CareTaker();
      originator.setState("State #1");
      originator.setState("State #2");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #3");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #4");

      System.out.println("Current State: " + originator.getState());    
      originator.getStateFromMemento(careTaker.get(0));
      System.out.println("First saved State: " + originator.getState());
      originator.getStateFromMemento(careTaker.get(1));
      System.out.println("Second saved State: " + originator.getState());
   }
}

十八、组合模式(Composite)

动机(Motivation)

  • 客户代码过多地依赖于对象容器复杂的内部实现结构,对象容器内部实现结构(而非抽象结构)的变化 引起客户代码的频繁变化,带来了代码的维护性、扩展性等弊端。
  • 如何将”客户代码与复杂的对象容器结构“解耦?让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器?

模式定义

  • 将对象组合成树形结构以表示”部分-整体“的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性(稳定)。

要点总结

  • Composite模式采用树性结构来实现普遍存在的对象容器,从而将”一对多“的关系转化为”一对一“的关系,使得客户代码可以一致地(复用)处理对象和对象容器, 无需关心处理的是单个的对象,还是组合的对象容器。
  • 客户代码与纯粹的抽象接口——而非对象容器的内部实现结构——发生依赖,从而更能”应对变化“。
  • Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技术来改善效率。

结构图
23种设计模式(下)_第9张图片

代码示例

代码示例是非常典型的目录和文件这种结构和整体的组合关系,文件和目录都实现了Component ,从而方便用户使用,而不必区分具体的类型。

import java.util.Iterator;
import java.util.List;
//抽象组件
public interface Component {
	void addFile(Component file);
	Component addFolder(Component folder);
	void removeFile(Component file);
	void removeFolder(Component folder);
	List<Component> getFiles();
	List<Component> getFolders();
	List<Component> getAll();
	Iterator<Component> iterator();
	void display();
}
import java.util.Iterator;
import java.util.List;
//Leaf节点
public class File implements Component{
	private String name;
	public File(String name){
		this.name = name;
	}
	@Override
	public void addFile(Component file) {}
	@Override
	public Component addFolder(Component folder) { return null; }
	@Override
	public void removeFile(Component file) {}
	@Override
	public void removeFolder(Component folder) {}
	@Override
	public List<Component> getFiles() { return null; }
	@Override
	public List<Component> getFolders() { return null; }
	@Override
	public List<Component> getAll() { return null; }
	@Override
	public Iterator<Component> iterator() { return null; }
	@Override
	public void display() {
		System.out.println(name);
	}
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Folder implements Component {
	private String name;
	private List<Component> files;
	private List<Component> folders;
	public Folder(String name){
		this.name = name;
		files = new ArrayList<Component>();
		folders = new ArrayList<Component>();
	}
	@Override
	public void addFile(Component file) {
		files.add(file);
	}
	@Override
	public Component addFolder(Component folder) {
		folders.add(folder);
		return this;
	}
	@Override
	public void removeFile(Component file) {
		files.remove(file);
	}
	@Override
	public void removeFolder(Component folder) {
		folders.remove(folder);
	}
	@Override
	public List<Component> getFiles() {
		return files;
	}
	@Override
	public List<Component> getFolders() {
		return folders;
	}
	@Override
	public List<Component> getAll() {
		List<Component> all = new ArrayList<Component>(folders);
		all.addAll(files);
		return all;
	}
	@Override
	public Iterator<Component> iterator() {
		List<Component> all = new ArrayList<Component>();
		add(all,this);
		return all.iterator();
	}
	private void add(List<Component> all,Component component){
		if(component==null) return;
		all.add(component);
		Iterator<Component> iterator = component.getFolders().iterator();
		while(iterator.hasNext()){
			add(all,iterator.next());
		}
		all.addAll(component.getFiles());
	}
	@Override
	public void display() {
		System.out.println(name);
	}
}
import java.util.Iterator;
public class TestUse {
	public static void main(String args[]){
		Component root = new Folder("root");//根目录
		Component folder1 = new Folder("java");
		Component folder2 = new Folder("c++");
		Component folder3 = new Folder("c#");
		Component file1 = new File("info.txt");
		root.addFolder(folder1).addFolder(folder2).addFolder(folder3).addFile(file1);//添加一级目录
		folder1.addFile(new File("info.java"));
		Iterator<Component> iterator = root.iterator();
		while(iterator.hasNext()){
			Component component = iterator.next();
			if(component instanceof Folder)
				System.out.print("folder:");
			else
				System.out.print("file:");
			component.display();
		}
	}
}

十九、迭代器模式(Iterator)

动机(Motivation)

  • 集合对象内部结构常常变化异常。但对于这些集合对象,我们希望不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素; 同时这种”透明遍历“也为”同一种算法在多种集合对象上进行操作“提供了可能。
  • 使用面向对象技术将这种遍历机制抽象为”迭代器对象“为”应对变化中的集合对象“提供了一种优雅的方式。

模式定义

  • 提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露(稳定)该对象的内部表示。

要点总结

  • 迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。
  • 迭代多态:为遍历不同的集合对象提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
  • 对C++来说是过时的,现在迭代器用模板,面向对象的方式性能低。

结构图
23种设计模式(下)_第10张图片
示例代码
在java的集合中,很多实现了迭代器模式。

二十、职责链模式(Chain of Resposibility)

动机(Motivation)

  • 一个请求可能被多个对象处理,但是每个请求在运行时只能有一个接收者,如果显式指定,将必不可少地带来请求发送者与接收者的紧耦合。
  • 如何使请求的发送者不需要指定具体的接收者?让请求的接收者自己在运行时决定来处理请求,从而使两者解耦。

模式定义

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

要点总结

  • 应用于”一个请求可能有多个接受者,但是最后真正的接受者只有一个“,这时候请求发送者与接受者有可能出现”变化脆弱“的症状,职责链解耦。
  • 有些过时。

结构图
23种设计模式(下)_第11张图片

示例代码

代码中Handler1Handler2Handler3组成一种职责链,我们将所有处理都给Handler1 如果它无法处理就发送给下一个链中的处理者。在实际应用的时候我们需要写一个缺省的处理方式。

//处理者
public interface IHandler {
	int handleRequest(int n);
	void setNextHandler(Handler next);
}
//第一个具体处理者,处理小于0的
public class Handler1 implements Handler {
	private Handler next;
	@Override
	public int handleRequest(int n) {
		if(n<0) return -n;
		else{
			if(next==null)
				throw new NullPointerException("next 不能为空");
			return next.handleRequest(n);
		}
	}
	@Override
	public void setNextHandler(Handler next) {
		this.next = next;
	}
}
//第二个具体处理者,处理>=0但小于10的
public class Handler2 implements Handler {
	private Handler next;
	@Override
	public int handleRequest(int n) {
		if(n<10) return n*n;
		else{
			if(next==null)
				throw new NullPointerException("next 不能为空");
			return next.handleRequest(n);
		}
	}
	@Override
	public void setNextHandler(Handler next) {
		this.next = next;
	}
}
//第三个具体处理者,处理>=0但小于10的
public class Handler3 implements Handler {
	private Handler next;
	@Override
	public int handleRequest(int n) {
		if(n<=Integer.MAX_VALUE) return n;
		else{
			if(next==null)
				throw new NullPointerException("next 不能为空");
			return next.handleRequest(n);
		}
	}
	@Override
	public void setNextHandler(Handler next) {
		this.next = next;
	}
}
  • 测试主类
public class TestUse {
	public static void main(String args[]){
		Handler h1,h2,h3;
		h1 = new Handler1();
		h2 = new Handler2();
		h3 = new Handler3();
		h1.setNextHandler(h2);
		h2.setNextHandler(h3);
		System.out.println(h1.handleRequest(-1));
		System.out.println(h1.handleRequest(5));
		System.out.println(h1.handleRequest(9999));
	}
}

二十一、命令模式(Command)

动机(Motivation)

  • ”行为请求者“与”行为实现者“通常呈现一种”紧耦合“。但在某些场合——比如需要对行为进行”记录、撤销、事务“等处理,这种无法抵御变化的紧耦合是不合适的。
  • 在这种情况下,如何将”行为请求者“与”行为实现者“解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。

模式定义

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

要点总结

  • Command模式的根本目的在于将”行为请求者“与”行为实现者“解耦,在面向对象语言中,常见的实现手段是”将行为抽象为对象“。
  • 与C++中的函数对象类似,C++函数对象以函数签名来定义行为接口规范,更灵活性能更高。

结构图
23种设计模式(下)_第12张图片
代码示例

代码中我们将行为的实现变为了类,通过不同命令作为中介,行为请求者和这些中介打交道,就可以执行相应的行为,并且行为实现者可以变动。

  • 不同的命令接口
//命令接口
public interface Command {
	void execute(String name) throws Exception;
}
//新建文件命令
public class CommandCreate implements Command {
	MakeFile makeFile;
	public CommandCreate(MakeFile makeFile) {
		this.makeFile = makeFile;
	}
	@Override
	public void execute(String name) throws Exception {
		makeFile.createFile(name);
	}
}
//删文件命令
public class CommandDelete implements Command{
	MakeFile makeFile;
	public CommandDelete(MakeFile makeFile) {
		this.makeFile = makeFile;
	}
	@Override
	public void execute(String name) throws Exception {
		makeFile.deleteFile(name);
	}
}
  • 行为请求者
//请求者
public class Client {
	Command command;
	public Client setCommand(Command command){
		this.command = command;
		return this;
	}
	public void executeCommand(String name) throws Exception{
		if(command==null)
			throw new Exception("命令不能为空!");
		command.execute(name);
	}
}
  • 行为实现者
import java.io.File;
import java.io.IOException;
//接收者
public class MakeFile {
	//新建文件
	public void createFile(String name) throws IOException{
		File file = new File(name);
		file.createNewFile();
	}
	//删除文件
	public boolean deleteFile(String name){
		File file = new File(name);
		if(file.exists()&&file.isFile()){
			file.delete();
			return true;
		}
		return false;
	}
}
  • 测试主类
public class TestUse {
	public static void main(String args[]) throws Exception{
		//接收者
		MakeFile makeFile = new MakeFile();
		//命令
		CommandCreate create = new CommandCreate(makeFile);
		CommandDelete delete = new CommandDelete(makeFile);
		//请求者
		Client client = new Client();
		//执行命令
		client.setCommand(create).executeCommand("d://test1.txt");
		client.setCommand(create).executeCommand("d://test2.txt");
		client.setCommand(delete).executeCommand("d://test2.txt");
	}
}

二十二、访问者模式(Visteor)

动机(Motivation)

  • 由于需求的变化,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改, 将会给子类带来很繁重的变更负担,甚至破坏原有设计。
  • 如何在不更改类层次结构的前提下,在运行时根据需要透明地为类层次结构上的各个类动态添加新的操作,从而避免上面的问题?

模式定义

  • 表示一个作用与某对象结构中的各元素的操作。使得可以在不改变(稳定)各元素的类的前提下定义(扩展) 作用于这些元素的新操作(变化)。

要点总结

  • Visitor模式通过所谓的双重分发来实现在不更改(编译时)Element类层次结构的前提下,在运行时 透明地为类层次结构上的各个类动态添加新的操作(支持变化)。
  • 所谓双重分发Visitor模式中间包括了两个多态分发:第一个为accept方法的多态辨析;第二个为visitElement方法的多态辨析。
  • Visitor模式的最大缺点在于扩展类层次结构(添加新的Element子类),会导致Visitor类的改变, 因此Visitor模式适用于“Element类层次结构稳定,而其中的操作却经常面临频繁改动”。
  • 由于要求太过严格(需要知道Element的所有子类),我们很少使用。

结构图

23种设计模式(下)_第13张图片

示例代码

在代码中原来的代码已经固定下来了,但是固定的代码中public void accept(Visitor visitor)预示了将来可能有扩展的操作。之后Visitor 接口及其子类实现了方法的扩展,如果要使用这些扩展的代码的话,我们需要调用public void accept(Visitor visitor)这个方法。不过这里只有一个Visitor 的子类,也就是扩展了一个功能,如果需要扩展其他功能的话,写这个接口的其他子类并替换public void accept(Visitor visitor)的参数即可。

  • 已经固定的部分,其中public void accept(Visitor visitor)预示了将来可能有扩展的操作。
//抽象元素
public interface User {
	void accept(Visitor visitor);
}
//普通用户,具体元素
public class UserOrdinary implements User{
	String estimation;
	public UserOrdinary(String estimation){
		this.estimation = estimation;
	}
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
	String getEstimation(){
		return estimation;
	}
}
//VIP用户,具体元素
public class UserVIP implements User{
	String estimation;
	public UserVIP(String estimation){
		this.estimation = estimation;
	}
	@Override
	public void accept(Visitor visitor) {
		visitor.visit(this);
	}
	String getEstimation(){
		return estimation;
	}
}
  • 需要为子类新添加的方法,我们需要知道有多少子类,为每个子类都写一个visit()方法
//抽象访问者
public interface Visitor {
	void visit(UserVIP user);
	void visit(UserOrdinary user);
}
//具体访问者
public class APPOwner implements Visitor{
	@Override
	public void visit(UserVIP user) {
		String estimation = user.getEstimation();
		if(estimation.length()>5)
			System.out.println("记录一条有效反馈:" + estimation);
	}
	
	@Override
	public void visit(UserOrdinary user) {
		String estimation = user.getEstimation();
		if(estimation.length()>10)
			System.out.println("记录一条有效反馈:" + estimation);
	}
}
  • 测试主类
public class TestUse {
	public static void main(String args[]){
		Visitor appOwner = new APPOwner();
		ArrayList<User> users = new ArrayList<User>();
		users.add(new UserOrdinary("普通用户短反馈"));
		users.add(new UserOrdinary("这是一个普通用户的比较长的反馈"));
		users.add(new UserVIP("VIP用户的短反馈"));
		users.add(new UserVIP("VIP用户的比较长的反馈反馈"));
		Iterator<User> iterator =  users.iterator();
		while(iterator.hasNext()){
			iterator.next().accept(appOwner);
		}
	}
}

二十三、解析器模式(Interpreter)

动机(Motivation)

  • 如果某一特定领域的问题比较复杂,类似的结构不断重复出现,如果使用普通的编程方式来实现将面临非常频繁的变化。
  • 在这种情况下,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。
  • 在特定领域内,某些变化虽然频繁,但可以抽象为某种规则。这时候,结合特定领域,将问题抽象为语法规则,从而给出该领域下的一般性解决方案。

模式定义

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

要点总结

  • Interpreter模式的应用场合是Interpreter模式应用中的难点,只有满足“业务规则频繁变化,且类似的结构不断重复出现, 并且容易抽象为语法规则的问题”才适合使用Interpreter模式。
  • 使用Interpreter模式来表示文法规则,从而可以使用面向对象技巧来方便地“扩展”文法。
  • Interpreter模式适合简单的文法表示,对于复杂的文法表示需要求助语法分析器标准工具。

结构图
23种设计模式(下)_第14张图片
示例代码

这是一种不太常用的设计模式,这里不给出示例代码。

什么时候不用模式

学习设计模式之后并不是一定需要使用设计模式,比如以下的一些情况,不要盲目的追求模式的使用,更多的是在实际的项目中体会。

  • 代码可读性很差时
  • 需求理解还很浅时
  • 变化没有显现时
  • 不是系统的关键依赖点
  • 项目没有复用价值时
  • 项目将要发布时

你可能感兴趣的:(23种设计模式(下))