重構 -改變既有代碼的設計 ---- 筆記


這是一篇《重構 》的總結 ,我在學習的同時並使用它作為參考。這不是一本書的替代品,所以你要想真的想學習里面的內容,買一本書使用這個文章作為參考和指南。

另外: 建議 評論 還 PR 都是十分歡迎的

1. TABLE OF CONTENT

目錄

3. BAD SMELLS IN CODE(代碼的壞味道)

1. Duplicated code (重復的代碼)

多個地方使用相同的代碼

2. Long Method(很長的方法)

一個很長的程序是很難被理解的

3. Large Classes(超級大的類)

當一個類變的越來越大的時候,是難以閱讀的

4. Long Parameter List(長參數列表)

長參數是很難去理解,不符合也較難使用

5. Divergent Change(發散的改變)

當一個類經常因為不同原因發生在不同的方向發生變化

6. Shotgun Surgery(散彈槍修改)

每次你做一小部分修改時,都不的不需要做大量的修改在很多不同的類中

7. Feature Envy(依戀情結)

某個方法似乎在另外的類的興趣高於自己所處的類

8. Data Clumps(數據泥團)

一堆數據雜糅在一起(字段, 參數)

9. Primitive Obsession(基本類型偏執)

使用基本類型替代小對象

10. Switch Statements(Switch 驚悚現身)

當程序中出現很多 switch 語句在很多地方,使用多態來進行替換

11. Parallel Inheritance Hierarchies(平行繼承類)

每當你為一個類增加一個子類,你不得不為另一個類增加相應的一個子類

12. Lazy Class(冗余類)

當一個類不足與為其自身買單它就應該被刪除

13. Speculative Generality(誇誇其談未來性)

所有的鈎子和特殊情況處理那些不需要的

14. Temporary Field(令人迷惑的臨時變量)

一個臨時變量僅僅為某種特殊情況做而定,這樣的代碼讓人難以理解

15. Message Chain(過長的消息鏈)

當一個類請求調用一個對象,但是這個類又在調用其他的方法

16. Middle Man(中間人)

當一個對象委托大部分功能,開發中可能會過度的使用委托模式,導致某個類中的方法大都委托給其他方法處理

17. Inappropriate Intimacy(不恰當的親密關系)

當兩個類過度的親密,需要將其拆散

18. Alternative Classes with Different Interfaces(異曲同工的類)

類的方法過度相似

19. Incomplete Library Class(不完美的庫)

當我們使用外部依賴庫時

20. Data Class(數據類)

不要操作數據類,我們通過封裝它的不變性

21. Refused Bequest(被拒絕的遺贈)

子類不想使用父類的方法

22. Comments(過多沒用的注釋)

當一個方法使用過度的注釋解釋其中的邏輯時,說明這個方法應該被重構了。

6. COMPOSING METHODS(重新組織函數))

1. Extract Method(提煉函數)

你可以將一些代碼組合起來,然后放到一個方法中


	void printOwing(double amount) {
		printBanner();
		//print details
		System.out.println ("name:" + _name);
		System.out.println ("amount" + amount);
	}

to


	void printOwing(double amount) {
		printBanner();
		printDetails(amount);
	}

	void printDetails (double amount) {
		System.out.println ("name:" + _name);
	System.out.println ("amount" + amount);
	}

動機

  • 增加代碼被復用的機會
    * 閱讀方法就像閱讀一系列聲明一樣簡單

	void printOwing(double previousAmount) {
		Enumeration e = _orders.elements();
		double outstanding = previousAmount * 1.2;
		printBanner();

		// calculate outstanding
		while (e.hasMoreElements()) {
			Order each = (Order) e.nextElement();
			outstanding += each.getAmount();
		}
		printDetails(outstanding);
	}

to


	void printOwing(double previousAmount) {
		printBanner();
		double outstanding = getOutstanding(previousAmount * 1.2);
		printDetails(outstanding);
	}

	double getOutstanding(double initialValue) {
		double result = initialValue;
		Enumeration e = _orders.elements();

		while (e.hasMoreElements()) {
			Order each = (Order) e.nextElement();
			result += each.getAmount();
		}
		return result;
	}

2. Inline Method (內聯函數)

一個函數本體和函數名稱一樣容易理解


	int getRating() {
		return (moreThanFiveLateDeliveries()) ? 2 : 1;
	}

	boolean moreThanFiveLateDeliveries() {
		return _numberOfLateDeliveries > 5;
	}

to


	int getRating() {
		return (_numberOfLateDeliveries > 5) ? 2 : 1;
	}

動機

  • 當間接不是必要的時候
  • 當一組方法被嚴格的分解,會使這個方法變得清晰

3. Inline Temp (內聯臨時變量)

你申明了一個臨時的變量在一段表達里面,然后臨時的變量將會阻擋你重構


	double basePrice = anOrder.basePrice();
	return (basePrice > 1000)

to


	return (anOrder.basePrice() > 1000)

Motivation

4. Replace Temp with Query (以查詢取代臨時變量)

你正在使用臨時變量來保存表達式的結果


	double basePrice = _quantity * _itemPrice;
	if (basePrice > 1000){
		return basePrice * 0.95;
	}
	else{
		return basePrice * 0.98;
	}

to


	if (basePrice() > 1000){
		return basePrice() * 0.95;
	}
	else{
		return basePrice() * 0.98;
	}
	...
	double basePrice() {
		return _quantity * _itemPrice;
	}

動機

  • 使用方法代替臨時變量,類中的任何方法都可以獲取信息
  • 這是一個十分重要的步驟在其之前1. Extract Method

5. Introduce Explaining Variable (引入解釋性變量)

有一個復雜的表達式


	if ( (platform.toUpperCase().indexOf("MAC") > -1) &&
		(browser.toUpperCase().indexOf("IE") > -1) &&
		wasInitialized() && resize > 0 )
	{
		// do something
	}

