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 )。原則説,設計一個類時候,不要設計為大而全的類,要設計為粒度小,功能單一的類。

二、開閉原則
軟件實體(類,模塊,方法等)應該對擴展開放,對修改關閉。通俗理解就是添加一個功能應該是在已有代碼基礎上進行擴展,而不是修改已有代碼。
以下代碼違背了開閉原則,在新增用户類型後,要對用户類型進行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);
}
}
}
執行結果(違背了里氏替換原則原則):

四、接口隔離原則
對於接口來説:如果某個接口承擔了與他無關的功能,則説該接口違背了接口隔離原則,可以把無關的接口剝離出去。
對於共同的代碼來説:應該將代碼的粒度細分出來,而不是定義一個大而全的接口,讓子類被迫去實現它
五、依賴倒置原則(框架和容器中用的比較多)
高層模塊不要依賴低層模塊,高層模塊和低層模塊應該通過抽象來互相依賴。除此之外,抽象不要依賴具體實現細節,具體實現細節依賴抽象。
高層模塊,從代碼角度來説就是調用者,底層模塊就是被調用者。調用者不要依賴於具體的實現,而應該依賴於抽象:如spring代碼中的各種Aware接口,框架依賴於Aware接口給予具體的實現增加功能,具體的實現通過實現接口來獲得功能。而具體的實現與框架之間並沒有直接耦合。
