《Head First设计模式》 读书笔记

发布于 2018-05-09  1.81k 次阅读


下文所有字符仅为个人观点,如有错误恳请指正。

All the characters below is a personal opinion, if there is any mistake, please correct me.

 

设计原则:

  • 找出程序会变化的方面,然后将其和固定不变的方面相分离

  • 针对接口编程,不针对实现编程

  • 多用组合,少用继承

  • 类应该对扩展开放,对修改关闭

  • 要依赖抽象,不要依赖具体类

  • 最少知识原则:只和你的密友谈话

  • 一个类应该只有一个引起变化的原因

 

策略模式(Strategy Pattern)

定义:

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

笔记:

  • 算法族:所谓族即具有一些共性,族中算法将具有某些类似点,具体算法类扩展于统一接口以方便同族算法的互相更换。
  • 具象化流程:定义算法族接口,实现接口构造具体算法类,客户声明持有算法族接口的引用(具体引用的对象可以改变可以藉此动态改变行为)并将任务委托给引用指向的对象(实现模块化,使行为和用户耦合程度更低)。

观察者模式(Observer Pattern)

定义:

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

笔记:

  • 主题与观察者:模式中包含两类角色,主题(Subject)拥有真正的数据,并且创建包含每个订阅者(即观察者)的容器,并且在自身数据有更新时,调用容器内观察者的更新行为(将更新值作为参数注入)。观察者(Observer)由于订阅了主题(即将自身加入主题所持有的容器)将能实时收到主题的更新(调用更新方法)。
  • 具象化流程:定义主题接口与观察者接口,定义基础行为(主题提供订阅,取消订阅与通知方法;观察者提供自身更新方法)。根据具体需求,扩展成主题类与观察者类,主题类持有数据以及观察者接口容器,观察者持有主题接口的引用(通过构造器注入具体主题类对象),以自己为参数调用主题类对象的订阅方法。主题类在数据更新时将遍历观察者容器,一一调用容器引用更新方法更新数据,或使用”拉”的方式,不由主题向每个观察者推送消息,代替的是将由每个观察者在接收到通知后调用主题暴露的get方法拉取自己需要的数据。

装饰者模式(Decorator Pattern)

定义:

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

笔记:

  • 装饰者与被装饰者:装饰者与被装饰者必须扩展于同一个接口(客户需要使用对象时创建接口的引用,并不需要知道具体实现类型即可以接受各种具体实现类型,符合针对接口编程)。被装饰者可以想象成一种相对原始(不符合定制化需求)的却未必通用(继承将被否认)的类,在想扩展或者修改其方法时,使用装饰者来修饰(包裹,即包装类持有指向被包装类对象的引用,类似链表节点,这种方式可以环环嵌套,嵌套时装饰者与被装饰者是相对的,在装饰者包裹被装饰者后,它也有可能成为被装饰者。可以环环嵌套能达到动态更改被装饰者功能的目的)该类以代替继承,可以在必要时为被装饰者添加修饰方法或者重写方法(由于扩展于相同接口,也由于持有被装饰者对象的引用)。
  • 具象化流程:当你需要修饰一个类时(增加额外功能或更改功能),定义装饰者,其持有指向被装饰者对象的引用(一般通过构造函数注入),在被装饰者类中增加你想要实现的新方法或者覆盖更改被装饰者的原有方法。客户在使用时只需要将原引用指向由被装饰者对象作为参数的装饰者构造方法返回的装饰者对象就行了。

工厂模式(Factory Pattern)

定义:

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

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

笔记:

  • 实例化的延迟:工厂方法模式是对简单工厂(产品实例化委托工厂类进行)的进一步抽象以便可以进行更深层次的定制(又可以兼顾不变内容的复用),工厂抽象超类持有抽象产品类的引用,并且包含对产品的处理方法(编译时并不需要知道引用指向的明确对象),包含产品具体构造的方法修饰为抽象,将由子类具体工厂来进行(定制)具体的产品构造方法以及实例化。
  • 工厂方法具象化流程:定义通用工厂(创建者)抽象类,持有产品抽象类的引用,包含对产品进行处理的方法以及抽象的产品类实例化方法。具体子类继承并实现产品的实例化方法(抽象工厂不涉及产品的初始生成,只包含对产品的简单通用加工)。
  • 依赖倒置原则:要依赖抽象,不要依赖具体类。
  • 更具一般性:抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的一种形态,抽象工厂创建的产品等级结构可以不同。抽象工厂接口仅提供所有结构产品构造方法声明,具体的产品族(即抽象工厂声明的都是产品接口而不是具体产品类)生成由工厂子类实现(由一个具体工厂类生产的产品称为属于同一产品族),因此客户在使用抽象工厂时就不需要知道产品的具体类型。
  • 产品族:是指位于不同产品等级结构中,功能相关联的产品组成的家族(可以理解为定位相似的品牌,例如Dell与ASUS)。一般是位于不同的等级结构中的相同位置上。显然,每一个产品族中含有产品的数目,与产品等级结构的数目是相等的,形成一个二维的坐标系,水平坐标是产品等级结构,纵坐标是产品族。叫做相图。
  • 抽象工厂具象化流程:当有多个不同的等级结构的产品时,如果使用工厂方法模式就势必要使用多个独立的工厂等级结构来对付这些产品的等级结构。如果这些产品等级结构是平行的,会导致多个平行的工厂等级结构。首先声明产品类别抽象接口,声明抽象工厂接口,在其中声明所有不同等级结构的产品的实例方法。声明具体产品类,在具体工厂子类中创建实例化具体产品的方法,用户类从构造函数获取具体工厂,直接从具体的工厂类中的对应方法获取产品。

