本文资料来源于:

  • 软院学长学姐代代相传的资料
  • 网络搜集的资料
  • AI辅助生成的文字
  • 本人的胡言乱语
  • 部分题目来源于书本或PPT,可能并非考试真题

耦合内聚判断

计算前50名成绩

1
2
3
4
5
6
7
8
9
10
11
public class Grade{
public float averageGradefroTop50(ArrayList<Student>allStudent){
ArrayList<Student> sortedStudent = allStudent.sort();
int totalGrade = 0;
for(int i=0;i<50;i++){
totalGrade += sortedStudent.get(i).getGrade();
}
double averageGrade = totalGrade/50.0;
return allStudent;
}
}

问题:averageGradeforTop50ArrayList<Student>类之间是哪种类型的耦合

解答:内容耦合。allStudent.sort()直接修改了数组的排序顺序。

修改:

1
2
3
4
5
6
7
8
9
10
11
12
public class Grade{
public float averageGradefroTop50(ArrayList<Student>allStudent){
ArrayList<Student> sortedStudent = new ArrayList<>(allStudent);
sortedStudent.sort();
int totalGrade = 0;
for(int i=0;i<50;i++){
totalGrade += sortedStudent.get(i).getGrade();
}
double averageGrade = totalGrade/50.0;
return allStudent;
}
}

验证信息

1
2
3
4
5
6
7
8
9
10
11
12
validate_checkout_request(input_form i){
if(!valid_string(i.name)){
error_message('"invalid name"');
}
if(!valid_month(i.date)){
error_message('invalid month');
}

}
int valid_month(date d){
return d.month>1 && d.month <=12;
}

解答:

  • 印记耦合:valid_month只需要data内的一部分信息
  • 通信内聚:valid_string和valid_string因为用到同一份数据i才被放在一起
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void validdata_checkout_request(input_form i){
if(!valid(i.name,STRING)){
error_message("invalid name");
}
if(!valid(i.data,DATE)){
error_message("invalid month");
}
}
int valid(string s, int type){
switch(type){
case STRING:return strlen(s) < MAX_STRING_SIZE;
case DATE:
date d = parse_date(s);
return d.month >=1 && d.month <=12;
}
}

解答:控制耦合。type是控制信息

image-20240618153210993

解答:公共耦合

image-20240618153253552

解答:内容耦合。修改了另一个模块的内部数据

sales

截屏2019-10-26下午8.16.54

解答(教材P237):类的内聚差,方法和行为不一致。Sales的属性是所有saleLineItem的Map,计算单个saleLineItem总价应该是saleLineItem的职责。

修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Sales{
public int getSubtotal(int commodityId){
SalesLineItem salesLineItem = SalesLineItemMap.get(commodityId);
return salesLineItem.getSubtoal();
}
}
class SalesLineItem{
Commodity commodity;
int nums;
public getSubtotal(){
return commodity.getPrice * nums;
}
}
class Commodity{
int price;

public int getPrice() {
return price;
}

public void setPrice(int price) {
this.price = price;
}
}

初始化过程

截屏2019-10-26下午9.04.25

解答:时间内聚。初始化财务报告、初始化天气、初始化计数器只是因为都是在初始化时执行而放在一起。

修改:

截屏2019-10-26下午9.05.00

Rous

image-20240618151401619

解答:偶然内聚

Email

image-20240618151452622

解答:印记耦合。发送邮件只需要Employee的emailID,不需要name

Employee

image-20240618155256566

解答:继承耦合,精化规格

其他例子

image-20240618153402280

解答:逻辑内聚、控制耦合

image-20240618153441426

解答:时间内聚

image-20240618153513566

解答:过程内聚

image-20240618153545295

解答:通信内聚

image-20240618153607242

解答:功能内聚

image-20240618153629357

解答:信息内聚

image-20240618153714850

解答:内容耦合。slant应该修改为Vector3D的方法。

image-20240618153748790

解答:控制耦合

image-20240618153822354

解答:数据耦合

image-20240618153850709

解答:公共耦合

用例

ATM机

截屏2019-10-26下午8.33.55

图书馆系统

为下列描述建立用例模型,要求给出明确的建模过程。

