软件构造心得3

  • 创建模式
  • factory method 工厂方法
  • abstract factory 抽象工厂
  • 结构模式
  • Adapter 适配器模式
  • Decorator 装饰器模式
  • Facade 外观模式
  • proxy 代理模式
  • 行为模式
  • Strategy 策略模式
  • Template method 模板方法模式
  • Iterator 迭代器模式
  • Observer/Observable 观察者模式
  • visitor 访问者模式
  • State 状态模式
  • Memento 备忘录模式


在软件构造这门课中,我们学习了6种面向复用的设计模式,和7种面向可维护性的设计模式,可谓是功能繁多类型繁杂,如果不自己手敲一遍代码,估计很难记住,因此我在复习的过程中大致分类整理了一下,放在一起写出来。

这边有两种分类方式

一、按照其功能性划分,可分为——创建、结构、行为

二、按照其继承树划分,可分为两种共性样式

共性样式1:

软件设计模式简明教程java版电子教材下载_System

共性样式2:

软件设计模式简明教程java版电子教材下载_System_02

创建模式

factory method 工厂方法

工厂方法可套用第二种共性模式

软件设计模式简明教程java版电子教材下载_客户端_03

但实现起来很麻烦,我们通常都是用静态工厂方法。
拿实验3举例(PlaneEntry是类PlaneEntry的构造方法)

public interface PlanningEntry
{
	public static PlaneEntry creatPlanePlan(location start, location end, Timeslot timeslot, String planName)
	{
		return new PlaneEntry(start, end, timeslot, planName);
	}	
}

客户端无需创建对象,直接调用如下代码即可

PlaneEntry.creatPlanePlan(start, end, timeslot, planName);

abstract factory 抽象工厂

抽象工厂方法也可套用共性模板2,它的本质就是把多个工厂的方法复合在一起。

软件设计模式简明教程java版电子教材下载_客户端_04

同样拿实验3举例

public interface PlanningEntry
{
	public static PlaneEntry creatPlanePlan(location start, location end, Timeslot timeslot, String planName)
	{
		return new PlaneEntry(start, end, timeslot, planName);
	}
	public static TrainEntry creatTrainPlan(List<location> locationList, List<Timeslot> timeslotList, String planNumber, String strLocation, String strTime)
	{
		return new TrainEntry(locationList, timeslotList, planNumber, strLocation, strTime);
	}
	public static ActEntry creatActPlan(location loc, Timeslot timeslot, String planNumber)
	{
		return new ActEntry(loc, timeslot, planNumber);
	}
}

把三个工厂方法放在了一起。

结构模式

Adapter 适配器模式

适配器模式可套用到第一个共性模式里

软件设计模式简明教程java版电子教材下载_System_05

适配器模式用于使用某个现有的类,但此类的接口不符合系统的需要,因此在二者之间加入接口,而适配器作为具体实现类来实现接口,通过调用被适配的类的方法来复用功能,客户端则面向接口编程。

我们有个旧的实现类,计算俩参数之和

public class Old
{
	public int plus(int a, int b)
	{
		return a + b;
	}
}

客户端想要得到a + b和的平方,但是输入参数不是a和b而是a和c,其中 c = b - a
设计一个抽象的接口

public interface New
{
	public int mul(int a, int c);
}

然后具体实现接口,完成了复用旧的方法

public class NewAdapter implements New
{
	@Override
	public int mul(int a, int c)
	{
		int sum = new Old().plus(a, a + c);
		return sum * sum;
	}
}

客户端调用

public class Client
{
	public static void main(String[] args)
	{
		NewAdapter newAdapter = new NewAdapter();
		System.out.print(newAdapter.mul(1, 2));//输出(1+(1+2))^2=16
	}
}

Decorator 装饰器模式

装饰器模式用于向一个现有的对象添加新的功能,同时又不改变其结构。添加不同的新功能可以写在不同的子类里,进行任意组合。

首先对于基本功能,有一个接口A,和一个具体实现类B来实现这个接口,之后装饰器C作为抽象类同样继承这个接口A,并把基本功能委派给B实现,然后需要拓展的各种功能,可以分别写在继承于装饰器C的子类D、E、F里。

以下图为例,IStack是A,Stack是B,StackDecorator是C,LockedStack、UndoStack、SecureStack对应D、E、F

软件设计模式简明教程java版电子教材下载_ide_06

