首页
开发技巧正文内容

在短短8分钟内掌握SOLID原则

2023年11月09日
阅读时长 2 分钟
阅读量 3
在短短8分钟内掌握SOLID原则

在您的React应用程序中使用SOLID原则提升性能、可维护性和可扩展性!

在本博客中,我将演示在React应用程序中实现SOLID原则。通过阅读本文,您将完全掌握SOLID原则。在我们开始之前,让我给您简要介绍一下这些原则。

什么是SOLID原则?

SOLID原则是五个设计原则,它们帮助我们保持应用程序的可重用性、可维护性、可扩展性和松耦合性。SOLID原则包括:

  • 单一职责原则

  • 开闭原则

  • 里氏替换原则

  • 接口隔离原则

  • 依赖倒置原则

好的,让我们逐个检查这些原则。我以React作为示例,但核心概念与其他编程语言和框架类似。

单一职责原则

"一个模块应该对一个角色负责,而且只对一个角色负责。" - 维基百科。

单一职责原则指出组件应该具有一个明确的目的或职责。它应该专注于特定的功能或行为,并避免承担无关的任务。遵循SRP使组件更加专注、模块化,并且更容易理解、修改和测试。让我们看看实际的实现。

// ❌ 不好的实践:具有多个职责的组件

const Products = () => {
    return (
        <div className="products">
            {products.map((product) => (
                <div key={product?.id} className="product">
                    <h3>{product?.name}</h3>
                    <p>${product?.price}</p>
                </div>
            ))}
        </div>
    );
};

在上面的示例中,Products组件违反了单一职责原则,因为它承担了多个职责。它管理产品的迭代并处理每个产品的UI渲染。这可能使组件在将来变得难以理解、维护和测试。

相反,遵循SRP应该这样做:

// ✅ 好的实践:将职责分离为更小的组件

import Product from './Product';
import products from '../../data/products.json';

const Products = () => {
    return (
        <div className="products">
            {products.map((product) => (
                <Product key={product?.id} product={product} />
            ))}
        </div>
    );
};

// Product.js
// 负责渲染产品详情的单独组件
const Product = ({ product }) => {
    return (
        <div className="product">
            <h3>{product?.name}</h3>
            <p>${product?.price}</p>
        </div>
    );
};

这种分离确保每个组件具有单一职责,使它们更容易理解、测试和维护。

开闭原则

"软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。" - 维基百科。

开闭原则强调组件应该对扩展开放(可以添加新的行为或功能),但对修改关闭(现有代码不应更改)。这个原则鼓励创建具有抵抗变化、模块化和易于维护的代码。让我们看看实际的实现。

// ❌ 不好的实践:违反开闭原则

// Button.js
// 现有的Button组件
const Button = ({ text, onClick }) => {
  return (
    <button onClick={onClick}>
      {text}
    </button>
  );
}

// Button.js
// 修改后的Button组件,添加了额外的icon属性(修改)
const Button = ({ text, onClick, icon }) => {
  return (
    <button onClick={onClick}>
      <i className={icon} />
      <span>{text}</span>
    </button>
  );
}

// Home.js
// 👇 避免:修改现有组件的属性
const Home = () => {
  const handleClick= () => {};

  return (
    <div>
      {/* 避免这种情况 */}
      <Button text="Submit" onClick={handleClick} icon="fas fa-arrow-right" /> 
    </div>
  );
}

在上面的示例中,我们通过添加icon属性修改了现有的Button组件。修改现有组件以适应新需求违反了开闭原则。这些更改使组件更加脆弱,并在不同的上下文中使用时引入了意外的副作用的风险。

相反,应该这样做:

// ✅ 好的实践:开闭原则

// Button.js
// 现有的Button函数组件
const Button = ({ text, onClick }) => {
  return (
    <button onClick={onClick}>
      {text}
    </button>
  );
}

// IconButton.js
// IconButton组件
// ✅ 好的:您没有在这里修改任何内容。
const IconButton = ({ text, icon, onClick }) => {
  return (
    <button onClick={onClick}>
      <i className={icon} />
      <span>{text}</span>
    </button>
  );
}