现在需要开发一个简化了的大学图书馆系统,它有几种类型的借书人。包括教职工借书人,研究生借书人和本科生借书人等。借书人的基本信息包括姓名、地址和电话号码等。对于教职工借书人,还要包括诸如办公室地址和电话等信息。对于研究生借书人,还要包括研究项目和导师信息等。对于本科借书人,还要包括项目和所有学分信息等。
图书馆系统要跟踪借出书本信息,当一个借书人捧着一堆书去借书台办理借书手续时,借出这个事件就发生了,随着时间的过去,一个借书人可以多次从图书馆中借书,一次可以借出多本图书。如果借书人想要的书已经被借出,他可以预约,每个预约只针对一个借书人和一个标题,预约日期,优先权和完成日期等信息需要维护,当借书完成,系统会将这本书与借出联系起来。
借书人根据图书馆的信息来检索书名,同时检索这本书是否可以被借出,如果一本书的所有副本都被借出了,那么借书人可以根据书名预定这本书,当借书人把书拿到借书台的时候,管理员可以为这些书办理归还手续,管理员要跟踪新书到达的情况。图书的管理者有属于自己的活动,他们要分类打出关于书的标题的表格,还要在线检查所有过期未还的图书也标出来,而且图书馆系统还可以从另外一个大学的数据库中访问和下载借书人的信息。

解答:

建模过程:

  • 明确系统的目标和确定解决方向:管理图书馆的书本借阅与维护信息
  • 寻找参与者:借书人,管理员
  • 寻找用例:借阅图书,归还图书,预约图书,检索图书,管理图书,查看借阅情况,查看借书人信息
  • 细化用例:

支付宝

消费者可以使用支付宝“扫一扫”,输入金额和密码进行支付,也可以让商家扫描用户的付钱码进行支付。
卡包内有优惠券、红包等可以在支付时用于抵现。

ATM取款

截屏2019-10-28下午3.01.34

机场导游

image-20240618123127832

超市销售系统

c6d5f89aedd4e7cb60cd3e60a047595

  • 不要将用户管理细化 为增加、修改和删除三个更小的用例,因为它们要联合起来才能体现出业务价值。

  • 不要将同一个业务目标细化为不同用例。例如特教策略制定和赠送策略制定

  • 不要将没有业务价值的内容作为用例

    • 登录(应该是安全性质量需求)
    • 数据验证(应该是数据需求)
    • 连接数据库(软件内部实现,不是需求)

    d6fed29d342579c9cf29ea0af33ba51

    96a9d2729a2a2e7196f9230f635c143

    2ba2e5c2dd6a0b2bcabb433e82af107

    589f3805c71af7e3f7dc6dfcf1212bf

概念类图

支付宝

以下为活动“扫描商家的⼆二维码进⾏行行付款”的⽤用例例的概念类的候选类:
消费者,商家,蚂蚁积分,付款码,付款⽅方式,花呗⽀支付,余额⽀支付,余额宝⽀支付,
银⾏行行卡⽀支付
请识别概念类之间的关系(依赖、聚合、关联、组合、继承等),识别重要属性,画
出概念类图。

image-20240617215624005

自驾游路线

截屏2019-10-27下午10.48.41

创建路单

截屏2019-10-27下午9.14.16

购房评估系统

截屏2019-10-28下午4.28.53

按钮-门

截屏2019-10-28下午4.47.01

  1. 每个Button实例控制一盏门,抽象Door,使得Door可灵活更改。二者直接依赖关系
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
public class Button{
Door door;
public Button(Door d){
door = d;
}
public void turnON(){
door.open();
}
public void turnOFF(){
door.clost();
}
}
public interface Door{
public void open();
public void close();
}
public class DoorA implements Door{

}
public class DoorB implements Door{

}
public class DoorC implements Door{

}
public class TestClient{
public static void main(string []args){
Door doora = new DoorA();
Button buttona = new Button(doora);
buttona.turnON();
}
}

截屏2019-12-08下午7.44.50

  1. 一个Button控制多个灯
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
import java.util.ArrayList;
public class Button{
private ArrayList<Door> doorList = new ArrayList<>();
public Button(ArrayList<List> doorlist{
doorList = doorlist;
}
public void turnON(){
for(door:doorList){
door.open
}
}
public void turnOFF(){
for(door:door:List){
door.close();
}
}
public void addDoor(Door d){
doorList.add(d);
}
public boolean deleteDoor(Door d){

}
}
public interface Door{
public void open();
public void close();
}
public class DoorA implements Door{

}
public class DoorB implements Door{

}
public class DoorC implements Door{

}
  1. 需要控制多种设备,把包括灯在内的所有设备抽象为Device接口,Button只持有Device接口,不区分具体是什么设备
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
37
38
39
40
41
42
43
import java.uitl.LinkedList;
public class Button{
LinkedList<Device> deviceList = new LinkedList<>();
public Button(LinkedList devicelist){
deviceList = deviceList;
}
public void turnON(){
door.open();
}
public void turnOFF(){
door.clost();
}
public void addDevice(Device d){
deviceList.add(d);
}
public boolean deleteDevice(Device d){
deviceList.remove(d);
}
}
public interface Device{
public void open();
public void close();
}
public class Door implements Device{
public Door(){
super();
}
}
public class DoorA extends Door{

}
public class DoorB extends Door{

}
public class DoorC extends Door{

}
public class TV implements Device{

}
public class AirConditioner implenents Device{

}

销售系统

image-20240618124359428

image-20240618124901718

大富翁

image-20240618151016081

image-20240618151157801

ATM

题目(教材P114,来源))