首先对于基本功能,有一个抽象接口MyCompute,里面有共性方法plus用于计算a+b的和

public interface MyCompute
{
	public int plus(int a, int b);
}

然后有一个具体实现类实现它的基本功能

public class MyComputeImp implements MyCompute
{
	@Override
	public int plus(int a, int b)
	{
		return a + b;
	}
}

然后是装饰器作为抽象类继承接口MyCompute(注意mycompute用protected final修饰)

public abstract class ComputeDecorator implements MyCompute
{
	protected final MyCompute mycompute;
	public ComputeDecorator(MyCompute myCompute)
	{
		this.mycompute = myCompute;
	}
	@Override
	public int plus(int a, int b)
	{
		return mycompute.plus(a, b);
	}
}

接着我想拓展两种新功能,例如一种功能打印a,另一种功能打印b

public class MyComputePrintA extends ComputeDecorator implements MyCompute
{
	public MyComputePrintA(MyCompute myCompute)
	{
		super(myCompute);
	}
	@Override
	public int plus(int a, int b)
	{
		System.out.println("print a: " + a);
		return super.plus(a, b);
	}
}
public class MyComputePrintB extends ComputeDecorator implements MyCompute
{
	public MyComputePrintB(MyCompute myCompute)
	{
		super(myCompute);
	}
	@Override
	public int plus(int a, int b)
	{
		System.out.println("print b: " + b);
		return super.plus(a, b);
	}
}

