结构型设计模式实验

实验目的

  1. 结合实例,熟练绘制常见的结构型设计模式结构图
  2. 结合实例,熟练使用任意一种面向对象编程语言实现常见的结构型设计模式
  3. 通过本实验,理解每一种结构型设计模式的模式动机,掌握模式结构,学习如何使用代码实现这些设计模式

实验要求

  1. 结合实例,绘制常见结构型设计模式的结构图
  2. 使用任意一种面向对象编程语言实现常见结构型设计模式实例,代码运行正确

实验内容

练习1

某OA系统需要提供一个加密模块,将用户机密信息(例如口令、邮箱等)加密之后再存储在数据库中,系统已经定义好了数据库操作类。为了提高开发效率,现需要重用已有的加密算法,这些算法封装在一些由第三方提供的类中,有些甚至没有源代码。试使用适配器模式设计该加密模块,实现在不修改现有类的基础上重用第三方加密方法。要求绘制相应的类图并编程模拟实现,需要提供对象适配器和类适配器两套实现方案

使用适配器模式,使两个不兼容的接口实现交互,结合两个独立接口的功能。在此实例中,User为用户实体类,存储用户的信息,包括口令、邮箱等。系统定义好的数据库操作类用UserDBOperation来表示,用于实现将用户机密信息存储在数据库中。加密算法封装在第三方的EncryptionAlgorithm类中,无具体实现代码,即不需要知道算法如何实现,只需通过适配器模式下的适配器类来调用其中的方法即可。使用对象适配器方案,不适用继承的方式,而是采用委托的方式来实现;使用类适配器方案,EncryptionAdapter类继承EncryptionAlgorithm类来实现加密功能

类图与实现代码如下所示

对象适配器方案

2

类适配器方案

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package dp_lab3_q1;
public class Client {}
public class User {
private String password; // 口令
private String email; // 邮箱
// 还有其他用户的机密信息
// Setter & Getter
}
public interface UserDBOperation {
void operate(); // 将信息存储到数据库中
}
public class EncryptionAlgorithm {
protected void encrypt(); // 加密算法
}
// 对象适配器方案
public class EncryptionAdapter implements UserDBOperation {
private EncryptionAlgorithm encryptionAlgorithm = new EncryptionAlgorithm();
public void operate(); // 实现operate方法
}
// 类适配器方案
public class EncryptionAdatper extends EncryptionAlgorithm implements UserDBOperation {
public void operate(); // 实现operate方法
}

练习2

某手机美图APP软件支持多种不同的图像格式,例如JPG、GIF、BMP等常用图像格式,同时提供了多种不同的滤镜对图像进行处理,例如木刻滤镜(Cutout)、模糊滤镜(Blur)、锐化滤镜(Sharpen)、纹理滤镜(Texture)等。现采用桥接模式设计该APP软件,使得该软件能够为多种图像格式提供一系列图像处理滤镜,同时还能够很方便地增加新的图像格式和滤镜,绘制对应的类图并编程模拟实现

使用桥接模式,使抽象化与实现化解耦。首先创建Image实体类,以及继承该类的各子类JPGGIF以及BMP。同时创建Filter接口,CutoutBlurSharpen以及Texture类都实现Filter接口,用于实现各种滤镜

类图与实现代码如下所示

3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package dp_lab3_q2;
public class Image {
protected Filter filter;
}
public interface Filter {
void filter();
}
public class JPG extends Image {}
public class GIF extends Image {}
public class BMP extends Image {}
public class Cutout implements Filter {
public void filter();
}
public class Blur implements Filter {
public void filter();
}
public class Sharpen implements Filter {
public void filter();
}
public class Texture implements Filter {
public void filter();
}

练习3

某移动社交软件要增加一个群组(Group)功能。通过设置,用户可以将自己得动态信息(包括最新动态、新上传的视频以及分享的链接等)分享给某个特定的成员(Member),也可以分享给某个群组中的所有成员;用户可以将成员添加至某个指定的群组;此外,还允许用户在一个群组中添加子群组,以便更加灵活地实现面向特定人群的信息共享。现采用组合模式设计该群组功能,绘制对应的类图并编程模拟实现

使用组合模式,把一组相似的对象当作一个单一的对象,依据树形结构来组合对象。首先创建Component抽象类类作为Group类以及Member类的父类,提供增加成员、删除成员以及分享成员的方法。在Group类中创建成员容器以及转发方法,模拟群组的实现。在Member类中仅需要使用Component类中的方法即可。这样就可以实现成员创建群组,群组中的成员再创建子群组的功能;创建Moment类,使用字符串来模拟用户的动态信息

