设计模式

设计模式

单例模式

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

### 传统方式

上述代码中,每次new Singleton(), 都会创建一个Singleton 实例, 显然不符合一个类只有 一个实例的要求。所以需要对上述代码进行修改,具体修改点如下:

饿汉式

单例模式的写法有很多种,上述代码是一个最简单的饿汉模式的实现方法,在类加载的时 候就创建了单例类的对象。由上述代码可知,实现一个单例模式总共有三个步骤: ( 1 )构造函数私有化。 (2 )自行对外提供实例。 (3 )提供外界可以获得该实例的方法。 与饿汉模式相对应的还有懒汉模式,懒汉模式有延迟加载的意思,具体代码如下:

懒汉式

如果创建单例对象会消耗大量资源,那么延迟创建对象是一个不锚的选择,但是懒汉模式 有一个明显的问题,就是没有考虑线程安全问题,在多线程并发的情况下,会并发调用 getlnstance()方法,从而导致系统同时创建多个单例类实例,显然不符合要求。可以通过给 getlnstance()方法添加锁解决该问题,具体代码如下:

添加synchronized 锁虽然可以保证线程安全,但是每次访问getlnstance ()方法的时候,都会 有加锁和解锁操作,同时synchron ized 锁是添加在方法上面,锁的范围过大,而单例类是全局 唯一的,锁的操作会成为系统的瓶颈。因此,需要对代码再进行优化,由此引出了“双重校验 锁”的方式,具体代码如下:

双重校验锁

双重校验锁会出现指令重排的问题,所谓指令重排是指JVM 为了优化指令,提高程序运行 效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。singleton = new Singleton()看似原子操作,其实不然, singleton = new Singleton()实际上可以抽象为下面几条 JVM指令:

上面操作2 依赖于操作l ,但是操作3 并不依赖于操作2,所以JVM是可以针对它们进行 指令的优化重排序的,经过重排序后如下:

可以看到,指令重排之后, singleton 指向分配好的内存放在了前面,而这段内存的初始化 被排在了后面。在线程A 执行这段赋值语句,在初始化分配对象之前就己经将其赋值给 singleton 引用,恰好B 线程进入方法判断singleton 引用不为null , 然后就将其返回使用,导致 程序出错。

为了解决指令重排的问题,可以使用volatile 关键字修饰singleton 字段。volatile 关键字的 一个语义就是禁止指令的重排序优化,阻止JVM 对其相关代码进行指令重排,这样就能够按照 既定的顺序指令执行。修改后的代码如下:

除了双重校验锁的写法外,比较推荐读者使用最后一种单例模式的写法: 静态内部类的单 例模式,具体代码如下:

静态内部类

当第一次访问类中的静态字段时, 会触发类加载,并且同一个类只加载一次。静态内部类 也是如此,类加载过程由类加载器负责加锁,从而保证线程安全。这种写法相对于双重检验锁 的写法,更加简洁明了,也更加不会出错。

简单工厂模式

简单工厂模式( Simple Factory Pattern )用来定义一个工厂类,它可以根据参数的不同返回 不同类的实例,被创建的实例通常都具有共同的父类。因为在简单工厂模式中用于创建实例的 方法是静态( static )方法,因此简单工厂模式又被称为静态工厂方法( Static Factory Method ) 模式,它属于类创建型模式。

简单工厂模式的要点在于,当你需要什么,只需要传入一个正确的参数,就可以获取你所 需要的对象,而无须知道其创建细节。

在简单工厂模式结构图中包含如下几个角色。 • Factory (工厂角色):工厂角色即工厂类,它是简单工厂模式的核心,负责实现创建 所有产品实例的内部逻辑; 工厂类可以被外界直接调用, 创建所需的产品对象;在工 厂类中提供了静态的工厂方法factoryMethod(),它的返回类型为抽象产品类型Product 。 • Product ( 抽象产品角色) : 它是工厂类所创建的所有对象的父类,封装了各种产品对 象的公有方法,它的引入将提高系统的灵活性,使得在工厂类中只需定义一个通用的 工厂方法,因为所有创建的具体产品对象都是其子类对象。

