最近抄微机的实验报告抄得好烦啊!想必所有工科专业的哥们和姐们都没少受实验报告的摧残。貌似阿达曾经说过:“如果爱迪生知道要写实验报告,他就不会试一千多种材料了。”深表同意啊!好了,吐槽到此为止,凭心而论,写实验报告还是有必要的。(不过没必要非得手写吧!!!)
最近就以实验报告为题来讨论一下各种设计模式吧。今天先谈模板方法模式,因为23种设计模式中只有这个模式是我自己在写项目时完整领悟出来的(后来看书知道这叫做“模板模式”时,得意了一下,原来我悟性挺高啊)。
首先送出我们抄实验报告的真实过程的对应代码(以静态存储器扩展实验和点阵LED显示设计实验为例)
//点阵LED显示设计实验
public class DisplayInLatticeLED
{
public String experimentalName()
{
return "点阵LED显示设计实验";
}
public String experimentalPurpose()
{
return "学习LED点阵扫描显示程序的设计方法";
}
public String experimentalEquipment()
{
return "PC机适量,TD-PITE实验装置若干,示波器少许";//画外音:做菜呢这是?;
}
public String experimentalPrinciple()
{
return "略";//爱抄答案的我们最恨的就是这个字了!!!
}
public String experimentalContent()
{
return "编写程序,在8255单元控制点阵上显示字母'b'";
}
public String experimentalProcedure()
{
return "1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。";
}
public String experimentalResult()
{
return "实验运行时,LED点阵上显示的形状为字母'b'";
}
public String experimentalSummary()
{
return "通过本次实验,掌握了点阵LED扫描显示程序的设计方法";
}
public void fullReport()
{
StringBuffer report=new StringBuffer();
report.append("实验名称:");
report.append(this.experimentalName());
report.append("\n\r");
report.append("实验目的:");
report.append(this.experimentalPurpose());
report.append("\n\r");
report.append("实验设备:");
report.append(this.experimentalEquipment());
report.append("\n\r");
report.append("实验原理:");
report.append(this.experimentalPrinciple());
report.append("\n\r");
report.append("实验内容:");
report.append(this.experimentalContent());
report.append("\n\r");
report.append("实验步骤:");
report.append(this.experimentalProcedure());
report.append("\n\r");
report.append("实验结果:");
report.append(this.experimentalResult());
report.append("\n\r");
report.append("实验小结:");
report.append(this.experimentalSummary());
report.append("\n\r");
System.out.println(report.toString());
}
}
//静态存储器扩展实验
public class StaticMemoryExpansion
{
public String experimentalName()
{
return "静态存储器扩展实验";
}
public String experimentalPurpose()
{
return "了解存储器扩展的方法和存储器的读写";
}
public String experimentalEquipment()
{
return "PC机适量,TD-PITE实验装置若干,示波器少许";//画外音:做菜呢这是?
}
public String experimentalPrinciple()
{
return "存储器中,从偶地址开始存放的字成为规则字,否则成为非规则字。。。。。。";
}
public String experimentalContent()
{
return "编写程序,将0000H~000FH共16个数写入SRAM的从0000H其实的一段空间中。。。。。。";
}
public String experimentalProcedure()
{
return "1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。";
}
public String experimentalResult()
{
return "使用D命令可以观察到,从0000H起始的16个字的空间中内容分别为0000H~000FH";
}
public String experimentalSummary()
{
return "通过本次试验掌握了存储器扩展的方法和存储器的读写";
}
public void fullReport()
{
StringBuffer report=new StringBuffer();
report.append("实验名称:");
report.append(this.experimentalName());
report.append("\n\r");
report.append("实验目的:");
report.append(this.experimentalPurpose());
report.append("\n\r");
report.append("实验设备:");
report.append(this.experimentalEquipment());
report.append("\n\r");
report.append("实验原理:");
report.append(this.experimentalPrinciple());
report.append("\n\r");
report.append("实验内容:");
report.append(this.experimentalContent());
report.append("\n\r");
report.append("实验步骤:");
report.append(this.experimentalProcedure());
report.append("\n\r");
report.append("实验结果:");
report.append(this.experimentalResult());
report.append("\n\r");
report.append("实验小结:");
report.append(this.experimentalSummary());
report.append("\n\r");
System.out.println(report.toString());
}
}
//主函数
public class Main
{
public static void main(String[] args)
{
StaticMemoryExpansion reportA=new StaticMemoryExpansion();
DisplayInLatticeLED reportB=new DisplayInLatticeLED();
reportA.fullReport();
reportB.fullReport();
}
}
实验结果:
实验名称:静态存储器扩展实验
实验目的:了解存储器扩展的方法和存储器的读写
实验设备:PC机适量,TD-PITE实验装置若干,示波器少许
实验原理:存储器中,从偶地址开始存放的字成为规则字,否则成为非规则字。。。。。。
实验内容:编写程序,将0000H~000FH共16个数写入SRAM的从0000H其实的一段空间中。。。。。。
实验步骤:1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。
实验结果:使用D命令可以观察到,从0000H起始的16个字的空间中内容分别为0000H~000FH
实验小结:通过本次试验掌握了存储器扩展的方法和存储器的读写
实验名称:点阵LED显示设计实验
实验目的:学习LED点阵扫描显示程序的设计方法
实验设备:PC机适量,TD-PITE实验装置若干,示波器少许
实验原理:略
实验内容:编写程序,在8255单元控制点阵上显示字母'b'
实验步骤:1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。
实验结果:实验运行时,LED点阵上显示的形状为字母'b'
实验小结:通过本次实验,掌握了点阵LED扫描显示程序的设计方法
面向对象的特点:可复用,可维护,可拓展,灵活性好。
上面的代码四个方面都没做到,也不符合依赖倒转原则。也正是因为现实中我们不得不如此抄实验报告,实验报告才会让我们如此痛恨!
可复用那是一点都没体现,从头到尾没复用过一点东西(这也是抄实验报告时赤裸裸的现实啊!!!)。
这次的实验报告我坑爹了。我不知道黑板上已经写了实验报告的要求,结果没写实验原理和实验小结。等我已经抄了4份实验报告后才发现要写实验原理和实验小结。实验小结还好,直接在后面补上就是,可是实验原理应该写在实验步骤之前的啊!没写又能怎么办?现实生活就是这么的无可奈何,我真想活在虚拟世界里啊(原句:“我真想活在《新闻联播》里啊”,拒绝河蟹和查水表)。写程序就容易多了,因为在编辑器中插入是很容易做到的。但是,问题来了,大部分人在写刚才程序的时候都是Ctrl+C然后Ctrl+V的。那么这里就得在所有的fullReport函数里加上实验原理和实验小结了,也是挺烦的。这就是可维护性差的表现。一出错就要改好几个地方。(但是《大话设计模式》时里面认为只有类之间的耦合使得一个类的修改会引起另一个类的改动才是可维护性差。从这方面讲,由于各个实验报告类之间没有耦合,所以可维护性还是不错的。)
可拓展指的是增加功能的时候尽量通过增加代码解决,尽量不要改动原来的代码。(增加一个实验报告类好像勉强算得上增加功能。从这方面来讲,上面的代码可拓展性还行,因为增加一个实验报告类完全不用改动到原来的代码。但是这会使可维护性更差,因为类越多,出错后要修改的地方就越多。本来只要在静态存储拓展实验和点阵LED显示实验实验报告类里的fullReport方法里添加实验原理和实验小结就行,现在还要在新加的实验报告类里改。改动的地方变多了。但是从增加实验报告本身功能来讲,上面的代码可拓展性也很差。比如老师突然要求实验报告上要标上“指导老师”,那么每个实验报告类除了加上相应的teacher函数,还得在fullReport里面添上指导老师,毫无可拓展性可言。
灵活性嘛也完全看不出来。fullReport中数据的生成和显示都没分开,如果需要更改显示方式是十分麻烦的事情。
改进:
1、显示和数据的生成分离,这样不仅可以复用生成的数据,还可以轻易更改数据的显示方式(是横排显示还是竖排显示,是红色还是绿色,是宋体还是楷体)。
2、把相同的部分抽象到父类中,让子类继承父类,把不同的操作延迟到子类实现。(这就是模板方法模式)光说这些话有些抽象,请看下面改进代码:
//抽象出一个实验报告的虚类
public abstract class ExperimentalReport
{
//实验名称
abstract public String experimentalName();
//实验目的
abstract public String experimentalPurpose();
//实验设备
abstract public String experimentalEquipment();
//实验原理
abstract public String experimentalPrinciple();
//实验内容
abstract public String experimentalContent();
//实验步骤
abstract public String experimentalProcedure();
//实验结果
abstract public String experimentalResult();
//实验小结
abstract public String experimentalSummary();
//返回完整的实验报告
//不在fullReport直接打印到控制台的原因:
//1、为了fullReport代码复用,万一有些地方需要得到完整实验报告却不需要打印或需要修改打印的方式,那么就可以复用代码
//2、MVC模式要求逻辑代码和显示分离以提高可维护性(当然,也为了复用。而且这也符合单一职责原则。【不仅是对象,接口和函数都应该符合单一职责原则】)
//另外,使用final是为了防止子类恶意复写
final public String fullReport()
{
StringBuffer report=new StringBuffer();
report.append("实验名称:");
report.append(this.experimentalName());
report.append("\n\r");
report.append("实验目的:");
report.append(this.experimentalPurpose());
report.append("\n\r");
report.append("实验设备:");
report.append(this.experimentalEquipment());
report.append("\n\r");
report.append("实验原理:");
report.append(this.experimentalPrinciple());
report.append("\n\r");
report.append("实验内容:");
report.append(this.experimentalContent());
report.append("\n\r");
report.append("实验步骤:");
report.append(this.experimentalProcedure());
report.append("\n\r");
report.append("实验结果:");
report.append(this.experimentalResult());
report.append("\n\r");
report.append("实验小结:");
report.append(this.experimentalSummary());
report.append("\n\r");
return report.toString();
//注:不建议使用report.append(。。。).append(。。。).append(。。。)...的方式,这会使可读性变差,而且会给调试带来一定困难
}
}
//静态存储器扩展实验
public class StaticMemoryExpansion extends ExperimentalReport
{
public String experimentalName()
{
return "静态存储器扩展实验";
}
public String experimentalPurpose()
{
return "了解存储器扩展的方法和存储器的读写";
}
public String experimentalEquipment()
{
return "PC机适量,TD-PITE实验装置若干,示波器少许";//画外音:做菜呢这是?
}
public String experimentalPrinciple()
{
return "存储器中,从偶地址开始存放的字成为规则字,否则成为非规则字。。。。。。";
}
public String experimentalContent()
{
return "编写程序,将0000H~000FH共16个数写入SRAM的从0000H其实的一段空间中。。。。。。";
}
public String experimentalProcedure()
{
return "1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。";
}
public String experimentalResult()
{
return "使用D命令可以观察到,从0000H起始的16个字的空间中内容分别为0000H~000FH";
}
public String experimentalSummary()
{
return "通过本次试验掌握了存储器扩展的方法和存储器的读写";
}
}
//点阵LED显示设计实验
public class DisplayInLatticeLED extends ExperimentalReport
{
public String experimentalName()
{
return "点阵LED显示设计实验";
}
public String experimentalPurpose()
{
return "学习LED点阵扫描显示程序的设计方法";
}
public String experimentalEquipment()
{
return "PC机适量,TD-PITE实验装置若干,示波器少许";//画外音:做菜呢这是?;
}
public String experimentalPrinciple()
{
return "略";//爱抄答案的我们最恨的就是这个字了!!!
}
public String experimentalContent()
{
return "编写程序,在8255单元控制点阵上显示字母'b'";
}
public String experimentalProcedure()
{
return "1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。";
}
public String experimentalResult()
{
return "实验运行时,LED点阵上显示的形状为字母'b'";
}
public String experimentalSummary()
{
return "通过本次实验,掌握了点阵LED扫描显示程序的设计方法";
}
}
//主函数
public class Main
{
public static void main(String[] args)
{
ExperimentalReport reportA=new StaticMemoryExpansion();
ExperimentalReport reportB=new DisplayInLatticeLED();
System.out.println(reportA.fullReport());
System.out.println(reportB.fullReport());
}
}
程序运行结果不变,这里就不在显示了
这样改的可复用性是不是强多了?不仅fullReport的复用使得子类无需出现这个函数,而且把显示和数据分离也使得在其它地方要使用生成的fullReport变得更加容易。
可维护性似乎也强了不少。若是fullReport出错,只需改动父类即可。如我本来忘了写实验原理和实验小结,只需在父类中改动一次,而不必像原来一样见到fullReport函数就该。
可扩展性我觉得没什么变化。如果要增加一种实验报告只需再次继承ExperimentalReport并实现其虚函数即可(原来也只需直接再写一个类)。如果需要在每个fullReport标上实验的指导老师,除了需要改fullReport以外,还需在父类中添加虚拟的teacher函数。父类一添加这函数,所有子类都必须实现这个函数,这个是很不好的。
灵活性嘛,数据和显示分离当然也提高了灵活性(不过这和模板方法模式倒没任何关系)
下面来详细介绍下模板方法模式(懒得自己表达,直接抄《大话设计模式》):
定义:定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
优点:通过把不变行为搬移到超类,去除子类中重复代码,提供了很好的代码复用平台。
后续改进:1、做了一学期的实验相信大家也发现实验设备是一样的。好,这个也应该放到父类中。
2、实验目的总是“学会\了解……”,实验小结总是“通过本次实验,掌握了……”。实验小结掌握的东西就是实验目的要了解或学会的东西,因此这里也可以复用。
3、大家会发现,有些实验是没有实验原理的,如果我们在子类中只是简单地让experimentalPrinciple返回空字符串"",那么在fullReport中返回的结果会出现“实验原理:”实验原理后面直接没有内容。这样老师看来可能会以为你在偷懒或是失误而没写实验原理。他可能没反应过来这实验本来就没实验原理。换句话说,这里的灵活性不够,没办法不在fullReport中直接不出现实验原理的字眼。怎么办?我的建议是去掉父类中fullReport函数前的final修饰符。这样子类就可以重写fullReport,灵活性提高不少。不过相应的,就不能防止子类恶意复写了。遇到类似情况怎么取舍恐怕要看个人了。我的建议是去掉final修饰符。有人想恶意破坏总有办法的,总是防着很累不是?但是,子类没有实验原理但仍然必须实现experimentalPrinciple函数,这不应该,而且也不符合里氏代换原则。怎么办?我的建议是在写个子类,这个子类是简单的实验报告类,不包括实验原理等实验报告不一定会出现的东西。(当然,我觉得这里没必要这样做。我们不能为了为了符合某个原则而使得代码变得很麻烦。原则只不过是建议而已。不过为了演示我这边还是这么做了。)
具体的请看下面代码:
//简单的实验报告,没有实验原理
public abstract class EasyExperimentalReport
{
//实验名称
abstract public String experimentalName();
//实验目的
abstract public String tempExperimentalPurpose();
public String experimentalPurpose()
{
return "学习"+this.tempExperimentalPurpose();
}
//实验设备
public String experimentalEquipment()
{
return "PC机适量,TD-PITE实验装置若干,示波器少许";//画外音:做菜呢这是?;
}
//实验内容
abstract public String experimentalContent();
//实验步骤
abstract public String experimentalProcedure();
//实验结果
abstract public String experimentalResult();
//实验小结
public String experimentalSummary()
{
return "通过本次实验,掌握了"+this.tempExperimentalPurpose();
}
//返回完整的实验报告
//不在fullReport直接打印到控制台的原因:
//1、为了fullReport代码复用,万一有些地方需要得到完整实验报告却不需要打印或需要修改打印的方式,那么就可以复用代码
//2、MVC模式要求逻辑代码和显示分离以提高可维护性(当然,也为了复用)
//使用final是为了防止子类恶意复写
public String fullReport()
{
StringBuffer report=new StringBuffer();
report.append("实验名称:");
report.append(this.experimentalName());
report.append("\n\r");
report.append("实验目的:");
report.append(this.experimentalPurpose());
report.append("\n\r");
report.append("实验设备:");
report.append(this.experimentalEquipment());
report.append("\n\r");
report.append("实验内容:");
report.append(this.experimentalContent());
report.append("\n\r");
report.append("实验步骤:");
report.append(this.experimentalProcedure());
report.append("\n\r");
report.append("实验结果:");
report.append(this.experimentalResult());
report.append("\n\r");
report.append("实验小结:");
report.append(this.experimentalSummary());
report.append("\n\r");
return report.toString();
//注:不建议使用report.append(。。。).append(。。。).append(。。。)...的方式,这会使可读性变差,而且会给调试带来一定困难
}
}
//完整的实验报告,有实验原理
public abstract class ExperimentalReport extends EasyExperimentalReport
{
//实验原理
abstract public String experimentalPrinciple();
//返回完整的实验报告
//不在fullReport直接打印到控制台的原因:
//1、为了fullReport代码复用,万一有些地方需要得到完整实验报告却不需要打印或需要修改打印的方式,那么就可以复用代码
//2、MVC模式要求逻辑代码和显示分离以提高可维护性(当然,也为了复用)
//使用final是为了防止子类恶意复写
public String fullReport()
{
StringBuffer report=new StringBuffer();
report.append("实验名称:");
report.append(this.experimentalName());
report.append("\n\r");
report.append("实验目的:");
report.append(this.experimentalPurpose());
report.append("\n\r");
report.append("实验设备:");
report.append(this.experimentalEquipment());
report.append("\n\r");
report.append("实验原理:");
report.append(this.experimentalPrinciple());
report.append("\n\r");
report.append("实验内容:");
report.append(this.experimentalContent());
report.append("\n\r");
report.append("实验步骤:");
report.append(this.experimentalProcedure());
report.append("\n\r");
report.append("实验结果:");
report.append(this.experimentalResult());
report.append("\n\r");
report.append("实验小结:");
report.append(this.experimentalSummary());
report.append("\n\r");
return report.toString();
//注:不建议使用report.append(。。。).append(。。。).append(。。。)...的方式,这会使可读性变差,而且会给调试带来一定困难
}
}
//静态存储器扩展实验
public class StaticMemoryExpansion extends ExperimentalReport
{
public String experimentalName()
{
return "静态存储器扩展实验";
}
public String experimentalPrinciple()
{
return "存储器中,从偶地址开始存放的字成为规则字,否则成为非规则字。。。。。。";
}
public String experimentalContent()
{
return "编写程序,将0000H~000FH共16个数写入SRAM的从0000H其实的一段空间中。。。。。。";
}
public String experimentalProcedure()
{
return "1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。";
}
public String experimentalResult()
{
return "使用D命令可以观察到,从0000H起始的16个字的空间中内容分别为0000H~000FH";
}
public String tempExperimentalPurpose()
{
return "存储器扩展的方法和存储器的读写";
}
}
//点阵LED显示设计实验
public class DisplayInLatticeLED extends EasyExperimentalReport
{
public String experimentalName()
{
return "点阵LED显示设计实验";
}
public String experimentalContent()
{
return "编写程序,在8255单元控制点阵上显示字母'b'";
}
public String experimentalProcedure()
{
return "1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。";
}
public String experimentalResult()
{
return "实验运行时,LED点阵上显示的形状为字母'b'";
}
public String tempExperimentalPurpose()
{
return "LED点阵扫描显示程序的设计方法";
}
}
//主函数
public class Main
{
public static void main(String[] args)
{
EasyExperimentalReport reportA=new StaticMemoryExpansion();
EasyExperimentalReport reportB=new DisplayInLatticeLED();
System.out.println(reportA.fullReport());
System.out.println(reportB.fullReport());
}
}
程序运行结果:
实验名称:静态存储器扩展实验
实验目的:学习存储器扩展的方法和存储器的读写
实验设备:PC机适量,TD-PITE实验装置若干,示波器少许
实验原理:存储器中,从偶地址开始存放的字成为规则字,否则成为非规则字。。。。。。
实验内容:编写程序,将0000H~000FH共16个数写入SRAM的从0000H其实的一段空间中。。。。。。
实验步骤:1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。
实验结果:使用D命令可以观察到,从0000H起始的16个字的空间中内容分别为0000H~000FH
实验小结:通过本次实验,掌握了存储器扩展的方法和存储器的读写
实验名称:点阵LED显示设计实验
实验目的:学习LED点阵扫描显示程序的设计方法
实验设备:PC机适量,TD-PITE实验装置若干,示波器少许
实验内容:编写程序,在8255单元控制点阵上显示字母'b'
实验步骤:1、按图接线 2、编写实验程序,经编译连接无误后装入系统 3、运行程序 。。。。。。
实验结果:实验运行时,LED点阵上显示的形状为字母'b'
实验小结:通过本次实验,掌握了LED点阵扫描显示程序的设计方法
点阵LED实验本来是有实验原理的,但是由于答案是“略”,所以我们这边偷个懒,直接不写,也许可以骗过老师吧。本来LED实验的实验报告会由“实验原理:略”的字眼,老师一看就知道是抄的,而且还抄得很没水平。现在实验原理直接就没有,抄的水平也上了一个档次。(当然上面的代码也有缺点,就是客户端无法获得所有实验报告类的实验原理。)
要是我们抄实验报告也能像上面的代码那样,相信实验报告也没那么讨人厌了。
不过,上面的代码也有缺点。不同的实验报告逻辑上应该是实验报告类的不同对象而不应该直接是不同的实验报告类。这样的话类会变得很多,从这方面讲反而会提高维护的难度。如何让不同的实验报告成为对象而不是类呢?请看下集:《建造者模式》