1.1重构-第一章

重构是指在不改变软件外部行为的情况下,优化其内部结构。一个影片出租店的程序展示了重构的必要性,因为原本的`statement()`方法职责过重,导致扩展困难。当需要输出HTML格式详单或更改计费规则时,复制粘贴代码会带来维护难题。重构旨在应对这种变化,确保代码的可维护性和灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

重构是在不改变软件可观察行为的前提下改善其内部结构。

设计模式为重构提供了目标

1.1 起点

这是一个影片出租店用的程序,计算每一个顾客的消费金额并打印详单。操作者告诉程序:租客租了哪些影片租期多长,程序便根据租赁时间和影片类型算出费用。影片分为三类:普通片、儿童片、新片。除了计算费用,还要为常客计算积分,积分会根据租片种类是否为新片而有不同。


Movie(影片)- movie只是一个简单的纯数据类

/**
 * 影片
 * @author harry
 */
public class Movie {

	// 影片类型
	public static final int REGULAR = 0;//普通片
	public static final int NEW_RELEASE = 1;//新片
	public static final int CHILDRENS = 2;//儿童片
	
	private String _title;
	private int _priceCode;
	
	public Movie(String _title, int _priceCode) {
		super();
		this._title = _title;
		this._priceCode = _priceCode;
	}
	
	public String get_title() {
		return _title;
	}
	public void set_title(String _title) {
		this._title = _title;
	}
	public int get_priceCode() {
		return _priceCode;
	}
	public void set_priceCode(int _priceCode) {
		this._priceCode = _priceCode;
	}
}
Rental(租赁)- rental表示某个顾客租了一部影片

/**
 * 租赁实体
 * @author harry
 */
public class Rental {
	private Movie _movie;
	private int _dayRented;
	
	public Rental(Movie _movie, int _dayRented) {
		super();
		this._movie = _movie;
		this._dayRented = _dayRented;
	}
	
	public Movie get_movie() {
		return _movie;
	}
	public void set_movie(Movie _movie) {
		this._movie = _movie;
	}
	public int get_dayRented() {
		return _dayRented;
	}
	public void set_dayRented(int _dayRented) {
		this._dayRented = _dayRented;
	}
}
Customer(顾客)-customer表示顾客。与其他类一样,它也拥有数据和相应的访问函数

/**
 * 顾客实体
 * @author harry
 */
public class Customer {
	private String _name;
	private Vector<Rental> _rentals = new Vector<>();
	public Customer(String _name) {
		super();
		this._name = _name;
	}
	
	public void addRental(Rental arg){
		_rentals.addElement(arg);
	}
	public String getName() {
		return _name;
	}
	public void setName(String _name) {
		this._name = _name;
	}
	// 生成详情单
	public String statement(){
		double totalAmount = 0;//总金额
		int frequentRenterPoints = 0;//本次总积分
		
		Enumeration<Rental> rentals = _rentals.elements();
		// 租赁备案
		String result = "Rental Record for "+getName()+"\n";
		while(rentals.hasMoreElements()){
			double thisAmount = 0;
			Rental each = rentals.nextElement();
			
			// 计算金额
			switch(each.get_movie().get_priceCode()){
				case Movie.REGULAR:
					thisAmount += 2;
					if (each.get_dayRented() > 2) {
						thisAmount += (each.get_dayRented()-2)*1.5;
					}
					break;
				case Movie.NEW_RELEASE:
					thisAmount += each.get_dayRented()*3;
					break;
				case Movie.CHILDRENS:
					thisAmount += 1.5;
					if (each.get_dayRented() > 3) {
						thisAmount += (each.get_dayRented()-3)*1.5;
					}
					break;
			}
			
			// 常规积分累加
			frequentRenterPoints++;
			// 特殊新书积分计算
			if (each.get_movie().get_priceCode() == Movie.NEW_RELEASE &&
				each.get_dayRented() > 1) {
				frequentRenterPoints++;
			}
			
			// 显示凭条
			result += "\t"+each.get_movie().get_title()+"\t"+String.valueOf(thisAmount)+"\n";
			totalAmount += thisAmount;
		}
		
		// 组装页脚
		result += "Amount owed is "+String.valueOf(totalAmount)+"\n";
		result += "You earned "+String.valueOf(frequentRenterPoints)+" frequent renter points";
		return result;
	}
}

