开闭原则
定义:一个软件实体,如类、模块、函数应该对扩展开放,对修改关闭。例如面向抽象编程,而不要面向实现编程。
优点:提高软件系统的可复用性、可维护性。
例如对于一个汽车类Car来说,有汽车品牌属性和汽车引擎,如果按照下面的设计:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {
/**
* 汽车型号名称
*/
private String carName;
/**
* 汽车引擎
*/
private V4Engine engine;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
class V4Engine{
private String engineName;
}
@Slf4j
class Test{
public static void main(String[] args) {
Car car = new Car();
car.setCarName("皮卡");
car.setEngine(new V4Engine("V4发动机"));
log.info("{}",car);
}
}
如果要修改发动机,就不得不修改Car类。发动机的变化应该独立出来,不能修改原有类,但可以扩展原有类。如果要加新的发动机,不能修改原有的Car类,只需要添加新的发动机类就行了,如下所示:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Car {
/**
* 汽车型号名称
*/
private String carName;
/**
* 汽车引擎
*/
private Engine engine;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
abstract class Engine{
protected String engineName;
}
class V4Engine extends Engine{
V4Engine(String engineName){
super(engineName);
}
}
class V8Engine extends Engine{
V8Engine(String engineName){
super(engineName);
}
}
@Slf4j
class Test{
public static void main(String[] args) {
Car car = new Car();
car.setCarName("皮卡");
car.setEngine(new V4Engine("V4发动机"));
log.info("{}",car);
car.setEngine(new V8Engine("V8发动机"));
log.info("{}",car);
}
}
依赖倒置原则
定义:高层不应该依赖底层模块,二者都应该依赖抽象。要针对接口编程,不要针对实现编程。
优点:减少类的耦合性,提高稳定性、代码可读性、可维护性。
依赖倒置原则和开闭原则举例:
MyBatis在使用Mapper的时候
SqlSession sqlSession = sqlSessionFactory.openSession();
会调用DefaultSqlSessionFactory的openSessionFromDataSource方法。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
略
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
略
}
其中Configuration.newExecutor方法。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
Configuration持有的引用是Executor接口,而Executor接口有3个实现类:
BaseExecutor抽象类使用模板方法模式,定义了通用的方法实现。供ReuseExecutor、SimpleExecutor、BatchExecutor调用。
SimpleExecutor是最简单的Executor接口实现,ReuseExecutor和SimpleExecutor的区别是:ReuseExecutor能重用Statement。而BatchExecutor能够批量执行sql。
CachingExecutor使用了装饰模式,给Executor接口实现类封装了二级缓存的功能,稍后专写一篇关于它的介绍。
这里就遵循了依赖导致原则和开闭原则,Configuration持有的引用是Executor接口,依赖的是Executor接口,而非具体的实现类。同时对扩展开放,如果要增加新的Executor,只需要新增加一个XXXExecutor,并在初始化的时候new即可。
单一职责原则
定义:一个类、接口、方法应该只有一个职责。
现实编码中,很难严格遵守,基本上没有哪个类做到单一职责。建议方法级别做到单一职责原则。
优点:降低类的复杂度,提高类的可读性、可维护性。
单一职责举例:
在MyBatis的mapper文件中经常可以看到通用型的update方法:
public int update(Student student);
对应的xml文件往往是:
<update id="update">
UPDATE student SET
name = #{name},
age = #{age},
gender = #{gender},
phone = #{phone},
class = #{class},
type = #{type},
update_by = #{updateBy.id},
update_date = #{updateDate},
remarks = #{remarks}
WHERE id = #{id}
</update>
无论要修改Student的那个属性,所有属性都要修改一遍,如果要修改name,就要写一个单一职责的方法:
public int update(@Param("name") String name,@Param("id") String id);
<update id="update">
UPDATE student SET
name = #{name}
WHERE id = #{id}
</update>
迪米特法则
定义:又叫做最少知道原则。一个对象应该对其他对象保持最少的了解。
强调只和朋友交流,不和陌生人说话。
出现在成员变量、方法的输入输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
优点:降低耦合度。
迪米特法则举例:
Java中有PO/Entity: persistent object持久对象、VO:view object表现层对象、DTO:data transfer object,数据传输对象、DAO :data access object数据访问对象、POJO :plain ordinary java object用这个名字用来强调它是一个普通java对象,而不是一个特殊的对象。
它们有时可以通用,各自有各自的生命周期作用范围。
在实际的开发中,经常可以看到Entity从持久层一直传送到Controller层,违反了迪米特法则,Controller、Service、Dao层每一层应该仅和自己的“朋友”打交道。
接口隔离原则
定义:client不应该依赖它不需要的接口。接口依赖遵循最少依赖原则。尽量细化接口。
优点:使得类具有好的可读性、可扩展性、可维护性。
里氏替换原则
定义:如果将程序中的对象A替换为对象B,程序的行为没有发生变化,那么B就是A的子类型。
所有引用父类的地方必须能够透明的使用其字类的对象,字类对象能够替换父类对象, 同时程序逻辑不变。
子类可以扩展父类的功能,但不能改变父类原有功能。
子类可以实现父类的abstract方法,但不能覆盖父类的非abstract方法。子类可以增加自己特有的方法。
子类重载父类的方法时,方法的入参要比父类的方法的入参更宽松。方法的出参要比父类的方法出参更严格或一样。
优点:提高程序健壮性。提高可维护性、扩展性。
合成复用原则
尽量使用合成/聚合的方式,而不是使用继承。
总结
找出可变化之处,独立出来。
针对接口/抽象编程,而不是针对实现编程。
为了交互对象之间的松耦合而努力。