常见的几种设计模式

单例模式

为什么会有单例设计模式?

单例模式主要是为了避免因为创建了多个实例造成资源的浪费,且多个实例由于多次调用容易导致结果的出错。而使用单例模式能够保证整个应用只有一个实例

应用spring中IOC

在整个应用中(一般只用一个IOC容器),只创建Bean的一个实例,多次注入同一具体类时都是注入同一个实例。

IOC容器来实现过程简述如下:

​ 当需要注入Bean时,IOC容器首先解析配置找到具体类,然后判断其作用域(@Scope注解);

​ 默认的单例@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON),则查找容器中之前有没有为其创建了Bean实例;如果有则直接注入该Bean实例,如果没有生成一个放到容器中保存

ConcurrentHashMap -- map.put(bean_id, bean)),再注入。

注:其中解析配置查找具体类、生成Bean实例和注入过程都是通过Java反射机制实现的。

解决的问题

  1. 可以保证一个类在内存中的对象的唯一性,在一些常用个工具类中,线程池,缓存和数据库,账号登陆系统,配置文件,等程序中之允许我们创建一个对象,
  2. 如果创建多个对象会造成资源的严重浪费,也有可能会造成程序的错误。
  3. 比如我们共享一个配置文件,当我们创建一个对象A修改其中的内容的时候,并不会真正改变类B.我们可能会想到把共享数据设置为静态数据。
  4. 但是我们都知道静态数据的生命周期是很长的,假如ConfigFile中有很多数据时,如果将其全部设成静态的,那将是对内存的极大损耗。

设计思想

  1. 不允许其它的程序用New对象
  2. 在该类中创建对象
  3. 对外提供一个可以让其他程序获取该对象的方法

①懒汉式

/**
 @author maxu
 @date 2018/11/3
优点:从它的实现中我们可以看到,这种方式的实现比较简单,在类加载的时候就完成了实例化,避免了线程的同步
*	 问题。
缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到
*	  这个实例,但是它也会加载,会造成内存的浪费(但是这个浪费可以忽略,所以这种方式也是推荐使用的)。
*/
public class Singleton {

    private static Singleton instance = new Singleton();
    private Singleton() {}
    public static Singleton getInstance() {
        return instance;
    }
}  

②饿汉式

public class Singleton {

    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}

③双重校验锁

public class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}

④静态内部类

/**
* 这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同
* 的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时
* 并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。
* 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
* 优点:避免了线程不安全,延迟加载,效率高。
*/
public class Singleton {


    private Singleton() {
    }

    private static class Sinletonleader {
        private static Singleton singleton = new Singleton();
    }

    public Singleton getInstance() {
        return Sinletonleader.singleton;
    }
}

⑤枚举

/**
* SingletonEnum.instance.method();
* 借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,
* 而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中
* 很少见人这么写过,这种方式也是最好的一种方式,如果在开发中JDK满足要求的情况下建议使用这种方式。
*/
public enum Singleton {
    instance;

    private Singleton() {
    }

    public void method() {
    }
}

工厂模式

简单工厂模式

​ 是由一个对象根据收到的消息决定要创建哪一个类的对象实例。

​ 使用场景:工厂类负责创建的对象比较少,客户只需要传入工厂参数,对于如何创建对象不关心,违反了高内聚低耦合的原则。

​ 最大的优点:工厂类包含了必要的逻辑根据用户的需要逻辑动态实例化相关的类。

​ 缺点:只能生产同一级结构的产品,在添加新产品的时候需要修改代码

常见的几种设计模式

工厂方法模式

​ 定义一个创建对象的工厂接口,让子类去决定实例化哪一个类,将实例化的工作延迟到子类当中。

场景:数据库访问的时候用户不知到最后系统采用那一个数据库以及数据库有什么变化。

特点:用来产生统一等级下结构的产品,支持增加任意的产品。

常见的几种设计模式


观察者模式

观察者模式的定义

  • 在对象之间定义一对多的依赖,当一个对象改变状态的时候,依赖它的对象就会收到通知,并自动更新。
  • 其实就是发布订阅模式,发布者发布信息,订阅者获取信息,订阅了就能收到信息,没订阅就收不到信息。

首先定义一个被观察者的接口Observerable

/**
* @author maxu
* @date 2018/11/3
* 抽象者被观察者方法
*/
public interface Observerable {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObserver();

}
/**
* @author maxu
* @date 2018/11/3
* 定义了一个update()方法,当被观察者调用notifyObservers()方法时,观察者的update()方法会被回调。
* 观察者
*/
public interface Observer {
    void update(String message);
}
/**
* @author maxu
* @date 2018/11/3
*/
public class WechatServer implements Observerable {
    /**
     * 注意到这个List集合的泛型是Observer
     */
    // 这里保存着所有注册的观察者
    private List<Observer> list;
    private String message;

    public WechatServer() {
        list = new ArrayList<>();
    }

	// 注册观察者
    @Override
    public void registerObserver(Observer observer) {
        list.add(observer);
    }

    // 移除观察者
    @Override
    public void removeObserver(Observer observer) {
        if (!list.isEmpty()) {
            list.remove(observer);
        }
    }

    // 通知所有的观察者的具体实现
    @Override
    public void notifyObserver() {
        for (Observer observer : list) {
            observer.update(message);
        }
    }

    /**
     * 发布消息,并通知给所有观察者
     * @param message
     */
    public void setInfomation(String message) {
        this.message = message;
        notifyObserver();
    }
}
/**
* @author maxu
* @date 2018/11/3
* 观察者
*/
public class User implements Observer {
    private String name;
    private String message;

    public User(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        this.message = message;
        read();
    }

    public void read(){
        System.out.println(name + "收到的消息" + message);
    }
}
/**
* @author maxu
* @date 2018/11/3
*/
public class Test {
    public static void main(String[] args) {
        WechatServer server = new WechatServer();
        Observer user1 = new User("张山");
        Observer user2 = new User("小马");
        Observer user3 = new User("小白");

        server.registerObserver(user1);
        server.registerObserver(user2);
        server.registerObserver(user3);

        server.setInfomation("java是最好的语言");

        System.out.println("----------------------------------");
        server.removeObserver(user1);
        server.setInfomation("php是最好的语言");
    }
}

装饰模式

原有的类不能满足现有的需求,对原有类的进行增强

装饰器模式特点在于增强,他的特点是被装饰类和所有的装饰类必须实现同一个接口,而且必须持有被装饰的对象,可以无限装饰。

常见的几种设计模式

Component 是一个接口,或者是一个抽象类,这就是定义我们最核心的对象,也就是最原始的对象。

ConreteComponent这是最核心、最原始、最基本的接口或者抽象方法,它里面可不一定有抽象方法呀,

在它的属性里必然有一个private属性指向Component。

ConcreteDecoratorA和ConcreteDecoratorB 是两个具体的装饰类,你要把你最核心的、最原始的、最基本的东西装饰成什么。

装饰模式是对继承的的补充,装饰模式可以解决继承膨胀的问题,继承是静态的给类增加新的功能,然而装饰模式是对类进行动态的增加新的功能。

装饰模式还有一个有点就是很好的扩展,装饰模式通过封装一个类而不是通过继承来完成。

简单点说,有三个继承关系 Father Son GrandSon 三个类,我要在Son类上添加新的功能?那么对GrandSon的影响呢,如果是多个GrandSon的时候,你评估就要做的很心累,所以我们可以新创建一个DectoratorSon来封装Son这个类。这样在增强Son的时候对GrandSon就不会产生影响了。

模板方法

定义:

​ 定义一个模板结构,将具体的内容延迟到子类中去实现。

主要的作用:

​ 在不改变模板结构的前提下在子类中重新定义模板中的内容

​ 模板方法的模式是基于继承的。

优点:

  • 提高代码的复用性
  • 将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中。
  • 实现反向的控制
  • 通过父类调用子类的操作,通过对子类具体实现扩展出不同的行为,实现了反向控制、符合开闭原则

缺点:

  • 引入抽象类,每一个不同的实现都需要一个子类来实现,导致类的个数增加,从而增加了系统实现的复杂程度。

应用场景:

  • 一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来并集中到一个公共的父类中避免代码重复
  • 控制子类的扩展

常见的几种设计模式


适配器模式(Adapter Pattern)

定义

  • 将一个类的接口转换成用户希望的另一个接口。

  • 适配器模式剋让那些接口不兼容的类可以一起工作。

  • 通常情况下,客户端可以通过目标类的接口访问它所提供的服务。有时,现有的类可以满足客户类的功能需要,但是它所提供的接口不一定是客户类所期望的,这可能是因为现有类中方法名与目标类中定义的方法名不一致等原因所导致的。

  • 在适配器模式中,可以定义一个包装类,包装不兼容接口对象,这个包装类指的就是适配器,它所包装的对象就是适配者,就是被适配的类。

  • 适配器提供用户类所需要的接口,适配器的实现就是把客户类的请求转化为对适配者响应接口的调用。简单来说,当客户类调用适配器方法的时候,在适配器内部将调用适配者类的方法,而这个过程对客户类是透明的,客户类并不直接访问适配者类,因此,适配器可以使由于接口不兼容而不能相互工作的类一起工作。

