一個例子的重構過程到Spring的IoC之使用

從一個例子的重構過程到Spring的IoC之使用

IoC (反向控制)是Sping的核心,用以實現系統對象的鬆耦合,使用IoC ,對象是被動接收依賴類而不是主動去找,下面將結合一個訂單,折扣,價格的例子來說明IoC的優勢.看看它是怎麼降低系統對象的耦合的.

1.關於訂單,折扣,價格的說明
  訂單是貨物的訂單,主要貨物有計算機,文具,紙張三類,還有其它一些物件.每種貨物都有不同的折扣比例,從單價中扣除折扣比例後就是進貨時的真實價格.
  下面將使用各種例子實現這一過程.


2.傳統的翻譯式的處理:

這樣的代碼新手常些,老手在快速編程中也難以避免,認識到這段代碼是否需要重構,如何進行重構是二者的最大區別.
// 訂單類
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
  this.price=price;
  this.type=type;
}
public Order(){
  this(100.0f,"");
}

public float getPrice() {
  float discount=0.0f;
                // 真實價格的計算直接寫在了訂單類的方法中
  if(Type_Computer.equals(type)){
   discount=0.3f;
  }
  else if(Type_Stationary.equals(type)){
   discount=0.2f;
  }
  else if(Type_Paper.equals(type)){
   discount=0.1f;
  }
  return price*(1.0f-discount);
}
}

// 運行
Order computerOrder=new Order(100.0f,Order.Type_Computer);
System.out.println("computerOrder's price is /t"+computerOrder.getPrice());
Order stationaryOrder=new Order(100.0f,Order.Type_Stationary);
System.out.println("stationaryOrder's price is /t"+stationaryOrder.getPrice());
Order paperOrder=new Order(100.0f,Order.Type_Paper);
System.out.println("paperOrder's price is /t/t"+paperOrder.getPrice());
Order otherOrder=new Order();
System.out.println("otherOrder's price is /t/t"+otherOrder.getPrice());

// 輸出情況
computerOrder's price is  70.0
stationaryOrder's price is  80.0
paperOrder's price is   90.0
otherOrder's price is   100.0

優勢:直白易懂,基本就是把用戶的語言描述改寫成代碼,適用於小規模無變化的項目.
劣勢:計算價格的代碼屬於硬編碼,如果規模變大,則判斷將會變得冗長難懂,如果產生變化,如增加新的類型的貨物,貨物折扣發生變化等,將不容易增加新的功能,修改時將容易相互影響.

3.使用簡單類工廠進行重構解耦的方案(工廠模式)
// 折扣類
public interface Discount{
public float getDiscount();
}

// 計算機貨物的折扣類
public class ComputerDiscount implements Discount{
public float getDiscount(){
  return 0.3f;
}
}

// 文具折扣類
public class StationaryDiscount implements Discount{
public float getDiscount(){
  return 0.2f;
}
}

// 紙張折扣類
public class PaperDiscount implements Discount{
public float getDiscount(){
  return 0.1f;
}
}

// 其它貨物折扣類
public class OtherDiscount implements Discount{
public float getDiscount(){
  return 0.0f;
}
}

// 用於計算折扣的工廠
public class DiscountFactory{
public static float getDiscount(String type){
  Discount discount=null; 
  // 轉移過來的判斷過程
  if(Order.Type_Computer.equals(type)){
   discount=new ComputerDiscount();
  }
  else if(Order.Type_Stationary.equals(type)){
   discount=new StationaryDiscount();
  }
  else if(Order.Type_Paper.equals(type)){
   discount=new PaperDiscount();
  }
  else{
   discount=new OtherDiscount();
  }
  return discount.getDiscount();
}
}

// 訂單類
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
  this.price=price;
  this.type=type;
}
public Order(){
  this(100.0f,"");
}

public float getPrice() {
  float discount=DiscountFactory.getDiscount(this.type); 
  return price*(1.0f-discount);
}
}

輸出的結果還是:
computerOrder's price is  70.0
stationaryOrder's price is  80.0
paperOrder's price is   90.0
otherOrder's price is   100.0

優勢:歸納出了折扣接口及其四個子類,細分程度比上個方案高,修改折扣時不容易相互影響.
劣勢:訂單類Order還是和DiscountFactory類耦合在一起,使用IF進行判斷的硬編碼依然存在,增加新的貨物折扣類時還是容易相互影響.

4.利用反射進行解耦的方案(命令模式)
// 折扣類
public interface Discount{
public float getDiscount();
}

// 計算機貨物的折扣類
public class ComputerDiscount implements Discount{
public float getDiscount(){
  return 0.3f;
}
}

// 文具折扣類
public class StationaryDiscount implements Discount{
public float getDiscount(){
  return 0.2f;
}
}

// 紙張折扣類
public class PaperDiscount implements Discount{
public float getDiscount(){
  return 0.1f;
}
}

