设计模式之美笔记(一)

2021-01-18 11:01:29 浏览数 (1)

基于接口编程

  1. 函数的命名不能暴露任何实现细节
  2. 封装具体的实现细节。
  3. 为实现类定义抽象的接口 简单来说,也就是通过调用接口来实现方法的调用,怎么通过接口来实现方法的调用呢,那就是真实的方法继承接口实现接口的方法。这也是模版模式的一种形式吧

编程意识

1.“基于接口而非实现编程”,这条原则的另一个表述方式,是“基于抽象而非实现编程”。后者的表述方式其实更能体现这条原则的设计初衷。我们在做软件开发的时候,一定要有抽象意识、封装意识、接口意识。越抽象、越顶层、越脱离具体某一实现的设计,越能提高代码的灵活性、扩展性、可维护性。

一个类可以具有多重行为,但是不能是多种东西。

多用组合少用继承

  1. 组合优于继承
  2. 通过组合或者委托来消除每继承一个类就得实现相同的方法
  3. 继承主要有三个作用:表示 is-a 关系,支持多态特性,代码复用。而这三个作用都可以通过组合、接口、委托三个技术手段来达成。除此之外,利用组合还能解决层次过深、过复杂的继承关系影响代码可维护性的问题。(委托业也就是实现一个类)

开闭原则OPC

  1. 给对象添加属性属于扩展还是修改,在不同的粒度来看是不同的,在宏观角度类的角度确实是修改了这个类,但是在细一点但是并没有修改类的易有内容,而是扩展了属性
  2. 改代码的标准之一:只要它没有破坏原有的代码的正常运行,没有破坏原有的单元测试,我们就可以说,这是一个合格的代码改动。
  3. 有些必要的修改还是允许的,任何东西都是灵活的。
  4. 在我们平时写代码,评判你的代码是否可拓展也就是是否遵循OCP原则。 开闭原则讲的就是代码的扩展性问题,是判断一段代码是否易扩展的“金标准”
  5. 通过设计的代码相对于没有设计的代码来说很更难理解一点,这个需要一个权衡, 业务简单场景可以实现那就简单的实现,不要本末倒置,最后简单变复杂
  6. 时刻要保持抽象,封装,扩展的意识。多思考以后会有什么需求的变化

里式替换LSP(Liskov Substitution Principle)

  1. liskov 去掉,也就是替换原则。那就子类和父类相互替换不会影响代码的执行逻辑,也就是如果有一个方法,可以通过父类调用,也可以通过子类调用。这个方法执行后会产生一样的效果,那就是遵循的,反之则是违反的。
  2. 和多态进行比较的话,我认为LSP原则是建立在多态之上的。因为多态是面向对象语言的一种特性 而LSP是一种设计原则,能牵扯到LSP,是因为有多态下可以使用父类和子类通过继承才可以调用相同方法的。
  3. 经常出现违反LSP的场景,父类中定义的 withdraw() 提现函数的注释是这么写的:“用户的提现金额不得超过账户余额……”,而子类重写 withdraw() 函数之后,针对 VIP 账号实现了透支提现的功能,也就是提现金额可以大于账户余额,那这个子类的设计也是不符合里式替换原则的。
  4. 使用父类的单元测试测试子类的单元测试,这是验证代码是否遵循LSP的方法之一
  5. 子类完全遵循父类的约束条件包括注释。

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

  1. 这里接口的含义是 一组API接口集合,单个API接口或函数,oop中的接口
  2. 在接口集合中不同的业务逻辑进行几口集合的隔离 example:一个用户信息,有删除用户信息,还有获取用户信息更新用户信息,删除用户信息和正常的用户信息接口得隔离开来,但是在实现层我们可以都放在用户信息接口。 3.接口隔离原则和单一职责之间的区别,单一原则是针对于模块,类,接口的设计

