《Head First 设计模式》笔记

时间:2024-01-25 08:21:11

第一章 策略模式

00设计原则:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码放在一起。

把会变化的部分取出并封装起来,好让其它部分不会受到影响。结果如何?代码变化引起的不经意后果变少,系统变得更有弹性。

00设计原则:针对接口编程,而不是针对实现编程。

“针对接口编程”真正的意思是“针对超类型编程”:这里的接口有多个含义,接口是一个概念,也是一种java的interface构造。”针对接口编程“关键就在多态。利用多态,程序可以针对超类型编程,执行时会根据实际情况执行到真正的行为,不会被绑死在超类型的行为上。这句话可以更明确的说成”变量的声明类型应该是超类型,通常是一个抽象类或者是一个接口。如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着声明类时不用理会以后执行的真正对象类型。

OO设计原则:多用组合,少用继承

使用组合建立系统具有很大的弹性,不仅可以将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。

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

类图

JDK

  • java.util.Comparator#compare()
  • javax.servlet.http.HttpServlet
  • javax.servlet.Filter#doFilter()

第二章 观察者模式

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

主题和观察者定义了一对多的关系。观察者依赖于此主题,只要主题一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。

当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此之间的细节。

观察者提供了一种对象设计,让主题和观察者之间松耦合。

关于观察者的一切,主题只知道观察者实现了某个接口(observer接口)。主题不知道观察者的具体类是谁,做了些什么或其他任何细节。

有新类型的观察者出现时,主题的代码不需要修改,假如我们有个新的具体类需要当观察者,我们不需为了兼容新类型而修改主题代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。

我们可以独立地复用观察者和主题,如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合。

OO设计原则:为了交互对象之间的松耦合设计而努力。

松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

类图

JDK

  • java.util.Observer
  • java.util.Observable
  • Swing中的GUI框架
  • JavaBeans

第三章 装饰者模式

利用继承设计子类的行为,是在编译时静态决定的,而且所有的子类都会继承到相同的行为。然而,如果能够用组合的做法扩展对象的行为,就可以在运行时动态地进行扩展。可以利用此技巧把多个新职责,甚至是设计超类时还没想到的职责加在对象上,而且不用修改原来的代码。

下图表示在 DarkRoast 饮料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它们都继承自相同父类,都有 cost() 方法,外层类的 cost() 方法调用了内层类的 cost() 方法。

OO设计原则 开发-关闭原则:类应该对扩展开放,对修改关闭。

我们的目标是允许类容易扩展,在不修改现有代码的情况下,就可搭配新的行为,如能实现这样的目标,有什么好处?这样的设计具有弹性,可以应对改变,可以接受新的功能来应对改变的需求。

  • 装饰者和被装饰者对象有相同的类型
  • 可以用一个或多个装饰者包装一个对象
  • 既然装饰者和被装饰者对象有相同的超类型,所以在任何需要原始对象(被包装的)的场合,可以用装饰过的对象代替它。
  • 装饰者可以在所委托被装饰者的行为之前与/或之后,加上自己的行为,已达到特定的目的。
  • 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

组合和委托可用于在运行时动态地加上新的行为。

装饰者模式动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

类图

JDK

java.io类 InputStream是抽象组件,FileInputStream、StringBufferInputStream、ByteArrayInputStream都是可以被装饰者包起来的具体组件;而FilterInputStream是一个抽象装饰者,BufferedInputStream DataInputStream LineNumberInputStream都是具体装饰者

第四章 工厂模式

工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类。

所谓的“决定”,并不是指模式允许子类本身在运行时做决定,而是指在编写创建者类时,不需要知道实际创建的产品是哪一个。选择了哪个子类,自然就决定了实际创建产品是什么

OO设计原则(依赖倒置原则):要依赖抽象,不要依赖具体类。

不能让高层组件(由其他低层组件定义其行为的类)依赖底层组件,而且,不管高层组件或底层组件,两者都应该依赖于抽象。

类图

JDK

  • java.util.Calendar
  • java.util.ResourceBundle
  • java.text.NumberFormat
  • java.nio.charset.Charset
  • java.net.URLStreamHandlerFactory
  • java.util.EnumSet
  • javax.xml.bind.JAXBContext

抽象工厂模式:抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。

抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体产品中被解耦。

类图

JDK

  • javax.xml.parsers.DocumentBuilderFactory
  • javax.xml.transform.TransformFactory
  • javax.xml.xpath.XPathFactory

工厂方法模式和抽象工厂模式都是将对象创建的过程封装起来,以便将客户代码从具体类中解耦。

第五章 单例模式

单件模式:确保一个类只有一个实例,并提供一个全局访问点

延迟实例化(lazy instantiaze):如果我们不需要这个实例,它就永远不会产生,即懒汉式

处理多线程:只要把getInstance()变成同步(synchronized)方法,多线程灾难几乎可以轻易解决。

改善多线程

1.如果getInstance()的性能对应用程序不是很关键,就什么都别做。

