设计模式-

学习一下几个设计模式,做一下记录,仅供参考。

模式包括
创建型模式单例模式(Singleton Pattern)、工厂模式(Factory Pattern)、抽象工厂模式(Abstract Factory Pattern)、建造者模式(Builder Pattern)、原型模式(Prototype Pattern)

单例模式

  • 什么是单例模式?

单例模式是Java中最简单的设计模式之一,属于创建型设计模式,它提供了一种最佳的创建对象的方式。这种模式涉及到一个单一的类,这个类提供了一种访问其唯一对象的方式,可以直接访问,不需要实例化该类。

意图:保证一个类只有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁的创建和销毁。
何时使用:想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经存在这个单例,有则返回,没有则创建。
关键代码点:构造方法是私有的

例子:

  1. Windows是多进程多线程的,在操作一个文件的时候,就不可避免多个进程或线程同时操作一个文件的现象,所以对文件的处理必须通过唯一一个实例来进行。
  2. 一些设备管理器常常设置为单例模式。比如一个电脑有2台打印机,在输出的时候就要处理不能两台打印机打印同一个文件

优点:

  • 在内存中只有一个实例,减小开销,尤其是频繁的创建和销毁。
  • 避免对系统资源的多重占用(比如写文件操作)

缺点:

  • 没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关系外部怎么样来实例化。

使用场景

  • 要求产生唯一序列号
  • web中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来
  • 创建一个对象所消耗的资源过多,比如I/O与数据库的连接等
  • 文件系统、打印机、资源管理器等,因为底层资源只能同时被一方操纵,所以这些模块暴露的接口必然是单例的
  • Java 中的 dao、service 一般都是单例的,而 action 层一般都是多例。

注意点:

  • 单例类只能有一个实例
  • 单例类必须自己创建自己的唯一实例
  • 单例类必须提供给所有对象这一实例
  • getInstance()方法中需要使用同步锁synchronize防止多线程同时进入造成instance被多次实例化

实现:

1.懒汉式,线程不安全
特点:
1、懒加载
2、线程不安全
特点:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

public class SingleObject {

    public static SingleObject singleObject;

    private void SingleObject(){
    }

    public static SingleObject getInstance(){
        if(singleObject == null){
            singleObject = new SingleObject();
        }
        return singleObject;
    }
}

2.懒汉式,线程安全
特点:
1、懒加载
2、线程安全
优点:第一次调用才初始化,避免内存浪费
缺点:需要加synchronize关键字才能保证线程安全,但是加锁会影响效率。

public class SingleObject {

    public static SingleObject singleObject;

    private void SingleObject(){
    }

    public static synchronized SingleObject getInstance(){
        if(singleObject == null){
            singleObject = new SingleObject();
        }
        return singleObject;
    }
}

3.饿汉式
特点:
1、非懒加载
2、线程安全
优点:没有加锁,执行效率提高
缺点:类加载时就初始化,浪费内存
基于类加载器的机制避免了多线程同步的问题,但是无法实现懒加载了

public class SingleObject {
    public static SingleObject singleObject = new SingleObject();

    private void SingleObject(){
    }

    public static SingleObject getInstance(){
        return singleObject;
    }
}

4.双检锁/双重校验锁(DCL, double-checked locking)
要求:JDK1.5之后
特点:
1、懒加载
2、线程安全
采取双锁机制,不仅线程安全而且还高效

public class SingleObject {
	//利用volatile,防止指令重排导致的问题
    public static volatile SingleObject singleObject;