最后是客户端调用(注意客户端定义的类型仍然是接口MyCompute,创建的对象最基本的是MyComputeImp,附加功能再逐层嵌套,所以这种设计模式有个缺点:假如在MyComputePrintA内添加新方法,则不能直接调用添加的新方法,只能调用接口MyCompute已有的方法

public class Client
{
	public static void main(String[] args)
	{
		MyCompute myCompute = new MyComputeImp();
		System.out.println(myCompute.plus(1, 2));
		
		myCompute = new MyComputePrintA(new MyComputeImp());
		System.out.println(myCompute.plus(3, 4));
		
		myCompute = new MyComputePrintB(new MyComputePrintA(new MyComputeImp()));
		System.out.println(myCompute.plus(5, 6));
	}
}

输出结果

3
print a: 3
7
print b: 6
print a: 5
11

Facade 外观模式

外观模式隐藏了系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口,方便客户端使用。
例如我有两个基本功能a+b和a*b,但是客户端只想得到(a+b)^2

基本功能:

public class Mycompute
{
	public int plus(int a, int b)
	{
		return a + b;
	}
	public int mul(int a, int b)
	{
		return a * b;
	}
}

客户端需要这么调用,非常麻烦,同时也暴露了内部实现。

public class Client
{
	public static void main(String[] args)
	{
		Mycompute mycompute = new Mycompute();
		int sum = mycompute.plus(1, 2);
		System.out.println(mycompute.mul(sum, sum));
	}
}

所以Facade封装

public class MyComputeFacade
{
	public static int sqr(int a, int b)
	{
		MyCompute myCompute = new MyCompute();
		int sum = myCompute.plus(a, b);
		return myCompute.mul(sum, sum);
	}
}

客户端只需这么调用

public class Client
{
	public static void main(String[] args)
	{
		System.out.println(MyComputeFacade.sqr(1, 2));
	}
}

proxy 代理模式

代理模式也可套用到第一个共性模板里

软件设计模式简明教程java版电子教材下载_客户端_07

代理模式主要使用在两种场合:

1、如果某个对象比较“敏感”/“私密”/“贵重”,不希望被client直接访问到,于是设置proxy,在二者之间建立防火墙。

2、程序运行时可能访问真实对象代价过大,因此先访问proxy对象,等到真正用到真实对象时再通过委托访问。

可以看到它和适配器模式是十分相近的,都有一个接口和两个子类,两个子类都是原始类和一个用于隔离的类,差别在于proxy的原始类是继承接口的,而Adapter的原始类跟接口没关系。

同样是对于计算a+b,假设a+b的计算代价十分巨大,我想让它在客户端请求结果的时候才进行计算,于是使用代理模式。

首先有一个接口

public interface MyCompute
{
	public void plus();
	public int display();
}

原始类

public class MyComputeImp implements MyCompute
{
	int a, b, c;
	public MyComputeImp(int a, int b)
	{
		this.a = a;
		this.b = b;
	}
	@Override
	public void plus()
	{
		c = a + b;
	}

	@Override
	public int display()
	{
		plus();
		return c;
	}
}

代理类

public class MyComputeProxy implements MyCompute
{
	int a, b;
	public MyComputeProxy(int a, int b)
	{
		this.a = a;
		this.b = b;
	}
	@Override
	public void plus()
	{
		
	}
	@Override
	public int display()
	{
		MyCompute myCompute = new MyComputeImp(a, b);
		return myCompute.display();
	}
}

客户端

public class Client
{
	public static void main(String[] args)
	{
		MyComputeProxy myComputeProxy = new MyComputeProxy(1, 2);
		System.out.println(myComputeProxy.display());
	}
}

可以看到,在创建对象myComputeProxy时,是没有调用加法运算的,只是对内部属性a和b进行赋值,只有调用myComputeProxy.display()时,才委托给MyComputeImp进行加法的计算。

行为模式

Strategy 策略模式

策略模式可套用共性模板2

软件设计模式简明教程java版电子教材下载_System_08

在策略模式中,一个类的行为或其算法可以在运行时更改。

同样是a+b,我有两种算法:a+b和a-b+2b

首先是上图右侧继承树的Interface2,写我们的Strategy接口

public interface MyComputeStrategy
{
	public int plus(int a, int b);
}

然后是右侧继承树的两个子类,写两种不同的算法

public class MyComputeStrategyImp1 implements MyComputeStrategy
{

	@Override
	public int plus(int a, int b)
	{
		return a + b;
	}
}
public class MyComputeStrategyImp2 implements MyComputeStrategy
{

	@Override
	public int plus(int a, int b)
	{
		return a - b + 2 * b;
	}
}

接着是左侧继承树的接口Interface1(MyCompute实现了多种方法,其中plus方法要委托策略模式进行两种不同算法的实现)

public interface MyCompute
{
	public int plus(int a, int b, MyComputeStrategy myComputeStrategy);
	public int mul(int a, int b);
}

最后是Interface1的具体实现类MyComputeImp

public class MyComputeImp implements MyCompute
{
	@Override
	public int plus(int a, int b, MyComputeStrategy myComputeStrategy)
	{
		return myComputeStrategy.plus(a, b);
	}
	@Override
	public int mul(int a, int b)
	{
		return a * b;
	}
}

客户端调用哪种算法,只需new右侧继承树的哪种子类即可

public class Client
{
	public static void main(String[] args)
	{
		MyCompute myCompute = new MyComputeImp();
		System.out.println(myCompute.plus(1, 2, new MyComputeStrategyImp1()));
		System.out.println(myCompute.plus(3, 4, new MyComputeStrategyImp2()));
	}
}

Template method 模板方法模式

模板方法模式可以套用第一种共性模板

软件设计模式简明教程java版电子教材下载_客户端_09

在模板方法模式中,一个抽象类公开定义了执行它的方法的顺序/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的顺序进行。

注意:模板只能是抽象类,不能是接口,否则无法实现共性方法(如果使用接口,用default方法也能实现)

例如,我想求a2 + b2,分为三步,第一步算出a2,第二步算出b2,第三步算出a2 + b2
于是设计抽象类模板(模板中具体写出的共性方法,必须用final修饰,防止子类篡改

public abstract class MyComputeTemplate 
{
	public final int order(int a, int b)
	{
		a = step1(a);
		b = step2(b);
		return step3(a, b);
	}
	public abstract int step1(int a);
	public abstract int step2(int b);
	public abstract int step3(int a, int b);
}

然后写出具体实现的步骤

public class MyComputeTemplateImp extends MyComputeTemplate
{
	@Override
	public int step1(int a)
	{
		return a * a;
	}
	@Override
	public int step2(int b)
	{
		return b * b;
	}
	@Override
	public int step3(int a, int b)
	{
		return a + b;
	}
}

最后客户端进行调用

public class Client
{
	public static void main(String[] args)
	{
		MyComputeTemplate myComputeTemplate = new MyComputeTemplateImp();
		System.out.println(myComputeTemplate.order(2, 3));//输出结果2^2+3^2=13
	}
}

Iterator 迭代器模式

迭代器模式可套用第二种共性模板,通常用于集合类的迭代。

软件设计模式简明教程java版电子教材下载_客户端_10


根据上图信息,我们需要实现自己的ADT(Sub Type1)和个性化的迭代器方法(Sub Type 2),而Interface1 JDK已经提供,就是Iterable,直接继承即可。Interface2 JDK也提供了,就是Iterator。

所以我自己的ADT(MyCollection )直接继承Iterable,写出工厂方法public Iterator<String> iterator()返回Iterator实例MyIterator(也就是Sub Type2),然后在MyIterator内重写next() hasNext() remove()就好了。

public class MyCollection implements Iterable<String>
{
	private List<String> list;
	int i;
	public MyCollection(List<String> list)
	{
		this.list = list;
		i = -1;
	}
	@Override
	public Iterator<String> iterator()
	{
		return new MyIterator();
	}
	
	private class MyIterator implements Iterator<String>
	{
		@Override
		public boolean hasNext()
		{
			if(i != list.size() - 1)
				return true;
			else
				return false;
		}
		@Override
		public String next()
		{
			if(hasNext())
			{
				i++;
				return list.get(i);
			}
			throw new NoSuchElementException();
		}
		@Override
		public void remove()
		{
			list.remove(i);
			i--;
		}
	}
}

客户端进行调用

public class Client
{
	public static void main(String[] args)
	{
		List<String> list = new ArrayList<String>();
		list.add("1");
		list.add("2");
		list.add("3");
		MyCollection myCollection = new MyCollection(list);
		Iterator<String> it = myCollection.iterator();
		while(it.hasNext())
		{
			System.out.println(it.next());
			it.remove();
		}
		System.out.println(list.size());
	}
}

可以看到,遍历并输出,结果为1 2 3,且list的size为0,表示remove成功。

Observer/Observable 观察者模式

Observer同样可套用第二套模板

软件设计模式简明教程java版电子教材下载_客户端_11

visitor 访问者模式

Visitor模式亦可套用第二个模板

软件设计模式简明教程java版电子教材下载_ide_12

Visitor通常用于对已有的ADT添加新的功能

假设我想对已有的ADT添加加法和乘法

先写出已有的ADT的接口(Interface1),它除了获得内部变量a和b,还有预留扩展的accept外没有任何功能

public interface MyCompute
{
	public int accept(MyComputeVisitor visitor);
	public int getA();
	public int getB();
}

写出这个ADT的具体实现(Sub Type 1),预留的accept传入的参数类型为Visitor接口,也就是Interface2

public class MyComputeImp implements MyCompute
{
	private int a, b;
	public MyComputeImp(int a, int b)
	{
		this.a = a;
		this.b = b;
	}
	public int getA()
	{
		return a;
	}
	public int getB()
	{
		return b;
	}
	@Override
	public int accept(MyComputeVisitor visitor)
	{
		return visitor.visit(this);
	}
}

然后写我的Visitor接口(Interface2)

public interface MyComputeVisitor
{
	public int visit(MyCompute myCompute);
}

给出Visitor接口的两种具体实现类(Sub Type2),分别完成加法功能和乘法功能

public class MyComputeVisitorImpPlus implements MyComputeVisitor
{
	@Override
	public int visit(MyCompute myCompute)
	{
		return myCompute.getA() + myCompute.getB();
	}
}
public class MyComputeVisitorImpMul implements MyComputeVisitor
{
	@Override
	public int visit(MyCompute myCompute)
	{
		return myCompute.getA() * myCompute.getB();
	}
}

客户端可以根据传入的参数(Visitor的两种具体实现类)来切换算法

public class Client
{
	public static void main(String[] args)
	{
		MyCompute myCompute = new MyComputeImp(2, 3);
		System.out.println(myCompute.accept(new MyComputeVisitorImpPlus()));//结果为5
		System.out.println(myCompute.accept(new MyComputeVisitorImpMul()));//结果为6
	}
}

State 状态模式

状态模式也符合共性模板2,它常用于模拟状态机来控制程序执行

软件设计模式简明教程java版电子教材下载_ide_13

首先有一个Context,用于保存对象的状态、设置初始状态、接受外部输入进行状态转换,也就是(Interface1)。

public class Context
{
	private State state;
	public Context(State state)
	{
		this.state = state;
	}
	public void move(String str)
	{
		state = state.move(str);
	}
	public String getState()
	{
		return state.getState();
	}
}

然后是State接口

public interface State
{
	public State move(String c);
	public boolean accept();
	public String getState();
}

接着是State的各种子类(参照实验3的状态,Start->Running->Ended,输入1前进,输入0后退,start后退仍是start,ended前进仍是ended,其他报错)

public class Start implements State
{
	public static Start instance = new Start();
	private Start() {}
	@Override
	public State move(String c)
	{
		switch(c)
		{
			case "1": return Running.instance;
			case "0": return Start.instance;
			default: throw new IllegalArgumentException();
		}
	}
	@Override
	public boolean accept()
	{
		return false;
	}
	@Override
	public String getState()
	{
		return "Start";
	}
}
public class Running implements State
{
	public static Running instance = new Running();
	private Running() {}
	@Override
	public State move(String str)
	{
		switch(str)
		{
			case "1": return Ended.instance;
			case "0": return Start.instance;
			default: throw new IllegalArgumentException();
		}
	}

	@Override
	public boolean accept()
	{
		return false;
	}
	@Override
	public String getState()
	{
		return "Running";
	}
}
public class Ended implements State
{
	public static Ended instance = new Ended();
	private Ended() {}
	@Override
	public State move(String str)
	{
		switch(str)
		{
			case "1": return Ended.instance;
			case "0": return Running.instance;
			default: throw new IllegalArgumentException();
		}
	}

	@Override
	public boolean accept()
	{
		return true;
	}
	@Override
	public String getState()
	{
		return "Ended";
	}
}

客户端调用

public class Client
{
	public static void main(String[] args)
	{
		Context context = new Context(Start.instance);
		context.move("1");
		System.out.println(context.getState());
		context.move("1");
		System.out.println(context.getState());
		context.move("1");
		System.out.println(context.getState());
		context.move("0");
		System.out.println(context.getState());
		context.move("0");
		System.out.println(context.getState());
		context.move("0");
		System.out.println(context.getState());
	}
}

输出结果

Running
Ended
Ended
Running
Start
Start

Memento 备忘录模式

备忘录模式可套用第二种模板,记住对象的历史状态,以便于“回滚”

软件设计模式简明教程java版电子教材下载_ide_14

它是基于状态模式构建的,首先是我们的Memento类,用于记录单个状态

public class Memento
{
	private State state;
	
	public Memento(State state)
	{
		this.state = state;
	}
	
	public State getState()
	{
		return state;
	}
}

然后是Caretaker,记录历史备忘录,其中addMenmento用于添加新的记录,getMemento用于取出最后一个状态,同时回滚

public class Caretaker
{
	private List<Memento> mementos = new ArrayList<>();
	public void addMenmento(Memento m)
	{
		mementos.add(m);
	}
	public Memento getMemento()
	{
		if(mementos.size() - 1 < 0)
			throw new RuntimeException("Cannot rollback so many back!");
		Memento ans = mementos.get(mementos.size() - 1);
		mementos.remove(mementos.size() - 1);
		return ans;//取出最后一个的状态
	}
}

然后创建一个新的类NewContext,继承Context,并拓展新功能,save用于返回Memento供添加备忘录时记录,restore用于将备忘录取出的状态覆盖当前状态。

public class NewContext extends Context
{
	public NewContext(State s)
	{
		super(s);
	}
	public Memento save()
	{
		return new Memento(state);
	}
	public void restore(Memento memento)
	{
		state = memento.getState();
	}
}

最后是客户端

public class Client
{
	public static void main(String[] args)
	{
		Caretaker caretaker = new Caretaker();
		NewContext newContext = new NewContext(Start.instance);
		caretaker.addMenmento(newContext.save());
		newContext.move("1");
		caretaker.addMenmento(newContext.save());
		newContext.move("1");
		caretaker.addMenmento(newContext.save());
		System.out.println(newContext.getState());
		
		newContext.restore(caretaker.getMemento());
		System.out.println(newContext.getState());
		newContext.restore(caretaker.getMemento());
		System.out.println(newContext.getState());
		newContext.restore(caretaker.getMemento());
		System.out.println(newContext.getState());
	}

}

输出

Ended
Ended
Running
Start

可以看到,每次状态的移动都加入了备忘录,状态从Start移动到了Ended,第一次打印出来。之后每次回滚都打印了一次,可见状态从Ended回滚到了Start