首页 > 数据库 > 数据库开发技术 > 架构设计系列 3 : 基本理论(一)
在软件开发和软件架构领域,深厚的理论基础是构建高质量、可维护、可扩展系统的关键,本部分内容将围绕这些基础理论展开。
一、编程三范式
编程范式是程序设计的基本思想和方法,对于程序员来说,他是告诉我们在实际编码和设计的过程中不能做什么,而非可以做什么。了解和灵活运用编程范式,有助于构建更加清晰、灵活、可维护的代码。
1、结构化编程范式
结构化编程是基于过程的一种编程范式,将问题分解为一系列的步骤,每个步骤都由一个过程或函数表示。强调程序的执行过程,是用于简单的任务和流程。
优势
直观且易于理解和实现
适用于线性、简单的任务
适用场景
独立脚本编写
简单的操作和流程
2、面向对象编程范式
面向对象编程是一种以对象为基础的编程范式,将数据和操作封装在对象中,强调对象之间的交互和关系。
核心思想是通过抽象、继承、封装和多态等概念,提高代码的重用性和可维护性。
优势
模块化,提高代码的可维护性
重用性高,减少代码重复
适用场景
复杂系统设计
大规模团队协作
3、函数式编程范式
函数式编程是一种以函数为基础的编程范式,将计算视为数据函数的求值过程。强调纯函数、不可变性和无副作用,通过组合函数来构建复杂的系统。
优势
副作用少,易于调试和测试
并行计算友好
适用场景
数据处理和转换
并行和分布式系统
了解并灵活运用这三个范式,有助于根据问题的性质选择最合适的编程方式,提高代码的质量和可维护性。在实际项目中,通常会综合运用多种范式,以充分发挥他们的优势。
二、架构设计原则
1、合适原则
架构设计应该全面考虑时间、人力、能力和效率成本,选择当前最为合适的方案。
合适的方案并非简单化,而是在充分考虑投资回报率(ROI)的基础上,根据项目需求和实际情况进行权衡和决策。
在实现功能的前提下,追求高效率和降低成本是合适方案的关键。
2、简单原则
简单原则强调在满足功能需求的前提下,追求简洁和可读性。
简单设计有助于提高研发效率、降低成本、减少错误率,并能够提高系统的可维护性和可扩展性。
核心思想是简单优于复杂。在实现功能的前提下,减少代码量和复杂性,避免过度设计带来的复杂度。
简单的设计更容易理解、测试和部署,同时也易于维护和扩展,减少未来的开发难度和成本。
3、演化原则
演化原则是指导设计出能够适应未来变化的重要原则,要求技术人员具备前瞻性。
架构设计应该具备灵活性和可扩展性,以便在未来的开发过程中能轻松应对新功能的研发、适应新的需求和变化。
核心思想是演化强于一步到位。任何一个软件产品和系统都需要不断变化和演进,因此在设计过程中要避免过于复杂、难以维护和扩展的结构。
持续的演化有助于保持系统的健康状态,适应业务和技术的不断发展。
三、软件设计七原则
现在一般讲软件设计原则的时候,绝大部分的书籍和文章中都是介绍的 SOLID 原则,不过我今天这里要讲的是除了 SOLID 原则以外,还得加上 CARP 和 LoD 两个原则。
1、SRP
SRP
Single responsibility Principle
单一职责原则
SRP
是一项简单易懂且重要的原则,但是在实 际过程中往往最容易忽略的,它强调在应用中,一个类应该只有一个引起变化的原因,只有一个职责。
SRP 的优点
降低类之间的耦合:将不同职责分解为不同的类,降低类之间的依赖关系,提高系统的灵活性和可维护性
提升类的可维护性和可重用性:当一个类只有一个职责时,修改该职责不会影响到其他职责,使得类更加稳定,易于维护和重用
简化设计过程:SRP 原则使得类的设计更加清晰,每个类都专注于解决一个问题,降低了设计的复杂性
示例代码
不符合 SRP 的代码
public class UserService {/*** 运营部员工绩效计算 */public BigDecimal calculateKPIResultForOperation() { }/*** 商务员工绩效计算 */public BigDecimal calculateKPIResultForBusiness() { }/*** 产品部员工绩效计算 */public BigDecimal calculateKPIResultForProduct() { }/*** 技术部员工绩效计算 */public BigDecimal calculateKPIResultForTechnology() { }}
使用 SRP 优化后的代码
public interface UserService {public BigDecimal calculateKPIResult();}// 不同部门实现接口public class OperationUserService implement UserService {}public class BusinessUserService implement UserService {}public class ProductUserService implement UserService {}public class TechnologyUserService implement UserService {}
使用 SRP 的注意点
拆分多职责类:如果一个类承担了多个职责,就一定要拆分为多个类,确保每个类只有一个职责
识别变化原因:识别引起类变化的原因,然后职责分配到对应的类,确保每个类的变化原因是明确的
避免过度设计:尽可能简单,避免过度设计和复杂性,SRP 的目标是简化设计,而不是增加不必要的结构
2、OCP
OCP
Open/Close Principle
开闭原则
OCP
的核心思想是通过扩展已有的代码,增加新的行为和功能,而不是修改已有的代码。
OCP 的优点
提高代码的可维护性和可重用性:允许系统通过扩展已有的代码来应对新的需求,而不是修改已有的代码,降低引入错误的风险
减少代码的耦合度:模块化的设计使得系统个部分之间的耦合度降低,一个模块的修改不会对其他模块产生影响
提高系统的可扩展性和可升级性:新功能可以通过添加新的类或模块来实现,而不是修改已有的代码,使得系统更容易扩展和升级
示例代码
不符合 OCP 的代码
public class Calculator {public int add(int a, int b) {return a + b;}public int subtract(int a, int b) {return a - b;}public int multiply(int a, int b) {return a * b;}public int divide(int a, int b) {return a / b;}}
使用 OCP 优化后的代码
public interface Operation {int calculate(int a, int b);}public class AddOperation implements Operation {public int calculate(int a, int b) {return a + b;}}public class SubtractOperation implements Operation {public int calculate(int a, int b) {return a - b;}}public class MultiplyOperation implements Operation {public int calculate(int a, int b) {return a * b;}}public class DivideOperation implements Operation {public int calculate(int a, int b) {return a / b;}}
使用 OCP 的注意点
使用 OCP 原则:把实体设计成可扩展,通过新加类、模块或者函数来扩展功能和行为
抽象化已有代码:对已有代码进行抽象,将具体的实现细节封装,对外仅提供抽象的接口
避免过度设计:OCP 原则的目标是简化设计而不是增加不必要的结构
3、LSP
LSP
Liskov Substitution Principle
里氏替换原则
LSP
强调在软件中,子类必须能够替换其父类,即子类应该具有与父类相同的行为和功能,而不仅仅是继承父类的属性和方法。
LSP 的优点
提高代码的可读性和可维护性:子类与父类具有一致的行为和功能,使得代码更易于理解和维护
减少代码的冗余和复杂性:子类继承父类的方法和属性,可以避免重复编写相似的代码
提高系统的可扩展性和可升级性:新的子类可以无缝地替换父类,不会影响系统的其他部分
示例代码
不符合 LSP 的代码
class Shape {void draw() {System.out.println("Drawing a shape");}}class Circle extends Shape {void draw() {System.out.println("Drawing a circle");}}
使用 LSP 优化后的代码
interface Drawable {void draw();}class Shape implements Drawable {public void draw() {System.out.println( "Drawing a shape");}}class Circle extends Shape {public void draw() {System.out.println("Drawing a circle");}}
使用 LSP 的注意点
子类具有一致的行为和功能:子类必须具有与父类相同的行为和功能,但不能改变父类的行为和功能
抽象类定义抽象方法:抽象类应该定义抽象方法,具体方法在子类中实现
避免继承滥用:避免使用继承来共享行为,继承是用来实现多态行为的,而不是为了代码的重用。如果子类需要不同的功能和行为,那应该通过重写父类方法来实现
4、ISP
ISP
Interface Segregation Principle
接口隔离原则
ISP
强调在应用中使用多个特定的接口,而不是一个单一的总接口,从而避免端侧就不需要被强制依赖他们不需要的接口。
ISP 的优点
提高代码的可维护性和可重用性,特定接口提供特定服务,代码可以更加模块化和可定制化
减少端侧的复杂性,端侧只需要依赖实际使用的接口,避免对不相关接口的强制依赖
提高系统的可扩展性和可升级性,新的接口可以被添加而不会影响实际使用的接口,使得系统更容易扩展和升级
示例代码
不符合 ISP 的代码
interface ShoppingCart {void addItem(Product product, int quantity);void removeItem(Product product);void updateQuantity(Product product, int quantity);}class ShoppingCartImpl implements ShoppingCart {private Map<Product, Integer> items = new HashMap<>();public void addItem(Product product, int quantity) {items.put(product, quantity);}public void removeItem(Product product) {items.remove(product);}public void updateQuantity(Product product, int quantity) {int currentQuantity = items.get(product);items.put(product, currentQuantity + quantity);}}
使用 ISP 优化后的代码
interface AddToCart {void addItem(Product product, int quantity);}interface RemoveFromCart {void removeItem(Product product);}interface UpdateQuantity {void updateQuantity(Product product, int quantity);}class ShoppingCartImpl implements AddToCart, RemoveFromCart, UpdateQuantity {private Map<Product, Integer> items = new HashMap<>();public void addItem(Product product, int quantity) {items.put(product, quantity);}public void removeItem(Product product) {items.remove(product);}public void updateQuantity(Product product, int quantity) {int currentQuantity = items.get(product);items.put(product, currentQuantity + quantity);}}
使用 ISP 的注意点
接口定义尽可能小:每个接口提供有限的服务,方法尽可能少,不要妄想一个接口走遍天下
分离不相关功能:如果接口中提供的功能不相关,需要将接口进行分离操作,形成独立接口,代码可更模块化和可定制化
避免使用过大的总接口:总接口应该根据需要提供适当的功能,而不是一刀切提供所有功能
5、DIP
DIP
Dependency Inversion Principle
依赖倒置原则
DIP
强调在应用中,高层模块不应该依赖于底层模块,它们应该依赖于抽象。
DIP 的优点
提高代码的可读性和可维护性:高层模块依赖于抽象,而不是具体实现,使得代码更灵活和易于理解
降低类之间的耦合度:依赖抽象不依赖具体实现,减少了高层模块和底层模块之间的直接依赖,提高了系统的灵活性
提高系统的可扩展性和可升级性:新的实现可以通过实现抽象来引入,不需要修改高层模块的代码
示例代码
不符合 DIP 的代码
class UserService {private UserDao userDao;public UserService(UserDao userDao) {this.userDao = userDao;}public User getUserById(int userId) {return userDao.getUserById(userId);}}class UserDao {public User getUserById(int userId) {// 具体实现逻辑,如从数据库中获取用户信息return new User(userId, "John Doe");}}
使用 DIP 优化后的代码
interface UserDataAccess {User getUserById(int userId);}class UserDao implements UserDataAccess {public User getUserById(int userId) {// 具体实现逻辑,如从数据库中获取用户信息return new User(userId, "John Doe");}}class UserService {private UserDataAccess userDataAccess;public UserService(UserDataAccess userDataAccess) {this.userDataAccess = userDataAccess;}public User getUserById(int userId) {return userDataAccess.getUserById(userId);}}
使用 DIP 的注意点
通过接口或抽象类定义依赖关系:使用接口或抽象类来定义高层模块和底层模块之间的依赖关系
避免直接依赖具体类:如果直接依赖具体类,一旦有修改,依赖元就要同步改动,影响和成本都较高
使用依赖注入解耦:使用依赖注入来解耦类之间的依赖关系,通过注入抽象的实现来实现高层模块对底层模块的依赖
6、CARP
CARP
Composition/Aggregation Reuse Principle
合成/聚合附庸原则
CARP
强调在应用设计过程中优先使用合成/聚合的关系,而不是继承的关系来实现复用。
CARP 的优点
更好地代码封装:通过使用合成/聚合,可以将对象的不同部分封装在不同的类中,更好地隐藏细节,提高代码的模块化和可维护性
更灵活的代码结构:通过使用合成/聚合,可以更容易地改变对象的行为和结构,只需要修改相关的类,不需要修改整个继承体系
更好的可重用性:通过使用合成/聚合,可以根据需要组合不同的对象来实现代码的可重用性,且合成/聚合本身是一种松耦合,可以更便捷地组装新的对象类型
更好的可扩展性:通过使用合成/聚合,更便捷地添加新的功能和行为到应用中,这种灵活的关系,可以更容易适应新的需求和变化
示例代码
不符合 CARP 的代码
class Car {private Engine engine;private Transmission transmission;private Wheel wheel;private Door door;public Car(Engine engine, Transmission transmission, Wheel wheel, Door door) {this.engine = engine;this.transmission = transmission;this.wheel = wheel;this.door = door;}public void start() {engine.start();}public void shift(int gear) {transmission.shift(gear);}public void turn(int degrees) {wheel.turn(degrees);}public void open() {door.open();}}
使用 CARP 优化后的代码
interface Engine {void start();}interface Transmission {void shift(int gear);}interface Wheel {// 可以添加一些方法,例如 rotate() 和 brake() 等}interface Door {void open();}class Car {private Engine engine;private Transmission transmission;private Wheel wheel;private Door door;public Car(Engine engine, Transmission transmission, Wheel wheel, Door door) {this.engine = engine;this.transmission = transmission;this.wheel = wheel;this.door = door;}}
使用 CARP 的注意点
封装变化:将变化的部分封装起来,使得变化对其他部分的影响最小
强调组合/聚合:首选使用组合/聚合关系,而不是直接继承关系,以提高灵活性和可维护性
松耦合:合成/聚合是一种松耦合关系,允许系统更容易地适应变化
7、LoD
LoD
Law of Demeter
迪米特法则
LoD
强调在应用中应该尽量减少对象之间的直接依赖关系,降低耦合度,提高可维护性和可重用性。
核心思想是一个对象对其他对象保持最少的了解,并且只和那些和自己最有直接关系的对象进行交互。
一个对象只暴露必要的接口给其他对象,并且应该通过这些接口与其他对象进行交互。
LoD 的优点
降低耦合度:减少对象之间的直接依赖关系,使得系统更容易扩展和维护
提高可维护性:对象之间的松耦合关系使得修改一个对象的内部实现不会影响其他对象
提高可重用性:松耦合关系允许对象更容易地被独立重用在不同的上下文中
示例代码
不符合 LoD 的代码
class Account {private User user;private List<Transaction> transactions;public Account(User user, List<Transaction> transactions) {this.user = user;this.transactions = transactions;}public double getBalance() {double balance = 0.0;for (Transaction transaction : transactions) {balance += transaction.getAmount();}return balance;}public void debit(double amount) {user.setBalance(user.getBalance() - amount);}}
使用 LoD 优化后的代码
interface UserService {double getBalance(User user);}interface TransactionService {void debit(User user, double amount);}class Account {private UserService userService;private TransactionService transactionService;public Account(UserService userService, TransactionService transactionService) {this.userService = userService;this.transactionService = transactionService;}public double getBalance() {return userService.getBalance(userService.getUser());}public void debit(double amount) {transactionService.debit(userService.getUser(), amount);}}
使用 LoD 的注意点
定义接口:将对象的相关操作定义在接口中,而不是直接依赖于具体的实现
通过接口交互:对象应该通过接口进行交互,而不是直接调用其他对象的方法
减少依赖关系:一个对象应该只与其直接的朋友发生交互,避免依赖过多的对象
本文说明:本文中大部分内容来自于《架构整洁之道》学习总结,关于架构设计的理论和指导的基础用书,首推 Bob 大叔的这本《架构整洁之道》
原文作者:灸哥漫谈;如有侵权,请联系管理员删除。