设计模式设计原则

设计原则

前言:了解设计模式之前,一定要先理解什么设计原则,只有这样才能悟透设计模式的根本。

快速入口

  1. 工厂模式
  2. 单例模式

1.何为设计?

  • 按哪一种思路或者标准来实现的功能;
  • 功能相同,可以有不同设计的方式;
  • 需求如果不断变化,设计的作用才能体现出来;

2. SOLID 五大设计原则

  1. 单一职责原则(S) 单一功能原则认为对象应该仅具有一种 单一功能 的概念;
  2. 开放封闭原则(O)开闭原则认为 软件体应该是对于扩展开放的,但是对于修改封闭的 概念;
  3. 里式替换原则 (L)里式替换原则认为 程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的 概念;
  4. 接口隔离原则 (I) 接口隔离原则认为 多个特定客户端接口要好于一个宽泛用途的接口 的概念;
  5. 依赖反转原则 (D)依赖反转原则认为 应该遵从依赖于抽象而不是一个实例 的概念 ,依赖注入是该原则的一种实现方式;

2.1 单一职责原则 (Single Responsibility Principle)

  • 一个类或者模块只负责一个职责,如果功能特别复杂就进行拆分;
  • 单一职责可以降低类的复杂性,提高代码可读性、可维护性;
  • 当类代码函数过多、方法过多、功能太多、职责太杂的时候就要对类进行拆分了;
  • 拆分不能过度,如果拆分过度会损失内聚性和维护性;

一个商品类,有name,price属性,以及可以修改商品名,和修改价格,那么就可细分

/**
 * 商品类
 */
class Commodity {
  public name: string;
  public price: number;
  // 更新商品名
  public updateName() { }
  // 更新商品价格
  public updatePrice() { }
}

一个类中的方法尽量维持在10个方法以内,这样可以做到高内聚和维护性,但是也不能过分拆分,取决于业务的功能。

2.2 开放封闭原则 (Open Closed Principle)

  • 对扩展开放,对修改关闭

  • 增加需求时,扩展新代码,而非修改已有代码;

  • 开闭原则是设计模式中的总原则;同时也是软件设计的终极目标;

  • 对近期可能会变化并且如果有变化但改动量巨大的地方要增加扩展点,扩展点过多会降低可读性;

例如顾客去商场购物,普通用户不打折,VIP用户打9折,那么我们现在用代码来实现一下


/**
 * 会员类
 * @param {string} rank 会员等级
 */
class User {
  constructor(public rank: string) {
    this.rank = rank;
  }
}

/**
 * 超市类
 * @param name 商品
 * @param price 价格
 */
class Shopping {
  constructor(public name: string, public price: number) {

  }
  cashier(user: User) {
    switch (user.rank) {
      case 'general':
        console.log(`您是普通会员,折扣后的价格是:${this.price * 1}`);
        break;
      case 'vip':
        console.log(`您是vip会员,折扣后的价格是:${this.price * .9}`);
        break;
      default:
        console.log(`您的价格是:${this.price}`);
        break;

    }
  }
}

let goods = new Shopping('MacBook Pro', 10000);
let general = new User('general');
let vip = new User('vip');
console.log(goods.cashier(general)); // 您是普通会员,给您的折扣是:10000
console.log(goods.cashier(vip)); // 您是普通会员,给您的折扣是:9000

假如现在超市又新增了一个黄金VIP,那么现在是不是得修改 Shopping 类中的 cashier 方法,这样就违背了 开放封闭原则 ,所以上面就不是一个完美的设计。

我们现在改进一下上面的代码, 让它符合 开放封闭原则


/**
 * 会员类
 * @param {string} rank 会员等级
 */
class User {
  constructor(public rank: string, public discount: number = 1) {
    this.rank = rank;
    this.discount = discount;
  }
}

/**
 * 超市类
 * @param name 商品
 * @param price 价格
 */
class Shopping {
  constructor(public name: string, public price: number) { }
  cashier(user: User) {
    return this.price * user.discount;
  }
}