2.使用"急切"创建实例,而不用延迟实例化的做法,即饿汉式。在私有静态初始化器中创建单件,保证了线程安全

3.用”双重检查加锁“,在getInstance()中减少使用同步:利用双重检查加锁,首先检查是否实例已经创建了,如果尚未创建,“才”进行同步,这样一来,只有第一次才会同步。这个做法可以大大减少getInstance()的时间耗费。

violatile关键词确保:当uniqueInstance变量被初始化成Singleton实例时,多个线程正确的处理uniqueInstance变量。

类图

JDK

  • java.lang.Runtime#getRuntime()
  • java.awt.Desktop#getDesktop()
  • java.lang.System#getSecurityManager()

第六章 命令模式

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

类图

JDK

  • java.lang.Runnable
  • javax.swing.Action

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

适配器将一个接口转换成另一个接口,以符合客户的期望。

适配器工作起来就如同一个中间人,它将客户所发出的请求转换成厂商类所能理解的请求。

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

应用:适配迭代器的枚举适配器EnumeratorIterator

类图

JDK

  • [java.util.Arrays#asList()
  • [java.util.Collections#list()
  • [java.util.Collections#enumeration()
  • [javax.xml.bind.annotation.adapters.XMLAdapter

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

外观模式没有封装子系统的类,外观只提供简化的接口。特征:提供简化接口的同时,依然将系统完整的功能暴露出来,以供需要的人使用。

外观模式不只是简化了接口,也将客户从组件的子系统中解耦。

外观模式和适配器模式可以包装一个类或许多类。

外观与适配器区别是:外观的意图是为了简化接口,提供一个子系统的简化接口。而适配器的意图是将接口转换成不同的接口以满足客户期望。

OO设计原则 最少知识原则:只和你的密友谈话。不要让太多类耦合在一起,免得修改系统中一部分,会影响到其他部分。如果许多类之间相互依赖,那么这个系统就会变成一个易碎的系统,它需要花许多成本维护,也会因为太复杂而不容易被其他人了解。

如何做到最少知识原则:1.如果某对象是调用其它方法的返回结果,不调用该对象的方法。

组件:想象成是被实例变量所引用的任何对象,即"has - a"关系。

类图

第八章 模板方法模式

模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。(即把这些步骤的实现延迟到子类)。

模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。

这个模式是用来创建一个算法的模板。模板就是一个方法。这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。

为防止子类改变模板方法中的算法,课件模板方法声明为final的。

钩子能够作为条件控制影响抽象类中的算法流程。

钩子目的:1.让子类实现算法中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以对钩子置之不理。2.让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤作出反应。

好莱坞原则:别调用我们,我们会调用你。(将决策权放在高层模块中,以便决定如何以及何时调用底层模块)

Java API:Arrays.sort()方法 InputStream类的read()方法 JFrame的paint()方法 Applet类的init()、start()、stop()、destroy()、paint()、队列同步器AbstractQueuedSynchronized等

模板方法模式和策略模式的比较:

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

类图

JDK

  • java.util.Collections#sort()
  • java.io.InputStream#skip()
  • java.io.InputStream#read()
  • java.util.AbstractList#indexOf()

第九章 迭代器模式与组合模式

迭代器模式提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。

迭代器模式能让我们游走于聚合内的每个元素,而又不暴露其内部的表示。

把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所,让聚合更专注在它所应该专注的事情上面(管理对象集合)。

OO设计原则:一个类应该只有一个引起变化的原因。

类的每个责任都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。

这个原则告诉我们,尽量让每个类保持单一责任。

内聚,用来度量一个类或者模块紧密地达到单一的目的或责任。当一个模块或一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;相反,当被设计成支持一组不相关的功能时,我们说它具有低内聚。

类图

JDK

  • java.util.Iterator
  • java.util.Enumeration

组合模式允许你将对象组合成树形结构来表现”整体/部分“层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

组合模式能让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。

使用组合结构,我们能把相同的操作应用在组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。

组合模式提供一个结构,可同时包容个别对象和组合对象。组合模式允许客户对个别对象以及组合对象一视同仁。组合结构内的任意对象称为组件,组件可以是组合,也可以是叶子节点。

类图

JDK

  • javax.swing.JComponent#add(Component)
  • java.awt.Container#add(Component)
  • java.util.Map#putAll(Map)
  • java.util.List#addAll(Collection)
  • java.util.Set#addAll(Collection)

第十一章 代理模式

代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

使用代理模式创建代理对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。

三种代理控制访问的方式:

  • 远程代理控制访问远程对象
  • 虚拟代理控制访问创建开销大的资源
  • 保护代理基于权限控制对资源的访问

代理在结构上类似于装饰者,但是目的不同。装饰者模式为对象加上行为,而代理则是控制访问。

Java内置的代理支持,可以根据需要建立动态代理,并将所有调用分配到所选的处理器。

类图

JDK

  • java.lang.reflect.Proxy
  • RMI