设计模式的前言——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

posted @ 2026-02-08 15:55  Carey_ccl  阅读(16)  评论(0)    收藏  举报