let goods = new Shopping('MacBook Pro', 10000);
let general = new User('general');
let vip = new User('vip', .9);
let goldVip = new User('goldVip', .8);
console.log('折扣后的价格是:' + goods.cashier(general)); // 10000
console.log('折扣后的价格是:' + goods.cashier(vip)); // 9000
console.log('折扣后的价格是:' + goods.cashier(goldVip)); // 8000

前端实际应用中符合该原则的较多,几乎大部分库都符合该原则,例如 Axios 中的拦截器;

2.3 里式替换原则 (Liskov Substitution Principle)

  • 所有引用基类的地方必须能透明地使用其子类的对象;
  • 子类能替换掉父类,使用者可能根本就不需要知道是父类还是子类,反之则不行;
  • 里式替换原则是开闭原则的实现基础,程序设计的时候尽量使用基类定义及引用,运行时再决定使用哪个子类;
  • 里式替换原则可以提高代码的复用性,提高代码的可扩展性,也增加了耦合性;
  • 相对于多态,这个原则是讲的是类如何设计,子类如果违反了父类的功能则表示违反了里式替换原则

一个用去买咖啡喝,那么想要知道当前购买咖啡的价格

	/**
 * 尽可能使用父类或者抽象类
 * 任何再能使用父类的地方都要可以使用子类
 * 类似多态
 */

abstract class AbstractCoffe {
  abstract getPrice(): number;
}

class Mocha extends AbstractCoffe {
  getPrice() {
    return 28;
  }
}

class Americano extends AbstractCoffe {
  getPrice() {
    return 26;
  }
}

class Cappuccino extends AbstractCoffe {
  getPrice() {
    return 30;
  }
}

class Customer {
  // 这里可以传父类,也可以传子类,所以就可以替换
  // 这就是里式替换原则
  // 但是这里的子类不能违反父类的约定,也就是 子类中的 getPrice 不能反悔其它的类型,只能返回 Number
  drink(abstractCoffe: AbstractCoffe) {
    console.log("这个咖啡价格是:" + abstractCoffe.getPrice());
  }
}

let c1 = new Customer();
c1.drink(new Mocha())
c1.drink(new Americano())
c1.drink(new Cappuccino())

2.4 依赖倒置原则(Dependence Inversion Principle)

  • 面向接口编程,依赖于抽象而不依赖于具体实现;
  • 要求我们在程序代码中传递参数时或在光联关系中,尽量引用层次高的抽象层类;
  • 使用方只关注接口而不关注具体类的实现;
/**
 * 依赖抽象,而非依赖具体的实现
 */

interface GrilFriend {
  age: number;
  height: number;
  weight: number;
  beautiful(): Boolean
}

class TongLiYa implements GrilFriend {
  age: 25;
  height: 168;
  weight: 105;
  beautiful() {
    return true
  }
}

class XiaoHone implements GrilFriend {
  age: 25;
  height: 168;
  weight: 105;
  beautiful() {
    return true
  }
}

class SingleDog {
  constructor(public grilFriend: GrilFriend) { }
}

let dog1 = new SingleDog(new TongLiYa());

// 不需要是TongLiYa ,但是可以与她类似就行了,依赖抽象GrilFriend,而非具体的 TongLiYa
let dog2 = new SingleDog(new XiaoHone());

2.5 接口隔离原则(Interface Segregation Principle)

  • 保持接口的单一独立,避免出现胖接口;
  • 客户端不应该依赖它不需要的接口,类间的依赖关系应该建立在最小的接口上;
  • 接口尽量细化,而且接口中的方法尽量的少;
  • 类似于单一职责原则,更关注接口;
interface IUerManager {
  updateUserInfo(): void; // 更新用户信息
  updatePassword(): void; // 更新密码
}

interface IProductManager {
  updateProduct(): void;
  updatePrice(): void;
}

/************************* 举例说明 ****************************/
/**
 * 可以吃饭的接口
 */
interface IEating {
  eat(): void
}

/**
 * 可以吃唱歌的接口
 */
interface ISinging {
  singing(): void
}

/**
 * 可以说话的接口
 */
interface ISpeaking {
  speaking(): void
}