• ConcreteProduct (具体产品角色) :它是简单工厂模式的创建目标,.所有被创建的对 象都充当这个角色的某个具体类的实例。每一个具体产品角色都继承了抽象产品角 色,需要实现在抽象产品中声明的抽象方法。

一个简单工厂模式的例子:

上述代码中,交通工具都被抽象为Vehicle 类, 而Car、Bus 、Bicycle 类是Vehicle 类的实 现类, 并实现Vehicle 的run 方法,打印相关的信息。Factory 是工厂类, 类中的静态方法 produce 根据传入的不同的交通工具的类型,生产相关的交通工具,返回抽象产品类Vehicle 。 上述代码的类结构如图3-3 所示。

简单工厂模式的主要优点如下: (1 )工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端 可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使 用的分离。 (2 )客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即 可,对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。 简单工厂模式的主要缺点如下: ( 1 )由于工厂类集中了所有产品的创建逻辑,职责过重, 一旦不能正常工作,整个系统都 会受到影响。 (2 )使用简单工广模式势必会增加系统中类的个数(引入了新的工厂类),增加了系统的 复杂度和理解难度。 (3 )系统扩展困难, 一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能 造成工厂逻辑过于复杂,不利于系统的扩展和维护。 (4 )简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级 结构。

工厂方法模式

在简单工厂模式中只提供一个工厂类,它需要知道每一个产品对象的创建细节,并决定何 时实例化哪一个产品类。简单工厂模式最大的缺点是当有新产品要加入到系统中时,必须修改 工厂类,需要在其中加入必要的业务逻辑,这违背了“开闭原则” 。此外,在简单工厂模式 中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂 类之间的梢合度高,严重影响了系统的灵活性和扩展性,而工厂方法模式则可以很好地解决这 一问题。 在工厂方法模式中,不再提供一个统-的工厂类来创建所有的产品对象,而是针对不同的 产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。 工厂方法模式的定义:工厂方法模式( Factory Method Pattern )用来定义一个用于创建对象 的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂 方法模式又简称为工厂模式( Factory Pattern ),还可称作虚拟构造器模式( Virtual Constructor Pattern )或多态工厂模式( Polymorphic Factory Pattern ) 。工厂方法模式是一种类创建型模式。 工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方 法,创建具体的产品对象。

在工厂方法模式结构图中包含如下几个角色。 • Product ( 抽象产品类):它是定义产品的接口,是工厂方法模式所创建对象的超类型, 也就是产品对象的公共父类。 • ConcreteProduct (具体产品类) : 它实现了抽象产品接口,某种类型的具体产品由专 门的具体工厂创建,具体工厂和具体产品之间一一对应。 • Facto叩( 抽象工厂类):在抽象工厂类中,声明了工厂方法( Factory Method ),用于返回 一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。 • ConcreteFactory (具体工厂类) : 它是抽象工厂类的子类,实现了抽象工厂中定义的 工厂方法,并可由客户端调用,返回一个具体产品类的实例。 下面来看一个汽车与工厂实例, 具体代码如下:

上述代码中, Vehicle 类是抽象产品类,而Car 、Bus 、Bicycle类是具体产品类,并且实现 Vehicle 类的run 方法。每一种具体产品类都有一一对应的工厂类CarFactory 、BusFactory 、BicycleFactory 等,所有的工厂都有共同的抽象父类Factory。

与简单工厂模式相比, 工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以 是接口,也可以是抽象类或者具体类。在抽象工厂中声明了工厂方法但并未实现工厂方法, 具 体产品对象的创建由其子类负责, 客户端针对抽象工厂编程,可在运行时再指定具体工厂类, 具体工厂类实现了工厂方法, 不同的具体工厂可以创建不同的具体产品。

代理模式

代理模式给某一个对象提供一个代理或占位符, 并由代理对象来控制对原对象的访问。 代理模式是一种对象结构型模式。在代理模式中引入了一个新的代理对象,代理对象在客 户端对象和目标对象之间起到中介的作用,它去掉客户不能看到的内容和服务或者增添客户需 要的额外的新服务。 代理模式的结构比较简单, 其核心是代理类, 为了让客户端能够一致性地对待真实对象和 代理对象, 在代理模式中引入了抽象层, 代理模式结构如图3 -9 所示。