University Bank will be opening in Oxford, Mississippi, in January, 2000. We plan to use a full service automated teller machine (ATM) system.

The ATM system will interact with the customer through a display screen, numeric and special input keys, a bankcard reader, a deposit slot, and a receipt printer.

Customers may make deposits, withdrawals, and balance inquires using the ATM machine, but the update to accounts will be handled through an interface to the Accounts system.

Customers will be assigned a Personal Identification Number (PIN) and clearance level by the Security system. The PIN can be verified prior to any transaction.

In the future, we would also like to support routine operations such as a change of address or phone number using the ATM

概念类:

  • FinancialTransaction
  • Account
  • BalanceInquiry
  • Withdrawal
  • Deposit
  • AuthorizeSystemInteraction
  • BankCard

参考资料:ATM UML Diagrams (startertutorials.com)

ATM-class-diagram

设计原则

手机应用

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
public class Application{
private String applicationName;
private float averageRate;
private ArrayList<NewFeature> newFeatureItems = new ArrayList<NewFeature>();

public String getDescriptionForIOS(){
StringBuffer result = new StringBuffer();
result.append("This is "+ this.applicationName + " for IOS platform\n");
for(int i= 0;i<newFeatureItems.size();i++){
result.append(newFeatureItems.get(i).getDescription);
}
result.append("Average Rate from APP Store\n");
result.append(String.valueOf(avarageRate));
return result.toString();
}

public String getDescriptionForAndriod(){
StringBuffer result = new StringBuffer();
result.append("This is "+ this.applicationName + " for Andriod platform\n");
for(int i= 0;i<newFeatureItems.size();i++){
result.append(newFeatureItems.get(i).getDescription);
}
result.append("Average Rate from Google Play\n");
result.append(String.valueOf(avarageRate));
return result.toString();
}
}

问题:违反了什么原则,解释并修改代码;应用某种设计模式来重构

解答:

  • 违反了OCP原则getDescriptionForIOSgetDescriptionForAndriod 方法中有大量重复的代码,尤其是拼接字符串和遍历 newFeatureItems 的部分。如果需要修改描述生成逻辑,比如改变 newFeatureItems 的描述方式,必须修改这两个方法,这违反了开闭原则。
  • 违反Do not repoat:大量重复代码

修改:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 平台描述接口
interface PlatformDescription {
String getPlatformName();
String getStoreName();
}

// IOS平台描述类
class IOSPlatformDescription implements PlatformDescription {
@Override
public String getPlatformName {
return "IOS";
}

@Override
public String getStoreName {
return "APP Store";
}
}

// Android平台描述类
class AndroidPlatformDescription implements PlatformDescription {
@Override
public String getPlatformName {
return "Android";
}

@Override
public String getStoreName {
return "Google Play";
}
}


// Application类
public class Application {
private String applicationName;
private float averageRate;
private ArrayList<NewFeature> newFeatureItems = new ArrayList<NewFeature>();

public String getDescription(PlatformDescription platform) {
StringBuffer result = new StringBuffer();
result.append("This is " + this.applicationName + " for " + platform.getPlatformName() + " platform\n");
for (NewFeature feature : newFeatureItems) {
result.append(feature.getDescription());
}
result.append("Average Rate from " + platform.getStoreName() + "\n");
result.append(String.valueOf(averageRate));
return result.toString();
}

public String getDescriptionForIOS(){
return getDescription(new IOSPlatformDescription());
}

public String getDescriptionForAndriod(){
return getDescription(new AndriodPlatformDescription());
}
}

使用策略模式

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 平台描述接口(策略类)
interface PlatformDescription {
String getPlatformName();
String getStoreName();
}

// IOS平台描述类
class IOSPlatformDescription implements PlatformDescription {
@Override
public String getPlatformName {
return "IOS";
}

@Override
public String getStoreName {
return "APP Store";
}
}