to


	final boolean isMacOs = platform.toUpperCase().indexOf("MAC") >-1;
	final boolean isIEBrowser = browser.toUpperCase().indexOf("IE") >-1;
	final boolean wasResized = resize > 0;
	if (isMacOs && isIEBrowser && wasInitialized() && wasResized) {
		// do something
	}

動機

  • 當一個表達式難以理解時

6. Split Temporary Variable (分解臨時變量)

你能有一個臨時變量聲明不止一次,但是它不是循環體中的變量或者要被存儲的變量


	double temp = 2 * (_height + _width);
	System.out.println (temp);
	temp = _height * _width;
	System.out.println (temp);

to


	final double perimeter = 2 * (_height + _width);
	System.out.println (perimeter);
	final double area = _height * _width;
	System.out.println (area);

動機

  • 變量不應該有多次的聲明
  • 使用臨時變量在兩次不同的地方,是閱讀者十分迷惑的

7. Remove Assignments to Parameters(移除對參數的賦值)

下的代碼將對參數進行了賦值


	int discount (int inputVal, int quantity, int yearToDate) {
		if (inputVal > 50) {
			inputVal -= 2;
		}
	}

to


	int discount (int inputVal, int quantity, int yearToDate) {
		int result = inputVal;
		if (inputVal > 50) {
			result -= 2;
		}
	}

動機

  • 改變內部的對象時可以的,但是不能將這個對象指向別的對象
  • 參數的作用僅僅是表達傳遞對象

8. Replace Method with Method Object (以函數對象取代函數)

將這個函數放進一個單獨的對象,如此一來局部變量就變成對象內部的字段,然后你可以在同一個對象中將這個大型函數分解為多個小型函數


	class Order...
		double price() {
			double primaryBasePrice;
			double secondaryBasePrice;
            double tertiaryBasePrice;
            // long computation;
			...
		}

to


	class Order...
		double price(){
			return new PriceCalculator(this).compute()
		}
	}

	class PriceCalculato...
	compute(){
		double primaryBasePrice;
		double secondaryBasePrice;
		double tertiaryBasePrice;
		// long computation;
		return ...
	}

動機

  • 當一個方法有很多的本地變量時進行分解時不容易的

它的樣本本不應該這樣的重構,但是為顯示這樣做的方法


	Class Account
		int gamma (int inputVal, int quantity, int yearToDate) {
			int importantValue1 = (inputVal * quantity) + delta();
			int importantValue2 = (inputVal * yearToDate) + 100;
			if ((yearToDate - importantValue1) > 100)
			importantValue2 -= 20;
			int importantValue3 = importantValue2 * 7;
			// and so on.
			return importantValue3 - 2 * importantValue1;
		}
	}

to


	class Gamma...
		private final Account _account;
		private int inputVal;
		private int quantity;
		private int yearToDate;
		private int importantValue1;
		private int importantValue2;
		private int importantValue3;

		Gamma (Account source, int inputValArg, int quantityArg, int yearToDateArg) {
			_account = source;
			inputVal = inputValArg;
			quantity = quantityArg;
			yearToDate = yearToDateArg;
		}

		int compute () {
			importantValue1 = (inputVal * quantity) + _account.delta();
			importantValue2 = (inputVal * yearToDate) + 100;
			if ((yearToDate - importantValue1) > 100)
			importantValue2 -= 20;
			int importantValue3 = importantValue2 * 7;
			// and so on.
			return importantValue3 - 2 * importantValue1;
		}

		int gamma (int inputVal, int quantity, int yearToDate) {
			return new Gamma(this, inputVal, quantity,yearToDate).compute();
		}

9. Substitute Algorithm (算法替換)

你想更換一個更為清晰高效的算法


	String foundPerson(String[] people){
		for (int i = 0; i < people.length; i++) {
			if (people[i].equals ("Don")){
				return "Don";
			}
			if (people[i].equals ("John")){
				return "John";
			}
			if (people[i].equals ("Kent")){
				return "Kent";
			}
		}
		return "";
	}

to


	String foundPerson(String[] people){
		List candidates = Arrays.asList(new String[] {"Don", "John","Kent"});
	for (int i = 0; i<people.length; i++)
		if (candidates.contains(people[i]))
			return people[i];
	return "";
	}

動機

  • 打破一些復雜的概念
  • 使算法更容易修改
  • 替換一個大的復雜的算法是十分困難的,讓算法變的簡單更容易對算法進行替換

7. Moving features between elements(移動對象)

10. Move method (移動方法)

在進行方法的初始定義的時候要想下以后會不會有其他的類也將會用到它

一個類它通常會創建一個新的簡單的方法體, 同時它會將9⃣舊的方法做一個簡單的委托或者移除它


	class Class1 {
		aMethod()
	}

	class Class2 {	}

to


	class Class1 {	}

	class Class2 {
		aMethod()
	}

動機

當一個類做了很多工作,或者這個類過度的耦合

11. Move field (移動字段)

當一個字段被定義的時候,可能不僅被不止一個類使用。
_創建一個字段在一個目標類中,然后改變所有的擁有者 _


	class Class1 {
		aField
	}

	class Class2 {	}

to


	class Class1 {	}

	class Class2 {
		aField
	}

動機

如果一個字段被超過多個類引用

12. Extract Class (提取類)

你有一個類,但是這個類做了它份外的事情

_創建一個新的類,然后將相關字段移入到新的類中 _


	class Person {
		name,
		officeAreaCode,
		officeNumber,
		getTelephoneNumber()
	}

to


	class Person {
		name,
		getTelephoneNumber()
	}

	class TelephoneNumber {
		areaCode,
		number,
		getTelephoneNumber()
	}

動機

類隨着業務的增長在變化

在合適的時候進行分解它

  • 相似的方法組合在一起
  • 數據子集通常一起變化或者相互依賴

13. Inline Class (一致的類)

一個其實沒做多少事情的類

_將這個類整合到另外一個類中,然后刪除這個類 _


	class Person {
		name,
		getTelephoneNumber()
	}

	class TelephoneNumber {
		areaCode,
		number,
		getTelephoneNumber()
	}

