前言
确实,没有设计模式也能完成开发。但为什么还需要它们?为了代码的层次感、可读性以及维护性。面对复杂多变的业务场景,尤其是要求高适配性和扩展性的地方,设计模式能提供成熟的解决方案。当然,不要为了用而用,简单稳定的业务不必强求。
接下来结合实际案例探讨一下设计模式的一些原则。
1. 开闭原则
public class Seller {
public BigDecimal sellCar(Car car) {
return car.getPrice();
}
}
上面模拟了 4S 店销售卖车。假设老板突然搞促销:双十一打折。如果在 sellCar 方法内直接增加计算逻辑,势必影响整个业务,导致所有车都打折,且后续活动结束或升级时还需反复改动。如果直接在 Car 类里操作,各种逻辑判断会让代码臃肿不堪。
对于复杂多变的业务,设计模式能解决上述问题。最重要的原则就是开闭原则(Open-Closed Principle):软件实体如类、模块和函数应该对扩展开放,对修改关闭。我们需要将业务行为抽象出来,使用抽象构建,具体业务通过实现来解决。
例如,创建一个 DiscountCar 继承自 Car,这样 sellCar 执行什么逻辑就取决于具体的实现,不会影响原有逻辑,保证接口可靠性和稳定性。
public class DiscountCar extends Car {
private BigDecimal price;
private BigDecimal discount;
@Override
public BigDecimal getPrice() {
return price.multiply(discount);
}
}
2. 依赖倒置原则
经过一系列活动,老板想扩展周边业务,让销售同时卖玻璃水、防冻液等。需求抛给程序员后,如果一直增加 sellGlassWater、sellAntifreeze 等方法,未来卖大米、鸡蛋饼怎么办?
我们需要抽象所有卖东西的场景。把卖的物品抽象成概念(Java 中对应接口),把卖的行为抽象成 sell 方法。
public interface Any {
String getName();
BigDecimal getPrice();
}
public class Seller {
public BigDecimal sell(Any any) {
return any.getPrice();
}
}
这样无论以后卖什么,都可以通过该方法处理,只需关注 Any 的实现。
3. 单一职责原则
销售发现单纯卖车吸引力不大,老板又提出新要求:销售还要会唱、跳、Rap。人与人能力不同,有的只会唱,有的全能。为了适配这种情况,必须把每种技能独立出来,根据不同的人组合这些技能。
public class Seller implements Sing, Jump, Rap {
public BigDecimal sell(Any any) {
return any.doSell();
}
@Override
public void sing() {
System.out.println("seller sing");
}
@Override
public void jump() {
System.out.println("seller jumping");
}
@Override
public void rap() {
System.out.println("seller raping");
}
}
注意一定要适度,根据业务细分。否则会导致接口过多,反而增大开发难度和代码复杂度。
4. 迪米特法则
老板想看报表检验效果。程序员可能这么写:
public class Boss {
private Seller seller;
private Report report;
public void read() {
seller.apply(report);
}
}
乍看功能实现了,细看逻辑不对。老板已经持有了报表,如果老板直接知道了业绩还叫销售干什么?这种逻辑肯定是不对的。也就是说 Boss 直接依赖了 Report,而这个 Report 不应该由 Boss 直接处理,应由 Seller 控制。
public class Boss {
private Seller seller;
public void read() {
seller.apply();
}
}
public class Seller {
private Report report;
public void apply() {
report.show();
}
}
这种写法最大化隔离了类与类之间的关系,降低了耦合度。迪米特法则因此又得名最少知道原则。
5. 接口隔离原则
用多个专门的接口,而不使用单一的总接口。客户端不应该依赖它不需要的接口。一个类对一个类的依赖应该建立在最小的接口上。
建立单一接口,不要建立庞大臃肿的接口,尽量细化接口,方法尽量少。注意适度原则,不能滥用。就像上面的唱跳 Rap,分离是最好的。
6. 里氏代换原则
主要针对类的继承关系。正式定义是:如果对每一个类型为 S 的对象 o1,都有类型为 T 的对象 o2,使得以 T 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 S 是类型 T 的子类型。
在 4S 店老板眼里,只要新来的能在销售岗位上像老手一样卖出汽车,他就是一名合格的销售。这就像那句名言:不管黑猫白猫,能抓老鼠的都是好猫。
从某种含义上,里氏代换有着以下契约:
- 子类必须完全实现父类的方法。父类出现的地方子类都可以代替父类。
- 子类可以有自己的个性定义。里氏替换原则可以正着用,但是不能反过来用。在子类出现的地方,父类未必就可以胜任。子类一般比父类有个性。
- 覆盖或实现父类的方法时输入参数可以被放大。如果老板规定基础车谈价折扣最多九折,销售打个九五折没问题,打八折老板肯定要跟你说道说道了。
- 覆写或实现父类的方法时输出结果可以被缩小。同样是 15W 本来只能卖出乞丐版,结果换了个销售给出旗舰版,怕不是过不了试用期哦。
7. 合成/复用原则
它要求在软件复用时,要尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现。
如果要使用继承关系,则必须严格遵循里氏替换原则。合成复用原则同里氏替换原则相辅相成,两者都是开闭原则的具体实现规范。
总结
这七种设计原则是软件设计模式必须尽量遵循的原则,各种原则要求的侧重点不同。其中,开闭原则是总纲,告诉我们要对扩展开放,对修改关闭;里氏替换原则告诉我们不要破坏继承体系;依赖倒置原则告诉我们要面向接口编程;单一职责原则告诉我们实现类要职责单一;接口隔离原则告诉我们在设计接口的时候要精简单一;迪米特法则告诉我们要降低耦合度;合成复用原则告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。在实际开发中我们可以根据业务来进行设计模式的使用,但是很重要的一点千万不要被这些条条框框束缚了你的手脚。

