设计模式(单例模式、工厂、简单工厂、抽象工厂、代理模式、装饰者模式、观察者模式、适配器模式)

☆樱花仙子☆ 提交于 2019-11-27 12:50:55

一、单例模式
二、代理模式

一、单例模式
1.单例模式的定义

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

2.单例模式的特点

单例类只能有一个实例。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
3.单例模式的应用

在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。

这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。

4.单例模式的Java代码

单例模式分为懒汉式(需要才去创建对象)和饿汉式(创建类的实例时就去创建对象)。

5.饿汉式

属性实例化对象

//饿汉模式:线程安全,耗费资源。
public class HugerSingletonTest {
    //该对象的引用不可修改
    private static final HugerSingletonTest ourInstance = new HugerSingletonTest();
 
    public static HugerSingletonTest getInstance() {
        return ourInstance;
    }
 
    private HugerSingletonTest() {
 
    }
 
}

在静态代码块实例对象

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

分析:饿汉式单例模式只要调用了该类,就会实例化一个对象,但有时我们并只需要调用该类中的一个方法,而不需要实例化一个对象,所以饿汉式是比较消耗资源的。

6.懒汉式

非线程安全

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

分析:如果有两个线程同时调用getInstance()方法,则会创建两个实例化对象。所以是非线程安全的。

线程安全:给方法加锁

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

分析:如果有多个线程调用getInstance()方法,当一个线程获取该方法,而其它线程必须等待,消耗资源。

线程安全:双重检查锁(同步代码块)

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

分析:为什么需要双重检查锁呢?因为第一次检查是确保之前是一个空对象,而非空对象就不需要同步了,空对象的线程然后进入同步代码块,如果不加第二次空对象检查,两个线程同时获取同步代码块,一个线程进入同步代码块,另一个线程就会等待,而这两个线程就会创建两个实例化对象,所以需要在线程进入同步代码块后再次进行空对象检查,才能确保只创建一个实例化对象。

二、代理模式
即为指定的目标对象提供一个代理商,由代理商来完成对目标对象的操作。
代理类图:
在这里插入图片描述
代理模式有一个接口,两个对象(代理对象和真实对象,这两个对象都实现前一个接口,代理对象内部调用了真实对象)。

接口:

public interface Subject
{
    void doSomething();
}

真实对象:

package com.njupt.study.designmodle.proxy;

public class RealSubject implements Subject {

    @Override
    public void doSomething() 
    {
        System.out.println("做些什么呢?");
    }

}

代理对象:

package com.njupt.study.designmodle.proxy;

public class ProxySubject implements Subject {

    private RealSubject realSubject;
    
    public ProxySubject()
    {
        if(realSubject == null)
        {
            realSubject = new RealSubject();
        }
    }
    
    
    @Override
    public void doSomething()
    {
    //可以对真实对象进行调用,且在调用时进行一些额外的操作
        System.out.println("befor do something");
        realSubject.doSomething();
        System.out.println("after do something");
    }

}

客户端访问:

package com.njupt.study.designmodle.proxy;

public class Client {

    /**
     * @param args
     */
    public static void main(String[] args) 
    {
        Subject subject = new ProxySubject();
        subject.doSomething();
    }

}

输出:
在这里插入图片描述

总结:

代理模式的功能 : 通过创建一个代理对象,用这个代理对象去代表真实的对象,客户端使用这个代理对象来操作真实的对象(当客户端操作这个代理对象的时候,实际上功能最终还是会由真实的对象来完成,只不过是通过代理操作的,也就是客户端操作代理,代理操作真正的对象)

三、简单工厂模式
实例化对象的时候不再使用 new Object()形式,可以根据用户的选择条件来实例化相关的类。对于客户端来说,去除了具体的类的依赖。只需要给出具体实例的描述给工厂,工厂就会自动返回具体的实例对象。
在这里插入图片描述
具体实现如下:

  1. 定义一个操作接口:
public interface Operation {
 
    public double getResult(double numberA,double numberB) throws Exception;
 
}
  1. 定义具体的操作类:
public class Add implements Operation{
 
    // 加法计算
    public double getResult(double numberA, double numberB) {
 
        return numberA + numberB;
    }
}
 
 
public class Sub implements Operation{
 
    // 减法计算
    public double getResult(double numberA, double numberB) {
        return numberA-numberB;
    }
}
 
 
public class Mul implements Operation{
 
    // 乘法计算
    public double getResult(double numberA, double numberB) {
        return numberA * numberB;
    }
}
 
 
public class Div implements Operation {
 
    // 除法计算
    public double getResult(double numberA, double numberB) throws Exception {
        if (numberB == 0) {
            throw new Exception("除数不能为0!");
        }
        return numberA / numberB;
    }
}
  1. 定义简单工厂类:
public class EasyFactory {
 
    // 简单工厂,根据字符串创建相应的对象
    public static Operation createOperation(String name) {
        Operation operationObj = null;
        switch (name) {
            case "+":
                operationObj = new Add();
                break;
            case "-":
                operationObj = new Sub();
                break;
            case "*":
                operationObj = new Mul();
                break;
            case "/":
                operationObj = new Div();
                break;
        }
        return operationObj;
    }
}
  1. 用户端代码:
public class Client {
 
    public static void main(String[] args) throws Exception {
 
        Operation add = EasyFactory.createOperation("+");
        Operation sub = EasyFactory.createOperation("-");
        Operation mul = EasyFactory.createOperation("*");
        Operation div = EasyFactory.createOperation("/");
 
        System.out.println(add.getResult(1, 1));
        System.out.println(sub.getResult(1, 1));
        System.out.println(mul.getResult(1, 1));
        System.out.println(div.getResult(1, 1));
    }
}

Result:

2.0
0.0
1.0
1.0

我们无需提供具体的子类类名,只需要提供一个字符串即可得到相应的实例对象。这样的话,当子类的类名更换或者增加子类时我们都无需修改客户端代码,只需要在简单工厂类上增加一个分支判断代码即可。

使用这种模式,我们在生成工厂的时候可以加一些业务代码,如日志、判断业务等,这时候可以直接在switch case中加上去就行了,如下:

public class EasyFactory {
 
    private static Operation operationObj = null;
 
    private static Operation add(){
        System.out.println("加法运算");
        return new Add();
    }
    private static Operation sub(){
        System.out.println("减法运算");
        return new Sub();
    }
    private static Operation mul(){
        System.out.println("乘法运算");
        return new Mul();
    }
    private static Operation div(){
        System.out.println("除法运算");
        return new Div();
    }
 
    // 简单工厂,根据字符串创建相应的对象
    public static Operation createOperation(String name) {
 
        switch (name) {
            case "+":
                operationObj = add();
                break;
            case "-":
                operationObj = sub();
                break;
            case "*":
                operationObj = mul();
                break;
            case "/":
                operationObj = div();
                break;
        }
        return operationObj;
    }
}

简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。但是抽象工厂违背了开闭原则。

四、工厂模式
工厂方法模式是对简单工厂模式进一步的解耦,在工厂方法模式中是一个子类对应一个工厂类,而这些工厂类都实现于一个抽象接口。这相当于是把原本会因为业务代码而庞大的简单工厂类,拆分成了一个个的工厂类,这样代码就不会都耦合在同一个类里了。
把简单工厂模式的工厂类变成了一个工厂抽象接口和多个具体生成对象的工厂。当要添加功能的时候不需要更改原有的工厂类,只需要增加此功能的运算类和相应的工厂类。
在这里插入图片描述
这时上面那个例子的结构图为:
在这里插入图片描述

  1. 首先定义一个工厂接口:
import org.zero01.operation.Operation;
 
public interface Factory {
 
    public Operation createOperation() ;
 
}
  1. 然后是具体的工厂类:
// 加法类工厂
public class AddFactory implements Factory{
 
    public Operation createOperation() {
        System.out.println("加法运算");
        return new Add();
    }
}
 
// 减法类工厂
public class SubFactory implements Factory{
 
    public Operation createOperation() {
        System.out.println("减法运算");
        return new Sub();
    }
}
........
  1. 运算类跟简单工厂一样。
public class Add implements Operation{
 
    // 加法计算
    public double getResult(double numberA, double numberB) {
 
        return numberA + numberB;
    }
}
 
 
public class Sub implements Operation{
 
    // 减法计算
    public double getResult(double numberA, double numberB) {
        return numberA-numberB;
    }
}
 
 
public class Mul implements Operation{
 
    // 乘法计算
    public double getResult(double numberA, double numberB) {
        return numberA * numberB;
    }
}
 
 
public class Div implements Operation {
 