to


	class Person {
		name,
		officeAreaCode,
		officeNumber,
		getTelephoneNumber()
	}

動機

在重構的時候將這個類的基本信息移入到另外一個類中,然后在移除這個類

14. Hide Delegate (隱藏委托)

客戶端其實調用的是對象的委托類
在服務端創建一個方法,然后隱藏這個委托類


	class ClientClass {
		//Dependencies
		Person person = new Person()
		Department department = new Department()
		person.doSomething()
		department.doSomething()
	}

to


	class ClientClass {
		Person person = new Person()
		person.doSomething()
	}

	class Person{
		Department department = new Department()
		department.doSomething()
	}

解決方法


	class ClientClass{
		Server server = new Server()
		server.doSomething()
	}

	class Server{
		Delegate delegate = new Delegate()
		void doSomething(){
			delegate.doSomething()
		}
	}
	//委托類其實隱藏在客戶類里面
	// 改變不會傳播到客戶端那邊,因為它之后影響到服務端這邊
	class Delegate{
		void doSomething(){...}
	}

動機

關鍵在於封裝
類應該盡量的使用其他的類
> manager = john.getDepartment().getManager();




	class Person {
		Department _department;
		public Department getDepartment() {
			return _department;
		}
		public void setDepartment(Department arg) {
			_department = arg;
			}
		}

	class Department {
		private String _chargeCode;
		private Person _manager;
		public Department (Person manager) {
			_manager = manager;
		}
		public Person getManager() {
			return _manager;
		}
		...

to

> manager = john.getManager();


	class Person {
		...
		public Person getManager() {
			return _department.getManager();
		}
	}

15. Remove Middle Man (移除中間人)

一個類通過代理干了太多的事情
讓客戶直接調用委托


	class ClientClass {
		Person person = new Person()
		person.doSomething()
	}

	class Person{
		Department department = new Department()
		department.doSomething()
	}

to


	class ClientClass {
		//Dependencies
		Person person = new Person()
		Department department = new Department()
		person.doSomething()
		department.doSomething()
	}

動機

當客戶類使用過多的中間人調用委托的方法

16. Introduce Foreign Method (引入外加的函數)

一個類是引用的外部開源包,但是不能修改其內部的邏輯
創建一個新的方法在這個類中,並以第一個參數的形式傳入一個服務類實例


	Date newStart = new Date(previousEnd.getYear(),previousEnd.getMonth(),previousEnd.getDate()+1);

to


	Date newStart = nextDay(previousEnd);

	private static Date nextDay(Date date){
		return new Date(date.getYear(),date.getMonth(),date.getDate()+1);
	}

動機

當你使用一個類,這個類你又不能對其進行修改的時候可以采用這樣方式

17. Introduce Local Extension (引入本地擴展)

你需要為一個服務類提供一些額外的方法,但是你無法修改這個子類
創建一個新的類,使它包含這些額外的方法。這個擴展的類成為源類的子類或者包裝類


	class ClientClass(){

		Date date = new Date()
		nextDate = nextDay(date);

		private static Date nextDay(Date date){
			return new Date(date.getYear(),date.getMonth(),date.getDate()+1);
		}
	}

to


	class ClientClass() {
		MfDate date = new MfDate()
		nextDate = nextDate(date)
	}
	class MfDate() extends Date {
		...
		private static Date nextDay(Date date){
			return new Date(date.getYear(),date.getMonth(),date.getDate()+1);
		}
	}

動機

當我們使用 16. Introduce Foreign Method 我們需要在這個類中添加額外的方法

8. ORGANIZING DATA (組織數據)

18. Self Encapsulate Field (對字段獲取進行封裝)

你可以直接獲取對象,但是這樣的話會變得越來越復雜
通過創建setting getting 方法來獲取這些字段


	private int _low, _high;
	boolean includes (int arg) {
		return arg >= _low && arg <= _high;
	}

to


	private int _low, _high;
	boolean includes (int arg) {
		return arg >= getLow() && arg <= getHigh();
	}
	int getLow() {return _low;}
	int getHigh() {return _high;}

動機

允許子類可以覆蓋如何get方法,並且這樣的話它更加的支持靈活的管理,例如延遲加載

19. Replace Data Value with Object (用對象替換數據值)

當你有個數據項需要進行添加數據或行為
將數據項轉換為對象


	class Order...{
		private String _customer;
		public Order (String customer) {
			_customer = customer;
		}
	}

to


	class Order...{
		public Order (String customer) {
			_customer = new Customer(customer);
		}
	}

	class Customer {
		public Customer (String name) {
			_name = name;
		}
	}

動機
簡單的數據對象並不簡單

20. Change Value to Reference (將值改為引用)

你有個類擁有很多單個對象,這些對象需要用一個單獨的對象替代
將這個對象轉換為引用對象


	class Order...{
		public Order (String customer) {
			_customer = new Customer(customer);
		}
	}

	class Customer {
		public Customer (String name) {
			_name = name;
		}
	}

to


	//Use Factory Method
	//使用工廠方法
	class Customer...
		static void loadCustomers() {
			new Customer ("Lemon Car Hire").store();
			new Customer ("Associated Coffee Machines").store();
			new Customer ("Bilston Gasworks").store();
		}
		private void store() {
			_instances.put(this.getName(), this);
		}
		public static Customer create (String name) {
			return (Customer) _instances.get(name);
		}

動機
引用對象是類似於消費者或者賬單這樣的對象,每個對象代表這一類對象在一個真實的世界,並使用對象表示來測試它們是否相同

21. Change Reference to Value (將引用改為值)

你有一個有一個引用對象是很小,不變,難以管理的
將其轉換為值對象


	new Currency("USD").equals(new Currency("USD")) // returns false

to


	new Currency("USD").equals(new Currency("USD")) // now returns true

動機
使用引用對象是變得越來月復雜,並且引用對象是不變和單一的。尤其在分布式和並發系統中

22. Replace Array with Object (用對象代替數組)

你擁有一個數組,其中這些元素是不同的
使用一個對象來替換這個數組,將數組的元素賦值在對象的屬性上


	String[] row = new String[3];
	row [0] = "Liverpool";
	row [1] = "15";

to


	Performance row = new Performance();
	row.setName("Liverpool");
	row.setWins("15");

動機

數組應該被用在一些相似的集合對象序列中

23. Duplicate Observed Data (監控數據對象)

可能你有一些domain數據是通過GUI控制的與此同時這些domain數據是需要訪問的
往一些實體對象復制一些數據,通過設置觀察者來同步兩部分數據

動機
為了將代碼從用戶界面分解到業務處理層

24. Change Unidirectional Association to Bidirectional(將單向聯系改為雙向聯系)

你有兩個對象,這兩個對象需要使用對方的特征屬性,但是目前只有一種連接方式
添加返回指針,然后更改修飾符已更改兩個對象


	class Order...
		Customer getCustomer() {
			return _customer;
		}
		void setCustomer (Customer arg) {
			_customer = arg;
		}
		Customer _customer;
	}

