设计模式的前言——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 )。原则说,设计一个类时候,不要设计为大而全的类,要设计为粒度小,功能单一的类。

二、开闭原则
软件实体(类,模块,方法等)应该对扩展开放,对修改关闭。通俗理解就是添加一个功能应该是在已有代码基础上进行扩展,而不是修改已有代码。
以下代码违背了开闭原则,在新增用户类型后,要对用户类型进行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接口给予具体的实现增加功能,具体的实现通过实现接口来获得功能。而具体的实现与框架之间并没有直接耦合。


浙公网安备 33010602011771号