Java与设计模式
设计模式是解决问题的方案,学习现有的设计模式可以做到经验复用;使用设计模式可以重用代码,让代码更容易被他人理解,也保证了代码的可靠性。Java设计模式总体上分为三大类:创建型模式、结构型模式、行为型模式,而每一大类都细分有多种设计模式,每种模式都有相应的原理与之对应。
1. 单例模式(Singleton)
1.1 单例模式的特点
- 单例类只能有一个实例;
- 单例类必须自己创建自己的唯一实例;
- 单例类必须给所有其他对象提供这一实例。
1.2 Java单例模式
Java的单例模式实现方式有四种:饿汉模式、懒汉模式、静态内部类、枚举类;使用一个私有构造函数、一个私有静态变量以及一个公有静态函数来实现。
Ⅰ. 懒汉式——线程不安全
懒汉式的私有静态单例被延迟实例化,当没有用到该类时将不会被实例化,达到节约资源的目的;缺点是如果多个线程同时访问可能导致单例被多次实例化。
/**
* 1、懒汉模式-线程不安全
*/
class Singleton01{
private static Singleton01 singleton;
private Singleton01(){
}
public static Singleton01 getSingleton(){
if (null == singleton){
singleton = new Singleton01();
}
return singleton;
}
}
Ⅱ. 饿汉式——线程安全
饿汉式是在类加载时直接实例化该单例类达到线程安全的目的,但这样直接实例化的方式失去了延迟实例化带来的节约资源的好处。
/**
* 2、饿汉模式-线程安全,不够节约资源
*/
class Singleton02{
private static Singleton02 singleton = new Singleton02();
private Singleton02(){
}
public static Singleton02 getSingleton(){
return singleton;
}
}
Ⅲ. 懒汉式——线程安全
只需要对获取单例的公有方法加锁,使得在一个时间点只能有一个线程进入该方法,从而避免了被多次实例化;但当一个线程进入该方法后,其他试图进入的线程都必须等待,这样会让线程阻塞时间过长,所以不推荐使用。
/**
* 3、懒汉模式-线程安全,会导致线程阻塞
*/
class Singleton03{
private static Singleton03 singleton;
private Singleton03(){
}
public static synchronized Singleton03 getSingleton(){
if (null == singleton){
singleton = new Singleton03();
}
return singleton;
}
}
Ⅳ. 懒汉式双重校验锁——线程安全
加锁操作只需要对实例化的那部分代码进行,双重校验锁先判断单例是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
/**
* 4、双重校验锁-线程安全
* 1).判断是否被实例化,再对实例化语句加锁;
* 2).内部再次进行判断是否实例化,并加锁;
* 3).为单例对象添加采用 volatile 关键字修饰,禁止JVM的指令重排。
*/
class Singleton04{
private volatile static Singleton04 singleton;
private Singleton04(){
// 防反射
if(null !=singleton){
throw Exception();
}
}
public static Singleton04 getSingleton(){
if (null == singleton){
synchronized (Singleton04.class){
if (null == singleton){
synchronized (Singleton04.class){
singleton = new Singleton04();
}
}
}
}
return singleton;
}
}
singleton 采用 volatile 关键字修饰也是很有必要的, singleton = new Singleton4();
这段代码其实是分为三步执行:
- 为 singleton 分配内存空间
- 初始化 singleton
- 将 singleton 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getSingleton() 后发现 singleton 不为空,因此返回 singleton,但此时 singleton 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
Ⅴ. 静态内部类实现
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getUniqueInstance()
方法从而触发 SingletonHolder.INSTANCE
时 SingletonHolder 才会被加载,此时初始化 INSTANCE 实例,并且 JVM 能确保 INSTANCE 只被实例化一次。
这种方式不仅具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。
/**
* 5、静态内部类实现
*/
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getUniqueInstance() {
return SingletonHolder.INSTANCE;
}
}
Ⅵ. 枚举类实现
使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,可以防止反射攻击,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。
public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例。
*/
INSTANCE;
/**
* 单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
}
2. 责任链模式(Chain Of Responsibility)
责任链模式是一种对象的行为模式。在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
2.1 责任链模式的结构
-
**抽象处理者(Handler)角色:**定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。
-
**具体处理者(ConcreteHandler)角色:**具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
2.2 Java责任链模式
/**
* 抽象处理者角色
*/
public abstract class Handler {
//1.持有后继的责任对象
protected Handler successor;
//2.构造处理对象并传入下家引用
public Handler(Handler successor) {
this.successor = successor;
}
//3.示意处理请求的方法
protected abstract void handleRequest(Request request);
}
/**
* 具体处理者角色1
*/
public class ConcreteHandler1 extends Handler {
//1.构造处理对象并传入下家引用,可以传入空值null
public ConcreteHandler1(Handler successor) {
super(successor);
}
//2.具体处理请求的方法
@Override
protected void handleRequest(Request request) {
if (request.getType() == RequestType.TYPE1) {//处理自己所能处理的请求
System.out.println(request.getName() + " is handle by ConcreteHandler1");
return;
}
//3.判断是否有后继的责任对象,如果有,就转发请求给后继的责任对象。
if (successor != null) {
successor.handleRequest(request);
}
}
}
/**
* 具体处理者角色2
*/
public class ConcreteHandler2 extends Handler {
//1.构造处理对象并传入下家引用,可以传入空值null
public ConcreteHandler2(Handler successor) {
super(successor);
}
//2.具体处理请求的方法
@Override
protected void handleRequest(Request request) {
if (request.getType() == RequestType.TYPE2) {//处理自己所能处理的请求
System.out.println(request.getName() + " is handle by ConcreteHandler2");
return;
}
//3.判断是否有后继的责任对象,如果有,就转发请求给后继的责任对象。
if (successor != null) {
successor.handleRequest(request);
}
}
}
/**
* 请求
*/
public class Request {
private RequestType type;
private String name;
public Request(RequestType type, String name) {
this.type = type;
this.name = name;
}
public RequestType getType() {
return type;
}
public String getName() {
return name;
}
}
public enum RequestType {
TYPE1, TYPE2
}
/**
* 客户类
*/
public class Client {
public static void main(String[] args) {
//1.组装责任链
Handler handler1 = new ConcreteHandler1(null);
Handler handler2 = new ConcreteHandler2(handler1);
//2.构建请求
Request request1 = new Request(RequestType.TYPE1, "request1");
Request request2 = new Request(RequestType.TYPE2, "request2");
//3.处理请求
handler2.handleRequest(request1);
handler2.handleRequest(request2);
}
}
运行结果:
request1 is handle by ConcreteHandler1
request2 is handle by ConcreteHandler2
3. 迭代器模式(Iterator)
迭代器模式又叫游标(Cursor)模式,是对象的行为模式。迭代器模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象(internal representation)。
3.1 迭代器模式的结构
- 抽象迭代子(Iterator)角色:此抽象角色定义出遍历元素所需的接口。
- 具体迭代子(ConcreteIterator)角色:此角色实现了Iterator接口,并保持迭代过程中的游标位置。
- 聚集(Aggregate)角色:此抽象角色给出创建迭代子(Iterator)对象的接口。
- 具体聚集(ConcreteAggregate)角色:实现了创建迭代子(Iterator)对象的接口,返回一个合适的具体迭代子实例。
- 客户端(Client)角色:持有对聚集及其迭代子对象的引用,调用迭代子对象的迭代接口,也有可能通过迭代子操作聚集元素的增加和删除。
3.2 Java迭代器模式
/**
* 抽象迭代器角色类
*/
public interface Iterator<Item> {
Item next();
boolean hasNext();
//还可以定义一些其他迭代方法:移动到第一个元素、返回当前元素、是否为最后一个元素等等。
}
/**
* 具体迭代器角色类
*/
public class ConcreteIterator<Item> implements Iterator {
private Item[] items;
private int position = 0;
public ConcreteIterator(Item[] items) {
this.items = items;
}
@Override
public Object next() {
return items[position++];
}
@Override
public boolean hasNext() {
return position < items.length;
}
}
/**
* 聚合类接口
*/
public interface Aggregate {
//工厂方法,创建相应迭代子对象的接口
Iterator createIterator();
}
/**
* 具体聚合类
*/
public class ConcreteAggregate implements Aggregate {
private Integer[] items;
//构造方法,构建(或传入)聚合对象的具体内容
public ConcreteAggregate() {
items = new Integer[10];
for (int i = 0; i < items.length; i++) {
items[i] = i;
}
}
@Override
public Iterator createIterator() {
return new ConcreteIterator<Integer>(items);
}
}
/**
* 客户类
*/
public class Client {
public static void main(String[] args) {
Aggregate aggregate = new ConcreteAggregate();
Iterator<Integer> iterator = aggregate.createIterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
4. 模板方法模式(Template Method)
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
4.1 模板方法模式的结构
抽象模板(Abstract Template)角色有如下责任:
■ 定义了一个或多个抽象操作,以便让子类实现。这些抽象操作叫做基本操作,它们是一个顶级逻辑的组成步骤。
■ 定义并实现了一个模板方法。这个模板方法一般是一个具体方法,它给出了一个顶级逻辑的骨架,而逻辑的组成步骤在相应的抽象操作中,推迟到子类实现。顶级逻辑也有可能调用一些具体方法。
具体模板(Concrete Template)角色有如下责任:
■ 实现父类所定义的一个或多个抽象方法,它们是一个顶级逻辑的组成步骤。
■ 每一个抽象模板角色都可以有任意多个具体模板角色与之对应,而每一个具体模板角色都可以给出这些抽象方法(也就是顶级逻辑的组成步骤)的不同实现,从而使得顶级逻辑的实现各不相同。
4.2 Java模板方法模式
如下给出例子:冲咖啡和冲茶都有类似的流程,但是某些步骤会有点不一样,要求复用那些相同步骤的代码。
/**
* 抽象模板角色类
*/
public abstract class CaffeineBeverage {
/**
* 模板方法
*/
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
/**
* 基本方法的声明(泡)
*/
abstract void brew();
/**
* 基本方法的声明(加料)
*/
abstract void addCondiments();
/**
* 基本方法的声明(烧水)
*/
void boilWater() {
System.out.println("boilWater");
}
/**
* 基本方法的声明(倒入杯中)
*/
void pourInCup() {
System.out.println("pourInCup");
}
}
/**
* 具体模板角色类——泡咖啡
*/
public class Coffee extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Coffee.brew");
}
@Override
void addCondiments() {
System.out.println("Coffee.addCondiments");
}
}
/**
* 具体模板角色类——泡茶
*/
public class Tea extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Tea.brew");
}
@Override
void addCondiments() {
System.out.println("Tea.addCondiments");
}
}