to


	class Order...
		Customer getCustomer() {
			return _customer;
		}
		void setCustomer (Customer arg) {
			if (_customer != null) _customer.friendOrders().remove(this);
			_customer = arg;
			if (_customer != null) _customer.friendOrders().add(this);
		}
		private Customer _customer;

		class Customer...
			void addOrder(Order arg) {
				arg.setCustomer(this);
			}
			private Set _orders = new HashSet();

			Set friendOrders() {
				/** should only be used by Order */
				return _orders;
			}
		}
	}

	// Many to Many
	class Order... //controlling methods
		void addCustomer (Customer arg) {
			arg.friendOrders().add(this);
			_customers.add(arg);
		}
		void removeCustomer (Customer arg) {
			arg.friendOrders().remove(this);
			_customers.remove(arg);
		}
	class Customer...
		void addOrder(Order arg) {
			arg.addCustomer(this);
		}
		void removeOrder(Order arg) {
			arg.removeCustomer(this);
		}
	}

動機
當對象引用需要互相引用的時候,你應該采用這種方法

25. Change Bidirectional Association to Unidirectional (將單向改為雙向的聯系)

當你有個雙向聯系的類,但是在后期一個類不在需要另一個類中的屬性了
扔掉不需要的聯系

動機
當雙向聯系不在需要,減少復雜度,移除僵屍對象,消除相互依賴

26. Replace Magic Number with Symbolic Constant (使用符號來替代魔法字符串)

你有一個特定含義的字符串

創建一個常量,名稱根據它的意思命名然后替換那個數字


	double potentialEnergy(double mass, double height) {
		return mass * 9.81 * height;
	}

to


	double potentialEnergy(double mass, double height) {
		return mass * GRAVITATIONAL_CONSTANT * height;
	}
	static final double GRAVITATIONAL_CONSTANT = 9.81;

動機
避免使用魔法數字

27. Encapsulate Field (封裝字段)

這里有一個公共的字段
將它改為私有的並提供訪問函數


	public String _name

to


	private String _name;
	public String getName() {
		return _name;
	}
	public void setName(String arg) {
		_name = arg;
	}

動機
你應該將你數據公開

28. Encapsulate Collection (封裝集合)

一個返回幾個的方法
確保返回一個只讀的影像對象,然后提供添加和移除方法


	class Person {
		Person (String name){
			HashSet set new HashSet()
		}
		Set getCourses(){}
		void setCourses(:Set){}
	}

to


	class Person {
		Person (String name){
			HashSet set new HashSet()
		}
		Unmodifiable Set getCourses(){}
		void addCourses(:Course){}
		void removeCourses(:Course){}
	}

動機

  • 封裝減少了擁有類和和其客戶端的耦合
  • getter 方法不應該返回集合的本身
  • getter方法應返回對集合進行操作的內容並隱藏其中不必要的細節
  • 這個幾個不應該有setter方法,只能添加和移除操作

29. Remove Record with data class (在數據類中移除 記錄值)

你必須面對一個記錄值在傳統的編程環境中
使用一個僵屍數據對象代替記錄值

動機

  • 復制一個傳奇代碼
  • 使用傳統的API編程和數據庫來代替一個記錄值

30. Replace Type Code with Class (使用類來替代類別代碼)

一個類擁有數字類別碼,不能影響其行為
使用類來代替數字


	class Person{
		O:Int;
		A:Int;
		B:Int;
		AB:Int;
		bloodGroup:Int;
	}

to


	class Person{
		bloodGroup:BloodGroup;
	}

	class BloodGroup{
		O:BloodGroup;
		A:BloodGroup;
		B:BloodGroup; 
		AB:BloodGroup;
	}

動機
靜態類別檢查

31. Replace Type Code with Subclasses (使用子類來替代類別代碼)

在一個類中擁有一個不變的類型碼影響整個類的行為
使用子類來替代這個不變的類型碼


	class Employee...
		private int _type;

		static final int ENGINEER = 0;
		static final int SALESMAN = 1;
		static final int MANAGER = 2;

		Employee (int type) {
			_type = type;
		}
	} 

to


	abstract int getType();
	static Employee create(int type) {
		switch (type) {
			case ENGINEER:
				return new Engineer();
			case SALESMAN:
				return new Salesman();
			case MANAGER:
				return new Manager();
			default:
				throw new IllegalArgumentException("Incorrect type code value");
		}
	}

動機

  • 執行不同的代碼邏輯取決於這個type的值
  • 當每個type對象有着唯一的特征
  • 應用於架構體

32. Replace Type Code with State/Strategy(通過狀態模式或者策略模式來代替類型碼)

