SOLID:软件设计原则。成为更好的开发人员
SOLID设计原则源自面向对象编程指南。它旨在开发易于维护和扩展的软件;防止代码异味;易于重构;提高敏捷性,并最终快速整合快速频繁的变更,避免出现 Bug。
一般来说,技术债务的产生是因为优先考虑快速交付而非完美代码。为了控制技术债务,请在开发过程中遵循 SOLID 原则。
Robert Martin被认为是 SOLID 原则的作者,他指出,如果不严格遵循 SOLID,软件将面临四大问题。它们是:
-
刚度:
- 实施哪怕是很小的改变都是困难的,因为它可能会引发一系列的改变。
-
脆弱性:
- 任何变化都可能在很多地方破坏软件,甚至在概念上与变化不相关的领域。
-
不动:
- 我们无法重用其他项目或同一项目内的模块,因为这些模块具有很多依赖关系。
-
粘度:
- 很难以正确的方式实现新功能。
SOLID 是一条指导方针,而非规则。重要的是理解其核心,并将其与清晰的判断结合起来。在某些情况下,所有原则中可能只需要几条即可。
SOLID 代表:
- 单一职责原则(SRP);
- 开放封闭原则(OCP);
- 里氏替换原则(LSP);
- 接口隔离原则(ISP);
- 依赖倒置原则(DIP);
单一职责原则(SRP)
每个函数、类或模块都应该有且仅有一个改变的原因,这意味着应该只有一项工作并封装在类中(类内凝聚力更强)。
It supports "Separation of concerns" — do one thing, and do it well!"
附注:本文最初发表于Box Piper来源。
例如,考虑这个类:
class Menu {
constructor(dish: string) {}
getDishName() {}
saveDish(a: Dish) {}
}
这个类违反了 SRP。原因如下:它管理菜单的属性,同时也处理数据库。如果数据库管理功能有任何更新,也会影响属性管理功能,从而导致耦合。
更具凝聚力且耦合度更低的类实例。
// Responsible for menu management
class Menu {
constructor(dish: string) {}
getDishName() {}
}
// Responsible for Menu management
class MenuDB {
getDishes(a: Dish) {}
saveDishes(a: Dish) {}
}
开放封闭原则(OCP)
类、函数或模块应该对可扩展性开放,但对修改关闭。
如果你创建并发布了一个类,而对这个类进行修改,可能会破坏那些开始使用这个类的人的实现。抽象是正确实施 OCP 的关键。
例如,考虑这个类:
class Menu {
constructor(dish: string) {}
getDishName() {}
}
我们想要遍历菜肴列表并返回其菜肴。
class Menu {
constructor(dish: string){ }
getDishName() { // ... }
getCuisines(dishName) {
for(let index = 0; index <= dishName.length; index++) {
if(dishName[index].name === "Burrito") {
console.log("Mexican");
}
else if(dishName[index].name === "Pizza") {
console.log("Italian");
}
}
}
}
函数 getCuisines() 不符合开放封闭原则,因为它无法对新种类的菜肴进行关闭。
如果我们添加一道新菜Croissant
,我们需要改变功能并添加这样的新代码。
class Menu {
constructor(dish: string){ }
getDishName() { // ... }
getCuisines(dishName) {
for(let index = 0; index <= dishName.length; index++) {
if(dishName[index].name === "Burrito") {
console.log("Mexican");
}
if(dishName[index].name === "Pizza") {
console.log("Italian");
}
if(dishName[index].name === "Croissant") {
console.log("French");
}
}
}
}
如果你观察一下,就会发现每添加一道新菜,getCuisines() 函数中都会添加一条新的逻辑。根据开放封闭原则,该函数应该开放以进行扩展,而不是修改。
以下是我们如何使代码库符合 OCP 标准。
class Menu {
constructor(dish: string) {}
getCuisines() {}
}
class Burrito extends Menu {
getCuisine() {
return "Mexican";
}
}
class Pizza extends Menu {
getCuisine() {
return "Italian";
}
}
class Croissant extends Menu {
getCuisine() {
return "French";
}
}
function getCuisines(a: Array<dishes>) {
for (let index = 0; index <= a.length; index++) {
console.log(a[index].getCuisine());
}
}
getCuisines(dishes);
这样,每当需要添加新菜品时,我们就不需要修改代码。我们只需创建一个类,并用基类来扩展它即可。
里氏替换原则(LSP)
子类必须可以替换其基类,表明我们可以用子类替换其基类而不影响行为,从而帮助我们符合“is-a”关系。
换句话说,子类必须履行基类定义的契约。从这个意义上讲,它与Bertrand Meyer首次提出的契约式设计相关。
例如,Menu 具有getCuisines
Burrito、Pizza、Croissant 使用的功能,而没有创建单独的功能。
class Menu {
constructor(dish: string) {}
getCuisines(cuisineName: string) {
return cuisineName;
}
}
class Burrito extends Menu {
constructor(cuisineName: string) {
super();
this.cuisine = cuisineName;
}
}
class Pizza extends Menu {
constructor(cuisineName: string) {
super();
this.cuisine = cuisineName;
}
}
class Croissant extends Menu {
constructor(cuisineName: string) {
super();
this.cuisine = cuisineName;
}
}
const burrito = new Burrito();
const pizza = new Pizza();
burrito.getCuisines(burrito.cuisine);
pizza.getCuisines(pizza.cuisine);
接口隔离原则(ISP)
客户端不应该被迫实现它不使用的接口,或者客户端不应该被迫依赖他们不使用的方法。
原则名称中的“接口”一词并不严格地表示接口,它可能是一个抽象类。
例如
interface ICuisines {
mexican();
italian();
french();
}
class Burrito implements ICuisines {
mexican() {}
italian() {}
french() {}
}
如果我们在接口中添加一个新方法,所有其他类都必须声明该方法,否则将引发错误。
为了解决这个问题
interface BurritoCuisine {
mexican();
}
interface PizzaCuisine {
italian();
}
class Burrito implements BurritoCuisine {
mexican();
}
许多特定于客户端的接口比一个通用接口更好。
依赖倒置原则(DIP)
实体必须依赖于抽象,而不是具体。它指出高级模块不能依赖于低级模块,应将它们解耦并利用抽象。
高级模块是应用程序的一部分,用于解决实际问题和用例。
它们更加抽象,并映射到业务领域(业务逻辑);
它们告诉我们软件应该做什么(不是如何做,而是做什么);
低级模块包含执行业务策略所需的实现细节;关于软件如何执行各种任务;
例如
const pool = mysql.createPool({});
class MenuDB {
constructor(private db: pool) {}
saveDishes() {
this.db.save();
}
}
这里,MenuDB 类是高级组件,而池变量是低级组件。为了解决这个问题,我们可以分离 Connection 实例。
interface Connection {
mysql.createPool({})
}
class MenuDB {
constructor(private db: Connection) {}
saveDishes() {
this.db.save();
}
}
结束语
遵循 SOLID 原则的代码可以轻松共享、扩展、修改、测试和重构,不会出现任何问题。随着这些原则在实际应用中的不断应用,其优势将更加凸显。
反模式和不正确的理解会导致愚蠢的代码:单例、紧耦合、不可测试、过早优化、命名不规范以及重复。SOLID 可以帮助开发人员避免这些问题。
要阅读更多此类有趣的主题,请关注并阅读BoxPiper 博客。
支持我的工作,请我喝杯咖啡。这对我来说意义重大。😇
文章来源:https://dev.to/boxpiperapp/solid-software-design-principles-be-a-better-developer-1615