// 其它貨物折扣類
public class OtherDiscount implements Discount{
public float getDiscount(){
  return 0.0f;
}
}

// 訂單類
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
  this.price=price;
  this.type=type;
}
public Order(){
  this(100.0f,"Other");
}

public float getPrice() {
  Discount discount = null;
  String className="discount."+type+"Discount";
  try{
              Class cls=Class.forName(className);
              discount=(Discount)cls.newInstance();// 由類得到類實例
      }
      catch(Exception ex){
          ex.printStackTrace();
      }     
  return price*(1.0f-discount.getDiscount());
}
}

// 輸出如下:
computerOrder's price is  70.0
stationaryOrder's price is  80.0
paperOrder's price is   90.0
otherOrder's price is   100.0

優勢:使用反射替代了IF判斷,這有效消除了增加新貨物折扣的相互影響.
劣勢:如果要修改折扣大小,必須由程序員來修改類文件,這是不適應經常變化的,變化還會帶來一系列的測試,編譯,發佈等事情.

到這裏,如果不使用IoC,重構就基本結束了,相對而言,用反射方式較好些.
但是使用了IoC,我們將有更佳的選擇.

5.使用Spring的Ioc進行解耦的方案

// 折扣類
public interface Discount{
public float getDiscount();
}

// 計算機貨物的折扣類
public class ComputerDiscount implements Discount{
public float getDiscount(){
  return 0.3f;
}
}

// 文具折扣類
public class StationaryDiscount implements Discount{
public float getDiscount(){
  return 0.2f;
}
}

// 紙張折扣類
public class PaperDiscount implements Discount{
public float getDiscount(){
  return 0.1f;
}
}

// 其它貨物折扣類
public class OtherDiscount implements Discount{
public float getDiscount(){
  return 0.0f;
}
}

// Order類
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
  this.price=price;
  this.type=type;
}
public Order(){
  this(100.0f,"Other");
}

public float getPrice() {
  ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
  Discount discount = null;
  discount = (Discount) ctx.getBean(this.type);
  return price*(1.0f-discount.getDiscount());
}
}

// 配置文件Bean.xml
<?xml version="1.0" encoding="UTF-8"?>






// 輸出如下
computerOrder's price is  70.0
stationaryOrder's price is  80.0
paperOrder's price is   90.0
otherOrder's price is   100.0

優勢:使用Spring的IoC代替了反射,易懂而不容易出錯.
劣勢:和使用反射的情況一致.

6.進一步使用Ioc進行解耦的方案
// 折扣接口
public interface Discount{
public float getDiscount();
public void  setDiscount(float discount);
}

// 計算機折扣類
public class ComputerDiscount implements Discount{
private float discount=0.0f;
public float getDiscount(){
  return discount;
}
public void setDiscount(float discount){
  this.discount=discount;
}
}

// 文具折扣類
public class StationaryDiscount implements Discount{
private float discount=0.0f;
public float getDiscount(){
  return discount;
}
public void setDiscount(float discount){
  this.discount=discount;
}
}

// 紙張折扣類
public class PaperDiscount implements Discount{
private float discount=0.0f;
public float getDiscount(){
  return discount;
}
public void setDiscount(float discount){
  this.discount=discount;
}
}

// 其它折扣類
public class OtherDiscount implements Discount{
private float discount=0.0f;
public float getDiscount(){
  return discount;
}
public void setDiscount(float discount){
  this.discount=discount;
}
}

// Order類
public class Order{
public static final String Type_Computer="Computer";
public static final String Type_Stationary="Stationary";
public static final String Type_Paper="Paper";
private String type;
private float price;
public Order(float price,String type){
  this.price=price;
  this.type=type;
}
public Order(){
  this(100.0f,"Other");
}

public float getPrice() {
  ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
  Discount discount = null;
  discount = (Discount) ctx.getBean(this.type);
  return price*(1.0f-discount.getDiscount());
}
}

// 配置文件bean.xml
<?xml version="1.0" encoding="UTF-8"?>



 
   0.3
 


 
   0.2
 


 
   0.1
 


 
   0.05
 

輸出:
computerOrder's price is  70.0
stationaryOrder's price is  80.0
paperOrder's price is   90.0
otherOrder's price is   95.0

優勢:對於折扣數額變化不需要修改類文件,也沒有修改類文件的帶來的系列問題.
劣勢:增加新類時仍需配置xml文件,沒有達到完全的即插即用.這也許是我沒有學到,也許Spring也不能解決,還有一段路要走.

IoC的根在於反射,其它使用XMl文件進行配置的框架如Struts,Hibernate也在反射上做了很多文章,可以說不充分理解和運用反射就難以把握現代框架,永遠是一個使用者.
可以說反射,面向接口等是java界的大道,某個API的運用,某個效果的實現只是旁門左道,對於一個有志於站在時代風口浪尖的程序員應該在大道上多花功夫.

發佈了52 篇原創文章 · 獲贊 3 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章