由图3 - 9 可知,代理模式包含如下三个角色:

• Subject (抽象主题角色):它声明了真实主题和代理主题的共同接口,这样一来在任 何使用真实主题的地方都可以使用代理主题,客户揣通常需要针对抽象主题角色进行 编程。 • Proxy (代理主题角色) : 它包含了对真实主题的引用,从而可以在任何时候操作真实 主题对象; 在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候 都可以替代真实主题;代理主题角色还可以控制对真实主题的使用,负责在需要的时 候创建和删除真实主题对象,并对真实主题对象的使用加以约束。通常,在代理主题 角色中, 客户端在调用所引用的真实主题操作之前或之后还需要执行其他操作,而不 仅仅是单纯调用真实主题对象中的操作。 • RealSubject (真实主题角色):它定义了代理角色所代表的真实对象,在真实主题角 色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中 定义的操作。

静态代理模式

具体的代码:

上面介绍的代理模式也被称为“静态代理模式”,这是因为在编译阶段就要为每个 RealSubject 类创建一个Proxy 类, 当需要代理的类很多时,就会出现大量的Proxy 类,所以可以 使用JDK 动态代理解决这个问题。

动态代理模式

参考:《AOP日志框架实现》中使用动态代理模式实现的日志框架

 

装饰器模式

装饰器模式( Decorator Pattern )可以在不改变一个对象本身功能的基础上给对象增加额外 的功能。装饰器模式是一种用于替代继承的技术, 它通过一种无须定义子类的方式来给对象动 态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰器模式中引入了装饰 类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的 功能。

装饰器模式动态地给一个对象增加一些额外的职责, 就增加对象功能来说,装饰器模式比 生成子类实现更为灵活。装饰器模式是一种对象结构型模式。

在装饰模式中,为了让系统具有更好的灵活性和可扩展性,通常会定义一个抽象装饰类, 而将具体的装饰类作为它的子类,装饰器模式的结构如图9- 8 所示。

 

  • Component (抽象构件): 它是具体构件和抽象装饰类的共同父类,声明了在具体构件 中实现的业务方法, 它的引入可以使客户端以一致的方式处理未被装饰的对象以及装 饰之后的对象,实现客户端的透明操作。

  • ConcreteComponent (具体构件):它是抽象构件类的子类,用于定义具体的构件对 象,实现了在抽象构件中声明的方法,装饰器可以给它增加额外的职责(方法) 。

  • Decorator ( 抽象装饰类) : 它也是抽象构件类的子类,用于给具体构件增加职责,但 是具体职责在其子类中实现。它维护一个指向抽象构件对象的引用, 通过该引用可以 调用装饰之前构件对象的方法,并通过其子类扩展该方法,以达到装饰的目的.

  • ConcreteDecorator (具体装饰类) :它是抽象装饰类的子类,负责向构件添加新的职 贵。每一个具体装饰类都定义了一些新的行为,它可以调用在抽象装饰类中定义的方 法,并可以增加新的方法用以扩充对象的行为。

由于具体构件类和装饰类都实现了相同的抽象构件接口,因此装饰器模式以对客户透明的 方式动态地给一个对象附加上更多的责任,换言之,客户端并不会觉得对象在装饰前和装饰后 有什么不同。装饰器模式可以在不需要创造更多子类的情况下,将对象的功能加以扩展。

装饰器模式的核心在于抽象装饰类的设计, Decorator (装饰器)的典型代码如下所示:

在抽象装饰类Decorator 中定义Component 类型的对象, 维持一个对抽象构件对象的引用, 并可以通过构造方法或Setter 方法将一个Component 类型的对象注入进来,同时由于Decorator 类实现了抽象构件Component 接口, 因此需要实现在其中声明的业务方法operation() 。需要注意 的是,在Decorator 中并未真正实现operation()方法,而只是调用原有component 对象的operation() 方法, 它没有真正实施装饰,而是提供一个统一的接口,将具体装饰过程交给子类完成。

Decorator 的子类即具体装饰类ConcreteDecorator 中将继承operation()方法并根据需要进行 扩展,典型的具体装饰类代码如下:

策略模式

posted on 2019-06-05 14:55  kinglead  阅读(241)  评论(0编辑  收藏  举报

导航