// Android平台描述类(具体策略)
class AndroidPlatformDescription implements PlatformDescription {
@Override
public String getPlatformName {
return "Android";
}

@Override
public String getStoreName {
return "Google Play";
}
}


// Application类(具体策略)
public class Application {
private String applicationName;
private float averageRate;
private ArrayList<NewFeature> newFeatureItems = new ArrayList<NewFeature>();
private PlatformDescription platform;

public void setPlatformDescription(PlatformDescription platform) {
this.platform = platform;
}

public String getDescription() {
StringBuffer result = new StringBuffer();
result.append("This is " + this.applicationName + " for " + platform.getPlatformName() + " platform\n");
for (NewFeature feature : newFeatureItems) {
result.append(feature.getDescription());
}
result.append("Average Rate from " + platform.getStoreName() + "\n");
result.append(String.valueOf(averageRate));
return result.toString();
}
}

影片出租店

Rukawa同学开发一个影片出租店用的程序,其中需要计算客户的积分,如果电影是新发布的电影并且租用的时间超过1天,则可以得到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
24
25
26
27
28
29
30
31
32
33
public class Customer{
Rental rental;
int getNewRentPoint(){
Movie m = rental.getMovieRented();
if((m.getPriceCode == Movie.NEW_RELEASE)&& rental.getDaysRented()>1){
return 2;
}else {
return 1;
}
}
}
public class Rental{
private int daysRented;
private Movie movieRented;


private int getDaysRented(){
return daysRented;
}
public Movie getMovieRented{
return movieRented;
}
}
public class Movie {
private int priceCode;
public static final int CHILDRENS = 2;
public static final int REGUALR = 20;
public static final int NEW_RELEASE = 1;

public int getPriceCode{
return priceCode;
}
}

解释:违反了迪米特法则。Customer的方法调用了Movie类的方法,但Movie不是Customer的朋友。

修改:

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
public class Customer{
Rental rental;
int getNewRentPoint(){
rental.getMovieRentPoint();
}
}
public class Rental{
private int daysRented;
private Movie movieRented;

public int getDaysRented(){
return daysRented;
}

public int getMovieRentPoint{
if(movieRented.getPriceCode == Movie.NEW_RELEASE && this.getDaysRented()){
return 2;
} else{
return 1;
}
}
}
public class Movie {
private int priceCode;
public static final int CHILDRENS = 2;
public static final int REGUALR = 20;
public static final int NEW_RELEASE = 1;

public int getPriceCode{
return priceCode;
}
}

栈的设计

数据结构栈有四个功能:压栈、弹栈、得到栈的大小、得到栈是否为空。Akagi同学使用继承如下设计了栈。

1
2
3
4
5
6
7
8
9
10
public class MyStack extends Vector{
public void push(Object element){
insertElementAt(element,0);
}
public Object pop(){
Object result = firstElement();
removeElementAt(0);
return result;
}
}

解答:违反了LSP。MyStack修改了父类的规格,不能用子类替换父类并起同样的作用

修改:用组合代替继承

1
2
3
4
5
6
7
8
9
10
11
public class MyStack{
Vector vector = new Vector();
public void push(Object element){
vector.insertElement(element, 0);
}
public Object pop(){
Object result = vector.firstElement();
vector.removeElement(0);
return result;
}
}

雇员

1
2
3
4
5
6
7
8
9
public class person{
private string name;
public string getName(){
return name;
}
}
public class employee extends Person(){

}

解答(存疑):违反了LSP。子类应该表达一个特殊类型,而不是一个扮演角色

修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
//组合
public class person{
private string name;
public string getName(){
return name;
}
}
public class employee{
public Person person = new Person();
public string getName(){
person.getName()
}
}

图书管理系统

图书管理系统中有多个借阅者角色。本科生、研究生和教师。所有借阅者都可以借阅图书。教师借阅图书的行为和本科生,研究生略有不同时。当教师希望借阅的某种图书被借空时,系统将自动通知借阅者归还图书,本科生只可借阅普通图书,最多可同时借阅5本;研究生可以最多可同时借阅10本;老师可以借阅20本。Hyoga同学熟悉结构化编程,给出了如下设计。请根据以上借阅图书相关的功能性需求和面向对象的思想,指出Hyoga设计的问题,画出关于借阅者的设计类图,并且写出各个类和借阅相关的属性和方法的定义(不用实现)。

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
37
public class Borrower {
private static final int MAX_FOR_BACHELOR = 5;
private static final int MAX_FOR_MASTER = 10;
private static final int MAX_FOR_TEACHER = 20;

public void borrowBookForBorrower(int i) {
if (i == 0) {
borrowBookForBachelor();
} else if (i == 1) {
borrowBookForMaster();
} else if (i == 2) {
borrowBookForTeacher();
}
}

private void borrowBookForBachelor() {
borrowBook();
}

private void borrowBookForMaster() {
borrowBook();
}

private void borrowBookForTeacher() {
borrowBook();
notifyReturnBook();
}

private void borrowBook() {
// 借书逻辑
}

private void notifyReturnBook() {
// 通知还书逻辑
}
}