单件模式(Singleton Pattern)

定义:

确保一个类只有一个实例,并提供一个全局访问点。

笔记:

  • 唯一实例:单例模式下的类,仅有自己持有自己的一个私有静态实例,构造器私有化,只暴露一个公开方法提供外部获取这个唯一实例。
  • 延迟实例化or急切创建:单例类的实例化依据实例化时间点分为外部引用单例时实例化与类创建时实例化(声明时实例化)。
  • 线程安全性:仅延迟实例化的单例模式具有线程安全性的问题(往往是多次实例化),解决方法通常有
  1. 同步块:在获取单例方法上加synchronize关键字。
  2. 双重检查:设置静态私有实例的线程可见性(volatile),在第一次确认单例引用是否为null后,添加被synchronize块包裹的第二次是否为null的检查。
  3. 静态内部类:
    public class Singleton {    
        private static class LazyHolder {    
           private static final Singleton INSTANCE = new Singleton();    
        }    
        private Singleton (){}    
        public static final Singleton getInstance() {    
           return LazyHolder.INSTANCE;    
        }    
    }
  4. 内部枚举类(最推荐的方式):
    public class Singleton {
        private Singleton (){}
        public static Singleton getInstance(){
            return LazyHolder.INSTANCE.getInstance();
        }
        
        private static enum LazyHolder {
            INSTANCE;
            
            private Singleton singleton;
            //JVM会保证此方法绝对只调用一次
            private LazyHolder(){
                singleton = new Singleton ();
            }
            public Singleton getInstance(){
                return singleton;
            }
        }
    }

命令模式(Command Pattern)

定义:

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

笔记:

  • 发出请求对象与执行请求对象的解耦:两者通过命令对象(将请求本身封装成对象实现传递)实现通讯。
  • 具象化流程:声明命令(Command)接口(包含统一执行,撤销等方法),扩展其声明具体命令类(ConcreteCommand)。具体命令类持有真正执行命令对象(接受者:Receiver)的引用,并实现详细功能(通过调用接受者行为)。初始化时由装配者(Client)将接收者注入命令对象,再由调用者(Invoker)持有这些具体命令对象(类似遥控器或其他控制面板),用户操纵调用者进行命令请求从而实现与真正命令执行者的解耦。

适配器模式(Adapter Pattern)

定义:

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

笔记:

  • 包装:为了满足客户需求吗,提供现有类接口的转换(包装)。
  • 具象化流程:声明扩展于用户期望接口适配器类,包含用户期望使用的方法。扩展成具体适配器类,具体适配器类持有指向被适配类对象的接口类型引用,将被适配者的方法包装到对应用户期望的方法里,以便被客户调用。
  • 外观模式:提供一个统一的接口,用来访问子系统中的一群接口(即将所需要的一系列行为包装成一个行为)。外观定义了一个高层接口,让子系统更容易使用。

模版方法模式(Template Pattern)

定义:

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

笔记:

  • 算法大纲:模版方法定义了完成一个算法(通常用final修饰)需要的步骤,其中通用的不变的步骤由抽象类实现,在不同子类(情况)会产生变化的步骤修饰为抽象,具体实现将由子类进行(按需定制算法中的某些步骤)。
  • 钩子:提供子类对算法中可选操作的选择权(通过重写钩子方法),钩子方法一般在模版方法中定义了默认返回值。
  • 具象化操作:声明抽象超类,声明算法,将算法中不变的方法在超类中实现,变化的仅提供声明(由具体子类实现)。若要使用钩子则声明钩子方法作为算法中某一步骤的判定条件,在子类中重写钩子方法即可。

适配器模式(Adapter Pattern)

定义:

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

笔记:

  • 封装遍历:不仅仅将遍历操作与用户隔离,更是将遍历元素操作从容其中独立出来,遍历具体顺序实现可定制(为一个容器实现多个迭代器)。
  • 具象化流程:声明自定义迭代器(扩展于Iterator),迭代器持有容器对象引用,实现具体诸如next(),hasNext()等方法。

组合模式(Composite Pattern)

定义:

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

笔记:

  • 组合与单一的模糊化:不管是否拥有子节点都将继承于同一个抽象类(不管是否拥有子节点都属于“节点”),使用者可以忽略两者的不同(解耦),统一了操作(尽管将有些不适用操作,将做抛出异常处理)。
  • 递归:树形结构的遍历。
  • 透明性与安全性:主要体现在无子节点的节点与有子节点的节点之间操作的差异。为了保持透明性,两者继承于同一个抽象类,但是实际上两者的操作是不能统一的,此时将牺牲安全性,通常在不恰当的操作抛出异常。
  • 具象化流程:声明节点抽象类,声明增加,删除,打印,获取子节点等方法(若有冲突在具体实现里面重写)。扩展出无子节点的节点类,按照需求重写方法(将增加删除等不适用方法抛出异常)。扩展出拥有子节点的节点类,持有节点容器,实现抽象类的方法。

状态模式(State Pattern)

定义:

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

笔记:

  • 封装状态:当一个对象会因为处于不同状态而对相同请求做出不同反应时,此时将状态封装成类,在该状态下执行应有的具体动作(将动作委托给状态类中的方法执行),以此替代原先的条件语句(每个状态对应的具体动作)。
  • 与策略模式的差异:两者最终结果相似,都将行为委托给了第三方类 执行。策略模式通常只希望对象具有一种模式(策略对象),其提供的更改弹性不是为了使对象能随时更改行为。
  • 具象化流程:声明状态接口,在内部声明行为。实现具体状态类,持有实际的对象(拥有多个内部状态的对象,类图中的context类),实现具体行为(可以在context中变更当前状态,也可以在当前状态对象的行为中变更context的当前状态)。context持有指向所有状态对象的引用与一个当前状态的引用,其行为将委托给当前状态对象执行。

代理模式(Proxy Pattern)

定义:

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

笔记:

  • 访问控制:为另一个对象提供代表,使客户不直接直接操作对象(控制访问),从而通过对代理进行操作能管理客户对对象的访问。
  • 远程代理:Java中的RMI
  • 虚拟代理:提供一个真正对象加载中的过渡方案,虚拟代理模式中的真实对象往往直到客户真正需要时才进行加载,而加载过程中代理成为真正对象的替身(拥有相对应过渡方法,例如打印出资源加载中)。代理检测对象加载完成后,请求将直接委托给对象。
  • 保护代理:面向不同的客户创建不同访问权限的代理。
  • Java动态代理: 代理(Proxy)与主题(Subject)扩展于相同的接口。代理实际上包含两个类(ProxyInvocationHandler),ProxyJava自动生成(关联对应InvocationHandler),编写InvocationHandlerProxy上的任何方法都会传入InvocationHandlerinvoke方法中执行,invoke方法中将调用真实对象RealSubject的方法),InvocationHandler是代理接收到方法调用后,请求做实际工作的对象。
  • 与装饰者模式的差别:装饰者模式为对象添加行为,代理模式则是控制访问。

复合模式(Compound Pattern)

定义:

结合两个以上的模式,组成一个解决方案,解决一再发生的一般性问题。

笔记:

  • MVC(Model-View-Controller):模型(Model)包含实体对象的数据与程序逻辑,不依赖于视图与控制器,对外提供一些操纵器内部数据与检索数据的接口并发送状态改变通知给观察者(视图);视图(View)包含UI界面(用来呈现模型),持有模型与控制器的引用,通过控制器改变模型或者直接从模型获取数据并展示;控制器(Controller)是视图与模型之间通讯介质,创建视图,解读用户输入,更改视图,操纵模型;
  • MVC中所用到的设计模式:1)观察者模式:模型作为可观察者视图或控制器可注册为其的观察者,监听它的改变并作出相应动作。2)策略模式:视图对模型的操作都将委托给控制器执行。3)组合模式:视图中的UI组件。

真实世界中的模式(summary)

  • 模式是在某情境(context)下,针对某些问题的某种解决方案。

附《Head First 设计模式》源代码:

Link: https://pan.baidu.com/s/1nUdrGU3MVTg1Dj2UGxanjw

Password:u27a

 


面向ACG编程