当前位置: 首页 > 编程日记 > 正文

《JAVA与模式》之备忘录模式

备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,是对象的行为模式。

  备忘录对象是一个用来存储另外一个对象内部状态的快照的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常常与命令模式和迭代子模式一同使用。

备忘录模式的结构

备忘录模式的结构图如下所示

备忘录模式所涉及的角色有三个:备忘录(Memento)角色、发起人(Originator)角色、负责人(Caretaker)角色

备忘录(Memento)角色

备忘录角色又如下责任:

(1)将发起人(Originator)对象的内战状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人(Originator)对象的内部状态。

(2)备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。

备忘录有两个等效的接口:

●  窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。

●  宽接口:与负责人对象看到的窄接口相反的是,发起人对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。

发起人(Originator)角色

发起人角色有如下责任:

(1)创建一个含有当前的内部状态的备忘录对象。

(2)使用备忘录对象存储其内部状态。

负责人(Caretaker)角色

负责人角色有如下责任:

(1)负责保存备忘录对象。

(2)不检查备忘录对象的内容。

“白箱”备忘录模式的实现

备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。因此这个实现又叫做“白箱实现”。

“白箱”实现将发起人角色的状态存储在一个大家都看得到的地方,因此是破坏封装性的。但是通过程序员自律,同样可以在一定程度上实现模式的大部分用意。因此白箱实现仍然是有意义的。

下面给出一个示意性的“白箱实现”。

源代码

发起人角色类,发起人角色利用一个新创建的备忘录对象将自己的内部状态存储起来。

复制代码
public class Originator {private String state;/*** 工厂方法,返回一个新的备忘录对象*/public Memento createMemento(){return new Memento(state);}/*** 将发起人恢复到备忘录对象所记载的状态*/public void restoreMemento(Memento memento){this.state = memento.getState();}public String getState() {return state;}public void setState(String state) {this.state = state;System.out.println("当前状态:" + this.state);}}
复制代码

备忘录角色类,备忘录对象将发起人对象传入的状态存储起来。

复制代码
public class Memento {private String state;public Memento(String state){this.state = state;}public String getState() {return state;}public void setState(String state) {this.state = state;}}
复制代码

负责人角色类,负责人角色负责保存备忘录对象,但是从不修改(甚至不查看)备忘录对象的内容。

复制代码
public class Caretaker {private Memento memento;/*** 备忘录的取值方法*/public Memento retrieveMemento(){return this.memento;}/*** 备忘录的赋值方法*/public void saveMemento(Memento memento){this.memento = memento;}
}
复制代码

客户端角色类

复制代码
public class Client {public static void main(String[] args) {Originator o = new Originator();Caretaker c = new Caretaker();//改变负责人对象的状态o.setState("On");//创建备忘录对象,并将发起人对象的状态储存起来c.saveMemento(o.createMemento());//修改发起人的状态o.setState("Off");//恢复发起人对象的状态o.restoreMemento(c.retrieveMemento());System.out.println(o.getState());}}
复制代码

在上面的这个示意性的客户端角色里面,首先将发起人对象的状态设置成“On”,并创建一个备忘录对象将这个状态存储起来;然后将发起人对象的状态改成“Off”;最后又将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。

系统的时序图更能够反映出系统各个角色被调用的时间顺序。如下图是将发起人对象的状态存储到白箱备忘录对象中去的时序图。

可以看出系统运行的时序是这样的:

(1)将发起人对象的状态设置成“On”。

(2)调用发起人角色的createMemento()方法,创建一个备忘录对象将这个状态存储起来。

(3)将备忘录对象存储到负责人对象中去。

将发起人对象恢复到备忘录对象所记录的状态的时序图如下所示:

  