在類中有個類型碼,並通過這個類型碼來影響行為,但是你不能使用子類
通過狀態對象來代替這個類型碼


	class Employee {
		private int _type;

		static final int ENGINEER = 0;
		static final int SALESMAN = 1;
		static final int MANAGER = 2;

		Employee (int type) {
			_type = type;
		}
		int payAmount() {
			switch (_type) {
				case ENGINEER:
					return _monthlySalary;
				case SALESMAN:
					return _monthlySalary + _commission;
				case MANAGER:
					return _monthlySalary + _bonus;
				default:
					throw new RuntimeException("Incorrect Employee");
				}
			}
		}
	}

to


	class Employee...
		static final int ENGINEER = 0;
		static final int SALESMAN = 1;
		static final int MANAGER = 2;

		void setType(int arg) {
			_type = EmployeeType.newType(arg);
		}
		class EmployeeType...
			static EmployeeType newType(int code) {
				switch (code) {
					case ENGINEER:
						return new Engineer();
					case SALESMAN:
						return new Salesman();
					case MANAGER:
						return new Manager();
					default:
						throw new IllegalArgumentException("Incorrect Employee Code");
				}
			}
		}
		int payAmount() {
			switch (getType()) {
				case EmployeeType.ENGINEER:
					return _monthlySalary;
				case EmployeeType.SALESMAN:
					return _monthlySalary + _commission;
				case EmployeeType.MANAGER:
					return _monthlySalary + _bonus;
				default:
					throw new RuntimeException("Incorrect Employee");
			}
		}
	}

動機

  • 31. Replace Type Code with Subclasses 是相似的,但是它可以使用在類型碼發生了改變在對象的生命周期發生了變化或者另一個原因阻止了子類的變化,則可以使用它
  • 它通常是和狀態模式或者策略模式配合使用

32. Replace Subclass with Fields(用字段代替子類)

你的子類僅在返回常數變量數據變量的方法中有所不同
將這個方法提升到父類中,並移除這個子類


	abstract class Person {
		abstract boolean isMale();
		abstract char getCode();
		...
	}
	class Male extends Person {
			boolean isMale() {
			return true;
		}
		char getCode() {
			return 'M';
		}
	}
	class Female extends Person {
		boolean isMale() {
			return false;
		}
		char getCode() {
			return 'F';
		}
	}

to


	class Person{
		protected Person (boolean isMale, char code) {
			_isMale = isMale;
			_code = code;
		}
		boolean isMale() {
			return _isMale;
		}
		static Person createMale(){
			return new Person(true, 'M');
		}
		static Person createFemale(){
			return new Person(false, 'F');
		}
	}

動機

  • 當子類的某個方法不足與繼續存在
  • 將這個子類徹底刪除,並將這個字段上移到父類中
  • 刪除額外的子類

9. SIMPLIFYING CONDITIONAL EXPRESSIONS(簡化條件表達式)

33. Decompose Conditional (分解條件)

你有一個復雜的條件(大量的if else then )
使用額外的方法代替這個表達式,將then 放在一部分,else 放在一部分


	if (date.before (SUMMER_START) || date.after(SUMMER_END))
		charge = quantity * _winterRate + _winterServiceCharge;
	else 
		charge = quantity * _summerRate;

to


	if (notSummer(date))
		charge = winterCharge(quantity);
	else 
		charge = summerCharge (quantity);

動機

  • 將條件表達式高亮,這樣的話你能清楚將其分開
  • 對分叉之后的結果進行高亮

34. Consolidate Conditional Expression


	double disabilityAmount() {
	if (_seniority < 2) return 0;
	if (_monthsDisabled > 12) return 0;
	if (_isPartTime) return 0;
	// compute the disability amount

to


	double disabilityAmount() {
	if (isNotEligableForDisability()) return 0;
	// compute the disability amount

35. Consolidate Duplicate Conditional Fragments (合並重復的條件片段)

在條件表達式的每個分支上有着相同的一片代碼
將這段重復代搬移到條件表達式之外


	if (isSpecialDeal()) {
		total = price * 0.95;
		send();
	}
	else {
		total = price * 0.98;
		send();
	}

to


	if (isSpecialDeal()) {
		total = price * 0.95;
	}
	else {
		total = price * 0.98;
	}
	send();

動機
使得變量清晰並保持相同

36. Remove Control Flag (移除控制標記)

在一系列的布爾表達式中,某個變量帶有“控制標記”的作用
已break或者return語句取代控制標記


	void checkSecurity(String[] people) {
		boolean found = false;
			for (int i = 0; i < people.length; i++) {
				if (! found) {
					if (people[i].equals ("Don")){
						sendAlert();
						found = true;
					}
					if (people[i].equals ("John")){
						sendAlert();
						found = true;
					}
				}
		}
	}

to


	void checkSecurity(String[] people) {
		for (int i = 0; i < people.length; i++) {
			if (people[i].equals ("Don")){
				sendAlert();
				break; // or return
			}
			if (people[i].equals ("John")){
				sendAlert();
				break; // or return
			}
		}
	}

動機

  • 控制標記的作用是在於決定是否繼續下面流程,但是現代語言注重於使用breakcontinue
  • 確保真實的條件表達式是清晰的

37. Replace Nested Conditional with Guard Clauses (以衛語句取代嵌套的條件表達式)

函數的條件邏輯使人難以看清正常的執行路徑
使用衛語句表現所有的特殊情況


	double getPayAmount() {
		double result;
		if (_isDead) result = deadAmount();
		else {
			if (_isSeparated) result = separatedAmount();
			else {
				if (_isRetired) result = retiredAmount();
				else result = normalPayAmount();
			};
		}
		return result;
	};

to


	double getPayAmount() {
		if (_isDead) return deadAmount();
		if (_isSeparated) return separatedAmount();
		if (_isRetired) return retiredAmount();
		return normalPayAmount();
	};

動機

  • 如果這個條件是非同尋常的條件,檢查這條件是否符合然后返回true
  • 這樣大度的檢查被稱為“衛語句”
  • 對某一條分支已特別的重,如果使用if-then-else 結構,你對if分支和else分支的重要性是同等的
  • 各個分支具有同一樣的重要性
  • 取代之前的觀念 “每個函數只能有一個入口和一個出口”

38. Replace Conditional with Polymorphism (以多態取代條件表達式)

你手上有一個條件表達式,它根據對象的類型的不同選擇不同的行為
將條件表達式的所有分支放進一個子類內的覆蓋函數中,然后將原始函數聲明為抽象函數


	class Employee {
		private int _type;

		static final int ENGINEER = 0;
		static final int SALESMAN = 1;
		static final int MANAGER = 2;

		Employee (int type) {
			_type = type;
		}
		int payAmount() {
			switch (_type) {
				case ENGINEER:
					return _monthlySalary;
				case SALESMAN:
					return _monthlySalary + _commission;
				case MANAGER:
					return _monthlySalary + _bonus;
				default:
					throw new RuntimeException("Incorrect Employee");
				}
			}
		}
	}

to


	class Employee...
		static final int ENGINEER = 0;
		static final int SALESMAN = 1;
		static final int MANAGER = 2;

		void setType(int arg) {
			_type = EmployeeType.newType(arg);
		}
		int payAmount() {
			return _type.payAmount(this);
		}
	}

	class Engineer...
		int payAmount(Employee emp) {
			return emp.getMonthlySalary();
		}
	}

