软件构造心得3
- 创建模式
- factory method 工厂方法
- abstract factory 抽象工厂
- 结构模式
- Adapter 适配器模式
- Decorator 装饰器模式
- Facade 外观模式
- proxy 代理模式
- 行为模式
- Strategy 策略模式
- Template method 模板方法模式
- Iterator 迭代器模式
- Observer/Observable 观察者模式
- visitor 访问者模式
- State 状态模式
- Memento 备忘录模式
在软件构造这门课中,我们学习了6种面向复用的设计模式,和7种面向可维护性的设计模式,可谓是功能繁多类型繁杂,如果不自己手敲一遍代码,估计很难记住,因此我在复习的过程中大致分类整理了一下,放在一起写出来。
这边有两种分类方式
一、按照其功能性划分,可分为——创建、结构、行为
二、按照其继承树划分,可分为两种共性样式
共性样式1:
共性样式2:
创建模式
factory method 工厂方法
工厂方法可套用第二种共性模式
但实现起来很麻烦,我们通常都是用静态工厂方法。
拿实验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,它的本质就是把多个工厂的方法复合在一起。
同样拿实验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 适配器模式
适配器模式可套用到第一个共性模式里
适配器模式用于使用某个现有的类,但此类的接口不符合系统的需要,因此在二者之间加入接口,而适配器作为具体实现类来实现接口,通过调用被适配的类的方法来复用功能,客户端则面向接口编程。
我们有个旧的实现类,计算俩参数之和
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
首先对于基本功能,有一个抽象接口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 代理模式
代理模式也可套用到第一个共性模板里
代理模式主要使用在两种场合:
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
在策略模式中,一个类的行为或其算法可以在运行时更改。
同样是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 模板方法模式
模板方法模式可以套用第一种共性模板
在模板方法模式中,一个抽象类公开定义了执行它的方法的顺序/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的顺序进行。
注意:模板只能是抽象类,不能是接口,否则无法实现共性方法(如果使用接口,用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 迭代器模式
迭代器模式可套用第二种共性模板,通常用于集合类的迭代。
根据上图信息,我们需要实现自己的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同样可套用第二套模板
visitor 访问者模式
Visitor模式亦可套用第二个模板
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,它常用于模拟状态机来控制程序执行
首先有一个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 备忘录模式
备忘录模式可套用第二种模板,记住对象的历史状态,以便于“回滚”
它是基于状态模式构建的,首先是我们的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