    private void SingleObject(){
    }

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

上面代码用了volatile,为的是防止多线程下指令重排导致的问题

5.静态内部类/登记式
特点:
1、懒加载
2、线程安全
这种方式能达到双检锁的效果,而且实现方式更简单。对静态域使用延迟初始化但是又不是使用双检锁的方式。这种方式只适用于静态域的情况,双检锁方式可以在实例域延迟初始化时使用。
也是利用了类加载器的加载机制保证初始化时只有一个线程。
跟第三种方式不同的是:第三种方式只要SingleObject 类被加载,instance就会被实例化,没有懒加载的效果。而这种方式,就算SingleObject 类被加载,instance不一定被初始化了,**因为SingleObjectHolder类没有被主动使用,**只有显式调用getInstance()方法时,才会显式装载SingleObjectHolder类,从而实例化instance。

public class SingleObject {
    public static class  SingleObjectHolder{
        private static final SingleObject INSTANCE = new SingleObject();
        public static final SingleObject getInstance(){
            return SingleObjectHolder.INSTANCE;
        }
    }
}

6.枚举
特点:
1、非懒加载
2、线程安全
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。
这种方式是 Effective Java 作者 Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。
不能通过 reflection attack 来调用私有构造方法。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

一般情况下,不建议使用第 1 种和第 2 种懒汉方式,建议使用第 3 种饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用第 5 种登记方式。如果涉及到反序列化创建对象时,可以尝试使用第 6 种枚举方式。如果有其他特殊的需求,可以考虑使用第 4 种双检锁方式。

工厂模式

工厂模式是什么:顾名思义,一个模型,用来大规模的生产同类产品。该模式将对象的具体实例过程抽象化,并不关心具体的创建过程。
为什么存在:可快速规模化实例化对象
简单工厂的实现:一个工厂类根据传入的参数,动态决定创造哪一个产品类实例(这些产品类继承同一个接口或父类)
特点:将对象的具体实例过程抽象化,并不关系具体的创建过程。
意图:定义一个创建对象的接口,让其子类自己决定实例化哪个类
何时使用:明确地计划不同条件时创建不同的实例
如何解决:让其子类实现工厂接口,返回的也是一个抽象的实例
关键代码:子类创建实例

  • 优点
    1.一个调用者想创建一个实例,知道它的名字就行了
    2.扩展性提高,如果想增加一个产品,就增加一个工厂类
    3.屏蔽具体的实现,调用者只需要关心接口

  • 缺点
    1、每次增加一个产品,都要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事

  • 例子
    我们经常使用的数据库中间件,我们无需关心具体的底层实现类,只需将用户名密码等连接信息传过去,就会直接获取到相应的数据库连接实例,这个角度,就可以将数据库中间件看作一个大的工厂

  • 使用场景
    日志记录器:记录可能记录到本地硬盘、系统事件、远程服务器等,用户可以选择记录日志到什么地方
    数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时

  • 注意事项
    作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度

  • 分类:简单普通工厂、多方法简单工厂、静态方法简单工厂

  • 实现
    普通简单工厂
    UML图
    在这里插入图片描述
    先新建一个共有的接口:

public interface Sender {
    public void send();
}

再新建三个实现类:

public class EmailSender implements Sender{
    @Override
    public void send() {
        System.out.println("send email");
    }
}

public class SmsSender implements Sender{
    @Override
    public void send() {
        System.out.println("send sms");
    }
}

public class ExpressSender implements Sender{
    @Override
    public void send() {
        System.out.println("send express");
    }
}

然后,新建一个工厂类来生成“产品”:

public class SenderFactory {
    public Sender produce(String type){
        if(type == "email"){
            return new EmailSender();
        }else if(type == "express"){
            return new ExpressSender();
        }else if(type.equals( "sms")){
            return new SmsSender();
        }else{
            return null;
        }
    }
}

然后开始测试:

public class Demo {
    public static void main(String[] args) {
        SenderFactory senderFactory = new SenderFactory();
        
        Sender sms = senderFactory.produce("sms");
        sms.send();

        Sender email = senderFactory.produce("email");
        email.send();

        Sender express = senderFactory.produce("express");
        express.send();
    }
}

多方法简单工厂:

多方法简单工厂是根据普通工厂的基础改进的,简单工厂方法如果传递的参数错误,那就无法正确创建对象。多方法直接将produce的逻辑展开到具体方法中,从而避免该问题。

uml图:
在这里插入图片描述

工厂类:

public class SenderFactory {
    
      public Sender produceEmail(){
        return new EmailSender();
    }
    public Sender produceSms(){
        return new SmsSender();
    }
    public Sender produceExpress(){
        return new ExpressSender();
    }
}

测试:

public class Demo {
    public static void main(String[] args) {
        SenderFactory senderFactory = new SenderFactory();

        Sender sms = senderFactory.produceSms();
        sms.send();

        Sender email = senderFactory.produceEmail();
        email.send();

        Sender express = senderFactory.produceExpress();
        express.send();
    }
}

静态方法简单工厂
普通工厂模式和多方法工厂模式有一个弊端,就是需要频繁的实例化工厂类,一般我们会将 “多方法” 设置为静态的,从而避免类的频繁实例化,拿来即用

public class Main {

