结构型模式

在本节作业中,你将主要关注适配器模式装饰器模式外观模式代理模式这四种结构型设计模式。

在本节你一共需要绘制两张类图,请任选两个STEP,在选择设计模式后额外画出类图。

在完成所有STEP的任务之后,你还需要用自己的语言简单总结上述四种设计模式的意图和应用场景,并和STEP的解答一同放在最终提交的PDF中。

或许在正式开始作答前,先去看看TIPS章节会对你有所帮助。

STEP1

Bob 觉得std::vector的成员函数太多了,每次IDE都给他弹一个超长的列表让他觉得很烦躁;而且记成员函数名太困难了,因为这些名字根本不是他自己起的,他找不到规律。因此Bob 决定设计一个新的类BobVector,这个类也用来表示一个向量,但是有哪些成员函数,成员函数叫什么名字全由Bob 决定!

Bob 初步计划为BobVector设计这些函数:

  • void push(Elem):在向量末端插入一个新元素
  • Elem pop():移除向量末端的元素并将其返回
  • size_t len():返回向量中的元素个数
  • bool is_empty():如果向量为空,返回true,否则返回false
  • Elem& get(size_t):返回对应位置元素的引用

虽然Bob 雄心壮志,但是他根本不会写一个高质量的向量实现,所以他打算尽可能地复用std::vector的功能。

请为Bob 选择合适的设计模式,并画出类图。

STEP2

在你的帮助下,Bob 完成了 BobVector 的实现。Bob 很快发现,虽然 BobVector 已经比 std::vector 顺眼多了,但是每次真正使用向量时,他还是要写一大堆重复代码。

比如 Bob 经常需要对一个 BobVector<int> 做这样一套操作:先判断向量是不是空,再对它进行排序,然后去掉重复元素,最后把处理后的结果打印出来。为了不让所有代码都挤在一起,Bob 把这些功能分别交给了几个小类:

  • BobVectorSorter:负责给 BobVector<int> 排序
  • BobVectorDeduplicator:负责删除重复元素
  • BobVectorPrinter:负责输出向量内容

虽然每个小类都很努力地工作,但是 Bob 每次使用时都要记住应该先调用谁、后调用谁,还要自己组织它们之间的调用顺序。Bob 觉得这件事非常不优雅,甚至有点折磨。

于是 Bob 打算创建一个新的类,叫 BobVectorServant。这个类对外只提供简单的高级接口,例如:

void tidy_and_print(BobVector<int>& v);

调用这个函数后,它会自动完成“判空、排序、去重、输出”等一整套操作。这样,用户就不需要直接和BobVectorSorterBobVectorDeduplicatorBobVectorPrinter 这些类打交道了。

请为Bob 选择合适的设计模式,并画出类图。

STEP3

Bob 在实现BobVectorget()函数时,只是简单复用了std::vector[]运算符,他很快发现了一个问题,他使用的编译器根本没对这个运算符做越界检查!

Bob 可是学过软设的人,他非常关注自己代码的健壮性,所以他打算给BobVectorget()函数加上越界检查功能。不过考虑到Bob 之前已经用这个类写过一些代码了,他并不想直接动BobVector的代码,以免出现什么别的问题。也就是说,在新的实现完成后,应该有两个接口完全相同的向量类,其中一个和原来一样,另一个的get()函数内部悄咪咪加上了越界检查,用户可以自由选择他们想要的向量类进行使用。

请为Bob 选择合适的设计模式,并画出类图。

STEP4

Bob 觉得普通的向量已经无法满足他的需要了,Bob 打算研发多功能向量!

Bob 灵活的小脑瓜想要为向量设计这些功能:

  • 末位保护功能:每次调用BobVector::pop()移除元素时,改为移除并返回倒数第二个元素而不是最后一个。
  • 宾邦功能:每次调用BobVector::push()向向量中插入元素时,都会重复执行两次插入。
  • 虚假繁荣功能:每次调用BobVector::len()时,都会在原来返回值的基础上增加一个用户自定义值x,然后再乘以一个用户自定义值y
  • 悲观功能:每次调用BobVector::is_empty()时,如果BobVector::len()的返回值小于等于5,就会返回true,否则返回false

抛开事实不谈,这些功能还是挺有用的。Bob 希望对于一个BobVector对象来说,可以自由地添加这些功能,甚至同一个功能还可以多次添加,并且因为Bob以后还可能想出更多实用的功能,Bob 还希望设计可以尽可能方便新功能的拓展。

请为Bob 选择合适的设计模式,并画出类图。

TIPS

  1. 代理(Proxy)这个概念在计算机的很多领域中都有出现,它们的含义很可能并不相同。最好不要强行把你先前理解的代理和代理模式结合起来,它们很有可能是两种完全不同的东西。
  2. 在1的基础上,再仔细想想适配器模式和代理模式之间的区别
  3. 如果你还记得 C++ 标准库中的输入输出操作符(I/O Manipulators),它们实际上就是典型的装饰器模式实现。
  4. 在面向对象世界观中,是不存在孤立的函数的,一切都依附于对象。设计模式基于面向对象世界观,而不是某一门具体语言;理解设计模式时,不要被 C++ 的语言特性迷惑。
Last Updated:
Contributors: Lychnis