解答:

  • 违反OCP:添加借阅者时需要修改borrowBookForBorrower的实现。
  • 单一职责原则

修改:

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
37
38
39
40
41
42
43
44
// 定义借书接口
interface Borrowable {
void borrowBook();
}

// 学士生借书实现类
class BorrowerBachelor implements Borrowable {
private static final int MAX_BOOKS = 5;

@Override
public void borrowBook() {
// 学士生借书逻辑
System.out.println("Borrowing books for bachelor...");
}
}

// 硕士生借书实现类
class BorrowerMaster implements Borrowable {
private static final int MAX_BOOKS = 10;

@Override
public void borrowBook() {
// 硕士生借书逻辑
System.out.println("Borrowing books for master...");
}
}

// 教师借书实现类
class BorrowerTeacher implements Borrowable {
private static final int MAX_BOOKS = 20;

@Override
public void borrowBook() {
// 教师借书逻辑
System.out.println("Borrowing books for teacher...");
notifyReturnBook();
}

private void notifyReturnBook() {
// 通知还书逻辑
System.out.println("Notifying return of books...");
}
}

正方形-长方形

正方形是否可以直接继承长方形?(教材P235)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Rectangle{
int length;
int width;

public int area(){
return length*width;
}

public int getLength() {
return length;
}

public void setLength(int length) {
this.length = length;
}

public int getWidth() {
return width;
}

public void setWidth(int width) {
this.width = width;
}
}

解答:违反LSP。Square会修改Rectangle的接口和实现,不能替代Rectangle。

修改:组合代替继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Square{
Rectangle rectangle;
int edge;
public Square(int edge){
rectangle = new Rectangle(edge,edge);
}
public void setEdge(int edge) {
this.rectangle.setWidth(edge);
this.rectangle.setWidth(edge);
}
public int area(){
return rectangle.area();
}
}

Comparator

解答:

  • 代码格式差,没有缩进
  • 控制耦合
  • 违反单一职责原则
  • 违反OCP

修改:

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
37
38
39
40
41
42
43
44
45
46
47
// 定义比较策略接口
public interface ComparisonStrategy {
boolean compare(double leftOperand, double rightOperand);
}

// 实现具体的比较策略
public class EqualStrategy implements ComparisonStrategy {
public boolean compare(double leftOperand, double rightOperand) {
return leftOperand == rightOperand;
}
}

public class NotEqualStrategy implements ComparisonStrategy {
public boolean compare(double leftOperand, double rightOperand) {
return leftOperand != rightOperand;
}
}

public class LargeEqualStrategy implements ComparisonStrategy {
public boolean compare(double leftOperand, double rightOperand) {
return leftOperand >= rightOperand;
}
}

public class LargeStrategy implements ComparisonStrategy {
public boolean compare(double leftOperand, double rightOperand) {
return leftOperand > rightOperand;
}
}

public class LessEqualStrategy implements ComparisonStrategy {
public boolean compare(double leftOperand, double rightOperand) {
return leftOperand <= rightOperand;
}
}

public class LessStrategy implements ComparisonStrategy {
public boolean compare(double leftOperand, double rightOperand) {
return leftOperand < rightOperand;
}
}

public class NoneStrategy implements ComparisonStrategy {
public boolean compare(double leftOperand, double rightOperand) {
return true;
}
}

雇员2

截屏2019-10-28下午3.02.26

解答:

  • 违背单一职责原则。
  • 产生问题:
    • 代码难以维护,一个类中包含了不同职责的代码,当其中某一部分需要修改时,可能会影响到其他部分。
    • 复用性差:由于一个类承担了过多职责,无法在其他上下文中重用这些逻辑。

修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 员工类,负责管理员工信息
public class Employee {
private String employeeName;
private int employeeNo;
}

// 数据库操作类,负责处理与员工相关的数据库操作
public class EmployeeRepository {
public void insert(Employee employee) {
// 插入员工信息到数据库
}

public Employee findById(int id) {
// 从数据库中根据ID查找员工信息
}
}