const Home = () => {
  const handleClick = () => {
    // 处理按钮点击事件
  }

  return (
    <div>
      <Button text="Submit" onClick={handleClick} />
      {/* 
      <IconButton text="Submit" icon="fas fa-heart" onClick={handleClick} />
    </div>
  );
}

在上面的示例中,我们创建了一个单独的IconButton函数组件。IconButton组件封装了图标按钮的渲染,而不修改现有的Button组件。通过通过组合扩展功能而不是修改来遵循开闭原则。

里氏替换原则

"子类型对象应该能够替换其基类型对象" - 维基百科。

里氏替换原则(LSP)是面向对象编程的基本原则,强调在层次结构中对象的可替代性。在React组件的上下文中,LSP提倡派生组件能够替代其基本组件,而不影响应用程序的正确性或行为。让我们看看实际的实现。

// ⚠️ 不好的实践
// 这种方法违反了里氏替换原则,因为它修改了派生组件的行为,可能导致在替代基本Select组件时出现意外问题。
const BadCustomSelect = ({ value, iconClassName, handleChange }) => {
  return (
    <div>
      <i className={iconClassName}></i>
      <select value={value} onChange={handleChange}>
        <options value={1}>One</options>
        <options value={2}>Two</options>
        <options value={3}>Three</options>
      </select>
    </div>
  );
};

const LiskovSubstitutionPrinciple = () => {
  const [value, setValue] = useState(1);

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  return (
    <div>
      {/** 避免这种情况 */}
      {/** 下面的Custom Select没有基本`select`元素的特性 */}
      <BadCustomSelect value={value} handleChange={handleChange} />
    </div>
  );
};

在上面的示例中,我们有一个BadCustomSelect组件,用于在React中作为自定义选择输入。然而,它违反了里氏替换原则(LSP),因为它限制了基本select元素的行为。

相反,应该这样做:

// ✅ 好的实践
// 这个组件遵循了里氏替换原则,并允许使用select的特性。

const CustomSelect = ({ value, iconClassName, handleChange, ...props }) => {
  return (
    <div>
      <i className={iconClassName}></i>
      <select value={value} onChange={handleChange} {...props}>
        <options value={1}>One</options>
        <options value={2}>Two</options>
        <options value={3}>Three</options>
      </select>
    </div>
  );
};

const LiskovSubstitutionPrinciple = () => {
  const [value, setValue] = useState(1);

  const handleChange = (event) => {
    setValue(event.target.value);
  };

  return (
    <div>
      {/* ✅ 这个CustomSelect组件遵循了里氏替换原则 */}
      <CustomSelect
        value={value}
        handleChange={handleChange}
        defaultValue={1}
      />
    </div>
  );
};

在修改后的代码中,我们有一个CustomSelect组件,用于扩展React中标准select元素的功能。该组件接受valueiconClassNamehandleChange和其他使用扩展运算符...props的附加属性。通过允许使用select元素的特性并接受附加属性,CustomSelect组件遵循了里氏替换原则(LSP)。

接口隔离原则

"不应该强迫代码依赖于它不使用的方法。" - 维基百科。

接口隔离原则(ISP)建议接口应该专注于特定的客户端需求,而不是过于广泛并强迫客户端实现不必要的功能。让我们看看实际的实现。


// ❌ 避免:为此组件披露不必要的信息
// 这会为组件引入不必要的依赖和复杂性
const ProductThumbnailURL = ({ product }) => {
  return (
    <div>
      <img src={product.imageURL} alt={product.name} />
    </div>
  );
};

// ❌ 不好的实践
const Product = ({ product }) => {
  return (
    <div>
      <ProductThumbnailURL product={product} />
      <h4>{product?.name}</h4>
      <p>{product?.description}</p>
      <p>{product?.price}</p>
    </div>
  );
};

const Products = () => {
  return (
    <div>
      {products.map((product) => (
        <Product key={product.id} product={product} />
      ))}
    </div>
  );
}
```在上面的示例中,我们将整个产品详细信息传递给`ProductThumbnailURL`组件,即使它不需要。这给组件增加了不必要的风险和复杂性,并违反了接口隔离原则(ISP)。

### 让我们重构以遵守ISP:

```javascript

// ✅ 好的做法:减少不必要的依赖关系,使代码库更易于维护和扩展。
const ProductThumbnailURL = ({ imageURL, alt }) => {
  return (
    <div>
      <img src={imageURL} alt={alt} />
    </div>
  );
};

// ✅ 好的做法
const Product = ({ product }) => {
  return (
    <div>
      <ProductThumbnailURL imageURL={product.imageURL} alt={product.name} />
      <h4>{product?.name}</h4>
      <p>{product?.description}</p>
      <p>{product?.price}</p>
    </div>
  );
};

const Products = () => {
  return (
    <div>
      {products.map((product) => (
        <Product key={product.id} product={product} />
      ))}
    </div>
  );
};

在修改后的代码中,ProductThumbnailURL组件只接收所需的信息,而不是整个产品详细信息。这样可以避免不必要的风险,并遵守接口隔离原则(ISP)。

依赖倒置原则

"一个实体应该依赖于抽象,而不是具体实现" - 维基百科。

依赖倒置原则(DIP)强调高层组件不应依赖于低层组件。这个原则促进了松耦合和模块化,并有助于更容易维护软件系统。让我们看看实际的实现。

// ❌ 不好的做法
// 这个组件遵循具体实现而不是抽象,违反了依赖倒置原则