依赖翻转原则(DIP)

  1. 控制翻转的思想,是一种框架思想,将程序控制的补助,翻转为由框架来控制,
  2. 依赖注入,那到底什么是依赖注入呢?我们用一句话来概括就是:不通过 new() 的方式在类内部创建依赖类对象,而是将依赖的类对象在外部创建好之后,通过构造函数、函数参数等方式传递(或注入)给类使用。 直接new 和间接new 的关系
  3. 依赖注入是编写可测试代码的重要手段
  4. 依赖注入框架:
  5. High-level modules shouldn’t depend on low-level modules. Both modules should depend on abstractions. In addition, abstractions shouldn’t depend on details. Details depend on abstractions.(高层模块(high-level modules)不要依赖低层模块(low-level)。高层模块和低层模块应该通过抽象(abstractions)来互相依赖。除此之外,抽象(abstractions)不要依赖具体实现细节(details),具体实现细节(details)依赖抽象(abstractions)。)
  6. 依赖倒置原则,调用者和被调用者之间的依赖需要通过抽象进行依赖。抽象不能依赖于具体实现
  7. “基于接口而非实现编程”与“依赖注入”的联系是二者都是从外部传入依赖对象而不是在内部去new一个出来。 区别是“基于接口而非实现编程”强调的是“接口”,强调依赖的对象是接口,而不是具体的实现类;而“依赖注入”不强调这个,类或接口都可以,只要是从外部传入不是在内部new出来都可以称为依赖注入
KISS原则 和 YAGNI 原则

keep it simple and stupid keep it short and staightforward 你需需要他 过度设计原则 DRY 原则(Don’t Repeat Yourself)

迪米特法则(Law of Demeter)
  1. 只和他有关系的模块进行说话,不和陌生人说话 “不该有直接依赖关系的类之间,不要有依赖”。
  2. 不要和强依赖某个具体对象
  3. 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。迪米特法则是希望减少类之间的耦合,让类越独立越好。每个类都应该少了解系统的其他部分。一旦发生变化,需要了解这一变化的类就会比较少。

如何系统开发和设计

  1. 需求分析,根据用户护士节
  2. 合理地将功能划分到不同模块
  3. 设计模块与模块之间的交互关系
  4. 设计模块的接口、数据库、业务模型

规范与重构

重构的原因
重构的步骤

第一轮重构:提高代码的可读性第二轮重构:提高代码的可测试性第三轮重构:编写完善的单元测试第四轮重构:所有重构完成之后添加注释

  1. @VisibleForTesting 当某个类只是为了单元测试而不是私有的时候而暴露出来
程序出错该返回啥?NULL、异常、错误码、空对象?
  1. 有可能有空值的使用optional 来定义,
  2. 业务不存在空值的直接抛出业务异常
  3. 尽量返回空对象,或者空集合
  4. git remote set-url origin https://bitbucket.xforceplus.com/scm/xf-v4-ph-invoice-ass/purchaser-assist-service.git

设计模式-创建型设计模式

单列模式

  1. 为什么需要设计单例,因为单列是竞态数据安全实现在最简单的方式之一
  2. 防止这个实例被new 创建,使构造方法私有。
  3. 单列的几种方式:
    1. 饿汉式,在刚程序刚开始初始化的时候就会创建好,在后续的需要的时候直接获取,会占用内存。
    2. 懒汉式,在真正需要的时候才创建实力对象,问题是在进行创建的时候得加代码块锁,这就影响了代码的并发。
    3. double check 双重锁检测 解决延时加载的性能问题。voilte关键字的使用
    4. 静态内部类进行实现
  4. 我们平时所说的单列模式是进程间的单例模式
  5. 那我们如何实现一个线程间单列呢?其中thradlocal的实现方式。使用一个hashmap 进行存储,使用线程ID作为key,value为对应的单例
  6. 实现一个分布式的单例模式,在多个服务间只能创建一个实例,通过外部存储和分布式锁实现。
  7. 实现一个多例,那就是一个类可以生成多个实例比如3个,或4个等。hashMapkey 和list对象
  8. fail-fast 的设计原则 有问题提早暴露
  9. 单例类对象的唯一性的作用范围并非进程,而是类加载器(Class Loader)类加载器的双亲委派机制,委托父加载器读取对象,父加载器没有交给子加载器处理,同一个累出现在不同的加载器中则产生的对象也就不一样。一个进程的启动前就得依赖于加载器进行读取对象。