// 报告生成类,负责生成员工报告
public class EmployeeReport {
public void generateReport(Employee employee) {
// 生成员工的报告
}
}

Person

截屏2019-10-28下午3.22.16 1

解答:数据和行为不匹配

Point

截屏2019-10-28下午4.38.35

解答:没有集中数据和行为,内聚性低

修改:

1
2
3
4
5
6
public class Point{
private double x;
private double y;
public double calculateDistance(Point b){}
public double calculateDirection(Point c){}
}

计算和显示

image-20240618154052615

解答:违背的单一职责原则

image-20240618154148178

Server

image-20240618154811892

违背接口最小化原则

Application

image-20240618154944077

解答:违背接口最小化原则

image-20240618155010104

银行卡

image-20240618155506917

解答:违反了LSP,子类前置条件要求更多了

image-20240618155558416

乘客和导游

image-20240618160121361

解答:违背了LSP和用组合代替继承原则。Passenger和Agent只是Person扮演的角色,不能替代父类。

修改:组合代替继承

image-20240618160327403

Account

image-20240618160838665

违背了单一职责原则

image-20240618160859055

Route

image-20240618161525903

解答:暴露了内部结构

修改:

image-20240618161510809

Collection

image-20240618161623789

解答:暴露了内部结构—使用List存储。

修改:使用迭代器模式

Shape

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Shape {}
class Square extends Shape {
void drawSquare() {
// draw
}
}
class Circle extends Shape {
void drawCircle() {
// draw
}
}
void drawShapes(List<Shape> shapes) {
for (Shape shape : shapes) {
if (shapes instanceof Square) {
((Square) shapes).drawSquare();
} else if (shape instanceof Circle) {
((Circle) shape).drawCircle();
}
}
}

解答:RTTI(运行时类型信息)违反了LSP和OCP

修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Shape {
void draw();
}
class Square implements Shape {
void draw() {
// draw implementation
}
}
class Circle implements Shape {
void draw() {
// draw implementation
}
}
void drawShapes(List<Shape> shapes) {
for (Shape shape : shapes) {
shape.draw();
}
}

打印机

image-20240618162448788

image-20240618162512163

使用DIP修改

3d7fd18a7f011523610717e43e3214c

顺序图

影片出租店

代码参见:影片出租店

截屏2019-10-26下午7.10.33

修改后的代码:影片出租店

截屏2019-10-26下午7.11.47

ATM

截屏2019-10-26下午8.33.43

收银

image-20240618125439851

买票

image-20240618125601358

销售

image-20240618145908727

image-20240618151238684

大富翁

image-20240618151032374

状态图

销售处理

image-20240618125810098

需求分析

ATM机需求

  • 业务需求:ATM系统上线两个月,银行存取款业务效率提升20%
  • 用户需求:允许用户存款;取款
  • 系统需求:在用户点击退卡时,系统应将银行卡从卡槽中推出
  • 功能需求:系统应提供存款服务
  • 性能需求:所用用户操作在3秒内得到响应
  • 质量需求:在发生网络故障时,系统不能故障
  • 对外接口:系统与数据库系统的接口
  • 约束:用java开发
  • 数据需求:系统要存储一年的操作记录

判断需求

截屏2019-10-28下午2.55.37

  1. 业务需求
  2. 数据需求
  3. 用户需求
  4. 约束
  5. 系统级需求
  • 当用户输入的补货数中含有非数字字符的时候,系统必须提示输入错误
    • 功能需求。描述了用户希望系统能够执行的活动
  • 用户输入的收入金额中不能含有非数字字符
    • 对外接口:描述了系统与用户交互时对用户的输入做出的限制

需求描述正确性

  • 在支付过程完成后,相关信息应追加到日志文件中。
    • 该需求应该重新编写;它是模棱两可或不一致的。没有具体说明哪些相关信息应该记录
  • 系统的构建应该使将来易于添加新功能。
    • 没有说明“易于”的度量标准
  • 汽油购买的价格计算为所购汽油类型的每加仑价格乘以购买的加仑数(使用两位小数表示加仑的小数部分)。
    • 没毛病?(但这个应该是问题域信息)
  • 系统应每天24小时,每周7天可用。
    • 不现实
  • 用户查询界面应该友好
    • 不可验证,修改为:用户完成任何一个查询任务时鼠标点击数都不超过5次

测试

截屏2019-10-26下午7.52.27

有理数