    public static void main(String[] args) {
        //SendFactory sendFactory = new SendFactory();
        Sender senderEmail = SenderFactory.produceEmail();
        senderEmail.Send(); 

        Sender senderSms = SenderFactory.produceSms();
        senderSms.Send(); 

        Sender senderExpress = SenderFactory.produceExpress();
        senderExpress.Send(); 
    }
}

简单工厂的延申 — 工厂方法模式
简单工厂模式有个比较明显的弊端:工厂类集中了所有实例的创建逻辑,明显违背高内聚的责任分配原则,违背了闭包规则。

而工厂方法模式则是对该问题的进一步延伸解决,差异就是将原先存在于一个工厂类中的逻辑抽调出来,创建一个接口和多个工厂类。这样,一旦功能有新增,比如说我们要加一个 “发送导弹” 的功能,只需要加一个 “导弹发送工厂类”,该类实现 produce 接口返回实例化的 “导弹发送类”,再在 “导弹发送类” 中,实现具体的发送逻辑即可,无需修改之前的业务代码,拓展性较好。

在这里插入图片描述

首先,我们还是创建一个 Sender 接口:

public interface Sender {
    public void Send();
}

然后我们创建几个具体的实现类:

public class SmsSender implements Sender{
    @Override
    public void Send() {
        System.out.println("发送短信");
    }
}
public class ExpressSender implements Sender {
    @Override
    public void Send() {
        System.out.println("发送快递");
    }
}
public class EmailSender implements Sender{
    @Override
    public void Send() {
        System.out.println("发送邮件");
    }
}

然后,我们统一一下工厂类的接口行为:

public interface Provider {
    public Sender produce();
}

继续,定义几个工厂实现上面这种 “行为约束”:

public class ExpressSendFactory implements Provider {
    @Override
    public Sender produce() {
        return new ExpressSender();
    }
}
public class EmailSendFactory implements Provider{
    @Override
    public Sender produce() {
        return new EmailSender();
    }
}
public class SmsSendFactory implements Provider {
    @Override
    public Sender produce() {
        return new SmsSender();
    }
}

测试:

public class Main {

    public static void main(String[] args) {
        Provider providerSms = new SmsSendFactory();
        Sender senderSms = providerSms.produce();
        senderSms.Send(); // 发送短信

        Provider providerEmail = new EmailSendFactory();
        Sender senderEmail = providerEmail.produce();
        senderEmail.Send(); // 发送邮件

        Provider providerExpress = new ExpressSendFactory();
        Sender senderExpress = providerExpress.produce();
        senderExpress.Send(); // 发送快递
    }
}

工厂方法模式中,核心的工厂类(这里为 Provider 接口)不再负责所有产品的创建,而是将具体创建的工作交给子类去做,该核心类仅扮演抽象工厂的角色,负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应该被实例化的细节,拓展性较简单工厂模式提升明显。
这里真正的好处是,多方法简单工厂的最大弊端是:如果我需要增加一个新的实现,就不得不去修改工厂类,工厂类作为一个基础类尽量不要做改动,试想,如果每当要加一个实现类,大家就改动工厂,那这个类要多么膨胀,后期怎么维护,要符合开闭原则。如果用工厂方法就不一样了,我们对工厂进行接口化,并提供各个实现工厂类,如果你要新增新的实现类,那你就再扩展一个新的工厂,不会对接口进行改变,想怎么扩展就怎么扩展,别看代码量上去了,但是在实际业务中会极大的方便后期维护。这样是松耦合的,并且符合开闭原则。

抽象工厂模式

是什么:为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们具体类。
通俗一点理解就是对一组具有相同主题的工厂进行的封装。比如要生产一台电脑,如果使用的工厂模式,那么就会有显卡工厂,主板工厂等等……但是使用抽象工厂模式,只会有一个电脑工厂,而电脑工厂里面又涵盖了显卡工厂、主板工厂、cpu工厂等等……。所以工厂模式针对的是同一类或者同等级的产品,而抽象工厂则是针对多种的产品设计。
解决的问题:接口选择的问题
怎么解决:在一个产品族里面,定义多个产品
什么时候会用到:系统的产品里面有多余一个的产品族,而系统只消费某一族的产品。
优点