类适配器

常见的几种设计模式

public class Adapter extends Adaptee implements Target {
	public void request() {
		specificRequest();
	}
}

对象适配器

在这里插入图片描述

public class Adapter extends Target{
    
	private Adaptee adaptee;
	public Adapter(Adaptee adaptee){
		this.adaptee=adaptee;
	}
	public void request(){
		adaptee.specificRequest();
	}
}

优点

  • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
  • 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
  • 灵活性和扩展性都非常好,通过使用配置文件,可以很方便地更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,完全符合“开闭原则”。

类适配器优缺点

  • 由于适配器类是适配者类的子类,因此可以在适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
  • Java不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标抽象类只能为抽象类,不能为具体类,其使用有一定的局限性,不能将一个适配者类和它的子类都适配到目标接口。

对象适配器优缺点

  • 一个对象适配器可以把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和它的子类都适配到目标接口。
  • 与类适配器模式相比,要想置换适配者类的方法就不容易。如果一定要置换掉适配者类的一个或多个方法,就只好先做一个适配者类的子类,将适配者类的方法置换掉,然后再把适配者类的子类当做真正的适配者进行适配,实现过程较为复杂。

适用环境

  • 系统需要使用现有的类,而这些类的接口不符合系统的需要。
  • 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。

职责链模式

  • 职责链可以是一条直线、一个环或者一个树形结构,最常见的职责链是直线型,即沿着一条单向的链来传递请求。
  • 链上的每一个对象都是请求处理者,职责链模式可以将请求的处理者组织成一条链,并使请求沿着链传递,由链上的处理者对请求进行相应的处理,客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上即可,将请求的发送者和请求的处理者解耦。这就是职责链模式的模式动机。
  • 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

分析

  • 在职责链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。
  • 请求在这条链上传递,直到链上的某一个对象处理此请求为止。
  • 发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任。

常见的几种设计模式

public abstract class Handler{
	protected Handler successor;
    
	public void setSuccessor(Handler successor){
		this.successor=successor;
	}
	
	public abstract void handleRequest(String request);
} 

public class ConcreteHandler extends Handler {
    
	public void handleRequest(String request) {
		if(请求request满足条件) {
			......  //处理请求;
		} else {
			this.successor.handleRequest(request); //转发请求
		}
	}
} 

实例

审批假条

某OA系统需要提供一个假条审批的模块,如果员工请假天数小于3天,主任可以审批该假条;如果员工请假天数大于等于3天,小于10天,经理可以审批;如果员工请假天数大于等于10天,小于30天,总经理可以审批;如果超过30天,总经理也不能审批,提示相应的拒绝信息。

职责链模式的优点

  • 降低耦合度
  • 可简化对象的相互连接
  • 增强给对象指派职责的灵活性
  • 增加新的请求处理类很方便

职责链模式的缺点

  • 不能保证请求一定被接收。
  • 系统性能将受到一定影响,而且在进行代码调试时不太方便;可能会造成循环调用。

纯与不纯的职责链模式

  • 一个纯的职责链模式要求一个具体处理者对象只能在两个行为中选择一个:一个是承担责任,另一个是把责任推给下家。不允许出现某一个具体处理者对象在承担了一部分责任后又将责任向下传的情况。

  • 在一个纯的职责链模式里面,一个请求必须被某一个处理者对象所接收;在一个不纯的职责链模式里面,一个请求可以最终不被任何接收端对象所接收。


代理模式

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到的内容和服务或者添加客户需要的额外服务。

动机

通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。

常见的几种设计模式

public class Proxy implements Subject {
    // 创建真实对象
    private RealSubject realSubject = new RealSubject();
    
    public void preRequest(){...}
    public void request(){
        preRequest();
        realSubject.request();
        postRequest();
    }
    public void afterRequest(){...}
} 

优点

  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
  • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
  • 保护代理可以控制对真实对象的使用权限。

缺点

  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

适用环境

  • 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。
  • 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  • Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
  • 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
  • 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  • 防火墙(Firewall)代理:保护目标不让恶意用户接近。
  • 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
  • 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。

应用

  • pring 框架中的AOP技术也是代理模式的应用,在Spring AOP中应用了动态代理(Dynamic Proxy)技术。
  • RMI (Remote Method Invocation,远程方法调用)