    // 除法计算
    public double getResult(double numberA, double numberB) throws Exception {
        if (numberB == 0) {
            throw new Exception("除数不能为0!");
        }
        return numberA / numberB;
    }
}
  1. 客户端代码:
public class Client {
 
    public static void main(String[] args) throws Exception {
 
        // 使用反射机制实例化工厂对象,因为字符串是可以通过变量改变的
        Factory addFactory = (Factory) Class.forName("org.zero01.factory.AddFactory").newInstance();
        Factory subFactory=(Factory) Class.forName("org.zero01.factory.SubFactory").newInstance();
 
        // 通过工厂对象创建相应的实例对象
        Operation add = addFactory.createOperation();
        Operation sub = subFactory.createOperation();
 
        System.out.println(add.getResult(1, 1));
        System.out.println(sub.getResult(1, 1));
    }
}

比较:

工厂模式中,要增加产品类时也要相应地增加工厂类,客户端的代码也增加了不少。工厂方法把简单工厂的内部逻辑判断转移到了客户端代码来进行。

你想要加功能,本来是改工厂类的,而现在是修改客户端。而且各个不同功能的实例对象的创建代码,也没有耦合在同一个工厂类里,这也是工厂方法模式对简单工厂模式解耦的一个体现。工厂方法模式克服了简单工厂会违背开-闭原则的缺点,又保持了封装对象创建过程的优点。

但工厂方法模式的缺点是每增加一个产品类,就需要增加一个对应的工厂类,增加了额外的开发量。

五、抽象工厂模式
场景:对数据库中的表进行修改
此时,使用工厂模式结构图如下:
在这里插入图片描述

  1. 我们现在要对mysql/oracle数据库中的User表进行操作,User表定义如下:
public class User {
    private int uid;
    private String uname;
 
    public int getUid() {
        return uid;
    }
 
    public void setUid(int uid) {
        this.uid = uid;
    }
 
    public String getUname() {
        return uname;
    }
 
    public void setUname(String uname) {
        this.uname = uname;
    }
}
  1. 接下来我们定义一个对User进行操作的接口:
public interface IUser {
    public void insert(User user);
    public User getUser(int uid);
}
  1. 实现一个对mysql中User进行操作的类:
public class mysqlUser implements IUser{
 
    public void insert(User user){
        System.out.println("在mysql中的user表中插入一条元素");
    }
 
    public User getUser(int id){
        System.out.println("在mysql中的user表得到id为"+id+"的一条数据");
        return null;
    }
}

实现对oracle中User进行操作的类:

public class oracleUser implements IUser{
 
    @Override
    public void insert(User user) {
        System.out.println("在oracle中的user表中插入一条元素");
    }
 
    @Override
    public User getUser(int uid) {
        System.out.println("在oracle中的user表得到id为"+uid+"的一条数据");
        return null;
    }
}
  1. 接下来定义一个工厂接口,用于生产访问User表的对象:
public interface sqlFactory {
    public IUser createUser();     //用于访问User表的对象
}
  1. 生产mysqlUser对象的mysql工厂类:
public class mysqlFactory implements sqlFactory {
    @Override
    public IUser createUser() {
        return new mysqlUser();  //访问mysql中User表的对象
    }
}

生成oracleUser对象的oracle工厂类:

public class oracleFactory implements sqlFactory {
    @Override
    public IUser createUser() {
        return new oracleUser();   //访问oracle中User表的对象
    }
}
  1. 最后用户测试类如下:
public class test_abstractFactory {
    public static void main(String[] args) {
        sqlFactory factory1 = new mysqlFactory();
        IUser userOperator = factory1.createUser();
        userOperator.getUser(1);
        userOperator.insert(new User());
    }
}

结果为:

在mysql中的user表得到id为1的一条数据
在mysql中的user表中插入一条元素

到此为止,工厂模式都可以很好的解决,由于多态的关系,sqlFactory在声明对象之前都不知道在访问哪个数据库,却可以在运行时很好的完成任务,这就是业务逻辑与数据访问的解耦。

但是,当数据库中不止一个表的时候该怎么解决问题呢,此时就可以引入抽象工厂模式了,结构图如下:
在这里插入图片描述

  1. 比如说现在增加了一个Login类,用于记录登陆信息:
package DesignPattern.abstractFactory;
 
import java.util.Date;
 
public class Login {
    private int id;
    private Date date;
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public Date getDate() {
        return date;
    }
 
    public void setDate(Date date) {
        this.date = date;
    }
}
  1. 此时就要相应地添加 对login表操作的Ilogin接口,mysqlLogin类,oracleLogin类:
public interface ILogin {
 
    public void insert(Login login);
    public Login getLogin(int id);
 
}
public class MysqlLogin implements ILogin{
 
    public void insert(Login login) {
        System.out.println("对 MySQL 里的 Login 表插入了一条数据");
    }
 
    public Login getLogin(int id) {
        System.out.println("通过 uid 在 MySQL 里的 Login 表得到了一条数据");
        return null;
    }
}
public class OracleLogin implements ILogin{
 
    public void insert(Login login) {
        System.out.println("对 Oracle 里的 Login 表插入了一条数据");
    }
 
    public Login getLogin(int id) {
        System.out.println("通过 uid 在 Oracle 里的 Login 表得到了一条数据");
        return null;
    }
}
  1. 修改Factory接口及Factory实现类的内容:

IFactory,定义一个抽象的工厂接口,该工厂用于生产访问User表以及Login表的对象:

public interface IFactory {
 
    public IUser createUser();
    public ILogin createLogin();
}
public class MysqlFactory implements IFactory{
 
    public IUser createUser() {
        return new MysqlUser();
    }
 
    public ILogin createLogin() {
        return new MysqlLogin();
    }
}
public class OracleFactory implements IFactory{
 
    public IUser createUser() {
        return new OracleUser();
    }
 
    public ILogin createLogin() {
        return new OracleLogin();
    }
}
  1. 客户端代码:
public class Client {
 
    public static void main(String[] args){
 
        User user=new User();
        Login login = new Login();
 
        // 只需要确定实例化哪一个数据库访问对象给factory
        // IFactory factory=new MysqlFactory();
        IFactory factory=new OracleFactory();
 
        // 已与具体的数据库访问解除了耦合
        IUser userOperation=factory.createUser();
 
        userOperation.getUser(1);
        userOperation.insert(user);
 
        // 已与具体的数据库访问解除了耦合
        ILogin loginOperation=factory.createLogin();
 
        loginOperation.insert(login);
        loginOperation.getLogin(1);
 
    }
}

结果:

通过 uid 在 Oracle 里的 User 表得到了一条数据
对 Oracle 里的 User 表插入了一条数据
对 Oracle 里的 Login 表插入了一条数据
通过 uid 在 Oracle 里的 Login 表得到了一条数据

所以抽象工厂与工厂方法模式的区别在于:抽象工厂是可以生产多个产品的,例如 MysqlFactory 里可以生产 MysqlUser 以及 MysqlLogin 两个产品,而这两个产品又是属于一个系列的,因为它们都是属于MySQL数据库的表。而工厂方法模式则只能生产一个产品,例如之前的 MysqlFactory 里就只可以生产一个 MysqlUser 产品。
在这里插入图片描述
抽象工厂模式的优缺点:

优点:

  1. 抽象工厂模式最大的好处是易于交换产品系列,由于具体工厂类,例如 IFactory factory=new OracleFactory(); 在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。不管是任何人的设计都无法去完全防止需求的更改,或者项目的维护,那么我们的理想便是让改动变得最小、最容易,例如我现在要更改以上代码的数据库访问时,只需要更改具体的工厂即可。

  2. 抽象工厂模式的另一个好处就是它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操作实例,产品实现类的具体类名也被具体的工厂实现类分离,不会出现在客户端代码中。就像我们上面的例子,客户端只认识IUser和ILogin,至于它是MySQl里的表还是Oracle里的表就不知道了。

缺点:

  1. 如果你的需求来自增加功能,比如增加Login表,就有点太烦了。首先需要增加 ILogin,mysqlLogin,oracleLogin。 然后我们还要去修改工厂类: sqlFactory, mysqlFactory, oracleFactory 才可以实现,需要修改三个类,实在是有点麻烦。

  2. 还有就是,客户端程序肯定不止一个,每次都需要声明sqlFactory factory=new MysqlFactory(), 如果有100个调用数据库的类,就需要更改100次sqlFactory factory=new oracleFactory()。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!