笔者在复习哈工大软件构造的设计模式时,对最基本的五种设计模式,展开了探讨
当然,借着编程操作的机会,也练习了一下正则匹配,防御式编程参数检查等一些软构课上教的 (要考的) 内容
下面是五种基本的设计模式试用场景
工厂方法
基本介绍通过工厂类,将客户端与client进行隔离开
场景: 假如我们要为一个游戏写NPC,考虑到NPC以后的种类会很多,所以,应该建议采用工厂方法而不是new,对NPC进行创建
//main
public class CM {
public static void main(String []args) {
Person P=PersonFactory.createPeron("M");//由静态工厂方法调用
System.out.print(P.getName());
}
}
//NPC
public class Person {
private final String name;
public Person(String N) {
this.name=N;
}
public String getName() {
return name;
}
}
//NPC的工厂方法:
package Factory;
public class PersonFactory {
//普通工厂
public Person CreatPerson(String n) {
return new Person(n);
}
//静态工厂方法
public static Person createPeron(String n)
{
return new Person(n);
}
}
装饰器模式
场景: 假如我们的游戏需要对NPC进行包装
穿上了防弹衣可以防弹
穿上雨衣可以防雨
穿上了翅膀可以飞
客户需要构造穿着雨衣的鸟人和穿着防弹衣的鸟人
那么可以这样构造
//human 的接口
public interface Human {
/**
* 进行互动的行为
*/
public void action();
}
//human的实现基类
public class ConcreteHuman implements Human{
@Override
public void action() {
System.out.println("我是Human");
}
}
/**
* 装饰类基类
*/
public class BasicDecorator implements Human {
private final Human h0;
public BasicDecorator(Human h1) {
if (h1 == null) {//进行参数的检查
throw new NullPointerException();//runTimeException
}
h0 = h1;
}
@Override
public void action() {
h0.action();
}
}
//三种装饰器
public class RaincoatHuman extends BasicDecorator {
public RaincoatHuman(Human h1) {
super(h1);
}
@Override
public void action() {
System.out.println("这人穿了雨衣");
super.action();
}
}
public class FlyableHuman extends BasicDecorator {
public FlyableHuman(Human h1) {
super(h1);
}
@Override
public void action() {
System.out.println("这人长了翅膀可以飞");
super.action();
}
}
public class BullyProfHuman extends BasicDecorator {
public BullyProfHuman(Human h1) {
super(h1);
}
@Override
public void action() {
System.out.println("这人刀枪不入");
super.action();
}
}
//main函数
public static void main(String[] args) {
// 穿着雨衣的鸟人
Human H0 = new RaincoatHuman(new FlyableHuman(new ConcreteHuman()));
H0.action();
// 穿着防弹衣的鸟人
Human H1 = new BullyProfHuman(new FlyableHuman(new ConcreteHuman()));
H1.action();
}
输出:
具体的UML类图
适配器模式
场景: 假如我们由一个算法的黑盒子,只能够接受浮点数据为输入,以整形数为输出.而我们现在向用户提供的接口是字符串为输入,字符串为输出.这个时候,需要我们利用适配器,对类进行适配.
注意: 我们需要对前置条件的参数进行检查,同时,也需要检查后置条件是否满足.
采取的思路 :前面的前置,利用正则进行检查,抛出UncheckedException.后面的用assert 来进行判定,判定后置条件是否满足.
思考 :一个合法的浮点数,长什么样子?
- 不是0003.14这样子的,这样子太丑了
- 可以没有小数点,但是如果有小数点,我们需要对后面的数位进行判定
我们写正则的时候,需要体现对思考的check
//假如我们由一个黑盒:
/**
* 年份久远的黑盒,可以work
*/
public class OldBlackBox {
public int getAnswer(double d0) {
System.out.println("计算中...");
return 888;
}
}
//我们向用户承诺了一个API接口
public interface APIUserInterface {
/**
* 我们的API 向用户提供的方法
*
* @param 以小数形式的字符串,必须为合法的小数,如为非0098.xxx格式(大于一的,前方不能含有0)
* @return 字符串形式的整形数888
*/
public String APIWork(String input);
}
//现在我们对这个进行适配
public class Adapter implements APIUserInterface {
@Override
public String APIWork(String input) {
// 进行检查前置条件是否合法
if (!input.matches("([1-9][\\d]*|[0-9])(\\.[\\d]+)?")) //运用正则
{
throw new IllegalArgumentException("输入数据:" + input + "不是合法的浮点数");
}
System.out.println("你输入了:" + input);
// Delegate 黑盒进行计算
OldBlackBox OBB = new OldBlackBox();
Double d0 = Double.valueOf(input);
Integer i = OBB.getAnswer(d0);
String RET = i.toString();
// 检查后置条件是否合法
assert RET.equals("888");
return RET;
}
}
//用户代码:
public static void main(String[] args) {
APIUserInterface APIU = new Adapter();
System.out.println(APIU.APIWork("996"));
System.out.println("--------------");
System.out.println(APIU.APIWork("18.47"));
System.out.println("--------------");
System.out.println(APIU.APIWork("18..47"));
}
输出:
迭代器模式
场景: 假如我们向用户提供一个List的抽象,用户可以从头进行顺序遍历到尾部.
List需要满足其类型是Number的子类
//直接写了一个数组
public class UniqueList<L extends Number> implements Iterable {
private final List<L> L0;
public UniqueList() {
L0 = new ArrayList<>();
}
public void add(L ll) {
L0.add(ll);
}
// 按照王老师的做法,这里可以直接在类里面写迭代器,这样,就可以直接访问类里面的信息
private class UniqueIterator<L> implements Iterator<Object> {
private final List<L> myL = new ArrayList(L0);
private int current = 0;
@Override
public boolean hasNext() {
return current < myL.size();
}
@Override
public Object next() {
if (!hasNext()) {
throw new ArrayIndexOutOfBoundsException("你访问第" + (1 + current) + "个(1~n)元素,导致数组越界");
}
L lo = myL.get(current);
current++;
return lo;
}
@Override
public void remove() {
throw new UnsupportedOperationException("删除元素并没有得到支持");
}
}
@Override
public Iterator<L> iterator() {
return new UniqueIterator();
}
}
//客户端
public static void main(String[] args) {
UniqueList<Integer> UL = new UniqueList<Integer>();
UL.add(5);
UL.add(4);
UL.add(3);
UL.add(2);
UL.add(1);
Iterator<Integer> uli = UL.iterator();
while (uli.hasNext()) {
System.out.println(uli.next());
}
uli.next();//试图访问越界
结果:
访问者模式
场景:
假如我们已经写好了一个数组的类.考虑到未来的扩展,我们留存了一个accept的方法来允许visitor对其进行访问
现在目标逐渐明确,
1.用一个visitor来进行访问数组,求平均值
2.用一个visitor来访问数组,求总和
3,用一个visitor来访问数组,求向量的模长
默认数组是int类型的
//我们的数组接口
public interface ListInterface0 extends Iterable {
/**
* 获取idx位置的元素
*
* @param idx 合法的位置
* @return idx位置的元素
*/
public int getElement(int idx);
/**
* 添加元素
*
* @param val 添加的元素
*/
public void addElement(int val);
/**
* 接受访问者v0的扩展
*
* @param v0 访问者
*/
public void accept(Visitor v0);
}
//我们的访问者接口
public interface Visitor {
/**
* 对列表进行访问
*
* @param L0
*/
public void visit(ListInterface0 L0);
}
数组接口的实现代码
public class List2 implements ListInterface0 {
private final List<Integer> L0 = new ArrayList<>();
@Override
public int getElement(int idx) {
return L0.get(idx);
}
@Override
public void addElement(int val) {
L0.add(val);
}
// 按照王老师的做法,这里可以直接在类里面写迭代器,这样,就可以直接访问类里面的信息
private class UniqueIterator<L> implements Iterator<Object> {
private final List<L> myL = new ArrayList(L0);
private int current = 0;
@Override
public boolean hasNext() {
return current < myL.size();
}
@Override
public Object next() {
if (!hasNext()) {
throw new ArrayIndexOutOfBoundsException("你访问第" + (1 + current) + "个(1~n)元素,导致数组越界");
}
L lo = myL.get(current);
current++;
return lo;
}
@Override
public void remove() {
throw new UnsupportedOperationException("删除元素并没有得到支持");
}
}
@Override
public Iterator<Integer> iterator() {
return new UniqueIterator();
}
@Override
public void accept(Visitor v0) {
v0.visit(this);
}
}
三种访问者的实现代码:
//平均值访问者
public class MeanVisitor implements Visitor {
@Override
public void visit(ListInterface0 L0) {
int sum = 0;
int i = 0;
Iterator<Integer> ii = L0.iterator();
while (ii.hasNext()) {
sum += ii.next();
i++;
}
System.out.println("平均值为:" + (double) ((double) sum / i));
}
}
//总和访问者
public class SumVisitor implements Visitor {
@Override
public void visit(ListInterface0 L0) {
int sum = 0;
int i = 0;
Iterator<Integer> ii = L0.iterator();
while (ii.hasNext()) {
sum += ii.next();
i++;
}
System.out.println("总和为:" + (sum));
}
}
//向量长度访问者
public class VecLength implements Visitor {
@Override
public void visit(ListInterface0 L0) {
int sum = 0;
Iterator<Integer> ii = L0.iterator();
while (ii.hasNext()) {
int t = ii.next();
sum += (t * t);
}
System.out.println("向量长度为:" + (sum));
}
}
主函数
public static void main(String[] args) {
ListInterface0 L2 = new List2();
L2.addElement(5);
L2.addElement(4);
L2.addElement(3);
L2.addElement(2);
L2.addElement(2);
L2.accept(new MeanVisitor());
L2.accept(new SumVisitor());
L2.accept(new VecLength());
}
输出:
UML图:
可以发现,访问者与被访问者互相依赖
小结
根据王忠杰老师所言:这五种设计模式,均是对委托(两颗继承树)的变形与拓展.
在动手操作之后,发现老师说的,还真是蛮有道理的,脑海里对基本的设计模式的UML图有一个基本的映像,动手操作起来,还真是不难的.
以上便是这篇博客的全部内容.
谢谢.