类图与实现代码如下所示

4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package dp_lab3_q3;
public class Moment {
private String content; // 用字符串来模拟动态的内容
}
public abstract class Component {
private void addComponent(Component c) {}
private void removeComponent(Component c) {}
private void shareComponent(Component c) {}
private void shareMoment(Moment m,Component c) {}
}
public class Member extends Component {
private void addComponent(Component c) {}
private void removeComponent(Component c) {}
private void shareComponent(Component c) {}
private void shareMoment(Moment m,Component c) {}
}
public class Group extends Component {
private List<Component> GroupMemberList; // 群组成员容器
private void addComponent(Component c) {}
private void removeComponent(Component c) {}
private void shareComponent(Component c) {}
private void shareMoment(Moment m,Component c) {}
}

练习4

在某OA系统中提供一个报表生成工具,用户可以通过该工具为报表增加表头和表尾,允许用户为报表增加多个不同的表头和表尾,用户还可以自行确定表头和表尾的次序。为了能够灵活设置表头和表尾的次序并易于增加新的表头和表尾,现采用装饰模式设计该报表生成工具,绘制对应的类图并编程模拟实现

使用装饰模式,在不改变其结构的条件下向一个现有的对象添加新的功能。首先创建Table报表实体类作为被装饰者,其中包括设置表头表尾的方法以及存放多个表头表尾的列表。然后创建TableDecorator类作为装饰类,其中包括增加表头表尾的方法

类图与实现代码如下所示

5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package dp_lab3_q4;
public class Table {
private List<Header> headerList;
private List<Tail> tailList;
private int headerNum;
private int tailNum;
private setHeader() {};
private setTail() {};
}
public abstract class TableDecorator {
protected Table table;
public abstract addHeader();
public abstract addTail();
}
// 根据不同方法添加不同的表头表尾
public class TableDecorator1 extends TableDecorator {
protected Table table;
public addHeader() {}
public addTail() {}
}
public class TableDecorator2 extends TableDecorator {
protected Table table;
public addHeader() {}
public addTail() {}
}
// ......
public class TableDecoratorN extends TableDecorator {
protected Table table;
public addHeader() {}
public addTail() {}
}

练习5

某软件公司为新开发的智能手机控制与管理软件提供了一键备份功能,通过该功能可以将原来存储在手机中的通讯录、短信、照片、歌曲等资料一次性全部拷贝到移动存储介质(例如MMC卡或SD卡)中。在实现过程中需要与多个已有的类进行交互,例如通讯录管理类、短信管理类等。为了降低系统的耦合度,试使用外观模式来设计并编程模拟实现该一键备份功能

使用外观模式,隐藏系统的复杂性,仅向客户端提供一个可访问的接口即可。首先创建Manager管理类作为通讯录管理类DirectoryManager以及短信管理类MessageManager各管理类的父类。同时新建BackUpFacade类用来实现备份功能

类图与实现代码如下所示

6

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package dp_lab3_q5;
public abstract class Manager {
public abstract void manage();
}
public class DirectoryManager extends Manager {
public void manage() {}
}
public class MessageManager extends Manager {
public void manage() {}
}
public class BackUpFacade {
private DirectoryManager directoryManager;
private MessageManager messageManager;
private backUp() {
// 在方法中调用各管理类中的方法来实现备份
// 这样就实现了备份功能同时隐藏具体的实现细节
}
}

练习6

某OA系统采用享元模式设计权限控制与管理模块,在该模块中,将与系统功能相对应的业务类设计为享元类并将相应的业务对象存储到享元池中(提示:可使用Map实现,key为业务对象对应的权限编码,value为业务对象)。用户身份验证成功后,系统通过存储在数据库中的该用户的权限编码集从享元池获取相应的业务对象并构建权限列表,在界面上显示用户所拥有的权限。根据以上描述,绘制对应的类图并编程模拟实现

使用享元模式,减少所创建对象的数量,通过重用现有的同类对象,可减少内存的占用并且提高性能。首先创建FlyWeightPool享元池类,用来存储已实创建的业务类实例对象,使用Map来保存。创建Service业务类作为系统中各个业务类的父类

类图与实现代码如下所示

