又見開閉原則
開閉原則是Java世界裡最基礎的設計原則,它指導我們如何建立一個穩定的、靈活的系統,先來看開閉原則的定義:
Software entities like classes,modules and functions should be open for extension but closed for
modifications.(一個軟體實體如類、模組和函式應該對擴充套件開放,對修改關閉。)
我們做一件事情,或者選擇一個方向,一般需要經歷三個步驟:What——是什麼,Why
——為什麼,How——怎麼做(簡稱3W原則,How取最後一個w)。對於開閉原則,我們也
採用這三步來分析,即什麼是開閉原則,為什麼要使用開閉原則,怎麼使用開閉原則。
一個軟體產品只要在生命期內,都會發生變化,既然變化是一個既定的事實,我們就應
該在設計時儘量適應這些變化,以提高專案的穩定性和靈活性,真正實現“擁抱變化”。開閉
原則告訴我們應儘量通過擴充套件軟體實體的行為來實現變化,而不是通過修改已有的程式碼來完
成變化,它是為軟體實體的未來事件而制定的對現行開發設計進行約束的一個原則。我們舉
例說明什麼是開閉原則,以書店銷售書籍為例,其類圖如圖
package OpenOffP; public interface IBook { public String getName(); public int getPrice(); public String getAuthor(); }
package OpenOffP; public class NovelBook implements IBook{ private String name; private int price; private String author; public NovelBook(String name,int price,String author){ this.name = name; this.price = price; this.author = author; } @Override public String getName() { return this.name; } @Override public int getPrice() { return this.price; } @Override public String getAuthor() { return this.author; } }
package OpenOffP; import java.text.NumberFormat; import java.util.ArrayList; public class BookStore { private final static ArrayList<IBook> bookList = new ArrayList<IBook>(); static { bookList.add(new NovelBook("天龍八部",3200,"金庸")); bookList.add(new NovelBook("巴黎聖母院",5600,"雨果")); bookList.add(new NovelBook("悲慘世界",3500,"雨果")); bookList.add(new NovelBook("金瓶梅",4300,"蘭陵笑笑生")); } public static void main(String[] args){ NumberFormat numberFormat = NumberFormat.getCurrencyInstance(); numberFormat.setMaximumFractionDigits(2); System.out.println("------------------"); for (IBook book:bookList){ System.out.println("書籍名稱:" + book.getName()+"\t書籍作者:" + book.getAuthor()+"\t書籍價格:"+ numberFormat.format (book.getPrice()/ 100.0)+"元"); } } }
一個簡單的賣書程式碼。
打折啦,大甩賣。所有40元以上的書籍9折銷售,其他的8
折銷售。對這個專案來說,這就是一個變化,我們應該如何應對這樣一個需求變化?
有如下三種方法可以解決這個問題:
● 修改介面
在IBook上新增加一個方法getOffPrice(),專門用於進行打折處理,所有的實現類實現該
方法。但是這樣修改的後果就是,實現類NovelBook要修改,BookStore中的main方法也修
改,同時IBook作為介面應該是穩定且可靠的,不應該經常發生變化,否則介面作為契約的
作用就失去了效能。因此,該方案否定。
● 修改實現類
修改NovelBook類中的方法,直接在getPrice()中實現打折處理,好辦法,我相信大家在
專案中經常使用的就是這樣的辦法,通過class檔案替換的方式可以完成部分業務變化(或是
缺陷修復)。該方法在專案有明確的章程(團隊內約束)或優良的架構設計時,是一個非常
優秀的方法,但是該方法還是有缺陷的。例如採購書籍人員也是要看價格的,由於該方法已
經實現了打折處理價格,因此採購人員看到的也是打折後的價格,會因資訊不對稱而出現決
策失誤的情況。因此,該方案也不是一個最優的方案。
● 通過擴充套件實現變化
增加一個子類OffNovelBook,覆寫getPrice方法,高層次的模組(也就是static靜態模組
區)通過OffNovelBook類產生新的物件,完成業務變化對系統的最小化開發。好辦法,修改
也少,風險也小,修改後的類圖
package OpenOffP; public class OffNovelBook extends NovelBook{ public OffNovelBook(String name,int price,String author){ super(name,price,author); } @Override public int getPrice(){ int selfPrice = super.getPrice(); int offPrice = 0; if (selfPrice>4000){ offPrice = selfPrice*90/100; }else { offPrice = selfPrice*80/100; } return offPrice; } }
1. 抽象約束
抽象是對一組事物的通用描述,沒有具體的實現,也就表示它可以有非常多的可能性,
可以跟隨需求的變化而變化。因此,通過介面或抽象類可以約束一組可能變化的行為,並且
能夠實現對擴充套件開放,其包含三層含義:第一,通過介面或抽象類約束擴充套件,對擴充套件進行邊
界限定,不允許出現在介面或抽象類中不存在的public方法;第二,引數型別、引用物件盡
量使用介面或者抽象類,而不是實現類;第三,抽象層儘量保持穩定,一旦確定即不允許修
改。還是以書店為例,目前只是銷售小說類書籍,單一經營畢竟是有風險的,於是書店新增
加了計算機書籍,它不僅包含書籍名稱、作者、價格等資訊,還有一個獨特的屬性:面向的
是什麼領域,也就是它的範圍,比如是和程式語言相關的,還是和資料庫相關的,等等,修
改後的類圖如圖:
package OpenOffP; public interface IComputerBook extends IBook{ public String getScore(); }
package OpenOffP; public class ComputerBook implements IComputerBook{ private String name; private int price; private String author; private String score; public ComputerBook(String name,int price,String author,String score){ this.name = name; this.price = price; this.author = author; this.score = score; } @Override public String getScore() { return this.score; } @Override public String getName() { return this.name; } @Override public int getPrice() { return this.price; } @Override public String getAuthor() { return this.author; } }