@lasdtc
2013-12-24T07:29:11.000000Z
字数 9883
阅读 1115
创建型模式 | 结构型模式 | 行为型模式 | 其它 |
---|---|---|---|
工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 | 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 | 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。 | 并发型模式和线程池模式。 |
应用场景:需要根据用户的指定建立一个类时; 出现了大量的产品需要创建,并且具有共同的接口时。
public class SendFactory {
public Sender produce(String type) {
if ("mail".equals(type)) {
return new MailSender();
} else if ("sms".equals(type)) {
return new SmsSender();
} else {
System.out.println("请输入正确的类型!");
return null;
}
}
}
public class SendFactory {
public Sender produceMail(){
return new MailSender();
}
public Sender produceSms(){
return new SmsSender();
}
}
public class SendFactory {
public static Sender produceMail(){
return new MailSender();
}
public static Sender produceSms(){
return new SmsSender();
}
}
应用场景:需要增加工厂类而不想改动原有工厂类的代码时。
应用场景:保证在一个JVM中,该对象只有一个实例存在。
public class Singleton {
/* 持有私有静态实例,防止被引用,此处赋值为null,目的是实现延迟加载。按我的理解,延迟加载主要是防止第一次初始化之后失败就再也不能初始化了。(静态变量只被初始化一次) */
private static Singleton instance = null;
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 静态工程方法,创建实例,为保证多线程安全,加synchronized关键字,为保证性能,只有在第一次创建对象的时候需要加锁 */
public static Singleton getInstance() {
if (instance == null) {
synchronized (instance) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return instance;
}
}
上面的方法有个缺陷。在Java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();
语句是分两步执行的。但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分配空间(空白内存),然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这时,有可能在初始化singleton实例之前,CPU交给了另一个线程。从而导致在另一个线程中,instance只是一个空白的实例,并没有初始化,报错。改进代码:
public class Singleton {
/* 私有构造方法,防止被实例化 */
private Singleton() {
}
/* 此处使用一个内部类来维护单例 */
private static class SingletonFactory {
private static Singleton instance = new Singleton();
}
/* 获取实例 */
public static Singleton getInstance() {
return SingletonFactory.instance;
}
/* 如果该对象被用于序列化,可以保证对象在序列化前后保持一致 */
public Object readResolve() {
return getInstance();
}
}
在上面的代码中,单例模式使用内部类来维护单例的实现,JVM内部的机制能够保证当一个类被加载的时候,这个类的加载过程是线程互斥的。这样当我们第一次调用getInstance的时候,JVM能够帮我们保证instance只被创建一次,并且会保证把赋值给instance的内存初始化完毕,这样我们就不用担心上面的问题。
但是这个方法也不完美,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错。
应用场景:需要一个对象来管理具有同一接口的不同类的集合。
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性。
public class Builder {
private List<Sender> list = new ArrayList<Sender>();
public void produceMailSender(int count){
for(int i=0; i<count; i++){
list.add(new MailSender());
}
}
public void produceSmsSender(int count){
for(int i=0; i<count; i++){
list.add(new SmsSender());
}
}
}
应用场景:对象从另一个对象复制而来。
该模式的思想就是将一个对象作为原型,对其进行复制、克隆,产生一个和原对象类似的新对象。
代码为深浅复制的例子。
/* 浅复制 */
public Object clone() throws CloneNotSupportedException {
Prototype proto = (Prototype) super.clone();
return proto;
}
/* 深复制 */
public Object deepClone() throws IOException, ClassNotFoundException {
/* 写入当前对象的二进制流 */
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
/* 读出二进制流产生的新对象 */
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
应用场景:消除由于接口不匹配所造成的类的兼容性问题。
将某个类的接口转换成客户端期望的另一个接口表示。
类的适配器模式
有一个Source类,拥有一个方法,待适配,目标接口是Targetable,通过Adapter类,将Source的功能扩展到Targetable里。
对象的适配器模式
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。
接口的适配器模式
有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,因为并不是所有的方法都是我们需要的,有时只需要某一些,此处为了解决这个问题,我们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系,所以我们写一个类,继承该抽象类,重写我们需要的方法就行。
应用场景:动态的为一个对象增加功能,而且还能动态撤销。
装饰模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。
public class Decorator implements Sourceable {
private Sourceable source;
public Decorator(Sourceable source){
super();
this.source = source;
}
@Override
public void method() {
System.out.println("before decorator!");
source.method();
System.out.println("after decorator!");
}
}
应用场景:与装饰模式相同,只是更局限化了。
代理模式就是多一个代理类出来,替原对象进行一些操作,类似我们在租房子的时候去找的中介。
代理模式与装饰模式类似,只是代理类中聚合的不是接口类,而是实现了接口的实体类。
public class Proxy implements Sourceable {
private Source source;
public Proxy(){
super();
this.source = new Source();
}
@Override
public void method() {
before();
source.method();
atfer();
}
private void atfer() {
System.out.println("after proxy!");
}
private void before() {
System.out.println("before proxy!");
}
}
应用场景:用一个facade类管理其他类,从而保证了只使用一个类与“外界 ”打交道,提高了封装性。另外,也降低了(facade类中的)类与类之间的耦合度。
public class Computer {
private CPU cpu;
private Memory memory;
private Disk disk;
public Computer(){
cpu = new CPU();
memory = new Memory();
disk = new Disk();
}
public void startup(){
System.out.println("start the computer!");
cpu.startup();
memory.startup();
disk.startup();
System.out.println("start computer finished!");
}
public void shutdown(){
System.out.println("begin to close the computer!");
cpu.shutdown();
memory.shutdown();
disk.shutdown();
System.out.println("computer closed!");
}
}
如果我们没有Computer类,那么,CPU、Memory、Disk他们之间将会相互持有实例,产生关系,这样会造成严重的依赖,修改一个类,可能会带来其他类的修改,这不是我们想要看到的,有了Computer类,他们之间的关系被放在了Computer类里,这样Computer类作为facade类就起到了解耦的作用。
(所谓桥接,也就是类似与双掷开关一样的东东)
应用场景:一个类需要切换所使用类的类型。
桥接模式就是把事物和其具体实现分开,也就是将抽象化与实现化解耦,使得二者可以独立变化。
public abstract class Bridge {
private Sourceable source;
public void method(){
source.method();
}
public Sourceable getSource() {
return source;
}
public void setSource(Sourceable source) {
this.source = source;
}
}
//==============================================
public class MyBridge extends Bridge {
public void method(){
getSource().method();
}
}
//==============================================
public class BridgeTest {
public static void main(String[] args) {
Bridge bridge = new MyBridge();
/*调用第一个对象*/
Sourceable source1 = new SourceSub1();
bridge.setSource(source1);
bridge.method();
/*调用第二个对象*/
Sourceable source2 = new SourceSub2();
bridge.setSource(source2);
bridge.method();
}
}
我们常用的JDBC桥DriverManager一样,JDBC进行连接数据库的时候,在各个数据库之间进行切换,基本不需要动太多的代码,甚至丝毫不用动,原因就是JDBC提供统一接口,每个数据库提供各自的实现,用一个叫做数据库驱动的程序来桥接就行了。我们来看看关系图:
应用场景:将多个对象组合在一起进行操作,常用于表示树形结构中,例如二叉树,数等。
组合模式有时又叫部分-整体模式在处理类似树形结构的问题时比较方便,看看关系图:
应用场景:用共享池管理共享的资源(主要是控制资源,即相关类的实例,个数)。
由于资源的创建(即类的实例化)是需要消耗“大量”系统资源的,因此用资源池可以降低系统开销。
享元模式的主要目的是实现对象的共享,即共享池,当系统中对象多的时候可以减少内存的开销,通常与工厂模式一起使用。
FlyWeightFactory负责创建和管理享元单元,当一个客户端请求时,工厂需要检查当前对象池中是否有符合条件的对象,如果有,就返回已经存在的对象,如果没有,则创建一个新对象,FlyWeight是超类。
一提到共享池,我们很容易联想到Java里面的JDBC连接池。
应用场景:策略模式多用在算法决策系统中,系统封装好算法,外部用户只需要决定用哪个算法即可。
简单理解就是设计一个接口,提供统一的方法,然后多个实现类实现该接口。
吐槽:不就是单纯地使用接口嘛= =。
应用场景:有一个方法,其中的某一些步骤需要根据子类的类型来决定。于是把这些步骤抽出来成为一个抽象方法,放到子类中实现。
一个抽象类中,有一个主方法,再定义1...n个方法,可以是抽象的,也可以是实际的方法,定义一个类,继承该抽象类,重写抽象方法。这样调用抽象类的主方法时,对应子类的方法会被调用。
public abstract class AbstractCalculator {
/*主方法,实现对本类其它方法的调用*/
public final int calculate(String exp,String opt){
int array[] = split(exp,opt);
return calculate(array[0],array[1]);
}
/*被子类重写的方法*/
abstract public int calculate(int num1,int num2);
public int[] split(String exp,String opt){
String array[] = exp.split(opt);
int arrayInt[] = new int[2];
arrayInt[0] = Integer.parseInt(array[0]);
arrayInt[1] = Integer.parseInt(array[1]);
return arrayInt;
}
}
//==============================================
public class Plus extends AbstractCalculator {
@Override
public int calculate(int num1,int num2) {
return num1 + num2;
}
}
//==============================================
public class StrategyTest {
public static void main(String[] args) {
String exp = "8+8";
AbstractCalculator cal = new Plus();
int result = cal.calculate(exp, "\\+");
System.out.println(result);
}
}
吐槽:与strategy模式一样,都是用来指定不同类实现同一方法。不同的是,一个是让类实现某一接口来实现指定的方法,另一个是让类继承一个抽象类来实现其指定的抽象方法。
应用场景:用一个对象控制多个其它对象的更新。
当一个对象变化时,其它依赖该对象的对象都会收到通知,并且随着变化。对象之间是一种一对多的关系。
其实就实现上来说,Observer类才是被控制的类。一个目标物件(MySubject)管理所有相依于它的观察者物件(Observer),并且在它(MySubject)本身的状态改变时主动发出通知(notifyObserver),(调用Observer的update方法)更新观察者物件状态。
应用场景:用于顺序访问集合中的对象。
这个跟平常用的迭代器是一个东西。简而言之(我的理解)就是额外用一个类去管理一个集合类的顺序访问。
应用场景:在隐瞒客户端的情况下,对系统进行动态的调整。
有多个对象,每个对象持有对下一个对象的引用,这样就会形成一条链,请求在这条链上传递,直到某一对象决定处理该请求。但是发出者并不清楚到底最终那个对象会处理该请求。
链接上一个对象可以有多个对象的引用(List),因此责任链可以是树状的,但是,在一个时刻,命令只允许由一个对象传给另一个对象,而不允许传给多个对象。
实例。公司中请假时,需要经由上级审批。假设,如果你的请假时间小于0.5天,那么只需要向项目经理打声招呼就OK了。如果超过了0.5天,但是还小于2天,那么就要去找人事部处理,当然,这就要扣工资了。如果超过了2天,你就需要去找总经理了,工资当然也玩完了。这种例子就可以依据责任链模式设计。请假的请求可以按责任链一级一级上传。
应用场景:将“行为请求者”与“行为实现者”解耦。
在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做事务”等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式。
Invoker是调用者(行为请求者),Receiver是被调用者(行为实现者),MyCommand是命令,实现了Command接口,持有接收对象。
Struts其实就是一种将请求和呈现分离的技术,其中必然涉及命令模式的思想。
应用场景:保存一个对象的某个状态,以便在适当的时候恢复对象。
假设有原始类A,A中有各种属性,A可以决定需要备份的属性,备忘录类B是用来存储A的一些内部状态,类C呢,就是一个用来存储备忘录的,且只能存储,不能修改等操作。
应用场景: 当控制一个对象状态的条件表达式过于复杂时,把状态的判断逻辑转移到另一个类(状态类)中,从而把复杂的判断逻辑简化。
当对象的状态改变时,同时改变其所在类的行为。
State类是个状态类,Context类可以实现切换。Context类的method方法中,依据state对象的状态(value)来决定执行method1还是method2。
应用场景:适用于数据结构相对稳定算法又易变化的系统。
访问者模式把数据结构和作用于结构上的操作解耦合,使得操作集合可相对自由地演化。
可联想 Java 中Collection和Array类的静态方法。
应用场景:各个对象之间的交互操作非常多;每个对象的行为操作都依赖彼此对方,修改一个对象的行为,同时会涉及到修改很多其他对象的行为。
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
对象改变时会影响其它与之关联的对象,为了不使对象显式引用其它对象,使用mediator类来执行改变。mediator类拥有所有对象。
吐槽:图中work和allwork方法并不能直观表达对象之间有交互操作。
应用场景:主要应用在OOP开发中的编译器的开发中,所以适用面比较窄。
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
所有模式的核心思想都是减少依赖,降低耦合度。在不改变原代码的基础上扩充代码。