const CustomForm = ({ children }) => {
  const handleSubmit = () => {
    // 提交操作
  };
  return <form onSubmit={handleSubmit}>{children}</form>;
};

const DependencyInversionPrinciple = () => {
  const [email, setEmail] = useState();

  const handleChange = (event) => {
    setEmail(event.target.value);
  };

  const handleFormSubmit = (event) => {
    // 提交业务逻辑在这里
  };

  return (
    <div>
      {/** ❌ 避免:紧密耦合,难以更改 */}
      <CustomForm>
        <input
          type="email"
          value={email}
          onChange={handleChange}
          name="email"
        />
      </CustomForm>
    </div>
  );
};

CustomForm组件与其子组件紧密耦合,阻碍了灵活性,使更改或扩展其行为变得困难。

相反,应该这样做:

// ✅ 好的做法
// 这个组件遵循抽象,促进了依赖倒置原则

const AbstractForm = ({ children, onSubmit }) => {
  const handleSubmit = (event) => {
    event.preventDefault();
    onSubmit();
  };

  return <form onSubmit={handleSubmit}>{children}</form>;
};

const DependencyInversionPrinciple = () => {
  const [email, setEmail] = useState();

  const handleChange = (event) => {
    setEmail(event.target.value);
  };

  const handleFormSubmit = () => {
    // 提交业务逻辑在这里
  };

  return (
    <div>
      {/** ✅ 使用抽象 */}
      <AbstractForm onSubmit={handleFormSubmit}>
        <input
          type="email"
          value={email}
          onChange={handleChange}
          name="email"
        />
        <button type="submit">提交</button>
      </AbstractForm>
    </div>
  );
};

在修改后的代码中,我们引入了AbstractForm组件,它充当表单的抽象。它接收onSubmit函数作为属性,并处理表单提交。这种方法使我们能够轻松地替换或扩展表单行为,而无需修改更高级别的组件。

结论

SOLID原则提供了指导,使开发人员能够创建设计良好、易于维护和可扩展的软件解决方案。通过遵循这些原则,开发人员可以实现模块化、代码可重用性、灵活性和减少代码复杂性。

希望本文提供了有价值的见解,并激发您在现有或后续的React项目中应用这些原则。

保持好奇心,继续编码!

免责声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

相关文章

探索多种软件架构模式及其实用应用
2024年11月22日19:06
本文深入探讨了多种软件架构模式,包括有界上下文、边车模式、发布-订阅模式、应用网关、微服务、命令职责分离(CQRS)等,介绍了它们的优点、使用场景以及具体应用实例。文章强调根据具体项目需求和团队能力选择最合适的架构,以构建高效和可维护的解决方案,同时展示了各架构模式间的综合应用,提供了丰富的案例和技术细节。
15个高级Python快捷键助您更快编程
2024年11月21日07:02
本文分享了 15 个高级的 Python 编程快捷键,包括上下文管理器、行内字典合并、函数参数解包、链式比较、dataclasses、海象运算符、反转列表、备忘录缓存、splitlines、enumerate、字典推导、zip 用于并行迭代、itertools.chain 扁平化列表、functools.partial 部分函数和 os.path 文件路径管理等,帮助开发者提高编程效率和代码简洁性。
揭示网页开发的 11 个迷思:停止相信这些误区
2024年11月19日22:05
网页开发充满误解,这篇博文针对11个常见迷思进行揭秘。包括网站开发后不需更新、需掌握所有技术、AI会取代开发者等。强调持续学习、专业化、用户体验的重要性,澄清误区如多任务处理的必要性和最新技术的必需性。文章提醒开发者注重实用而非追求完美代码,以务实态度面对开发工作。
你知道 CSS 的四种 Focus 样式吗?
2024年11月18日21:41
本文介绍了四种 CSS focus 样式::focus、:focus-visible、:focus-within 以及自定义的 :focus-visible-within,帮助提升网站用户体验。:focus 样式应用于被选中元素;:focus-visible 仅在键盘导航时显示;:focus-within 用于父元素;自定义 :focus-visible-within 结合两者效果。合理运用这些样式能使网站更方便键盘用户导航。
利用 Python 实现自动化图像裁剪:简单高效的工作流程
2024年11月11日20:49
使用 Python 和 OpenCV 自动裁剪图像,轻松实现 16:9 的完美构图。这个指南介绍了如何通过代码进行灰度化、模糊处理和边缘检测,最终识别出最重要的部分进行裁剪。特别适合需要批量处理图像的情况,节省大量时间。
每位资深前端开发人员都应了解的 TypeScript 高级概念
2024年11月11日02:07
资深前端开发者应了解 TypeScript 的高级概念,如联合类型、交叉类型、类型保护、条件类型、映射类型、模板字面量类型和递归类型。这些特性可提升代码的可维护性和可扩展性,确保在开发复杂应用时实现更高的类型安全性和效率。