動機

  • 如果對象的行為因其類型而異,請避免編寫顯示的條件
  • Switch 聲明在面向對象語言中應該盡量少的被使用

39. Introduce Null Object (引入Null 對象)

你不得不檢查對象是否為Null對象
將null值替換為null對象


	if (customer == null){
		plan = BillingPlan.basic();
	} 
	else{
		plan = customer.getPlan();
	}

to


	class Customer {

	}

	class NullCusomer extends Customer {

	}

動機

  • 對象根據其類型做正確的事情,Null對象也應該遵守這個規則

40. Introduce Assertion (引入斷言)

某段代碼需要對程序狀態做出某種假設
已斷言明確表現這種假設


	double getExpenseLimit() {
		// should have either expense limit or a primary project
		return (_expenseLimit != NULL_EXPENSE) ?
			_expenseLimit:
			_primaryProject.getMemberExpenseLimit();
	}

to


	double getExpenseLimit() {
		Assert.isTrue (_expenseLimit != NULL_EXPENSE || _primaryProject	!= null);
		return (_expenseLimit != NULL_EXPENSE) ?
			_expenseLimit:
			_primaryProject.getMemberExpenseLimit();
	}

動機

  • 斷言是一種明確表達會為true的行為
  • 當斷言失敗時往往是一個非受控的異常
  • 斷言往往在生產代碼中移除
  • 交流層面 : 斷言能使程序的閱讀者理解代碼所做的假設
  • 在調試的角度 : 斷言能使編程者盡可能近的接觸bug

10. MAKING METHOD CALLS SIMPLER (簡化函數的調用)

41. Rename method (函數改名)

函數的名稱不能表達函數的用途
修改函數名稱

	getinvcdtlmt()

to

	getInvoiceableCreditLimit

動機
函數的名稱最好能表達函數的意圖

42. Add Parameter (添加參數)

某個函數需要從調用端得到更多的信息
為此函數添加一個對象函數,讓改對象帶進函數所需要信息

	getContact()

to

	getContact(:Date)

動機
在改變方法之后,你獲得更多的信息

43. Remove Parameter(移除參數)

一個參數不在函數中使用了
移除它

	getContact(:Date)

to

	getContact()

動機
一個參數不再使用還留着它干嘛?

44. Separate Query from Modifier (將查詢函數和修改函數分離)

某個函數即返回函數的狀態值,又修改對象的狀態
創建兩個不同的函數,其中一個負責查詢,另一個負責修改

	getTotalOutStandingAndSetReadyForSummaries()

to

	getTotalOutStanding()
	SetReadyForSummaries()

動機
將有副作用的方法和沒有副作用的方法分開

45. Parameterize Method (令函數攜帶參數)

若干函數做了類似的工作,但在函數本體中卻包含了不同的值
創建單一函數,已參數表達那些不同的值

	fivePercentRaise()
	tenPercentRaise()

to

	raise(percentage)

動機
移除重復的代碼提高靈活度

46. Replace Parameter with Explicit Methods (已明確函數取代參數)

🈶一個函數,其中完全取決於參數值而采取不同的行為
針對該函數的每一個可能值,建立一個獨立函數


	void setValue (String name, int value) {
		if (name.equals("height")){}
			_height = value;
			return;
		}
		if (name.equals("width")){
			_width = value;
			return;
		}
		Assert.shouldNeverReachHere();
	}

to


	void setHeight(int arg) {
		_height = arg;
	}
	void setWidth (int arg) {
		_width = arg;
	}

動機

  • 避免條件行為
  • 在編譯器進行檢查
  • 清晰的接口

47. Preserve Whole Object (保持對象的完整)

你從某個對象支行取出若干值,將他們作為某一次函數調用時的參數
改為傳遞一整個對象


	int low = daysTempRange().getLow();
	int high = daysTempRange().getHigh();
	withinPlan = plan.withinRange(low, high);

to


	withinPlan = plan.withinRange(daysTempRange());

動機

  • 使參數列表在變化的時候具有魯棒性
  • 使代碼更與易讀
  • 在代碼中移除相似的重復的代碼
  • 消極的 : 增加了函數參數在調用時的依賴

48. Replace Parameter with Method (已函數取代參數)

對象調用某個函數,並將其所得的結果作為參數,傳遞給另一個函數。而接受該參數的函數本身也能夠調用錢一個函數
將函數接受者去除該項參數,並直接調用前一個函數


	int basePrice = _quantity * _itemPrice;
	discountLevel = getDiscountLevel();
	double finalPrice = discountedPrice (basePrice, discountLevel);

to


	int basePrice = _quantity * _itemPrice;
	double finalPrice = discountedPrice (basePrice);

動機

  • 如果一個函數可以通過其他的途徑獲得參數值,那么它就不應該通過參數取得該值
  • 如果你能有其他的方法或者相同的計算值
  • 如果要在對調用對象的引用的其他對象上調用方法