截屏2019-10-26下午8.37.53

白盒黑盒

白盒优点:覆盖率高;发现的缺陷较多

白盒缺点:测试开销大;不能检验需求规格

黑盒优点:测试效率高;可以检验需求规格

黑盒缺点:覆盖率低:发现的缺陷少

人机交互

Eclipse

截屏2019-10-26下午7.57.09

浏览器

分析一款常用的浏览器。请支持至少3条该软件在人机交互方面的有些优点,分析它们体现了哪些人机交互的原则?

解答:(ChatGPT辅助生成)

  • 简洁设计:Chrome 的界面布局和图标设计非常简洁,没有冗余的元素。这样做减少了用户在使用过程中受到的干扰,使用户可以快速找到所需功能。
  • 一致性设计:无论是标签页的位置、设置选项的位置,还是图标的设计,Chrome 都保持了一致性,避免用户在不同平台上使用时感到困惑。这确保了用户的精神模型的一致性,减少了学习成本。
  • 低出错率设计:Chrome 在设计上通过灰色屏蔽不适用的菜单功能、禁止数值输入域出现字母字符等措施,减少了用户犯错的可能性。另外,它还通过输入提示和建议来帮助用户正确操作,进一步降低了出错率。
  • 易记性设计:Chrome 的设计减少了用户的短期记忆负担,通过设置有意义的默认值和使用直观的快捷方式,用户不需要记住大量的信息。逐层递进的方式展示信息,使用户可以一步步找到所需功能,而不必一次性记住所有内容。

eZip

image-20240618141712199

暴露了内部结构

设计模式

个人所得税系统

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
37
38
39
40
41
42
43
44
45
46
47
48
/*
个人所得说系统
用决策表来优化
*/

/*
题目:
小于 10000 tax is 10%
大于 10000 小于 20000 的部分 12%
大于 20000 小于 30000 的部分 15%
大于 30000 小于 40000 的部分 18%
大于 40000 的部分 20%
*/
class Main{
public int calculateTax(int taxable_income){
int tax = 0;
if(taxable_income == 0){
//goto Exit;
return 0;
}
if(taxable_income > 10000){
tax = tax + 1000;//10000以下的那部分税
}else{
tax = tax + taxable_income * 0.1;
//goto Exit;
return tax;
}
if(taxable_income > 20000){
tax = tax + 1200;
}else {//小于20000
tax += (taxable_income-10000)*0.12;
return tax;
}
if(taxable_income > 30000){
tax = tax + 1500;
}else {//小于 30000
tax += (taxable_income-20000)*0.15;
return tax;
}if(taxable_income > 40000){
tax = tax + 1800;
tax += (taxable_income-40000)*0.2;
}else {
tax += (taxable_income-30000)*0.18;
return tax;
}
return tax;
}
}

解答:

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
int[] percent = {0.1, 0.12,0.15,0.18,0.20};
int[] bracket = {0,10000,20000,30000,40000};
int[] base = {0,1000,2200,3700,5500}

public int calculateTax(int taxable_income){
int level = 0;
for(int i=0;i<5;i++){
if(taxable_income >= bracket[i]){
level++;
}
}
level--;
int tax = base[level] +(taxable_income - bracket[level])*percent[level];
return tax;
}

/*
参考表驱动编程 P307
*/

int[] prePoint = {1000,2000,5000};
int[] postPoint = {1000,2000,5000};
int[] levelArray = {1,2,3};
public int calculateLavel(int prePoint ,int postPoint){
for(int i=0;i<3;i++){
if(prePoint < prePoint[i] && postPoint >= postPoint[i]){
return levelArray[i];
}
}
}

求每月天数

1
2
3
4
5
6
class main{
int[] days = {31,28,31,30,31,30,31,31,30,31,30,31}
int getDaysofMonth(int month){
return days[i-1];
}
}

员工支付方式

image-20240618163053823

image-20240618163113059

用策略模式修改

image-20240618163419236

image-20240618163426335

image-20240618163431487

image-20240618163440176-17186996887547

image-20240618163500403

image-20240618163510834

image-20240618163731870

红绿灯

The “dumb” policy: change the green route every 5 seconds
Midnight policy: change to yellow always
Rush hour policy: double the “green time” in the busy route

image-20240618163926583

代码质量

Department

截屏2019-10-27下午9.37.06

  • 易读性差,格式不佳,没有缩进
  • 违反OCP

修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface Employee{}
public class SalariedEmployee implements Employee{

}
public class HourlyEmployee implements Employee{}
public class CommissionedEmployee implements Employee{}

