软件构造 2018

2.简答题

以下给出了某ADT的一部分代码,请判断其是否threadsafe。若非,请将其修改为threadsafe的代码。
答题时,请先指出有潜在threadsafe风险的行号或行号范围,然后阐述如何修改以消除每一.项潜 在风险,并尽
可能保证高并发性能。最后以注释的形式撰写改造后的ADT的线程安全策略。

public class Poem {
	public String title = new String();
	private List<String> originLines = new ArrayList<>();
	private final List<String> lines = Collections . synchronizedList(originLines);
	public void removeSomeLinesAndUnifyTit1e(String filter) {
		Iterator<String> iter = lines . iterator();
		while (iter. hasNext()) {
			String line = iter.next();
			if (line. contains (filter))
			iter . remove();
		}
		title = title. toUpperCase();//String . toUpperCase( )将字符串中的字符变为大写
		public String toString() {
			StringBuilder sb = new StringBuilder(title + "\n");
			for (String s : poem)
			sb. append(s + "\n");
			return sb. toString();
		}
		...//这里忽略其他读写rep的方法,但答题时请考虑这些方法可能带来的线程安全风险
	}

线程安全问题:

  • public String title = new String(); 中title的public属性可能会导致在多线程使用时,从外部修改其属性值,会得到意想不到的结果,导致程序错误。
  • 虽然List<String> lines 使用Col1ections . synchronizedList(originLines); 进行装饰,但是仍然保留了其装饰之前类的引用,因此对其的访问可以绕过 synchronizedList导致线程安全问题
  • removeSomeLinesAndUnifyTit1e()toString() 方法没有加锁,其中的很多操作非原子,可能会导致线程安全问题

修改:

因为两个方法都需要对类中的所有rep进行读写,需要用类本身作为锁
同时将originLines删除,并使用匿名类的方式构造lines
同时增加线程安全策略:

public class Poem {
 //Thread Safe Arguments:
 // 这个类是线程安全的因为:
 //		title 是一个不可变类型且为private,所有对title的读写都是同步的
 //     lines 类用synchronizedList()装饰,且所有对其的读写也是同步的
	private String title = new String();
	private final List<String> lines = Collections . synchronizedList(new ArrayList<>());
	public  synchronized void removeSomeLinesAndUnifyTit1e(String filter) {
		Iterator<String> iter = lines.iterator();
		while (iter. hasNext()) {
			String line = iter.next();
			if (line. contains (filter))
			iter.remove();
		}
		title = title. toUpperCase();//String . toUpperCase( )将字符串中的字符变为大写
	}
	public synchronized String toString() {
		StringBuilder sb = new StringBuilder(title + "\n");
		for (String s : poem)
		sb. append(s + "\n");
		return sb. toString();
	}
	...
	//这里忽略其他读写rep的方法,但答题时请考虑这些方法可能带来的线程安全风险
	}

3.综合设计题

背景描述:
●教师给学生发布了 一组论文,要求每个学生选择不少于1篇、不多于4篇论文加以阅读并做报告。
●由于每篇论文只能被1个学生选择,故多个学生选择论文时可能产生冲突。如果某个论文已被选择,则
其他学生不能再选它。如果某个学生已经选择了至少1篇论文,那么他不能再次选其他论文。
●论文分为学位论文Thesis、期刊论文JournalPaper和会议论文ConferencePaper三类。
●学生可 设定自己对论文类型的偏好和待选论文数量的偏好,例如:希望选择JournalPaper 1篇。
设计了一组ADT:论文Paper (及其三个子类型Thesis、JournalPaper,ConferencePaper)、 教师发布的
论文清单PaperList、学生论文选择结果PaperSelection、学生Student。Client.java是客户端程序。

问题1:代码快照图:

画的比较粗糙,可能存在错误
在这里插入图片描述


问题2:
(1)阅读PaperList类的代码,找出所有存在“表示泄露”风险的代码行,说明其为何存在风险,并详细说
明如何修改(文字陈述即可,无需给出代码)。
(2) PaperList类的selectPaperByRandom( )方法(见Paperlist类代码的第15-22行),将某个Student
对象作为参数传递进去,根据该学生希望选择的论文数量和偏好的论文类型,从PaperList中随机选择
符合要求的论文并返回结果List。第17和18行用于判断目前可选的论文数目与学生希望选择
的论文数量之间的关系,目前的代码逻辑是:如果可选论文数目少于学生希望选择的数量,则不再为学生
选择论文,而是直接返回null。但是,返回null并不是一种好的编程风格。为此,增加一个自定义checked
异常NoEnoughPapersException,当发生该异常时, 程序应在控制台提示输出“学生XX希望选论文X
篇,但目前只有Y篇可选”,并不再为该学生选择任何论文。
( a ) 写出该异常类的定义代码;
( b ) 对Paperlist类的相关代码进行修改,使之可抛出该异常:
( c ) 修改Client.java,捕获并处理该异常。

第一问:

// Mutable class教师发布的一-组论文,以及学生选择的结果
public class PaperList {
	private final String teacher;
	//教师姓名
	private final List<Paper> papers;
	//该 教师发布的论文清单
	private final PaperSelection selections = new PaperSelection();
	//选择论文结果
	public PaperList(String teacher, List<Paper> papers) {
		this. teacher = teacher;
		this. papers = papers;
		#未对paper进行防御式拷贝,因此paper可能从外部索引被更改
	}
	//根据论文类型,到papers中找出尚未被选择的论文,返回清单。type=Any 意味着任何未被他人
	//选择的论文都会包含在返回值中
	private List<Paper> getUnselectedPapersByType(PaperType type) {
		List<Paper> results = new ArrayList<>();
		for (Paper p : this . papers) {
			if(! this. selections . isSelected(p)) {
				if(type == PaperType . Any
				|I type.toString().equals(p. getClass(). getName()))
				//注:.上方代码的两个 getXX()得到p的具体类名
				results.add(p);
			}
			return Collections . unmodifiableList(results);
		}
		//根据学生偏好,为其随机选择一组论文
		public List<Paper> selectPaperByRandom(Student student) {
			List<Paper> availablePapers =
			getUnselectedPapersByType(student. getPreferredPaperType());
			.
			//若可选论文的数量少于学生希望选择的数量,不再继续执行,直接返回null
			if(availablePapers.size() < student.getPreferredQuantity())
			return null ;
			
			List<Paper> papers = randomSelectPapers( availablePapers ,
			student.getPreferredQuantity());
			for (Paper p : papers )
				selections.addSelection(student, p);
			return papers;
		}
		public PaperSelection getSelectionResult() {
		    #返回的PaperSelection是一个mutable的类,必须进行防御式拷贝
			return this.selections;
		}
			//从availablePapers论文清单中随机选择q篇论文,返回选择结果
	private List<Paper> randomSelectPapers(List<Paper> availablePapers, int q) {
			//code ignored here
		}
		}

第二问:
异常类代码:

public class NoEnoughPapersException() extends Exception{
	public NoEnoughPapersException(String name,int x,int y){
		super();
		System.out.println("学生"+name+"希望选论文"+X+"篇,但目前只有"+Y+"篇可选”,并不再为该学生选择任何论文");
	}
	
}

Paperlist的修改:

public List<Paper> selectPaperByRandom(Student student) {
			List<Paper> availablePapers =
			getUnselectedPapersByType(student. getPreferredPaperType());
			.
			//若可选论文的数量少于学生希望选择的数量,不再继续执行,直接返回null
			if(availablePapers.size() < student.getPreferredQuantity())
			throw new NoEnoughPapersException(student.getName,student.getPreferredQuantity(),availablePapers.size())
			
			List<Paper> papers = randomSelectPapers( availablePapers ,
			student.getPreferredQuantity());
			for (Paper p : papers )
				selections.addSelection(student, p);
			return papers;
		}

客户端的修改:
try -catch即可


第五题:
子问题1: Paperlist类的selectPaperByRandom()函数目前采用随机选择论文的方案(见第19行调用的
randomSelectPapers()方法),它有两个参数:可选论文清单availablePapers、需要选择的论文数量),返回
一组选定的论文。假如用户希望将来在“随机选择”之外还有其他更灵活的论文选择策略(例如按论文加入列表的
次序从早到晚来选),并可在client端灵活使用某种特定的论文选择策略。为遵循0CP原则,请基于Strategy
设计模式对当前设计进行修改,以最小的修改代价让程序灵活适应各种论文选择策略。
子问题2:考虑到将来对PaperSelection类的扩展,为了遵循0CP原则,使用Visitor设计模式对该类进
;行改造,程序员将来可开发多个新的visitor子类,实现对PaperSelection内部存储数据的各种灵活查询(例
如:查询有多少学生已经选择了至少1篇论文)。请对PaperSelection类进行改造,并试着撰写-一个具体的
visitor类,以查询有多少学生已选择了至少1篇论文。

子问题1:
UML类图:
在这里插入图片描述
子问题2:

在PaperSelection类中预留visit()方法:
将处理数据的 功能 delegate到 外部传入的 visitor

public accept(PaperSelectionVisitor psv){
 	psv.visit(this);
}

构造的接口

public interface PaperSelectionVisitor {
 int visit(PaperSelection ps);
  }

实现的具体类:

public class ConcentrateVisitor {
//查询有多少学生已选择了至少1篇论文
 int visit(PaperSelection ps){
   List<Student> stus=new Arraylist();
   Map selection=ps.getSelection();
   for(Student s:selection.values()){
   	 if(!stus.contains(s))
   	 		stus.add(s);
   }
   return stus.size();
  }

第六题
spec:
(1)下面给出了 PaperList类的selectPaperByRandom( )方法的spec,但不完整。请在空白处补充相应的
内容,使用中文和代码中出现的类名、方法名、变量名等。不要自己想象,要基于问题描述进行spec设计,但
无需- .定要遵循该方法已有的代码实现。在做此题时,你务必已经完成了第四题(2)。若你认为该函数需抛出
NoEnoughPapersException之外的其他异常以应对你设计的pre-condition,请自行在下方空白处补充。

@param student:
表示将要选去论文的学生,要求该学生尚未选择论文

@return
返回为学生选择的论文

@throws 如果没有论文可选,抛出NoEnoughPapersException异常

(2)为selectPaperByRandom()设计黑盒测试用例。考虑到该方法的复杂性,先从最基本场景开始测试:
对一个固定的PaperList对象,仅考虑一个 Student对象
。假如你的JUnit测试代码如下所示,1-7 行构建了
一个PaperList对象,第8行构建了一个Student对象,第9-13行进行具体测试。请设计若干Student对象,
逐个替换到第8行的位置,使测试尽可能充分。将测试用例填入下面表格里即可,并简要阐述你的testing
strategy。注:设计测试用例时,需要基于你在(1)中为该函数写出的spec.

@Test
void testSelectPaperByRandom() {
	final PaperList listOfRainy = new PaperList("Rainy",
	Arrays. asList(
	new Thesis("Reading"),
	new ConferencePaper ("Science"),
	new ournalPaper ("Technology"),
	new JournalPaper (" Art")
	)
	);
	final Student student = new Student( "Cloudy", 1, PaperType . Thesis);
	try {
		List<Paper> papers = listOfRainy.selectPaperByRandom( student);
		assertTrue(...);
		//开展测试, 判断后置条件@return是否满足
	}
	catch (NotEnoughPapersException e) {
		assertTrue(...);
		//开展测试, 判断后置条件@throws是否正确捕捉
	}
}

测试策略:

针对一个固定的PaperList对象,仅考虑一个 Student对象
分成一下几个等价类:
针对学生选择的论文种类:
	Any
	Thesis、JournalPaper,ConferencePaper
	
针对学生选择论文的数量关系:
	论文的总数量不满足学生的需求
	论文的总数量不满足学生的需求,但学生选择的论文种类的数量不满足学生的需求
	学生选择的论文种类的数量不满足学生的需求
	论文的总数量和学生选择的论文种类的数量都满足学生的需求
	
针对学生选择论文的具体数量:
	学生选择0篇论文
	学生选择1篇论文
	学生选择了全部的论文

按照上面的等价类,构造笛卡尔集即可

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值