  • 易于交换产品的系列
  • 让具体的创建实例过程和客户端分离

关键代码:在一个工厂里聚合多个同类产品

什么是产品族:比如上面电脑的例子:电脑中的主板和显卡就是两个相互依赖的产品族。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

例子:
加入项目中使用的是sql server数据库,结果有一天开会,需求更改,项目经理要求数据库改为Access。假设数据库操作的是用户,那么:
最原始的项目代码是:
用户类User:只有id和name字段

public class User {
    private Integer id;
    private String name;
}
  /**
    * getter setter
    */

SqlServerUser类:用于操作User,现在只有新增和修改

public class SqlserverUser {
    public void insert(User user){
        System.out.println("sql server 新增一个用户");
    }
    public User select(Integer id){
        System.out.println("sql server 获得一个用户");
        return null;
    }
}

客户端代码:

public class Demo {
    public static void main(String[] args) {
        //与sql server耦合
        SqlserverUser sqlserverUser = new SqlserverUser();
        User user = new User();
        //新增一个用户
        sqlserverUser.insert(user);
        //获得一个用户
        sqlserverUser.select(1);
    }
}

最开始这种写法非常简单,但是问题在于:
SqlserverUser sqlserverUser = new SqlserverUser() 使得sqlserverUser这个对象被写死在了Sql server数据库上,如果这里灵活一点,可以是多态的话,那么调用insert()和select()方法时,就不需要知道底层是用的Sql server 还是Access了。接着进行下一步的优化:

工厂方法模式:
新创建一个User接口,用于客户端访问,解除与具体数据库访问的耦合

public interface IUser {
    public User select(int id);

    public void insert(User user);
}

SqlServerUser类:用于访问sql server的user

public class SqlserverUser implements IUser{
    @Override
    public User select(int id) {
        System.out.println("sql server 获得一个用户");
        return null;
    }
    public void insert(User user){
        System.out.println("sql server 新增一个用户");
    }
}

AccessUser类:用于访问Access的user

public class AccessUser implements IUser{
    @Override
    public User select(int id) {
        System.out.println("Access获得一个用户");
        return null;
    }
    public void insert(User user){
        System.out.println("Access新增一个用户");
    }
}

DBFactory接口:定义一个创建User表对象的抽象工厂接口

public interface DBFactory {
    public IUser createUser();
}

SqlServerFactory类:实例化SqlServerUser

public class SqlServerFactory implements DBFactory {
    @Override
    public IUser createUser() {
        return new SqlserverUser();
    }
}

AccessFactory类:实例化AccessUser

public class AccessFactory implements DBFactory {
    @Override
    public IUser createUser() {
        return new AccessUser();
    }
}

客户端:

public class Demo {
    public static void main(String[] args) {
        User user = new User();
        DBFactory factory = new SqlServerFactory();
        //DBFactory factory = new AccessFactory();
        IUser iu = factory.createUser();
        iu.insert(user);
        iu.select(1);
    }
}

若要更换成Access数据库,那么简化为上面客户端代码:
DBFactory factory = new SqlServerFactory();→DBFactory factory = new AccessFactory();即可。
此时由于多态的关系,使得对象iu一开始根本不知道访问的是哪个数据库,这就是所谓的业务逻辑和数据访问的解耦。
那么如果数据库里不止一个User表,还有其他表,比如说部门表Dept,那怎么办?

新建一个部门接口:

public interface IDept {
    public void create(Dept dept);
    public Dept select(int id);
}

SqlServerDept类:用于访问Sql server的Dept:

public class SqlServerDept implements IDept {
    @Override
    public void create(Dept dept) {
        System.out.println("sql server 新建了一个部门");
    }

    @Override
    public Dept select(int id) {
        System.out.println("sql server 查询一个部门");
        return null;
    }
}

AccessDept类:用于访问Access的Dept:

public class AccessDept implements IDept {
    @Override
    public void create(Dept dept) {
        System.out.println("access新建了一个部门");
    }

    @Override
    public Dept select(int id) {
        System.out.println("access查询一个部门");
        return null;
    }
}

DBFactory接口新增一个新增部门的方法:
在这里插入图片描述
SqlServerFactory类和AccessFactory需要做对应的实现
在这里插入图片描述

在这里插入图片描述
客户端代码要更改为:

public class Demo {
    public static void main(String[] args) {
        User user = new User();
        DBFactory factory = new SqlServerFactory();
        //DBFactory factory = new AccessFactory();
        IUser iu = factory.createUser();
        iu.insert(user);
        iu.select(1);

        Dept dept = new Dept();
        IDept idept = factory.createDept();
        idept.create(dept);
        idept.select(1);
    }
}

此时部门和用户一样,若要更换成Access数据库,
DBFactory factory = new SqlServerFactory();
转化为:
DBFactory factory = new AccessFactory();

如果只有User类和User操作类,那么只需要工厂模式就可以了,但是现在数据库下有多张表,而Sql server和Access又是两个不同的分类,所以涉及到这种多个产品系列的问题,有一个专门的工厂模式叫抽象工厂模式

抽象工厂模式
下图源自《大话设计模式》
在这里插入图片描述
DBFactory是一个抽象工厂接口,里面包含所有产品创建的抽象方法。比如上面的:

public interface DBFactory {
        public IUser createUser();
        public IDept createDept();
}

而ConCreteFactory就相当于上面例子的SqlServerFactory和AccessFactory。这个具体的工厂再创建具有特定实现的产品对象,比如SqlServerFactory创建的就是SqlserverUser和SqlServerDept:

public class SqlServerFactory implements DBFactory {
    @Override
    public IUser createUser() {
        return new SqlserverUser();
    }

    @Override
    public IDept createDept() {
        return new SqlServerDept();
    }
}

也就是说,为了创建不同的产品对象,客户端就得使用不同的具体工厂。

  • 重回上面的优点:

- 易于交换产品的系列
由于具体工厂类,DBFactory factory = new SqlServerFactory();只需要初始化一次,就可以很轻易地改变具体工厂,只需要改变具体工厂就可以使用不同的产品配置。
- 让具体的创建实例过程和客户端分离
它让具体的创建实例过程和客户端分离, 客户端是通过它们的抽象接口操纵实例的,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。比如上面的例子,客户端所认识的只有IUser和IDept,至于是SqlServer还是Access实现的就不知道了。

  • 缺点:
    如果要新加一个Project产品,那么得加IProject、SqlServerProject、AccessProject,还要更改DBFactory、SqlServerFactory、AccessFactory。
    如果现在不止一个客户端在使用IUser和IDept,那么在每一个客户端类都得加上 DBFactory factory = new SqlServerFactory();,如果此时要改成Access,那么就得改多个地方,这是十分麻烦的。那么我们引进:用简单工厂改进抽象工厂

用简单工厂改进抽象工厂

去除DBFactory、SqlServerFactory、AccessFactory,取而代之的是DataFactory
由于db实现设置好,所以可以根据选择来实例化不同的对象

public class DataFactory {
    public static String db = "Sqlserver";
    //通过切换数据库名称,更换为Access
    //public static String db = "Access";

    public static IUser createUser(){
        IUser user = null;
        switch (db){
            case "Sqlserver":
                user = new SqlserverUser();
                break;
            case "Access":
                user = new AccessUser();
                break;
        }
        return user;
    }

    public static IDept createDept(){
        IDept dept = null;
        switch (db){
            case "Sqlserver":
                dept = new SqlServerDept();
                break;
            case "Access":
                dept = new AccessDept();
                break;
        }
        return dept;
    }
}

客户端代码:

public class Demo {
    public static void main(String[] args) {
        User user = new User();
        //直接得到实际的数据库访问实例,不存在任何依赖
        DataFactory factory = new DataFactory();
        IUser iu = factory.createUser();
        iu.insert(user);
        iu.select(1);

        Dept dept = new Dept();
        IDept idept = factory.createDept();
        idept.create(dept);
        idept.select(1);
    }
}

由于提前设置了db的值(Sqlserver和Access),所以简单工厂的方法不需要设置参数,这样客户端就只需要 factory.createUser()和factory.createDept()来生成具体的数据库访问类实例,客户端没有出现任何SqlServer和Access字样,达到了解耦的目的

但是这样做的缺点是,如果我要加一个oracle数据库,那么我得去DataFactory 的switch中多加一个case。接下来我们用反射+抽象工厂来设置

反射+抽象工厂

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值