// 我可以唱歌,可以吃饭,会讲话
// 拆分成三个,1. 为了复用,2. 为了低耦合,3. 为了单一职责
class Me implements IEating, ISinging, ISpeaking {
  eat() { }
  singing() { }
  speaking() { }
}

// zhangsan 只会吃
class Zhangsan implements IEating {
  eat() { }
}

3. 迪米特法则 (Law of Demter, LOD)

  • 有时候也叫做最少知识原则;
  • 一个软件实体应当尽可能少地与其它实体发生相互作用;
  • 迪米特法则的初衷在于降低类之间的耦合;
  • 类定义时尽量要实现内聚,少使用 public 修饰符,尽量使用 private、protected 等;
  • PS:现实中得你自己的对象,你只需要了解自己的对象的习惯爱好,你对别人的对象尽可能少的了解;

老板需要知道公司产品的一个名字,那么他就问了经理,经理就问了员工A


/**
 * 员工A
 */
class StaffA {
  getProductName() {
    console.log("这个产品的名字是:XXX");
  }
}

/**
 * 经理
 */
class Manager {
  private staffA: StaffA = new StaffA();
  getProductName() {
    this.staffA.getProductName()
  }
}

/**
 * Boss
 */

class Boss {
  private manager: Manager = new Manager();
  getProductName() {
    this.manager.getProductName()
  }
}

let boss = new Boss();
boss.getProductName();

4. 合成复用原则

2.1 类的关系

  • 类之间有三种基本关系,分别是关联(聚合和组合)、泛化和依赖
  • 如果一个类单向依赖另一个类,那么它们之间就是 单向关联。如果彼此依赖,则为相互依赖,即 双向关联
  • 关联关系包括两种特例:聚合和组合
    • 聚合,用来表示整体与部分的关系或者拥有关系,代表部分的对象可能会被整体拥有,但并不一定会随着整体的消亡而销毁,比如班级和学生
    • 合成或者说组合要比聚合关系强的多,部分和整体的生命周期是一致的,比如人和器官之间;
// 一个班级里面会有多个学生,还有老师,这就是聚合的关系
class Class {
  public students: Array<Student>
  public teacher: Teacher = new Teacher()
}

// 学生和老师都有书,这是组合的关系
class Student {
  public book: Book;
}

// 学生和老师都有书,这是组合的关系
class Teacher {
  public book: Book;
}

class Book { }

// 学生和老师就是人的泛化,泛化关系
class Person { }

// 老师教书依赖黑板,依赖关系
class Block { }

2.2 合成复用原则

  • 合成复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的;
  • 新对象可以调用已有对象的功能,从而达到复用;
  • 原则是尽量首先使用组合/聚合的方式,而不是使用继承;
  • 也就是说,专业的人做专业的事;
// 尽量使用组合或者聚合原则,而不是使用继承,因为继承的耦合性太强了
class Cooker {
  cook() { }
}

// class Person2 extends Cooker{ 尽量不使用继承
class Person2 {
  private cooker: Cooker;
  cook() {
    this.cooker.cook();
  }
}

5. 总结

  • 开闭原则是核心,对修改关闭对扩展开放是软件设计的基石;
  • 单一职责要求我们设计接口和模块功能的时候尽量保证单一性,修改一条不影响全局和其它模块;
  • 里式替换原则和依赖倒置原则要求面向接口和抽象编程,不要依赖具体实现,否则实现一改,上层调用者就要对应修改;

6. 如何写出好的代码?

  • 可维护性 BUG是否好改?
  • 可读性 是否容易看懂?
  • 可扩展性 是否可以添加新功能?
  • 灵活性 添加新功能是否容易?老方法和接口是否容易复用?
  • 简洁性 代码是否简单清晰?
  • 可复用性 相同的代码不要写多遍?
  • 可测试性 是否方便写单元测试和集成测试?

7. 23 种设计模式

创建性(5):

  • 工厂模式(简单工厂模式 详解1、工厂方法模式、抽象工厂模式)、建造者模式、单例模式
  • 原型模式

