博客 / 詳情

返回

設計模式的前言——Solid設計原則

  Solid原則是為針對面向對象的程序語言設計,從本質上來講,SOLID是5個原則的縮寫,這5個原則有助於軟件設計:更加容易理解,更靈活,可維護性更強。這個與掌握軟件設計原理無關,這個原理是很多原則的子集。

  • 單一職責原則(Single responsibility principle)
  • 開閉原則(open-closed principle)
  • 里氏替換原則(liskov substitution principle)
  • 接口隔離原則(interface segregation principle)
  • 依賴倒置原則(dependency inversion principle)

一、單一職責原則

一個類或者一個模塊只負責完成一個職責(A class or module should have a single reponsibility )。原則説,設計一個類時候,不要設計為大而全的類,要設計為粒度小,功能單一的類。
clipboard

二、開閉原則

  軟件實體(類,模塊,方法等)應該對擴展開放,對修改關閉。通俗理解就是添加一個功能應該是在已有代碼基礎上進行擴展,而不是修改已有代碼。

  以下代碼違背了開閉原則,在新增用户類型後,要對用户類型進行if...else判斷, 需要修改原有邏輯。

import java.math.BigDecimal;

public class OpenClosePrinciple {

   public static void main(String[] args) {
      OrderService orderService = new OrderService();
      BigDecimal bigDecimal = orderService.calculateDiscount(2, BigDecimal.valueOf(100));
      System.out.println(bigDecimal);
   }


}

class OrderService{
   //用數字(userType)判斷用户類型
   BigDecimal calculateDiscount(int userType,BigDecimal money){
      BigDecimal result = null;
      if (userType == 1){
         result = calculateNormal(money);
      } else if (userType == 2){
         result = calculateVip(money);
      } else if (userType == 3){
         result = calculateSupVip(money);
      } else if (userType == 4){
         result = calculateTeamUser(money);
      }
      return result;
   }

   //不同的計算方式,寫在一個類中,通過方法名來調用。以下為4種計算方式
   private BigDecimal calculateNormal(BigDecimal money){
      return money;
   }

   private BigDecimal calculateVip(BigDecimal money){
      return money.multiply(BigDecimal.valueOf(0.8d));
   }

   private BigDecimal calculateSupVip(BigDecimal money){
      return money.multiply(BigDecimal.valueOf(0.5d));
   }

   private BigDecimal calculateTeamUser(BigDecimal money){
      return money.multiply(BigDecimal.valueOf(0.7d));
   }

}

  為了避免更改原有邏輯,以下使用策略模式對代碼進行重構,遵循開閉原則進行代碼設計。

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

public class OpenClosePrinciple {

   public static void main(String[] args) {
      //通過 枚舉類的靜態方法+枚舉類的常量,獲取數據。
      //通過 調用策略控制器中的方法進行判斷,來獲取哪種class的策略類
      DiscountStrategy strategy1 = StrategyContext.getStrategy(UserTypeEnum.getUserType(UserTypeEnum.VIP));
      System.out.println(strategy1.calculateDiscount(BigDecimal.valueOf(100.0d)));

      //通過 枚舉類的常量,獲取數據。
      //通過 調用策略控制器中的方法進行判斷,來獲取哪種class的策略類
      DiscountStrategy strategy2 = StrategyContext.getStrategy(UserTypeEnum.VIP.getUserType());
      System.out.println(strategy2.calculateDiscount(BigDecimal.valueOf(100.0d)));
   }


}

//用户類型枚舉類
enum UserTypeEnum {
   NORMAL(1),
   VIP(2);
   private Integer userType;

   UserTypeEnum(Integer userType) {
      this.userType = userType;
   }

   public Integer getUserType() {
      return userType;
   }

   public void setUserType(Integer userType){
      this.userType = userType;
   }

   //枚舉靜態方法,通過哪一種枚舉類型,來返回枚舉中的數據
   //該方法一般用於枚舉中有兩個數據的情況,此處為枚舉中只有一個數據的情況
   public static Integer getUserType(UserTypeEnum userTypeEnum){
      //首先,對枚舉中的所有類型進行遍歷
      for (UserTypeEnum u : UserTypeEnum.values()) {
         if (u.equals(userTypeEnum)){
            return u.getUserType();
         }
      }

      return null;
   }
}
//策略類型的接口
interface DiscountStrategy{
   BigDecimal calculateDiscount(BigDecimal money);
}
//普通打折的策略類
class NormalDiscountStrategy implements DiscountStrategy{

   @Override
   public BigDecimal calculateDiscount(BigDecimal money) {
      return money;
   }
}
//VIP打折的策略類
class VipDiscountStrategy implements DiscountStrategy{

   @Override
   public BigDecimal calculateDiscount(BigDecimal money) {
      return money.multiply(BigDecimal.valueOf(0.8d));
   }
}
//策略控制器,在初始化時候進行不同策略類型的保存。
class StrategyContext{
   private static Map<Integer,DiscountStrategy> strategyMap = new HashMap<Integer, DiscountStrategy>();