49. Introduce Parameter Object (引入參數對象)

某些參數總是很自然的同時出現
以一個對象取代這些參數


	class Customer{
		amountInvoicedIn (start : Date, end : Date)
		amountReceivedIn (start : Date, end : Date)
		amountOverdueIn (start : Date, end : Date)
	}

to


	class Customer{
		amountInvoicedIn (: DateRange)
		amountReceivedIn (: DateRange)
		amountOverdueIn (: DateRange)
	}

動機

  • 減少參數列表的長度
  • 使代碼看的更加簡潔

50. Remove Setting Method (移除設值函數)

類中的某個字段應該在對象創建時被設值,然后就不再改變
去掉該字段的所有設值函數


	class Employee{
		setImmutableValue()
	}

to


	class Employee{
		¯\_(ツ)_/¯
	}

動機
確保你的清晰的目的 : 如果你想你的字段在創建之后就不要被改變了,你就不應該提供一個setting方法用於確保你的字段是否不被改變的

51. Hide Method (隱藏函數)

有一個函數,從來沒有被其他任何類用到
將這個函數修改為 private


	class Employee{
		public method()
	}

to


	class Employee{
		private method()
	}

動機
如果一個方法不需要被外部調用,那么就應該講這個方法隱藏

52. Replace Constructor with Factory Method (已工廠函數取代構造函數)

在創建對象時不僅僅是做簡單的健夠動作
將構造函數替換為工廠函數


	Employee (int type) {
		_type = type;
	}

to


	static Employee create(int type) {
		return new Employee(type);
	}

創建一個對象依賴於其子類,構造函數只能返回單一類型的對象,因此你需要將構造函數替換為一個工廠函數

53. Encapsulate Downcast (封裝向下轉型)

某個函數返回的對象,需要由調用者執行向下轉型
將向下轉型動作轉移到函數中


	Object lastReading() {
		return readings.lastElement();
	}

to


	Reading lastReading() {
		return (Reading) readings.lastElement();
	}

動機
將一個方法最有效的返回值進行返回給函數的調用者
如果類型是准確的,檢查使用這個對象的方法並提供一個更為有效的方法

54. Replace Error Code with Exception (以異常取代錯誤碼)

某個函數返回一個特定的代碼,用以表示某種錯誤情況
改用異常將其拋出去


	int withdraw(int amount) {
		if (amount > _balance)
			return -1;
		else {
			_balance -= amount;
			return 0;
		}
	}

to


	void withdraw(int amount) throws BalanceException {
		if (amount > _balance)
			throw new BalanceException();
		_balance -= amount;
	}

動機
當一個程序在發生了一個不可處理的錯誤時,你需要使這個函數的調用者知道。向上拋出異常,讓上層調用者知道

55. Replace Exception with Test (已測試取代異常)

面對一個調用者可以預先檢查的條件,你拋出了一個異常
修改調用者,使它在調用函數之前先做檢查


	double getValueForPeriod (int periodNumber) {
		try {
			return _values[periodNumber];
		} catch (ArrayIndexOutOfBoundsException e) {
			return 0;
		}
	}

to


	double getValueForPeriod (int periodNumber) {
		if (periodNumber >= _values.length)
			return 0;
		return _values[periodNumber];
}

動機

  • 不要過度使用異常,它們應該被用在檢查異常上
  • 不要用它來代替條件測試
  • 檢查異常錯誤條件在調用方法之前

11. DEALING WITH GENERALIZATION (處理概括關系)

56. Pull up field (字段上移)

兩個子類擁有相同的字段
將該字段移到超類中


	class Salesman extends Employee{
		String name;
	}
	class Engineer extends Employee{
		String name;
	}

to


	class Employee{
		String name;
	}
	class Salesman extends Employee{}
	class Engineer extends Employee{}

動機

  • 刪除重復的代碼
  • 允許將子類的行為轉移到父類中

57. Pull Up Method (構造函數本體上移)

你在各個子類中擁有一些構造函數,他們的本體幾乎完全一致
在超類中新建一個構造函數,並在子類構造函數中調用它


	class Salesman extends Employee{
		String getName();
	}
	class Engineer extends Employee{
		String getName();
	}

to


	class Employee{
		String getName();
	}
	class Salesman extends Employee{}
	class Engineer extends Employee{}

動機

  • 消除重復的行為

58. Pull Up Constructor Body (構造函數本體上移)

在子類中的構造函數與父類中的構造函數是相同的
在超類中創建一個構造函數,並在子類構造函數中調用它


	class Manager extends Employee...
		public Manager (String name, String id, int grade) {
		_name = name;
		_id = id;
		_grade = grade;
	}

to


	public Manager (String name, String id, int grade) {
		super (name, id);
		_grade = grade;
	}

動機

  • 構造函數與方法不同
  • 消除重復的代碼

59. Push Down Method (方法下移)

父類的某個方法至於某個子類相關
將其移到子類中


	class Employee{
		int getQuota();
	}
	class Salesman extends Employee{}
	class Engineer extends Employee{}

to


	class Salesman extends Employee{
		int getQuota();
	}
	class Engineer extends Employee{}

動機
當方法只在子類中顯現

60. Push Down Field (字段下移)

超類的字段只在某個子類中用到
將這個字段移到需要它的那些子類中去


	class Employee{
		int quota;
	}
	class Salesman extends Employee{}
	class Engineer extends Employee{}

to


	class Salesman extends Employee{
		int quota;
	}
	class Engineer extends Employee{}

動機
當一個字段只在子類中使用時

61. Extract Subclass (提煉子類)

類中的某些特性只被某些實例用到
新建一個子類,將上面所說的那一部分特性移到子類中去


	class JobItem	{
		getTotalPrices()
		getUnitPrice()
		getEmployee()
	}

to


	JobItem	{
		getTotalPrices()
		getUnitPrice()
	}
	class class LabotItem extends JobItem	{
		getUnitPrice()
		getEmployee()
	}