public class Department{
private List<Employee>emplyeeList;
public Department(){
}
public Emplyee addEmployee(Employee employee){
employee.setDepartment(this);
employeeList.add(employee);
return employee;
}

Deposit

下面是银行金融系统中账户类的取款方法 deposit 实现。它在可靠性上存在哪些问题?请应用契约式编程来改善或者防御性编程方法改写方法的代码

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
class BankAccount {
private Double balance; // 账户余额
private Double dailyBalance; // 当日可用额度

/**
* ATM 机取款方法
* 基本功能包括 100 元的整倍数人民币
* 一次取款金额不超过 3000 元
* 一天取款总金额不超过 20000 元
* 账户余额不足,或当日额度不足不能取款
*/

public void deposit(Double num) {
try {
exportCash(num);
balance -= num;
dailyBalance -= num;
} catch (ExportCashException e) {
ExportCashExceptionHandler(e);
}
}

// ATM 机控制的钞口出钞现金纸币
// 包括 100 元的整倍数人民币
// 如果失败抛出 ExportCashException 异常
public void exportCash(Double num) throws ExportCashException { }
}

解答:

  • 取款前未验证

修改:

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
class BankAccount {
private Double balance; // 账户余额
private Double dailyBalance; // 当日可用额度

public void deposit(Double num) {
// 前置条件
assert num % 100 == 0 : "取款金额必须是100的整倍数";
assert num <= 3000 : "一次取款金额不超过3000元";
assert dailyBalance >= num : "当日可用额度不足";
assert balance >= num : "账户余额不足";

try {
exportCash(num);
balance -= num;
dailyBalance -= num;
}
// 后置条件
assert balance >= 0 : "账户余额不能为负";
assert dailyBalance >= 0 : "当日可用额度不能为负";
} catch (ExportCashException e) {
ExportCashExceptionHandler(e);
}
}

// ATM 机控制的钞口出钞现金纸币
// 包括 100 元的整倍数人民币
// 如果失败抛出 ExportCashException 异常
public void exportCash(Double num) throws ExportCashException { }
}

Member

image-20240618173822152

坏味道:太多的方法参数

修改:

image-20240618173726045

image-20240618173814367

体系结构

大小写转换

image-20191027140257021

超市系统MVC

image-20240618133458481

KWIC

image-20240618133917053

image-20240618133924122

超市销售系统

  • 逻辑包图

image-20240618134533096

  • 物理包图

    image-20240618135448954

    image-20240618135459421

  • 进程图

    image-20240618135527737

  • 物理部署图

    image-20240618135543653

审批

image-20240618135947771

  • 写出接口

    image-20240618140010071

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
// 逻辑层接口
ReceiptVO[ ] firstStepShowList(String UserID);//UserID指的是审核⼈是谁
ReceiptDetailVO firstStepShowDetail(String UserID, String receiptID)
void firstExamine(ExamineVo[ ] list);//ExamineVo的内容:{receiptID result}
ReceiptVO[ ] SecondStepShowList(String UserID);//如果只有⼀个总经理,可能这个参数就不需要。
ReceiptDetailVO SecondStepShowDetail(String UserID, String receiptID)
ReceiptDetailVO EditDetail(String UserID, String receiptID)
void SecondExamine(ExamineVo[ ]);

// 数据层接口
interface ExamineLogicalService {
Pair<Bollean, Bill> examine(Bill bill);
List<Bill> showTodoBills(SecurityLevel securityLevel);
BillDetail showBillDetail(Bill bill);
void editBill(Bill bill, Inf inf);
Pair<Bollean, List<Bill>> tagBillsAndExamine(List<Bill> bills);
}
interface ExamineDataService {
Exception setPassed(Bill bill, Bollean passed);
Exception setInf(Bill bill, Inf inf);
}
interface BillDataService{
List<Bill> getTodoBills(SecurityLevel securityLevel);
BillDetail getBillDetail(Bill bill);
}

名词解释

软件工程
软件演化生命周期模型
螺旋模型
软件验证与确认
增量开发模型和迭代开发模型
演化模型
逆向工程与正向工程的区别,以及其关注点
  • 软件开发阶段(正向工程)会书写清晰的文档,留下可读性较好的程序源代码
  • 逆向工程可能没有文档,甚至没有源代码;基本原理是抽取软件系统的需求与设计而隐藏实现细节,然后在需求和设计的层次上描述软件系统,以建立对系统更加准确和清晰的理解
  • 正向工程:模型转换为代码
  • 逆向工程:代码转换为模型