Skip to content

Files

Latest commit

d8760a9 · Jun 21, 2020

History

History

head-first

让自己的大脑就范:

  • 慢一点,你理解的越多,需要记的笔记就越少。
  • 勤做练习,自己记笔记。
  • 注意要仔细阅读备注。
  • 上传之前不要看其他的有难度的书。
  • 要喝水,而却要多喝水。
  • 大声说出来。
  • 听听你的大脑怎么说。
  • 要有点感觉。
  • 自己设计一些东西。

第一章 设计模式入门

基本概念

基础:

  • 抽象
  • 封装
  • 多态
  • 继承

设计原则:

  • 找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混在一起。
  • 我们要针对接口编程,而不是针对实现编程。
  • 多用组合,少用继承。

策略模式

定义算法族,分别封装起来,让他们之前可以相互替换,此模式让算法的变化独立于使用算法的客户。

真正的策略模式核心即:封装行为,依赖接口,组合代替继承

有一个是一个更好。

第二章 观察者模式

  • 观察者模式是 JDK 中使用最多的模式之一。
  • 生活中常见的观察者模式:出版者 + 订阅者 = 观察者模式
  • 观察者模式中两个概念:
    • 主题:主题对象管理某些数据。
    • 观察者:也就是我们所说的订阅者,订阅主题以便在主题数据发生改变时能够收到更新。
  • 观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
  • 定义观察者模式类图

image-20200517193241310

  • 关于观察者的一切,主题只知道观察者实现了某个接口(也就是 Observer 接口)。主题不需要知道观察者的具体类是谁、做了哪些什么或者其他任何细节。
  • 任何时候我们都可以增加新的观察者。因为主题唯一一来的东西是一个实现 Observer 接口的对象列表,我们可以随时增加和移除观察者。
  • 改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间接口仍被遵守,我们就可以自由改变他们。
  • 对于订阅主题的观察者来说,有两种方式接收数据,一种是通过主题直接推送给观察者,一种是观察者主动去获取数据。
  • Java 内置的观察者模式
    • java.util 包内包含类最基本的 Observer 接口和 Observable 类,分别对应我们熟悉的观察者和主题。
    • Java 内置的观察者模式支持推(Push)或拉(Pull)两种方式传输数据。
    • 利用 Observable 接口产生“观察者”,然后需要两个步骤
      • 首先调用 setChanged() 方法,标记状态已经改变的事实。
      • 然后调用 notifyObservers()方法中的一个
        • notifyObservers() 或者 notifyObservers(Object arg)
    • 如果你想“推”数据给观察者,你可以把数据对象传送给 notifyObservers(arg) 方法,否则,观察者必须采用“拉”数据。
    • setChanged() 方法可以满足灵活调用 notifyObservers() 方法。
    • 不足
      • java.uitl.Observable 实现了它的 notifyObservers() 方法,这可能导致通知观察者的次序不同于我们先前的次序。
      • 因为 Observeable 是这个类,如果想同时继承具有 Observeable 和 另一个超类的行为,就会陷入两难,这样限制了 Observable 的复用性。
      • Observable 将关键的方法保护起来,例如 setChanged() 方法被保护,违反了“多用组合,少用继承”的原则。

第三章 装饰者模式

  • 设计原则:类应该对扩展开放,对修改关闭。
  • 装饰者和被装饰者对象都有相同的超类型。
  • 你可以用一个或多个装饰者包装一个对象。
  • 既然装饰者和被装饰者对象有相同的超类型,所以在任何需要院士对象(被包装的)的场合,可以用装饰过的对象代替它。
  • 装饰者可以再所委托被装饰者的行为之前或之后,加上自己的行为,以达到特定的目的。
  • 对象可以再任何时候被装饰,所以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。
  • 装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。
  • 在装饰者模式中,继承一个抽象类,是为了有正确的类型,而不是继承它的行为。行为来自装饰者和基础组件,或与其他装饰者之间的组合关系。
  • Java 语言中的装饰者(Java I/O)
    • FileInputStream 是被装饰的“组件”。Java I/O 程序库提供了几个组件,包括了 FileInputStream、StringBufferInpustStream、ByteArrayInputStream...等。这些类都是提供了最基本的字节读取功能 。
    • BufferedInpustStream 是一个具体的装饰者,他加入两种行为:利用缓冲输入来改进性能;用一个 readLine() 方法(用来一次读取一行文本输入数据)来增强接口。
    • LineNumberInputStream 也是一个具体的“装饰者‘。他加上了计算行数的能力。
    • image-20200517223354950

