(solid中文翻译是固体) solid是五种准则的首字母组成的
(主要说的是我自己)
我们一方面或在课本上或专门学习了SOLID原则,
另一方面在写代码的时候却不会从这些原则的角度出发,而是写完了代码才去比对这份代码用了什么、遵循了什么。
导致了一种割裂感,这份代码可能也不会是一份非常好的代码,是缺乏思考的;
与此同时也没有对这些原则有很深的理解,常常忘是很自然的。
因此我想要加强理解,最好能从代码的角度去看设计模式。
原则名称 | 核心思想 | 主要目标 |
---|---|---|
S - 单一职责原则 (SRP) | 一个类应该有且仅有一个引起它变化的原因。 | 降低复杂度,提高内聚性。 |
O - 开放封闭原则 (OCP) | 软件实体应对扩展开放,对修改关闭。 | 提升系统的稳定性和可扩展性。 |
L - 里氏替换原则 (LSP) | 子类必须能够替换其父类,且程序行为不变。 | 确保继承关系的正确性。 |
I - 接口隔离原则 (ISP) | 客户端不应被迫依赖它不使用的接口。 | 减少不必要的耦合。 |
D - 依赖倒置原则 (DIP) | 高层模块不应依赖低层模块,二者都应依赖抽象。 | 降低模块间的耦合度,提高灵活性。 |
单一职责原则
单一职责原则:一个类应该仅有一个引起它变化的原因
我之前在使用的时候秉称的就是每一个类只有一个功能,但不够准确
这里面的高内聚:单一职责原则鼓励我们将因相同原因而变化的元素聚集在一起,将因不同原因而变化的元素分离开。最终:类内部联系紧密(高内聚),与其他类的依赖关系简单(低耦合)。
实际的时候要灵活处置,感觉还是要写的代码看的代码够多,处理的场景够多
总结:单一职责原则:一个类应该仅有一个引起它变化的原因、修改一个功能时是否会影响其他模块、该类的功能能否很清晰的就描述出?
一个类应该仅有一个引起它变化的原因:
在设计或评审一个类时,问自己:“这个类未来可能会有哪些不同的理由导致它需要被修改?”如果答案多于一个,那么这个类就可能承担了多个职责,需要考虑拆分。例如,一个类如果既负责订单的计算逻辑,又负责订单数据的数据库存储,那么计算规则变化和数据库类型变化都会导致修改这个类,这就违反了SRP。
#include <iostream>
class OrderItem
{
public:
double getPrice() const {return price;}
int getQuantity() const {return quantity;}
private:
double price;
int quantity;
};
class Order
{
public:
std::vector<OrderItem> getItems() const {return items;}
std::string getOrderId() const {return orderId;}
private:
std::vector<OrderItem> items;
std::string orderId;
};
//模拟库存服务
class StockService
{
public:
static void deductStock(const std::string& productId,int quantity)
{
std::cout << "Deducting stock for product: " << productId
<< ", quantity: " << quantity << std::endl;
}
};
//订单服务类, 没有遵循单一职责原则
class OrderService
{
//计算订单价格,包含促销规则和运费
double calculateOrderPrice(const Order& order)
{
double totalPrice = 0.0;
//遍历订单商品
for(const auto& item:order.getItems())
{
totalPrice+=item.getPrice()*item.getQuantity();
}
// 应用促销规则:满100减10
if (totalPrice >= 100) {
totalPrice -= 10;
}
// 计算固定运费
totalPrice += 10;
return totalPrice;
}
//扣减库存
void deductStock(const Order& order)
{
for(const auto& item:order.getItems())
{
//调用静态库存服务
StockService::deductStock(item.getProductId(), item.getQuantity());
}
}
// 记录订单日志(对应Java的logOrder方法)
void logOrder(const Order& order) {
// C++中可使用标准输出或日志库(如spdlog),此处使用std::cout模拟
std::cout << "Order created: " << order.getOrderId() << std::endl;
}
};
按照单一职责原则进行重构
//订单计算类
class OrderCalculator
{
double calculateOrderPrice(const Order& order)
{
double totalPrice = 0;
for(OrderItem item:order.getItems())
{
totalPrice +=item.getPrice()*item.getQuantity();
}
if(totalPrice>=100)
{
totalPrice-=10;
}
totalPrice+=10;
return totalPrice;
}
};
//库存管理类
class StockManager
{
void deductStock(const Order& order)
{
for(OrderItem item:order.getItems())
{
StockService.deductStock(item.getProductId(),item.getQuantity());
}
}
};
class OrderLogger
{
void logOrder(const Order& order) {
// C++中可使用标准输出或日志库(如spdlog),此处使用std::cout模拟
std::cout << "Order created: " << order.getOrderId() << std::endl;
}
};
开闭原则(OCP)
开闭原则的核心思想:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。
当软件系统需要添加新功能或应对需求变化时,应该通过扩展已有代码的方式来实现,
而不是修改已有的、经过测试且稳定运行的代码。
遵循开闭原则可以有效提高软件的可维护性和可扩展性,降低修改代码带来的风险 。
订单系统
上面这个链接中举了一个例子,是这样描述的
假设最初系统只支持信用卡支付,随着业务的发展,需要增加支付宝和微信支付。如果不遵循开闭原则,直接在订单支付类中添加支付宝和微信支付的逻辑,代码会变得越来越臃肿,维护难度也会大幅增加。而且,每次修改都可能引入新的问题,影响系统的稳定性。
里氏替换原则:
核心思想:
继承父类的子类应该是对父类功能的扩展,而非改变。
接口隔离原则:接口按需实现
依赖倒置原则:
核心思想是:高层模块不应该依赖低层模块的具体实现,而是依赖于抽象接口 ;抽象不应该依赖细节,细节应该依赖于抽象 。在软件系统中,高层模块通常负责实现核心业务逻辑,而低层模块负责提供基础的原子操作。
目的是当低层模块发生变化时,高层模块不需要进行修改。
要做的是:高层模块实现功能、低层模块实现抽象(抽象接口或抽象类)