架构整洁之道-第三部分-架构设计原则

200 阅读5分钟

单一职责

定义:每个软件模块都有一个且只有一个需要被改变的理由。

反例

public class Employee { 
  // calculatePay() 实现计算员工薪酬
  public Money calculatePay();
  // save() 将Employee对象管理的数据存储到企业数据库中
  public void save();
  // postEvent() 用于促销活动发布
  public void reportHours();
}

以上的Employee类具备三个功能,计算薪酬、保存数据、活动发布。此时如果要增加一个功能,那么就需要改动Employee,并且有可能对其他的方法造成影响。

具体实现

// 财务行为
public class PayCalculator {
  public Money calculatePay();
}

// 数据管理员行为
public class EmployeeSaver {
    public void save();
}

// 销售行为
public class HourReporter {
    public String reportHours();
}

每个功能独立一个类,保证每个类的改动互不影响。

核心

单一职责原则在架构层面用于奠定架构边界的变更轴心

开闭原则

定义:软件系统想要容易被改变,应该保持对扩展开放,对修改关闭。

这里的对扩展开放对修改关闭并不是指所有的类都不能修改,而且核心的类要做到能够扩展但是不允许修改。

示例

class FinacialReportInterceptor {

	@Resource
	private FinacialReportController finacialReportController;

	@Resource
	private FinacialData finacialData;
	

	public void finacialReport() {
		finacialReportController.screenPresenter();
		finacialData.save();
	}
}


class FinacialReportController {

	

	public void screenPresenter() {
		WebView web = new WebView();
	}

	public void printPresenter() {
		PDFView pdf = PDFView()
	}

}

class WebView {

}

class PDFVied {

}

class FinacialData {
	public void save() {
	}

}


Pasted image 20241208212501.png

以上的代码中FinacialReportInterceptor依赖FinacialReportController和FinacialData,FinacialReportInterceptor是整个逻辑的核心,这种单向的依赖关系改动FinacialReportController或者FinacialData并不会影响Interceptor的逻辑。

但是如果要新增核心逻辑是只要在Interceptor中新增一个逻辑即可,核心逻辑依赖的功能则依赖Controller和Data。

这样就保证了在Interceptor对扩展开放,对修改关闭(如果要修改只能修改依赖的controller或者data)

核心

系统易于扩展同时限制每次被修改的影响范围

里氏替换原则

定义:如果想用可替换的组件来构建软件系统,那么这些组件必须遵守同一个约定,以便让这些组件可以互相替换。

如何理解这句定义呢?

我的理解是要构建软件系统首先要有一个确定的标准,以该标准作为基础构建组件,那么基于相同的标准构建的组件就可以互相替换,这个在Java中就是接口和实现的关系。

Java中接口定义对外的实现标准,一个接口可以有多个实现,但是每个实现都必须按照接口定义的标准来实现具体的功能(比如接口方法的入参、出参),这样组件之间就可以基于接口的标准互相替换。

示例

public  interface Shape {  
  Integer area();
     
}


public class Circle implements Shape {
	@Override
	public Integer area() {
	}
}


public class Rectangle implements Shape {
	@Override
	public Integer area() {
	}
}

示例中Circle和Rectangle按照Shape的标准实现了area,两个实现逻辑上是可以互相替换的。

核心

应用与软件架构层面

接口隔离原则

定义:软件设计中避免不必要的依赖

反例

public class OPS {

	public void op1() {
	}
	public void op2(){
	};
	public void op3(){
	};
}

public class User1 extends OPS{
	public void test() {
		this.op1();
	}
}

public class User2 {
	public void test() {
		this.op2();
	}
}

public class User3 {
	public void test() {
		this.op3();
	}
}

以上代码中User1虽然不需要调用op2、op3,但在源代码层次上也与它们形成依赖关系,User1虽然不需要调用op2、op3,但在源代码层次上也与它们形成依赖关系。 Pasted image 20241209075302.png

接口隔离改动


public class OPS implements U1Ops,U2Ops,U3Ops{
	@Override
	public void op1() {
	}

	@Override
	public void op2(){
	}

	@Override
	public void op3(){
	}
}

-- 通过设计独立的接口供外部使用,底层的实现通过OPS做具体的功能实现,这样外部只感知具体的方法不感知具体的实现
public interface U1Ops {
	void op1();
}

public interface U2Ops {
	void op2();
}

public interface U3Ops {
	void op3();
}


public class User1  {
	@Resource 
	private U1Ops u1Ops;

	public void test() {
		u1Ops.op1();
	}
}

public class User2 {
	@Resource 
	private U2Ops u2Ops;

	public void test() {
		u2Ops.op2();
	}
}

public class User3 {
	@Resource 
	private U3Ops u3Ops;

	public void test() {
		u3Ops.op3();
	}
}

以上代码中通过接口隔离具体功能实现类,接口明确具体的功能,使用者只要明确接口能提供的功能即可。

Pasted image 20241209075331.png

这样就明确了该原则的核心【避免不必要的依赖】按需依赖

依赖翻转原则

定义:底层细节的代码应该依赖高层策略的代码(依赖高层的抽象接口)

如何理解这句话呢?

我的理解是当一个类使用另外一个类的实现不应该直接依赖该类,而且要依赖类的接口,避免与实现类强耦合不容易扩展。 具体体现

反例

class Lower {

	@Resource 
	private Higher higher;

	public void test() {
		higher.test();
	}

}


class Higher {
	void test() {
	}
}

上述代码中Lower直接依赖Higher,如果后续Higher做了调整,需要依赖Higher2那么需要需要修改Lower的以来关系,如果使用接口,则体现如下:

public class Lower {

	@Resource 
	private BaseService service;

	public void test() {
		//省略使用工厂模式获取具体的实现
		service.test();
	}

}

public interface BaseService {

	void test();

	String level();
}

class Higher implements BaseService{

	@Override
	public void test() {
	}

	@Override
	public String level() {
		return "Higher";
	}
}

class Higher2 implements BaseService{

	@Override
	void test() {
	}

	@Override
	public String level() {
		return "Higher2";
	}
}

以上代码中Higher和Higher2实现了BaseService,在Lower如果要做调整则基于获取的工厂可以动态调整,不需要修改Lower也实现了开闭原则。 Pasted image 20241209075233.png

架构要关注软件系统内部经常会变动的具体实现模块

  • 争取在不修改接口的情况下为软件新增新的功能
  • 抽象层尽量做到不做变更

基本守则

应用代码中多使用抽象接口,尽量避免使用多变的实现类

避免大量使用继承,继承关系式依赖关系最强的