7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package dp_lab3_q6;
public abstract class Service {
private String code;
public Service(String code) {
this.code=code;
}
private abstract void serve();
}
public class FlyWeightPool {
private static Map<String,Service> serviceMap=new HashMap<>();
public static getService(String code) {
Service service=serviceMap.get(code);
if(service==null) {
service=new Service(code);
serviceMap.put(code,service);
}
return service;
}
}
// 继承Service类, 实现不同的功能提供相关服务
public class Service1 extends Service {
private void server() {
// 实现业务功能
}
}
public class Service2 extends Service {
private void server() {
// 实现业务功能
}
}
// ......
public class ServiceN extends Service {
private void server() {
// 实现业务功能
}
}

练习7

在某电子商务系统中,为了提高查询性能,需要将一些频繁查询的数据保存到内存的辅助存储对象中(提示:可使用Map实现)。用户在执行查询操作时,先判断辅助存储对象中是否存在待查询的数据,如果不存在,则通过数据操作对象查询并返回数据,然后将数据保存到辅助存储对象中,否则直接返回存储在辅助存储对象中的数据。现采用代理模式中的缓冲代理实现该功能,要求绘制对应的类图并编程模拟实现

使用代理模式,用一个类代表另一个类,创建具有现有对象的对象,减少系统开销,可以近似理解为高速缓存的机制。首先Data类作为数据类,模拟从数据库中查询到的数据,数据对象用口令来标识;创建Storage辅助存储类,用来存储已创建的数据类实例对象;创建Database类于数据库进行交互,在辅助存储类中没有需要的数据时,使用此类中的方法查询数据库中的数据并存储在辅助存储类中

类图与实现代码如下所示

8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package dp_lab3_q7;
public class Data {
private String code; // 用于识别唯一数据实例对象
private String content;
}
public class Database {
public static Data search(String code) {
Data data;
// 经过数据库查询操作返回Data对象
return data;
}
}
public class Storage {
private static Map<String,Data> dataMap=new HashMap<>();
public static getData(String code) {
Data data=dataMap.get(code);
if(data==null) {
data=Database.search(code);
}
return data;
}
}

实验小结

在做一个项目之前的需求分析过程中就应该构思好整个系统的完整架构,运用的模式,以及类与类,类与接口之间的关系,以免造成不必要的麻烦。设计时要尽可能符合七大开发原则。对扩展开发而对修改关闭;面向接口编程,养成封装类以及使用抽象方法的习惯;控制每个类的粒度大小,尽量符合高内聚,低耦合的特性;确保父类所拥有的性质在子类中仍然成立;给每个类都建立他们需要的专用接口;每个类尽量只与具有直接关系的类进行交互;尽量使用组合或聚合的关联关系来实现系统的具体功能

在练习1中,使用适配器模式,实现两个原本不兼容的接口之间的交互,从而结合两个独立接口的功能。同时设计了对象适配器和类适配器两套实现方案,对象适配器通过委派来实现功能,类适配器通过继承的方式来实现功能,前者为动态的方式而后者是静态的方式

在练习2中,使用桥接模式,使抽象化与实现化解耦。此处桥的功能可理解为将两个独立的结构或功能联系起来,同时保证这两个被联系起来的结构可以独立变化。分离了抽象与实现,对外隐藏实现细节

在练习3中,使用组合模式,把一组相似的对象当作一个单一的对象,依据树形结构来组合对象。子类中也可以扩展新的属性和方法,且保证大体结构相似。组合模式可清晰表示系统的层次结构

在练习4中,使用装饰器模式,允许向一个现有的对象添加新的功能,同时又不改变其结构。可不断增加或修改实现抽象接口的具体实现类来实现新功能或修改已有的功能,不会影响系统的整体结构,且保证对外隐藏实现细节,提供额外功能

在练习5中,使用外观模式,隐藏系统的复杂性,向客户端提供一个可以访问系统的接口。对外隐藏实现细节,只需调用相关接口使用功能,无需知道如何实现。将具体功能打包,调用时使用外观类即可

在练习6中,使用享元模式,减少所创建对象的数量,通过重用现有的对象,减少内存占用同时提高性能。在实现时可使用HashMap来存储已创建的对象,在使用时在HashMap直接获取即可,已有对象数量不够使用时,才去创建新的对象

在练习7中,使用代理模式,用一个类代表另一个类,创建具有现有对象的对象,减少系统开销,可近似理解为计算机中高速缓存的机制。调用数据时先查看缓存中是否存在这些数据,如果存在则直接使用,如果不存在再从硬盘中读取数据到内存中。同理在代理模式下,一个类的创建或使用开销过大时,可先使用代理类来尝试实现功能,如果可以实现就直接使用代理类的功能,如果不能实现再调用原有类的方法