工厂模式

  1. 简单工厂
  • 根据某些条件去创建对象、就两个类一个factory 一个条件类。可读性高,扩展性相对来说比较低,违反开闭原则
  1. 工厂方法
  • 简单工厂到,工厂的进化,使用多态进行拆分创建对象的逻辑条件。主要还是消除if else 但是代码会比较难读。
  1. 抽象工厂
  2. DI的实现,实现一个解析类,也就是对象的创建方式,生成配置文件,通过配置文件通过工厂类生产对应的对象
  3. 面向对象设计的最后一步是什么? 组装类和提供执行入口 可以联系一下entry 和 value domain service 还有app service 其中app service 也就是将多个领域模型组装到一起然后提供入口给 controller 来使用的吗
  4. 容器的核心功能一般有三个:配置解析、对象创建和对象生命周期管理。
  5. spring中的循环依赖是如何解决的?

建造者模式

  1. 建造者模式的好处,可以防止代码在创建的时候参数传错等问题。

原型模式

  1. 如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式,简称原型模式。
  2. 原型模式的两种=实现方法:
  • 深拷贝和浅拷贝 ,浅拷贝只拷贝引用地址和基本类型 ,深拷贝会将地址和引用类型的数据一块进行复制。
  • 深拷贝的两种方式,第一种就是将这个引用对象里的进本类型都进行赋值带新的对象中,递归拷贝。
  • 通过序列化,进行深拷贝。先将对象序列化到内存,然后再反序列化读取到新的对象

结构型设计模式

结构型设计模式主要就是解决类和对象之间的关系

代理模式

  1. 在不改变原始类的情况下下增加新的逻辑,其中的思想也就是单一原则。通过多态和委托达到代理类进行进程,这就是静态代理模式。
  2. 但是静态代理会因为每个类都要去实现每个函数也都得委托,所以需要写很多的类似于模板的代码。所以通过Java反射机制从而根据已有代码在Java运行时期进行动态生成代理类。这也就是我们所说的动态代理的模式。
  3. 动态代理中我们常使用的场景 日志,监控,鉴权等等。
  4. Java的Spring框架其中的Spring aop 模块也就是通过动态代理来实现的。(其中有两种方式一种是JDK动态代理,一种是CGlib字节码的方式)

桥接模式

  1. “将抽象和实现解耦,让它们可以独立变化。” 抽象不指的是接口和抽象类。JDBC 和 Driver 独立开发,通过对象之间的组合关系,组装在一起。JDBC 的所有逻辑操作,最终都委托给 Driver 来执行。
  2. 一个类存在两个(或多个)独立变化的维度,我们通过组合的方式,让这两个(或多个)维度可以独立进行扩展。”,可以理解为“组合优于继承”
  3. 应用场景JDBC驱动就是桥接的经典用法

装饰器模式

  1. 装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类
  2. 装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。
  3. java IO中的InputStream为原始类,通过bufferInputStream等等 对其进行装饰。 然后达到缓冲等作用。
  4. 静态代理模式和装饰器模式的比较,代理类附加的是跟原始类无关的功能,而在装饰器模式中,装饰器类附加的是跟原始类相关的增强功能。
  5. 当一个原始类会被多个装饰器类进行装饰,且各个装饰器所装饰增强的函数不相同,那么我们可以通过再抽出公共方法,在对原始类进行集成。其主要目的就是进行重复代码的减少。 FilterInputStream 就是inputStrem 原始类和BufferINputStream 之间的一个中间类。 继承于inputStream 然后被注入到 BufferInputStream

0 人点赞