第四章 工厂模式

  • 对于经常使用“new” 来生成对象的,一旦有变化或者扩展,就必须重新打开这段代码进行检查和修改,通过这样修改过的代码将造成部分系统更难维护和更新,而且也更容易犯错误。
  • 使用工厂方法屏蔽创建对象的细节,最初我们可以想到的就是使用 if 进行区分。
  • 简单工厂其实不是一个设计模式,反而比较像是一种编程习惯。
  • 工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样,客户程序中关于超类的代码和子类对象创建代码解耦了。
  • 工厂方法是抽象的,所以依赖子类来处理对象的创建。
  • 工厂方法必须返回一个产品,超类中定义的方法,通常使用到工厂方法的返回值。
  • 工厂方法将客户和实际创建具体产品的代码分割开来。
  • 工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。
  • 如何避免在面向对象设计中违反依赖倒置原则
    • 变量不可以持有具体类的引用,如果使用 new 关键字,就会持有具体类的引用,你可以改用工厂来避开这样的做法。
    • 不要让类派生子具体类,如果派生自具体类,你聚会依赖具体类,请派生自一个抽象。
    • 不要覆盖基类中已实现的方法,如果覆盖基类中已经实现的方法,那么你的基类就不是一个真正适合被继承的抽象,基类中已实现的方法,应该由所有的子类共享。

第五章 单例模式

  • 单例模式:确保一个类只有一个实例,并提供一个全局访问点。
  • 处理多线程:
    • 可以使用同步方法实现
    • 懒汉模式和饱汉模式

第六章 命令模式

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

命令模式的多用途

队列请求

命令可以将运算块打包然后将它传来传去,就像是一般的抓了一样,现在即使在命令对象被创建许久之后,运算依然可以被调用。

常用的衍生应用:日志安排、线程池、工作牌队列。

想象有一个工作队列,你再某一个端添加命令,然后另一端再试现成。线程进行下面操作:从队列中取出一个命令,调用它的 execute() 方法,等等这个调用完成,然后将此命令对象丢弃,再取出下一个命令。

在某些应用中我们需要将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态,通过新增两个方法(store()、load()),命令模式就支持这一点。

命令模式要点:

  • 命令模式将发出请求的对象和执行请求的对象解耦
  • 在被解耦的两者之间通过命令对象进行沟通,命令对象封装了接受者和一个或一组动作。
  • 调用者通过调用命令对象的 execute() 发出请求,这会使得接收者的动作被调用。
  • 调用者可以接受命令当做参数,甚至在运行时动态地进行。
  • 命令可以支持撤销,做法是实现一个 undo() 方法来回到 execute() 被执行钱的状态。
  • 命令也可以用来实现日志和事务系统。

第七章 适配器模式与外观模式

  • 客户通过目标接口调用适配器的方法对适配器发出请求。

  • 适配器使用被适配者接口把请求转换成被适配者的一个或者多个调用接口。

  • 客户接受到调用的结果,但并未察觉这一切是适配器在起转换作用。

适配器模式:将一个类的接口,转换成酷虎期望的另一个接口,适配器让原本接口不兼容的类可以合作无间。

适配器模式充满着良好的 OO 设计原则:使用对象组合,已修改的接口包装被适配者,这种做法还有额外的优点,那就是被适配者的任何子类都可以搭配着适配器使用。

对于适配器模式实际使用中可以有两种含义的适配:一种是“对象”适配器,一种是“类”适配器。

针对于类的适配,主要实现方式是通过多重继承,需要明确的一点是 Java 不支持多重继承。

外观接口:提供了一个统一的接口,用来防蚊子系统的一群接口,外观定义了一个高层接口,让子系统更容易使用。

其中符合“最少知识”原则:减少对象之间的依赖和交互。

第八章、模版方法

模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

public abstract class Beverage {
  // 定义了算法步骤
  final void  prepareRecipe() {
    boilWater();;
    brew();
    pourInCup();
    addCondiments();
  }

  abstract void brew();

  abstract void addCondiments();

  void boilWater() {
    System.out.println("boilWater...");
  }

  void pourInCup() {
    System.out.println("pourInCup...");
  }
}

public class TeaBeverage extends Beverage{
  @Override
  void brew() {
    System.out.println("brew");
  }

  @Override
  void addCondiments(){
    System.out.println("add condiments");
  }
}
  • 模板方法模式在一个方法中定义 一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

  • 钩子是一种被声明在抽象类中的方法,但是只有空的活着默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩,要不要挂钩由子类决定。

  • 好莱坞原则:别调用我们,我们会调用你。

  • 好莱坞原则可以给我们一种防止“依赖腐败” 的方法,当高层组建以来底层组建,而底层组建又依赖高层组件,而高层组件又依赖边侧组件这种情况。

  • 使用好莱坞组件可以保证高层组件可以控制何时以及如何让底层组件参与其中。

  • 为了防止子类修改模板方法中的算法,可以将模板方法声明为 final

  • 策略模式和模板方法模式都封装算法,一个用组合,一个用继承方法。

  • 工厂方法是一种特殊的模板方法。