   static{
      strategyMap.put(UserTypeEnum.NORMAL.getUserType(), new NormalDiscountStrategy());
      strategyMap.put(UserTypeEnum.VIP.getUserType(), new VipDiscountStrategy());
   }
   //通過在hashMap中查詢策略類型,把對應策略類型的實例 返回。
   public static DiscountStrategy getStrategy(Integer userType){
      DiscountStrategy discountStrategy = strategyMap.get(userType);
      return discountStrategy;
   }

}

三、里氏替換原則

  子類對象能夠替換程序中父類對象出現的任何地方,並且能夠保證原來程序的邏輯行為不變及正確性不被破壞
  面向對象編程語言中有多態的實現場景,多態的實現場景和里氏替換原則有點類似,但是他們關注的角度是不同的,多態是面向對象編程的特性,而里氏替換原則,是用來指導繼承關係中子類該如何設計:子類的設計要確保在替換父類時候,不改變原有父類的約定。
  具體實現中可以理解為,子類在設計的時候,要遵循父類的行為規定,父類定義的方法行為,子類可以改變方法的內部實現邏輯,但不能改變方法原有的接口約定。原有的行為約定包括:接口/方法 的聲明,參數值,返回值,異常約定,甚至包括註釋中所羅列的任何特殊説明。

import java.util.HashMap;
import java.util.Map;

public class LiskovSubstitutionPrinciple {

   public static void main(String[] args) {
      //普通緩存實現
//    CacheManager cacheManager = new CacheManager();
//    UserService service = new UserService(cacheManager);

      //redis緩存實現
      RedisCentralCacheManager redisCacheManager = new RedisCentralCacheManager();
      UserService service = new UserService(redisCacheManager);

      String value1 = service.biz("key1");
      String value2 = service.biz("key1");
      String value3 = service.biz("key2");
      String value4 = service.biz("key2");

   }
}

class UserService{
   //模擬db操作
   private static Map<String,String> db = new HashMap<String, String>();
   
   //定義緩存
   private CacheManager cacheManager;
   //利用static代碼塊: 模擬db存儲數據
   static{
      db.put("key1","value1" );
   }
   //將實例化的緩存對象,引到這個對象中
   public UserService(CacheManager cacheManager) {
      this.cacheManager = cacheManager;
   }

   //查詢數據 
   public String biz(String key){
      //先從緩存中獲取數據       
      String value = cacheManager.get(key);
      //緩存中沒有該條數據,去數據庫查詢數據      
      if (value == null){
         value = dbData(key);
         cacheManager.put(key,value);
      }else{
         System.out.println(key+"命中,執行業務操作:"+value);
      }
      return value;
   }
   //模擬數據庫查詢數據    
   private String dbData(String key){
      String value = null;
      value = db.get(key);
      System.out.println("數據庫中獲取:  "+value);

      return value;
   }

}

//模擬:普通緩存查詢和存放數據
class CacheManager{
   private Map<String,String> cache = new HashMap<String, String>();

   public String get(String key){
      String value = cache.get(key);
      if (value == null || "".equals(value)){
         return null;
      }
      return value;
   }

   public void put(String key,String value){
      //此處定義規則:不用考慮數據庫是否有該記錄,即value是否為null,直接存放
      cache.put(key,value );
   }
}
//模擬:redis的集中緩存,存放數據,此處違背了里氏替換原則
class RedisCentralCacheManager extends CacheManager{
   private Map<String,String> redisCache = new HashMap<String, String>();
   private static final String empty = "empty";

   @Override
   public String get(String key) {
      String value = redisCache.get(key);
      if (null == value || "".equals(value)){
         return null;
      }
      return value;
   }

   //此處違背了里氏替換原則
   @Override
   public void put(String key, String value) {
      //根據父類接口中定義的規則,直接存放,不要自定義數據存放。 
      if (value == null || "".equals(value)){
         redisCache.put(key,empty );
      }else{
         redisCache.put(key, value);
      }
   }
}

執行結果(違背了里氏替換原則原則):
clipboard

四、接口隔離原則

  對於接口來説:如果某個接口承擔了與他無關的功能,則説該接口違背了接口隔離原則,可以把無關的接口剝離出去。
對於共同的代碼來説:應該將代碼的粒度細分出來,而不是定義一個大而全的接口,讓子類被迫去實現它

五、依賴倒置原則(框架和容器中用的比較多)

  高層模塊不要依賴低層模塊,高層模塊和低層模塊應該通過抽象來互相依賴。除此之外,抽象不要依賴具體實現細節,具體實現細節依賴抽象。
  高層模塊,從代碼角度來説就是調用者,底層模塊就是被調用者。調用者不要依賴於具體的實現,而應該依賴於抽象:如spring代碼中的各種Aware接口,框架依賴於Aware接口給予具體的實現增加功能,具體的實現通過實現接口來獲得功能。而具體的實現與框架之間並沒有直接耦合。
clipboard

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.