结构性模式(7):

  • 代理模式、桥接模式、装饰器模式、适配器模式
  • 外观模式、组合模式、享元模式

行为型(11):

  • 观察者模式、模板方法模式、策略模式、职责链模式、迭代器模式、状态模式
  • 访问者模式、备忘录模式、命令模式、解释器模式、中介者模式

详解1: 简单工厂不在23种设计模式中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/595935.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

成语:势如破竹、迎刃而解;明以前唯一同时入选文庙、武庙的牛人

千古流芳、身后能够进入文庙或武庙&#xff0c;是古人最高的荣誉&#xff0c;也是读书人和武将终极的追求&#xff0c;所谓的青史留名&#xff0c;享受万代祭祀、千秋敬仰&#xff0c;甚至都可以称之为圣人&#xff0c;但历史上&#xff0c;却有两人文武兼备、同时入选了文庙与…

单调栈-java

本次主要通过数组模拟单调栈来解决问题。 目录 一、单调栈☀ 二、算法思路☀ 1.暴力做法&#x1f319; 2.优化做法&#x1f319; 3.单调递增栈和单调递减栈&#x1f319; 三、代码如下☀ 1.代码如下&#xff08;示例&#xff09;&#xff1a;&#x1f319; 2.读入数据&a…

学习记录:AUTOSAR R20-11的阅读记录(一)【Foundation(FO)】

一、OverView 1、AUTOSAR R20-11文档下载 官网下载&#xff1a;AUTOSAR 打包文档地址&#xff1a;AUTOSAR R20-11 2、文档组说明 AUTOSAR定义了三个文档组&#xff1a;ClassicPlatform(CP)、Adaptive Platform(AP)和Foundation(FO)&#xff0c;基于CP和AP的ECU基于共同标准F…

php基础知识快速入门

一、PHP基本知识 1、php介绍&#xff1a; php是一种创建动态交互性的强有力的服务器脚本语言&#xff0c;PHP是开源免费的&#xff0c;并且使用广泛。PHP是解释性语言&#xff0c;按顺序从上往下执行&#xff0c;无需编译&#xff0c;直接运行。PHP脚本在服务器上运行。 2、ph…

【算法】滑动窗口——无重复字符的最长子串

本篇博客是一篇滑动窗口算法练习题——无重复字符的最长子串的思路详解&#xff0c;从最开始的暴力解法&#xff0c;优化以及怎么想到滑动窗口这种算法的一个详细思路过程&#xff0c;有需要借鉴即可。 目录 1.题目解读2.暴力求解3.暴力求解的优化4.题解代码示例 1.题目解读 题…

超详细——集成学习——Adaboost——笔记

资料参考 1.【集成学习】boosting与bagging_哔哩哔哩_bilibili 集成学习——boosting与bagging 强学习器&#xff1a;效果好&#xff0c;模型复杂 弱学习器&#xff1a;效果不是很好&#xff0c;模型简单 优点 集成学习通过将多个学习器进行结合&#xff0c;常可获得比单一…

无经验计科应届生前端面试遇到的问题整理

js数据类型有几种&#xff0c;分别是 原始数据类型&#xff08;Primitive data types&#xff09;: 字符串&#xff08;String&#xff09;: 用于表示文本数据&#xff0c;使用单引号&#xff08;‘’&#xff09;或双引号&#xff08;“”&#xff09;括起来。 数字&#xff…

27-代码随想录三数之和

15. 三数之和 中等 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重…

C++ 如何进阶?

一、C基础&#xff08;3个月&#xff09; 1、面向对象的三大特性&#xff1a;封装、继承、多态 2、类的访问权限&#xff1a;private、protected、public 3、类的构造函数、析构函数、赋值函数、拷贝函数 4、移动构造函数与接贝构造函数对比 5、深接贝与浅贝的区别 6、空…

创新指南|组织健康仍然是企业创新长期绩效的关键

麦肯锡关于组织健康的最新调查结果表明&#xff0c;它仍然是当今全球市场中价值创造的最佳预测者和竞争优势的可持续来源。在本文中&#xff0c;我们将探讨最新的 OHI 结果&#xff0c;并重点介绍该指数揭示的有关领导力、数据和技术以及人才管理的一些更引人注目的见解。我们还…