第九章 迭代器与组合模式

  • 迭代器模式,需要知道的第一件事情就是,它依赖一个名为迭代器的接口,其中有两个重要方法 hasNext()、next()。
  • 迭代器让我们能够便利聚合中的每个元素,而不会强迫聚合必须提供方法,让我们在它的元素中游走,我们也可有单上饿的外面实现迭代器,换句话说,我们封装了遍历。
  • 对于迭代器中 remove 方法,实现的过程中,我们需要根据自己的数据结构,如果是数组那么就需要修改下标。
  • 这样我们可以对不同对象实现相同接口,通过“针对接口编程,而不是针对实现编程”,减少迭代和具体嘞之间的依赖。
  • 迭代器模式提供了一种方法顺序访问一个聚合对象中的各个对象,而又不暴露其内部的表示。
  • 迭代器模式让我们能游走于聚合内的每一个元素,而又不暴露其内部的表示。把游走的任务放在迭代器上,而不是聚合上,这样贱货了聚合的接口和实现,也让责任各得其所。
  • 迭代器模式给你提供了一种方法,可以顺序访问一个聚合对象中的元素,而又不用知道内部是如何表示的。
  • 单一职责原则:一个类应该只有一个引起变化的原因。
  • 类的每个责任都有改变的潜在区域,超过一个责任,意味着超过一个改变的区域,这个原则告诉我们,尽量让每个类保持单一职责。
  • 组合模式:允许你讲对象组合成树形的结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
  • 组合模式让我们能用树形方式创建对象的结构,树里面彪悍了组合以及个别的对象。
  • 使用组合结构,我们能把相同的操作应用在组合个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。

第十章 状态模式

  • 状态模式允许对象内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

对应的状态模式的类图:

  • 策略模式是除了继承之外的一种弹性替代方案。
  • 如果你继承了一个类,那么你将被这个类行为给困住,但是如果使用策略模式可以灵活替换对象来改变行为。
  • 状态模式可以想成是不用在 Context 中放置许多条件判断的替代方法,通过将行为包装进状态对象中,你可以通过在 Context 内简单地改变状态对象来改变 Context 的行为。
  • 和状态机不同,状态模式使用类表示状态。
  • 状态模式和策略模式有相同的类图,但是他们的意图不同。
  • 使用状态模式通常会导致类的数量会大幅增加。
  • 状态类可以被多个 Context 共享。

第十一章 控制对象访问

  • 代理模式为另一个对象提供一个替身或者占位符以控制着个对象的访问。
  • Java 在 java.lang.reflect 包中有自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类,实际代理类是在运行时创建的,我们成这个技术为:动态代理。
  • 虚拟代理控制访问实例化大的对象。
  • 保护代理基于调用者控制对对象方法的访问。
  • 代理模式有多种变种,例如有缓存代理、同步代理、防火墙代理和写入时复制代理。
  • 代理结构和装饰着结构类似,但是目的不同。
  • 装饰者模式为对象加上行为,而代理则是控制访问。
  • Java 内置但代理支持,可以根据需要建立动态代理,并将所有调用分配到所选的处理器。

第十二章 复合模式

  • MVC 是复合模式,结合了观察者模式、策略模式和组合模式。
  • 模型使用观察者模式,以便观察者更新,同时保持两者之间解耦。
  • 控制器是试图的策略,试图可以使用不同的控制器实现,得到不同的行为。
  • 试图使用组合模式实现用户界面,用户界面通常组合了嵌套的组件,类似面板、框架和按钮。
  • 适配器模式用来将新的模型是配成一游的视图和控制器。

第十三章 与模式相处

  • 模式是在某种情境下,针对某问题的某种解决方案。
  • 情景是应用某个模式的情况,这应该是会不断出现的情况。
  • 问题是你想在某种情境下达到的目标,但也可以是某情景下的约束。
  • 解决方案就是你所追求,一个通用的设计,用来解决约束、达到目标。
  • 装饰者模式:包装一个对象,以提供新的行为。
  • 状态模式:封装了基于状态的行为,并使用委托在行为之前切换。
  • 迭代器模式:在对象的集合之中游走,而不暴露集合的实现。
  • 外观模式:简化一群类的接口
  • 策略模式:封装可以呼唤的行为,并使用委托来决定要使用哪一个。
  • 代理模式:包装对象,以控制次对象的访问。
  • 工厂方法:游子泪决定要创建的具体类是哪一个。
  • 适配器模式:封装对象,并提供不同的接口。
  • 观察者模式:让对象能够在状态改变时被通知。
  • 模板方法:由子类决定如何实现一个算法中的步骤。
  • 组合:客户用一致的方式处理对象集合和单个对象。
  • 单例模式:确保只有一个对象被创建。
  • 抽象工厂:允许客户创建对象的家族,而无需指定他们的具体类。
  • 命令模式:封装请求成为对象。