对起始程序的评价

         Customer里的长长的statement()做的事情太多了,它做了原本应该由其他类完成的事情。

         在这个例子中,如果用户希望以HTML格式输出详情单。上述程序中的statement()无法复用,唯一可以做就是编写一个全新的htmlStatement()。大量重复statement()的行为。当然这个还不是太费力,你可以把statement()复制一份按需求修改就可以了。

         但是如果计费标准发生变化,又会如何呢?你必须同时修改statement()和htmlStatement(),并确保两处修改一致。当你后续还要修改的时候,复制粘贴带来的问题就会浮现出来。如果你编写的是一个永远不会修改的程序,那么剪剪粘粘就还好,但如果程序要保存很长时间,而且可能需要修改,复制粘贴行为就会造成潜在威胁。

         现在第二个变化来了:用户希望改变影片分类规则,但是还没有决定怎么改。他们设想了几种方案,这些方案都会影响顾客消费和常客积分点的计算方式,作为一个经验丰富的开发者,你可以肯定:不论用户提出什么方案,你唯一能够获得的保证就是他们一定会在六个月之内再次修改它。

         为了应付分类规则和计费规则的变化,程序必须对statement()做出修改,但如果我们把statement()内的代码复制到用以打印HTML详单的函数中,就必须确保将来在任何修改这两个地方保持一致,随着各种规则变的越来越复杂,适当的修改点越来越难,不犯错的机会越来越少。

         接下来重构技术就该粉墨登场了。

### 如何在 WPS 中为公式添加类似 1.1 的自动编号 要在 WPS 文档中实现公式的自动编号功能,可以按照以下方法操作: #### 设置公式编号 通过使用域代码以及标题样式来创建带有章节号的公式编号。 1. **设置标题样式** 首先需要确保文档中的章节标题已经应用了合适的标题样式(如“标题1”)。这一步是为了让后续生成的编号能够基于这些标题进行分章管理。例如,如果第一章名为“绪论”,则应将其格式设为“标题1”[^3]。 2. **插入交叉引用作为公式编号的基础** 当完成标题样式的设定之后,在需要插入公式的位置按下 `Alt+F9` 显示隐藏的域代码区域。接着执行以下步骤: - 将光标放置于想要插入公式的地方; - 转到菜单栏上的【插入】-> 【题注】按钮组 -> 点击【题注】命令; - 在弹出对话框里的标签列表选择「方程」或其他适合名称,勾选『包含章节号』选项并确认当前使用的章节层次结构与实际相符; - 单击确定后即会在指定位置生成形如 `(1-1)` 或者 `[1.1]` 这样的初始序号形式[^4]。 3. **调整编号显示格式** 如果默认产生的编号不符合预期需求,则可以通过修改题注格式来自定义其外观。具体做法是在上述提到过的【题注】窗口底部找到“新建标签(项)”或编辑现有标签的功能入口,进而更改括号类型、连接符等内容直至满足个人偏好为止。 4. **利用书签更新整个文档内的所有关联引用关系** 对于那些已经被标注好的地方,一旦原始数据发生变化比如新增了一节新的内容导致原有次序被打乱等情况发生时,就需要重新同步一遍整体布局才能保证最终呈现效果的一致性和准确性。此时只需全选整篇文档(`Ctrl+A`)然后再次按快捷键组合`F9`,这样就能快速刷新全部动态字段值包括我们的公式序列号在内的各项信息啦! ```python # 示例 Python 伪代码展示如何批量处理文件夹内多个 Word 文件中的公式编号逻辑 (仅作参考用途) import os from docx import Document def update_equation_numbers(folder_path): for filename in os.listdir(folder_path): if not filename.endswith('.docx'): continue document = Document(os.path.join(folder_path, filename)) # 假定每一段落可能含有一个公式及其对应的编号 for paragraph in document.paragraphs: if 'EQ \\r' in paragraph.text: # 判断是否存在特定域代码 run = paragraph.runs[0] field_code = run._element.xml # 解析并重构域代码以适应新规则 new_field_code = modify_field_code(field_code) # 替换旧版域代码 run._element.getparent().remove(run._element) paragraph.add_run(new_field_code) document.save(filename) update_equation_numbers('your_folder_path') ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值