数据仓库基础理论(学习笔记)

数据仓库基础理论 1.数据仓库概念 2.数据仓库为何而来 3.数据仓库主要特征 4.OLTP、OLAP系统 5.数据仓库与数据库的区别 6.数据仓库与数据集市的区别 7.数据仓库分层架构 7.1为什么要分层&#xff1f; 8.ETL、ELT

【前端】创建跳动字符效果的前端技术实现

创建跳动字符效果的前端技术实现 在前端开发中&#xff0c;动态视效能够显著增强用户体验。本文介绍一种实现字符跳动效果的技术方案&#xff0c;通过简单的HTML、CSS和JavaScript代码&#xff0c;你可以为网页文本添加生动的交互动画。这种效果可以用于吸引用户注意、增强品牌…

C语言—控制语句

控制语句就是用来实现对流程的选择、循环、转向和返回等控制行为。 分支语句 if语句 基本结构 if(表达式) { 语句块1&#xff1b; } else { 语句块2&#xff1b; } 执行顺序&#xff1a; 如果表达式判断成立&#xff08;即表达式为真&#xff09;&#xff0c;则执行语句块…

华为先进芯片麒麟9010效能再升级,挑战新高度 | 百能云芯

根据最新的彭博资讯报道&#xff0c;华为再次引领了智能手机行业的先进技术&#xff0c;其最新发布的Pura 70系列智能手机搭载了由中芯国际生产的麒麟9010高阶处理器。这一消息再次证明了华为在芯片设计和生产领域的持续创新能力&#xff0c;并且表明华为对于提升智能手机性能和…

什么是虚拟货币?

随着科技的进步&#xff0c;虚拟货币逐渐进入公众视野&#xff0c;其影响深远且复杂。本文将从专业角度分析虚拟货币的发展现状、未来趋势&#xff0c;以及面临的挑战&#xff0c;并尝试提出一些思考。 一、虚拟货币的定义与现状 虚拟货币是一种基于区块链技术的数字资产&…

从固定到可变:利用Deformable Attention提升模型能力

1. 引言 本文将深入探讨注意力机制的内部细节&#xff0c;这是了解机器如何选择和处理信息的基础。但这还不是全部&#xff0c;我们还将探讨可变形注意力的创新理念&#xff0c;这是一种将适应性放在首位的动态方法。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2. 注…

Dockerfile创建Docker镜像

Dockerfile DOCKER镜像的组成 Docker 镜像的构建和使用是基于 UnionFS&#xff08;联合文件系统&#xff09;的原理。UnionFS 允许将多个目录挂载到一个虚拟文件系统下&#xff0c;并且可以对这些目录进行修改&#xff0c;这些修改会以一次提交的形式叠加在已有的文件系统层上…

CTF-WEB(MISC)

安全攻防知识——CTF之MISC - 知乎 CTF之MISC杂项从入门到放弃_ctf杂项 你的名字-CSDN博客 CTF MICS笔记总结_archpr 掩码攻击-CSDN博客 一、图片隐写 CTF杂项---文件类型识别、分离、合并、隐写_ctf图片分离-CSDN博客 EXIF&#xff08;Exchangeable Image File&#xff09;是…

笔记本电脑怎么多选删除文件?误删除文件怎么办

在日常使用笔记本电脑中&#xff0c;我们可能会遇到需要删除大量文件的情况&#xff0c;例如清理临时文件、整理文档或卸载不再需要的程序。手动一个一个地删除不仅效率低下&#xff0c;还可能遗漏某些文件。那么&#xff0c;如何在笔记本电脑上高效地进行多选删除操作呢&#…

Case中default的综合结果

在使用case语句时&#xff0c;不完备的case语句会导致Vivado综合时推断出锁存器。下面通过实例来详细看看各种情况下的综合结果&#xff1a; 1.完备的case语句 下述的verilog对应的电路结构是一个8选一的多路复用器&#xff1a; module case_test(input [2:0]sel,input data…
最新文章