  可以看出,将发起人对象恢复到备忘录对象所记录的状态时,系统的运行时序是这样的:

(1)将发起人状态设置成“Off”。

(2)将备忘录对象从负责人对象中取出。

(3)将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态。

“黑箱”备忘录模式的实现

备忘录角色对发起人(Originator)角色对象提供一个宽接口,而为其他对象提供一个窄接口。这样的实现叫做“黑箱实现”。

在JAVA语言中,实现双重接口的办法就是将备忘录角色类设计成发起人角色类的内部成员类。

将Memento设成Originator类的内部类,从而将Memento对象封装在Originator里面;在外部提供一个标识接口MementoIF给Caretaker以及其他对象。这样,Originator类看到的是Menmento的所有接口,而Caretaker以及其他对象看到的仅仅是标识接口MementoIF所暴露出来的接口。

使用内部类实现备忘录模式的类图如下所示。

源代码

发起人角色类Originator中定义了一个内部的Memento类。由于此Memento类的全部接口都是私有的,因此只有它自己和发起人类可以调用。

复制代码
package memento.sample2;/*** @author chen_dz* @date :2012-6-2 上午10:11:08*/
public class Originator {private String state;public String getState() {return state;}public void setState(String state) {this.state = state;System.out.println("赋值状态:" + state);}/*** 工厂方法,返还一个新的备忘录对象*/public MementoIF createMemento(){return new Memento(state);}/*** 发起人恢复到备忘录对象记录的状态*/public void restoreMemento(MementoIF memento){this.setState(((Memento)memento).getState());}private class Memento implements MementoIF{private String state;/*** 构造方法*/private Memento(String state){this.state = state;}private String getState() {return state;}private void setState(String state) {this.state = state;}}
}
复制代码

窄接口MementoIF,这是一个标识接口,因此它没有定义出任何的方法。

public interface MementoIF {}

负责人角色类Caretaker能够得到的备忘录对象是以MementoIF为接口的,由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容。

复制代码
public class Caretaker {private MementoIF memento;/*** 备忘录取值方法*/public MementoIF retrieveMemento(){return memento;}/*** 备忘录赋值方法*/public void saveMemento(MementoIF memento){this.memento = memento;}
}
复制代码

客户端角色类

复制代码
public class Client {public static void main(String[] args) {Originator o = new Originator();Caretaker c = new Caretaker();//改变负责人对象的状态o.setState("On");//创建备忘录对象,并将发起人对象的状态存储起来c.saveMemento(o.createMemento());//修改发起人对象的状态o.setState("Off");//恢复发起人对象的状态o.restoreMemento(c.retrieveMemento());}}
复制代码

客户端首先

(1)将发起人对象的状态设置为“On”。

(2)调用createMemento()方法,创建一个备忘录对象将这个状态存储起来(此时createMemento()方法还回的明显类型是MementoIF接口,真实类型为Originator内部的Memento对象)。

(3)将备忘录对象存储到负责人对象中去。由于负责人对象拿到的仅是MementoIF接口,因此无法读出备忘录对象内部的状态。

(4)将发起人对象的状态设置为“Off”。

(5)调用负责人对象的retrieveMemento()方法将备忘录对象取出。注意此时仅能得到MementoIF接口,因此无法读出此对象的内部状态。

(6)调用发起人对象的restoreMemento()方法将发起人对象的状态恢复成备忘录对象所存储的起来的状态,即“On”状态。由于发起人对象的内部类Memento实现了MementoIF接口,这个内部类是传入的备忘录对象的真实类型,因此发起人对象可以利用内部类Memento的私有接口读出此对象的内部状态。

多重检查点

前面所给出的白箱和黑箱的示意性实现都是只存储一个状态的简单实现,也可以叫做只有一个检查点。常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。

备忘录模式可以将发起人对象的状态存储到备忘录对象里面,备忘录模式可以将发起人对象恢复到备忘录对象所存储的某一个检查点上。下面给出一个示意性的、有多重检查点的备忘录模式的实现。

源代码

发起人角色源代码

复制代码
public class Originator {private List<String> states;//检查点指数private int index;/*** 构造函数*/public Originator(){states = new ArrayList<String>();index = 0;}/*** 工厂方法,返还一个新的备忘录对象*/public Memento createMemento(){return new Memento(states , index);}/*** 将发起人恢复到备忘录对象记录的状态上*/public void restoreMemento(Memento memento){states = memento.getStates();index = memento.getIndex();}/*** 状态的赋值方法*/public void setState(String state){states.add(state);index++;}/*** 辅助方法,打印所有状态*/public void printStates(){for(String state : states){System.out.println(state);}}
}
复制代码

备忘录角色类,这个实现可以存储任意多的状态,外界可以使用检查点指数index来取出检查点上的状态。

复制代码
public class Memento {private List<String> states;private int index;/*** 构造函数*/public Memento(List<String> states , int index){this.states = new ArrayList<String>(states);this.index = index;}public List<String> getStates() {return states;}public int getIndex() {return index;}}
复制代码

负责人角色类

复制代码
public class Caretaker {private Originator o;private List<Memento> mementos = new ArrayList<Memento>();private int current;/*** 构造函数*/public Caretaker(Originator o){this.o = o;current = 0;}/*** 创建一个新的检查点*/public int createMemento(){Memento memento = o.createMemento();mementos.add(memento);return current++;}/*** 将发起人恢复到某个检查点*/public void restoreMemento(int index){Memento memento = mementos.get(index);o.restoreMemento(memento);}/*** 将某个检查点删除*/public void removeMemento(int index){mementos.remove(index);}
}
复制代码

客户端角色源代码

复制代码
public class Client {public static void main(String[] args) {Originator o = new Originator();Caretaker c = new Caretaker(o);//改变状态o.setState("state 0");//建立一个检查点c.createMemento();//改变状态o.setState("state 1");//建立一个检查点c.createMemento();//改变状态o.setState("state 2");//建立一个检查点c.createMemento();//改变状态o.setState("state 3");//建立一个检查点c.createMemento();//打印出所有检查点o.printStates();System.out.println("-----------------恢复检查点-----------------");//恢复到第二个检查点c.restoreMemento(2);//打印出所有检查点o.printStates();}}
复制代码

运行结果如下:

可以看出,客户端角色通过不断改变发起人角色的状态,并将之存储在备忘录里面。通过指明检查点指数可以将发起人角色恢复到相应的检查点所对应的状态上。

将发起人的状态存储到备忘录对象中的活动序列图如下:

系统运行的时序是这样的:

(1)将发起人对象的状态设置成某个有效状态;

(2)调用负责人角色的createMemento()方法,负责人角色会负责调用发起人角色和备忘录角色,将发起人对象的状态存储起来。

将发起人对象恢复到某一个备忘录对象的检查点的活动序列图如下:

  由于负责人角色的功能被增强了,因此将发起人对象恢复到备忘录对象所记录的状态时,系统运行的时序被简化了:

(1)调用负责人角色的restoreMemento()方法,将发起人恢复到某个检查点。

“自述历史”模式

所谓“自述历史”模式(History-On-Self Pattern)实际上就是备忘录模式的一个变种。在备忘录模式中,发起人(Originator)角色、负责人(Caretaker)角色和备忘录(Memento)角色都是独立的角色。虽然在实现上备忘录类可以成为发起人类的内部成员类,但是备忘录类仍然保持作为一个角色的独立意义。在“自述历史”模式里面,发起人角色自己兼任负责人角色。

“自述历史”模式的类图如下所示:

备忘录角色有如下责任:

(1)将发起人(Originator)对象的内部状态存储起来。

(2)备忘录可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。

 发起人角色有如下责任:

(1)创建一个含有它当前的内部状态的备忘录对象。

(2)使用备忘录对象存储其内部状态。

客户端角色有负责保存备忘录对象的责任。

源代码

窄接口MementoIF,这是一个标识接口,因此它没有定义出任何的方法。

public interface MementoIF {}

发起人角色同时还兼任负责人角色,也就是说它自己负责保持自己的备忘录对象。

复制代码
public class Originator {public String state;/*** 改变状态*/public void changeState(String state){this.state = state;System.out.println("状态改变为:" + state);}/*** 工厂方法,返还一个新的备忘录对象*/public Memento createMemento(){return new Memento(this);}/*** 将发起人恢复到备忘录对象所记录的状态上*/public void restoreMemento(MementoIF memento){Memento m = (Memento)memento;changeState(m.state);}private class Memento implements MementoIF{private String state;/*** 构造方法*/private Memento(Originator o){this.state = o.state;}private String getState() {return state;}}
}
复制代码

客户端角色类

复制代码
public class Client {public static void main(String[] args) {Originator o = new Originator();//修改状态o.changeState("state 0");//创建备忘录MementoIF memento = o.createMemento();//修改状态o.changeState("state 1");//按照备忘录恢复对象的状态o.restoreMemento(memento);}}
复制代码

由于“自述历史”作为一个备忘录模式的特殊实现形式非常简单易懂,它可能是备忘录模式最为流行的实现形式。

转载于:https://www.cnblogs.com/jiangzhaowei/p/7287359.html

相关文章:

OpenCV 图像加载和显示

OpenCV 图像加载和显示OpenCV 图像加载和显示加载图像&#xff08;使用cv :: imread&#xff09;创建一个名为OpenCV的窗口&#xff08;使用cv :: namedWindow&#xff09;在OpenCV窗口中显示图像&#xff08;使用cv :: imshow&#xff09;**说明注意事项最后结果OpenCV 图像加…

汇编语言第五到八章总结

第五章 [BX]和loop 1.内存单元间接表示: [bx] mov dl, [0]; dl ← ((ds)16 0) mov bx, 0 mov dl, [bx]; dl ← ((ds)16 (bx)) 可以使用bx间接访问内存单元。默认&#xff0c;段地址在ds。 2.loop指令 (1) 语法格式 loop 标号 (2) CPU执行指令过程 ① (cx) ← (cx) …

提醒参加北京Tech.Ed2007会议并在九华山庄上网的朋友注意!

今年的会议内容很是精彩&#xff0c;但是所住的九华山庄的网络却出现了一些问题。这在开心就好的博客里也有所提到&#xff0c;我想应该问题类似吧。表现是网络速度非常慢&#xff0c;网页经常需要刷新才能打开。首先给大家看几个截图&#xff1a;我想做安全的朋友应该都很熟悉…

计算一下你的“手指率”

去年&#xff0c;Psytopic发布过《女子赛跑&#xff0c;看无名指长短就能知晓比赛结果》一个由英国伦敦大学医学专家得出的近乎“迷信”的结论&#xff0c;最近又有一项类似的研究结论&#xff1a;通过测算“手指率”&#xff08;digitratio&#xff0c;食指长度除以无名指长度…

Linux的常用命令!

文章目录1. linux常用命令的基本使用2. 目录&#xff08;文件夹&#xff09;常用命令2.1 创建文件夹的命令2.2 删除文件夹的命令&#xff08;谨慎使用&#xff0c;谨慎使用&#xff0c;谨慎使用&#xff09;2.3 修改文件夹的命令改变位置&#xff1a;mv 就路径 新路径&#xff…

性能测试八:jmeter进阶之beanshell

* BeanShell是一种完全符合Java语法规范的脚本语言,并且又拥有自己的一些语法和方法; * BeanShell是一种松散类型的脚本语言(这点和JS类似); * BeanShell是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器,具有对象脚本语言特性,非常精简的解释器jar文…

海量数据系统之道

-------------------------------------------------------------------------------------------------------------------------------------------- 今天先到这儿,希望对您在系统架构设计与评估&#xff0c;团队管理, 项目管理, 产品管理 有参考作用 , 您可能感兴趣的文章: …

char与varchar的区别

Varchar 对每个英文(ASCII)字符都占用2个字节&#xff0c;对一个汉字也只占用两个字节 char 对英文(ASCII)字符占用1个字节&#xff0c;对一个汉字占用2个字节 Varchar 的类型不以空格填满&#xff0c;比如varchar(100)&#xff0c;但它的值只是"qian",则它的值就是&…

Print Model SQL

凭证模块INSERT INTO RDOCSELECT *FROM db001.dbo.RDOCWHERE (DocCode JDT20024)goINSERT INTO RITMSELECT *FROM db001.dbo.RITMWHERE (DocCode JDT20024)go记得修改相应的表明和doccode(打印格式的编号,打开打印格式属性就可以看到了)模板名称保存在RDOC表中&#xff0c;而…

Python3模块Crypto改为pycryptodome

安装&#xff1a; # 安装方式1&#xff1a; pip3 install pycryptodome# 安装方式2&#xff1a; pip3 install -i https://pypi.douban.com/simple pycryptodome from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 转…

反射调用索引器

反射一个类型中的成员,可得到如、、 或 等信息,这些对象从字面上看似乎很难发现有跟索引器对得上的.但是仔细分析索引器的本质,其实索引器是被归类为属性的,即可以通过 Code1 Type genericType typeof(Dictionary<,>); 2 3 Type dictionaryType genericType…

VM虚拟机报错:An error occurred during the file system check.

VM虚拟机开机时遇到以下错误: 问题原因&#xff1a;在上一次VM虚拟机关机后&#xff0c;移除了某个硬件设备&#xff0c;但是在上一次开机时已经设置了永久挂载&#xff0c;没有在配置文件中将被移除的硬件设备信息删除掉&#xff0c;所以在下一次虚拟机启动时&#xff0c;找不…

sqlserver 2014使用时有Cannot find one or more components

好久没用sqlserver&#xff0c;今天打开却出现了一个错误,Cannot find one or more components&#xff0c;令人头疼。在启动Microsoft SQL Server Management Studio时,出现上面的错误提示,程序无法启动.在网搜了一下,发现遇到这样错误的人也不少,但是给出的大部分办法是删除注…

linux下的重要服务dns

首先谢谢版主帮我解决了图片上传问题,要不然这篇文章还真没办法做,呵呵.上一篇己较详细的讲解了LINUX下的DNS配置。因为LINUX下的DNS配置还是有点麻烦的&#xff0c;虽说前面讲那么多&#xff0c;但还是有些没提到的地方&#xff0c;下面将我自己配置的DNS拿出来跟大家一起分享…

MapReduce编程初体验

需求&#xff1a;在给定的文本文件中统计输出每一个单词出现的总次数 第一步&#xff1a; 准备一个aaa.txt文本文档 第二步&#xff1a; 在文本文档中随便写入一些测试数据&#xff0c;这里我写入的是 hello,world,hadoop hello,hive,sqoop,flume kitty,tom,jerry,world hadoo…

傻傻分不清的javascript运行机制

学习到javascript的运行机制时&#xff0c;有几个概念经常出现在各种文章中且容易混淆。Execution Context(执行环境或执行上下文)&#xff0c;Context Stack (执行栈)&#xff0c;Variable Object(VO: 变量对象)&#xff0c;Active Object(AO: 活动对象)&#xff0c;LexicalEn…

SVN linux 服务器端配置

一. SVN 简单介绍Subversion(SVN) 是一个开源的版本号控制系統, 也就是说 Subversion 管理着随时间改变的数据。 这些数据放置在一个中央资料档案库 (repository) 中。 这个档案库非常像一个普通的文件server, 只是它会记住每一次文件的变动。 这样你就能够把档案恢复到旧的版本…

用C++开发Web应用

表现: XHTML/CSS/Javascript 库&#xff1a;Extjs 逻辑/服务器端&#xff1a;C CGI/Fastcgi 库&#xff1a;Wt Boost 数据库&#xff1a;MySQL/XML Web应用计划项目&#xff1a; Web DesktopSoft RobotWeb Instant MessageWeb Office SuitWeb IDE(compiler,…

C#2.0实例程序STEP BY STEP--实例二:数据类型

C#2.0实例程序STEP BY STEP&#xff0d;&#xff0d;实例二:数据类型 与其他.NET语言一样,C#支持Common Type Sysem(CTS),其中的数据类型集合不仅包含我们熟悉的基本类型,例如int,char和float等,还包括比较复杂的类型,例如内部的string类型和表示货币值的decimal类型。而且&am…

注解--python库--matplotlib

import matplotlib.pyplot as plt import numpy as npx np.linspace(-3, 3, 50) y 2*x 1plt.figure(num1, figsize(8, 5),) plt.plot(x, y,)ax plt.gca() ax.spines[right].set_color(none)#右边框为空 ax.spines[top].set_color(none)#上边框为空 ax.xaxis.set_ticks_posi…

Parallels Desktop 重装系统

安装教程&#xff0c;大家可以在网上找找 现在我想重装系统&#xff0c;怎么弄呢&#xff1f; 1、~/Documents/Parallels 目录下那个PVM后缀的文件直接删除 2、重装找开虚拟机&#xff0c;会弹出一个框&#xff0c;说找不到系统&#xff0c;点击删除。 3、之后重新安装即可。 转…

用tcpdump查看端口包

例如tcpdump -X -s1600 -i eth1 dst port 20072 -wdump.dat 特别注意-s的使用&#xff0c;不使用该参数&#xff0c;则只会接收默认大小的一个数据包&#xff0c;我机器上是42b 得到的数据包可以通过Ethereal软件查看详细包结构和内容。 很好用&#xff01; 转载于:https://ww…

Caught exception java.lang.interruptedException(在集群上进行多个文件合并压缩时出错)

问题&#xff1a;将mapreduce程序打成JAR包提交给yarn集群,用hadoop命令启动后发现报以下错误&#xff1a; 原因&#xff1a;经检查后发现少了这一行代码&#xff0c;此代码的作用是通过传入的class&#xff0c;找到job的JAR包。 解决方法&#xff1a;添上此行代码&#xff0c;…

是北京晚报!不,是中国最大的讽刺!!!

转载请注明出处:[url]http://technet.blog.51cto.com/[/url],这还是很久以前写的呢,今天无意翻出来了,发上来与大家共享.是北京晚报!不,是中国最大的讽刺!!!  昨天在北大青鸟上完课&#xff0c;坐车回家&#xff0c;因要&#xff12;个小时的车程&#xff0c;怕闲来无聊&…

nomn文件分析

#encodinggbk import os import re import math from os import path 手动输入文件nmon文件路径&#xff0c;要截取的开始时间&#xff0c;结束时间 rootdirE:\\pylianxi\\ceshi #input("请输入文件路径&#xff1a;") start_time"14:46" #截取的…

Vmware Workstation VMX 在资源管理器中杀不掉(虚拟机繁忙导致无法关机)

使用vmware的时候出现 虚拟机繁忙 的情况导致无法关机&#xff0c;然后使用任务管理器结束vmware进程&#xff0c;之后却发现在资源监视器中有一个VMware Workstation VMX进程始终关不掉&#xff0c;获得管理员权限去杀或者重启都没有用。 解决办法&#xff1a;打开win10的应用…

BGA封装芯片手工焊接攻略

转载于&#xff1a;http://blog.sina.com.cn/s/blog_70bb32080100lx1y.html 我毕设的很多板上都有BGA芯片&#xff0c;刚开始我觉得这东西实在是没有办法焊接。幸运的是我们研究所的另外一个研究室花了30多万买了个BGA焊接设备&#xff0c;我去蹭了2次&#xff0c;可惜要看人家…

40个常用的网站制作技巧

1. οncοntextmenu "window.event.returnValuefalse " 将彻底屏蔽鼠标右键 <table border οncοntextmenureturn(false)> <td> no </table> 可用于Table 2. <body onselectstart "return false "> 取消选取…

[Flash开发笔记] 如何在as2.0中使用自定义类事件

as2编程中&#xff0c;我们通常要处理一些异步加载的数据&#xff0c;有点类似ajax中的callback&#xff0c;即我们不知道何时数据才会返回&#xff0c;并且只有当数据返回时&#xff0c;执行我们定义的操作。在flash6及以前&#xff0c;我们会常常碰到从外部加载一张图片或一段…

.NET开发不可不知、不可不用的辅助类(三)(报表导出---终结版)

.NET导出报表一般是采用导出Excel报表的方式输出内容。而这又分为两种方式&#xff1a;使用Excel模板方式和使用网页输出Excel格式两种。首先介绍简单的一种&#xff0c;网页输出Excel内容&#xff0c;这种不需要引用Excel的程序集。/**//// <summary> /// 报表导出辅…