设计模式学习笔记之七大原则
设计模式的七大原则
- 开闭原则(Open Closed Principle,OCP)
- 单一职责原则(Single Responsibility Principle, SRP)
- 里氏代换原则(Liskov Substitution Principle,LSP)
- 依赖倒转原则(Dependency Inversion Principle,DIP)
- 接口隔离原则(Interface Segregation Principle,ISP)
- 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)
- 最少知识原则(Least Knowledge Principle,LKP)或者迪米特法则(Law of Demeter,LOD)
一、开闭原则
一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。模块应尽量在不修改原(是"原",指原来的代码)代码的情况下进行扩展。
开闭原则的意义
软件需要发生变更的时候,尽量减少变更的范围。
对于BUG修改尽量减小影响范围。对于功能扩展,尽量不影响原来的逻辑。
对于使用这个类的代码逻辑,也不要轻易动它。
JAVA中的接口/抽象类与实现类、扩展类之间的关系体现了这一原则。
具体实现都放在实现类或者子类里面,这样需要扩展的时候,多定义一个实现类就可以了,而不用改变原来的逻辑。
接口/抽象类体现了对修改关闭,使用者只能关心接口中定义的方法。这就对接口定义有了更高的要求。不能轻易去动它。
public class BarChart {
public void draw(){
System.out.println("Draw bar chart...");
}
}
public class LineChart {
public void draw(){
System.out.println("Draw line chart...");
}
}
public class App {
public void drawChart(String type){
if (type.equalsIgnoreCase("line")){
new LineChart().draw();
}else if (type.equalsIgnoreCase("bar")){
new BarChart().draw();
}
}
}
修改后
public abstract class AbstractChart {
public abstract void draw();
}
public class BarChart extends AbstractChart{
@Override
public void draw() {
System.out.println("Draw bar chart...");
}
}
public class LineChart extends AbstractChart {
@Override
public void draw() {
System.out.println("Draw line chart...");
}
}
public class App {
/// 如果再扩展一个Chart,这部分的逻辑是完全不用动的。
/// 但是生成AbstractChart chart的逻辑,要同步扩展,这就可能要用到工厂模式。
public void drawChart(AbstractChart chart){
chart.draw();
}
}
二、单一职责原则
一个类或者模块应该有且只有一个改变的原因。如果一个类承担的职责过多,就等于把这些职责耦合在一起了。
一个类不能做太多的东西。在软件系统中,一个类(一个模块、或者一个方法)承担的职责越多,那么其被复用的可能性就会越低。
其实有的时候很难去衡量一个类的职责,主要是很难确定职责的粒度。这一点不仅仅体现在一个类或者一个模块中,也体现在采用微服务的分布式系统中。这也就是为什么我们在实施微服务拆分的时候经常会撕逼:"这个功能不应该发在A服务中,它不做这个领域的东西,应该放在B服务中"诸如此类的争论。存在争论是合理的,不过最好不要不了了之,而应该按照领域定义好每个服务的职责(职责的粒度最好找业务和架构专家咨询),得出相对合理的职责分配。
例:
public class Service {
///S ervice做了太多东西,包括数据库连接的管理,
///Sql的执行这些业务层不应该接触到的逻辑,更可怕的是,例如到时候如果数据库换成了Oracle,这个方法将会大改。
public UserDTO findUser(String name){
Connection connection = getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM t_user WHERE name = ?");
preparedStatement.setObject(1, name);
User user = //处理结果
UserDTO dto = new UserDTO();
//entity值拷贝到dto
return dto;
}
}
修改后:
public class Service {
private Dao dao;
public UserDTO findUser(String name){
User user = dao.findUserByName(name);
UserDTO dto = new UserDTO();
//entity值拷贝到dto
return dto;
}
}
public class Dao{
public User findUserByName(String name){
Connection connection = DataBaseUtils.getConnnection();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM t_user WHERE name = ?");
preparedStatement.setObject(1, name);
User user = //处理结果
return user;
}
}
可以反过来思考,我们这段逻辑里,哪些逻辑是可能被复现,主要的逻辑链条是核心的。
以此为标准去思考设计是否合理
组合复用原则
接口和抽象类的区别,从这个角度上看,接口更像是组合,而抽象类更像是继承。
接口代表一种能力,一种属性。多个接口的实现类组合到一起,就是一个组合。
而抽象类更像是继承。它的实现类,功能独立,复用了父类的实现。