動機
當一個類的行為只用在某些實例中而不用在其他類中

62. Extract Superclass (提煉超類)

兩個類具有相似特性
創建一個父類,然后將這兩個類中相同的部分移到父類中,然后在繼承這個父類


	class Department{
		getTotalAnnualCost()
		getName()
		getHeadCount
	}
	class Employee{
		getAnnualCost()
		getName()
		getId
	}

to


	class Party{
		getAnnualCost()
		getName()
	}
	class Department {
		getAnnualCost()
		getHeadCount
	}
	class Employee {
		getAnnualCost()
		getId
	}

動機
當兩個類有過多相似的地方的時候,就需要考慮下是否需要將這個類進行下抽象了

63. Extract Interface (提煉接口)

若干客戶使用類接口中的同一個子類,或者兩個類的接口有相同的部分
將相同的子集提煉到一個獨立接口中


	class Employee {
		getRate()
		hasSpecialSkill()
		getName()
		getDepartment()
	}

to


	interface Billable	{
		getRate()
		hasSpecialSkill()
	}
	class Employee implements Billable	{
		getRate
		hasSpecialSkill()
		getName()
		getDepartment()
	}

動機

  • 若一個類的子集明確被一系列的客戶使用
  • 如果一個類需要和多個類處理並能處理確定的請求

64. Collapse Hierarchy (折疊繼承體系)

超類和子類無太大區別
將它們合為一個


	class Employee{	}
	class Salesman extends Employee{	}

to


	class Employee{	}

動機
該子類沒有帶來任何價值

65. Form Template Method (塑造模板函數)

有些子類,其中對應的某些函數以相同順序執行類似的操作,但各個操作的細節上有所不同
將這些操作分別放進獨立函數中,並保持它們都有相同的簽名,於是原函數也就變得相同了。然后將原函數上移到超類


	class Site{}
	class ResidentialSite extends Site{
		getBillableAmount()
	}
	class LifelineSite extends Site{
		getBillableAmount()
	}

to


	class Site{ 	
		getBillableAmount()
		getBaseAmount()
		getTaxAmount()
	}
	class ResidentialSite extends Site{
		getBaseAmount()
		getTaxAmount()
	}
	class LifelineSite extends Site{
		getBaseAmount()
		getTaxAmount()
	}

動機

66. Replace Inheritance with Delegation (以委托取代繼承)

某個子類只使用了超類接口中的一部分,或是根本不需要繼承而來的數據
_在子類中創建一個字段用以保存超類,調整子類函數,令它改2而委托超類:然后去除兩者之間的繼承關系


	class Vector{
		isEmpty()
	}

	class Stack extends Vector {}

to


	class Vector {
		isEmpty()
	}

	class Stack {
		Vector vector
		isEmpty(){
			return vector.isEmpty()
		}
	}

動機

  • 當你使用委托類的時候會對你使用的數據字段更加的清晰
  • 在你控制之外的方法都會被忽略,你只需要關注於自身

67. Replace Delegation with Inheritance (以繼承取代委托)

你在兩個類之間使用簡單的委托關系,並經常為整個接口編寫許多很多簡單的委托函數
讓委托類繼承委托類


	class Person {
		getName()
	}

	class Employee {
		Person person
		getName(){
			return person.getName()
		}
	}

to


	class Person{
		getName()
	}
	class Employee extends Person{}

動機

  • 如果你使用所有的方法在委托類中
  • 如果你不能使用委托類的所有函數,那么久不應該使用它

12. BIG REFACTORINGS (大型重構)

68. Tease Apart Inheritance (梳理並分解繼承體系)

某個繼承體系同時承擔兩項責任
建立兩個繼承體系,並通過委托關系讓其中一個可以調用另外一個


	class Deal{}
	class ActiveDeal extends Deal{}
	class PassiveDeal extends Deal{}
	class TabularActiveDeal extends ActiveDeal{}
	class TabularPassiveDeal extends PassiveDeal{}

to


	class Deal{
		PresentationStyle presettationStyle;
	}
	class ActiveDeal extends Deal{}
	class PassiveDeal extends Deal{}

	class PresentationStyle{}
	class TabularPresentationStyle extends PresentationStyle{}
	class SinglePresentationStyle extends PresentationStyle{}

動機

  • 過度的繼承會導致代碼重復
  • 如果繼承體系中的某一特定層級上的所有類,其子類名稱都已相同的形容詞開始,那么這個體系可能肩負着兩項不同的責任。
    | |

69. Convert Procedural Design to Objects (過程化設計轉為對象設計)

你手上有些傳統過程化風格的代碼
將數據記錄變成對象,將大塊的行為分成小塊,並將行為移入相關對象中


	class OrderCalculator{
		determinePrice(Order)
		determineTaxes(Order)
	}
	class Order{}
	class OrderLine{}

to


	class Order{
		getPrice()
		getTaxes()
	}
	class OrderLine{
		getPrice()
		getTaxes()
	}

動機
使用面向對象思想進行變成

70. Separate Domain from Presentation (將領域和表述/顯示分離)

某些GUI類中包含了領域邏輯
將領域邏輯分離出來嗎,為他們創建獨立的領域類


	class OrderWindow{}

to


	class OrderWindow{
		Order order;
}

動機

  • 分裂兩個過於復雜的代碼,使他們更易於修改
  • 允許多層風格編寫的程序
  • 這是值得被使用的

71. Extract Hierarchy (提煉繼承體系)

有某個類做了太多的工作其中一部分工作是以大量的條件表達式完成的
創建一個繼承體系,已一個子類來表達某一種特殊的情況


	class BillingScheme{}

to


	class BillingScheme{}
	class BusinessBillingScheme extends BillingScheme{}
	class ResidentialBillingScheme extends BillingScheme{}
	class DisabilityBillingScheme extends BillingScheme{}

動機

  • 一個類實現一個概念演變成實現多個概念
  • 保持單一責任

GitHub地址歡迎Star Fork Follower


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM