设计模式总结

设计原则

1. 封装变化:

    1). 找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。

    2). 这个原则的另一种思考方式:把会变化的部分取出并封装起来,以便以后可以轻易地改动或扩充此部分,而不影响不需要变化的其他部分。

    3). 这样的概念很简单,几乎是每个设计模式背后的精神所在。所有的模式都提供一套方法"系统中某部分改变不会影响其他部分"。

2. 针对接口编程,而不是针对实现编程。

    1). 我们利用接口代表每个行为,比方说,FlyBehavior与QuackBehavior,而行为的每个实现都将实现其中一个接口。所以这次鸭子不会负责实现Flying与Quacking接口,反而由我们制造一组其他类专门实现

         FlyBehavior与QuackBehavior,这就称为"行为"类。由行为类而不是Duck类来实现行为接口。

    2). 这样的做法迥异于以往,以前的做法是:行为来自Duck超类的具体实现,或是继承某个接口并由子类自行实现而来。这两种做法都是依赖于"实现",我们被实现绑的死死的,没办法更改行为(除非写更多代码)。

         在我们的新设计中,鸭子的子类将使用接口(FlyBehavior与QuackBehavior)所表示的行为,所以实现的"实现"不会被绑死在鸭子的子类中。(话句话说,特定的具体行为编写在实现了FlyBehavior与QuackBehavior的类中)。

    4). 针对接口编程的真正意思是针对超类型编程。针对接口编程的关键在于多态。利用多态,程序可以针对超类型编程,执行时会根据实际状况执行到真正的行为,不会被绑死在超类型的行为上。"针对超类型编程"这句话,

         可以更明确地说成"变量的声明类型应该是超类型",通常一个抽象类或者是一个接口,如此,只要是具体实现比超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时不必理会以后执行时的真正对象类型。

3. 多用组合,少用继承

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

4. 为了交互对象之间的松耦合性设计而努力(观察者模式)

    针对接口去调用,调用者和实际被调用者之间松耦合。

5. 类应该对扩展开放,对修改关闭 (装饰模式)

6. 要依赖抽象,不要依赖具体类(工厂方法模式):

    这个原则听起来很像是"针对接口编程而不针对实现编程"。的确很相似,然而这里更强调抽象。这个原则说明:不能让高层组件依赖低层组件。而且,不管高层或底层组件,"两者"都应该依赖于抽象。

7. 最少知识原则:

    1). 这是说,当你正在设计一个系统,不管是任何对象,你都要注意它锁交互的类有哪些,并注意它和这些类时如何交互的。

    2). 这个原则希望我们在设计中,不要让太多的类耦合在一起,免得修改系统的一部分会影响其他部分。

    3). 这个原则还提供了一些方针:就任何对象而言,在该对象的方法内,我们只应该调用属于以下范围的方法:

   a. 该对象本身

        b. 被当做方法的参数而传递进来的对象

        c. 此方法所创建或实例化的任何对象

        d. 对象的任何组件(把"组件"想象成是被实例变量所引用的任何对象)

8. 好莱坞原则:

    1). 在好莱坞原则之下,我们允许低层组件将自己挂钩到系统上,但是高层组件决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是"被调用我们,我们会调用你们"

9. 单一责任:一个类应该只有一个引起变化的原因

    1). 我们知道要避免类内的改变,因为修改代码很容易造成许多潜在的错误。如果有一个类具有两个改变的原因,那么这会使得将来该类的变化几率上升,而当它真的改变时,你的设计中同时有两个方面将会受到影响。

    2). 当一个模板或者一个类被设计成只支持一组相关的功能时,我们说它具有高内聚;反之,当被设计成支持一组不相关的功能时,我们说它具有低内聚。

 

设计模式:

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

    1). 接口,相同的方法?

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

    1). setChange()方法把changed标志设置为true。notityObservers()只会在changed标志为"true"时通知观察者。在通知观察者之后,把changed标志设回"fasle"。这样做有其必要性。setChanged()方法可以让你在更新

         观察者时,有更多的弹性,你可以适当的通知观察者。在满足某些条件的情况下,再通知观察者。

    2). 被观察者中包含一组观察者的引用

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

    1). 装饰者和被装饰者有相同的超类型。

    2). 你可以用一个或多个装饰者包装一个对象

    3). 既然装饰者和被装饰对象有相同的超类型,所以在任何需要原始对象(被包装的) 的场合,可以使用装饰过的对象代替它。

    4). 装饰者可以在所委托被装饰者的行为之前或之后,加上自己的行为,已达到特定的目的。

    5). 对象可以在任何时候被装饰,所以可以在运行时动态地、不限量地用你喜欢的装饰者来装饰对象。

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

    例如:不同的披萨店实现了定义的创建对象的接口,然后根据具体参数,决定生产出该店哪种类型的披萨。

    1). 创建者类(Creator): 这是抽象创建者类。它定义了一个抽象的工厂方法,让子类实现此方法制造产品。创建者通常包含依赖于抽象产品的代码,而这些抽象产品由子类制造。创建者不需要真的知道在制造哪种具体产品。

    2). 产品类:工厂生产的产品。

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

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

    2). 工厂方法使用的是类,而抽象工厂使用的是对象。

    3). 工厂方法创建对象使用的是继承,而抽象工厂是通过对象的组合。

    4). 其实整个工厂方法模式,只不过就是通过子类来创建对象。用这种做法,客户只需要知道他们所使用的抽象类型就可以了,而由子类负责决定具体类型。所以,换句话说,工厂方法只负责将客户从具体类型中解耦。

         而抽象工厂创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法。要想使用这个工厂,必须先你实例化它,然后将它传入一些针对抽象类型所写的代码中。所以,和工厂方法一样,抽象工厂把

         客户从所使用的实际具体产品中解耦。抽象工厂使用工厂方法来实现具体的工厂。

    1). 简单工厂其实不是一个设计模式,反而比较像是一种编程习惯。简单工厂只是经创建对象的,会变动的代码抽取出来。

5. 单例模式(单件模式):

方法一  方法二 方法三

public class Singleton {

private static Singleton uniqueInstance;

private Singleton(){};

// 对方法加锁会降低性能
public static synchronized Singleton getInstance(){
if(uniqueInstance==null){
uniqueInstance = new Singleton();
}
return uniqueInstance;
}

}

public class Singleton02 {

// 可能会造成资源浪费。貌似没必要考虑,早晚需要,不然要这个类干嘛?
private static Singleton02 uniqueInstance = new Singleton02() ;

private Singleton02(){};

// 对方法加锁会降低性能
public static synchronized Singleton02 getInstance(){
return uniqueInstance;
}

}

public class Singleton03 {

// volatile关键字确保:当uniqueInstance变量被初始化成Singleton03实例时,多线程正确地处理uniqueInstance变量
private volatile static Singleton03 uniqueInstance;

private Singleton03(){};

// 对方法加锁会降低性能
public static synchronized Singleton03 getInstance(){
// 避免加锁
if(uniqueInstance==null){
synchronized (Singleton03.class) {
// 避免多线程,再判断一次
if(uniqueInstance==null){
uniqueInstance = new Singleton03();
}
}
}
return uniqueInstance;
}
}

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

   1). NoCommand对象是一个空对象(null object)的例子。当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。

   2). 请求中包含接收者和具体的执行动作。

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

    1). 客户使用适配器的过程如下:

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

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

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

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

    3). 也请留意,这个模式是如何把客户和接口绑定起来,而不是和实现绑定起来的。我们可以使用数个适配器,每一个负责转换不同组的后台类。或者,也可以加上新的实现,只要他们遵循目标接口就可以。

    4). "类"适配器需要多重继承才能实现,这在java中是不可能的。对象适配器利用组合的方式将请求传送给被适配者。

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

    在一个方法内,调用一组方法

    1). 外观的意图是要提供一个简单的接口,好让一个子系统更易于使用。

    2). 外观模式一个很好的特征:提供简化的接口的同时,依然将系统完整的功能暴露出来,以供需要的人使用。

    3). 适配器和外观模式的差异:两种模式的差异,不在于它们"包装"了几个类,而在于它们的意图。适配器模式的意图是,"改变"接口符合客户的期望;而外观模式的意图是,提供子系统的一个简化接口。

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

    1). 算法的模板就是一个方法。更具体的说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这可以确保算法的结果不变,同时由子类提供部分实现。

    2). 对模板方法进行挂钩:钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩(影响算法的流程)。要不要挂钩,由子类决定。

10. 迭代器模式:提供一种方法顺序访问一个聚合(集合,链表等各种数据结构的数据)对象中的各个元素,而又不暴露其内部的表示。 

      1). 这很有意义:这个模板给你提供了一种方法,可以顺序访问一个聚合对象中的元素,而又不暴露其内部的表示。

      2). 把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。

      3). java 5 及以后: for(Object obj:collection)

      4). 空迭代器:

           a. 方法一:返回null,我们可以让createIterator()方法返回null,但是如果这么做,我们的客户代码就需要条件语句来判断返回值是否为null。

           b. 方法二:返回一个迭代器,而这个迭代器的hasNext()永远返回false。

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

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

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

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

      1). 这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。

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

      a. 远程代理控制访问远程对象。远程代理可以作为另一个JVM对象的本地代表。调用代理的方法,会被代理利用网络转发到远程执行,并且结果会通过网络返回给代理,再由代理将结果转给客户。

      b. 虚拟代理控制访问创建开销大的资源。虚拟代理经常知道我们真正需要一个对象的时候才创建它。当对象在创建前和创建中时,由虚拟代理来扮演对象的替身。对象创建后,代理就会将请求直接委托给对象。(例如:

          swing加载远程图片,在没有加载到图片之前,先展示一些文字等)。

      c. 保护代理基于权限控制对资源的访问。

      d. 防火墙代理(Firewall Proxy):控制网络资源的访问,保护主题免于"坏客户"的侵害。使用场景:公司的防火墙系统。

      e. 智能引用代理(Smart Reference Proxy):当主题被引用时,进行额外的动作,例如计算一个对象被引用的次数。使用场景:

      f. 缓存代理(Caching Proxy):为开销大的运算结果提供暂时存储:它也允许多个客户共享结果,以减少计算或网络延迟。使用场景:Web服务器代理,以及内容管理与出版系统。

      g. 同步代理(Synchronization Proxy):在多线程的情况下为主题提供安全的访问。使用场景:JavaSpace , 为分散式环境内的潜在对象集合提供同步访问控制。

      h. 复杂隐藏代理(Complexity Hiding Proxy): 用来隐藏一个类的复杂集合的复杂度,并进行访问控制。有时候也成为外观代理(Facade Proxy),这不难理解。复杂隐藏代理和外观模式是不一样的,因为代理控制访问,

          而外观模式提供另一组接口。

      i. 写入时复制代理(Copy-On-Write Proxy):用来控制对象的复制,知道客户真的而需要为止。这是虚拟代理的变体。使用场景:Java 5的CopyOnWriteArrayList附近。

 

 

1. 策略模式:

    1). 良好的OO设计必须具备可复用、可扩充、可维护三个特性。

    2). 模式可以让我们建造出具有良好OO设计质量的系统

    3). 模式不是代码,而是针对设计问题的通用解决方案。你可把它们应用到特定的应用中。

    4). 大多数的模式都允许系统局部改变独立于其他部分。

    5). 我们常把系统中会变化的部分抽取出来封装。

    6). 模式让开发人员之间有共享的语言,能够最大化沟通的价值。

    7). 知道OO基础,并不足以让你设计出良好的OO系统。

    8). 模式被认为是历经验证的OO设计经验。

    9). 模式不是被发明,而是被发现的。

    10). 大多数的模式和原则,都着眼于软件变化的主题。

2. 观察者模式:

    1). 观察者模式定义了对象之间一对多的关系。

    2). 主题(也就是可观察者)用一个共同的接口来更新观察者。

    3). 观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。

    4). 使用此模式,你可从被观察者处推(push)或拉(pull)数据(然而,推的方式呗认为更"正确")

    5). 有多个观察者,不可以依赖特定的通知次序。

    6). Java有多种观察者模式的实现,包括了通用的java.util.Observable

    7). 要注意java.util.Observable实现上所带来的一些问题

         a. 因为Observable是一个类,你必须设计一个类继承它。如果某类想同时具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。这限制了Observable的复用潜力

         b.因为没有Observable接口,所以你无法建立自己的实现,和Java内置的ObserverAPI搭配使用,也无法将java.util的实现换成另一套做法的实现(比方说,Observable将关键的方法保护起来,如果你看看Observable API,

            你会发现setChange()方法被保护起来了(被定义成protected)。那又怎样?这意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来。这个设计违反了第二个设计原则:多用组合,

            少用继承。

    8). 如果有必要的话,可以实现自己的Observable,这并不难,不要害怕。

    9). Swing大量使用观察者模式,许多GUI框架也是如此。

3. 装饰着模式:

    1). 继承属于扩展形式之一,但不见得是达到弹性设计模式的最佳方式。

    2). 在我们的设计中,应该允许行为可以被扩展,而无需修改现有的代码。

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

    4). 除了继承,装饰者模式也可以让我们扩展行为。

    5). 装饰者模式意味着一群装饰者类,这些类用来包装具体组件。 

    6). 装饰者类反映出被装饰的组件类型(事实上,他们有相同的类型,都经过接口或继承实现)

    7). 装饰者类可以在被装饰者的行为前面与/或后面加上自己的行为,甚至将被装饰者的行为整个取代掉,而达到特定的目的。

    8). 你可以用无数个装饰者包装一个组件。

    9). 装饰者一般对组件的客户是透明的,除非客户程序依赖于组件的具体类型。

    10). 装饰者会导致设计中出现许多小对象,如果过度使用,会让程序变得很复杂。

4. 工厂方法模式

    1). 所有工厂都是用来封装对象的创建。

    2). 简单工厂,虽然不是真正的设计模式,但仍不是为一个简单的方法,可以将客户程序从具体类解耦。

    3). 工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象。

    4). 抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴漏出来的方法中。

    5). 所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合。 

    6). 工厂方法允许类经实例化延迟到子类进行。

    7). 抽象工厂创建相关的对象家族,而不需要依赖他们的具体类。

    8). 依赖倒置原则,指导我们避免依赖具体类型,而要尽量依赖抽象。

    9). 工厂是很有威力的技巧,帮助我们针对抽象变成,而不要针对具体类变成。

5. 单例模式(单件模式)

    1). 单件模式确保程序中一个类最多只有一个实例。

    2). 单件模式也提供访问这个实例的全局点。

    3). 在Java中实现单件模式需要私有的构造器,一个静态方法和一个静态变量。

    4). 确定在性能和资源的限制,然后小心的选择适当的方案来实现单件,已解决多线程的问题(我们必须认定所有的程序都是多线程的)

    5). 如果不是采用第五版的Java 2,双重检查枷锁实现会失效。

    6). 小心,如果你使用多个类加载器,可能导致单件失效而产生多个实例。

6. 命令模式:

    1). 命令模式将发出请求的对象和执行请求的对象解耦。

    2). 在被解耦的两者之间是通过命令对象进行沟融的,命令对象封装了接收者和一个或一组动作。

    3). 调用者通过命令对象的execute()发出请求,这会使得接受者的动作被调用。

    4). 调用者可以接受命令当做参数,甚至在运行时动态地进行。

    5). 命令可以支持撤销,撤销是实现一个undo()方法来得到execute()被执行前的状态。

    6). 宏命令是命令的一种新的延伸,允许调用多个命令。宏方法也是可以支持撤销的。

7. 适配器模式:

    1). 当需要使用一个现有的类而接口并不符合你的需要时,就使用适配器。

    2). 当需要简化并统一一个很大的接口或者一群复杂的接口时,使用外观。

    3). 适配器改变接口以符合客户的期望。

    4). 外观将客户从一个复杂的子系统中解耦。

    5). 实现一个适配器可能需要一番功夫,也可能不费功夫,视目标接口的的大小和复杂程度而定。

    6). 实现一个外观,需要经子系统组合进外观中,然后将工作委托给子系统执行。

    7). 适配器也有两种模式:对象适配器和类适配器。类适配器需要用到多重继承。

    8). 你可以为一个子类实现一个以上的外观。

    9). 适配器将一个对象包装起来以改变其接口;装饰者将一个对象包装起来以增加新的行为和责任;而外观将一群对象包装起来以简化其接口。

8. 模板方法模式:

    1). 模板方法定义了算法的步骤,把这些步骤的实现延迟到子类。

    2). 模板方法模式为我们提供了一种代码复用的重要技巧。

    3). 模板方法的抽象类可以定义具体方法、抽象方法和钩子。

    4). 抽象方法由子类实现。

    5). 钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖它。

    6). 为了防止子类改变模板方法中的算法,可以将模板方法生命为final

    7). 好莱坞原则告诉我们,将决策权放在高层模板中,以便决定如何以及何时调用低层模块。

    8). 你将在真实是世界代码中看到模板防范模式的许多变体,不要期待它们全都是一眼就可以被你认出的。

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

    10). 工厂方法是模板方法的一个特殊版本。

9. 迭代器和组合模式:

    1). 迭代器允许访问聚合的元素,而不需要暴露它的内部结构

    2). 迭代器将遍历聚合的工作封装进一个对象中。

    3). 当使用迭代器的时候,我们依赖聚合提供遍历。

    4). 迭代器提供了一个通用的接口,让我们遍历聚合的项,当我们编码使用聚合的项时,就可以使用多态机制。

    5). 我们应该努力让一个类只分配一个责任。

    6). 组合模式提供一个结构,可同时包容个别对象和组合对象。

    7). 组合模式允许客户对个别对象以及组合对象一视同仁。

    8). 组合结构内的任意对象称为组件,组件可以是组合,也可以是叶子节点。

    9). 在实现组合模式时,有组多设计上的折衷。你要根据需要平衡透明性和安全性。

10. 状态模式:

      1). 状态模式允许一个对象基于内部状态而拥有不同的行为。

      2). 和程序状态机(PSM)不同,状态模式用类代表状态。

      3). Context会将行为委托给当前状态对象。

      4). 通过经每个状态封装进一个类,我们把以后需要做的任何变局部化了。

      5). 状态模式和策略模式有相同的类图,但是他们的意图不同。

      6). 策略模式通常会用行为或算法来配置Context类。

      7). 状态模式允许Context随着状态的改变而改变行为。

      8). 状态转换可以由State类或Context类控制。

      9). 使用状态模式通常会导致设计中类的数目大量增加。

      10). 状态类可以被多个Context实例共享。

11. 代理模式:

      1). 代理模式为另外一个类提供代表,以便于控制客户对对象的访问,管理访问的方式有许多种。

      2). 远程代理管理客户和远程对象之间的交互。

      3). 虚拟代理控制访问实例化开销大的对象。

      4). 保护代理基于调用者控制对对象方法的访问。

      5). 代理模式有许多变体:缓存代理,同步代理,防火墙代理和写入是复制代理。

      6). 代理在结构上类似装饰模式,但是目的不同。

      7). 装饰者模式为对象上加上行为,而代理则是控制访问。

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

      9). 就和其他的包装者一样,代理会造成你的的设计中类的数目增加。

 

 

共享词汇 

 

使用枚举或静态常量作为参数,避免传入错误参数。

posted @ 2016-02-01 00:37  Jtianlin  阅读(391)  评论(0编辑  收藏  举报