设计模式:单例模式和工厂模式

一、单例模式

单例模式是一种常用的软件设计模式,定义是单例对象的类只能允许一个实例存在,该类负责创建自己的对象,同时确保只有单个对象被创建,相当于整个系统只需要拥有一个全局对象,这样有利于协调整个系统整体的行为,比如服务器中的某个配置文件信息存放在一个文件中,这些配置信息由一个单例对象统一获取,然后其他服务进程中的其他对象再通过这个单例对象获取这些配置信息,简化了在复杂环境下的配置管理。

Singleton类定义一个GetInstance操作,允许客户访问类的唯一实例,GetInstance是一个静态方法,主要负责创建自己唯一实例。

单例模式主要分为:饿汉单例模式、懒汉单例模式、双锁单例模式

应用场景

比如一个网站的计数器,一般采用单例模式实现,如果网站存在多个计数器,,每个用户的访问都刷新一次计数器的值,这样的话计数器的值是难以同步的,但单例模式就不会存在这类问题,而且还可以避免线程安全的问题。而在多线程的线程池中的设计一般也是采用的单例模式,这是因为线程池需要方便对线程池的池中的线程进行同样的控制,比如一些应用程序的日志应用,或者web中开发中读取配置文件都适合使用单例模式,例如httpApplication就是单例的典型应用。

适用场景:
1:需要生成唯一序列的环境

2:需要频繁实例化然后销毁的对象

3:创建对象时耗时过多或者消耗资源过多的,但又经常使用到的对象

4:方便资源互相通信的环境

具体实现

饿汉单例模式

当加载类的时候就实例化,且只加载一次,比较浪费空间,不用加synchronized锁,效率更高,在类被加载时,就会实例化一个对象并且交给自己引用,在类装载的时候就完成了实例化,避免了线程同步问题,且这个类在整个生命周期只会被加载一次,因此只会创建一个实例。

public class Singleton {
    /**
     * 饿汉单例模式
     */
    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

懒汉单例模式

单例模式被延迟加载,也就是在使用时再实例化,节省空间,但这样实现只能在单线程下,多线程下会出现,在一个线程进入了if(singleton == null)判断语句块时,还没来得及往下执行就又有另一个线程也通过了这个判断语句,这时会产生多个实例,因此需要加上synchronized锁保证线程安全。

public class Singleton {
    /**
     * 懒汉单例模式
     */
    private static Singleton instance;
    
    private Singleton (){}
    
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

双锁单例模式

基于上述懒汉单例模式进行修改,进行了两次判断,第一次是为了避免不要的实例,第二次是为了进行同步,避免多线程问题。由于singleton = new Singleton()对象的创建在JVM中可能会进行重排序,在多线程下访问存在一定的风险,使用volatile修饰实例变量有效,解决该问题。

public class Singleton {
    /**
     * 双锁单例模式
     */
    private volatile static Singleton instance;

    private Singleton (){}

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

volatile关键字的作用主要有如下两个:

1.线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

2.顺序一致性:禁止指令重排序

3.volatile是变量在多线程之间的可见性,因此只能修饰变量。

单例模式优缺点

优点:

  1. 在内存中只有一个对象,节省内存空间。
  2. 避免频繁的创建/销毁对象,可以提供性能。
  3. 避免对共享资源的多重占用,简化访问。
  4. 为整个系统提供一个安全的全局访问点。

缺点:

  1. 不适用变化频繁的对象。
  2. 使用单例模式的一些问题,比如数据库连接池对象设计单例模式,可能会导致共享连接池对象的程序过多而导致出现连接池溢出。
  3. 如果实例化后的对象长时间不被利用,系统会认为该对象是垃圾而被回收,这可能会导致对象的状态丢失。

要想实现效率高的线程安全的单例,需要注意尽量减少同步块的作用域和尽量使用细粒度的锁。


二、工厂模式

工厂模式是创建型模式的一种,提供了一种创建对象的最佳方式,就是在创建一个对象时,需要把容易发生变化的地方给封装起来,来控制变化(哪里变化,封装哪里),以适应客户的变动,项目的扩展,是一种代替new操作的一种模式。

在工厂模式中,我们创建对象不会对客户端暴露创建逻辑,而是通过一个共同的接口来指向创建的新对象,比如我们创建一个工厂类,然后当创建者想船舰一个对象的时候只需要告诉工厂类即可,由工厂类去创建对象,调用者不需要知道如何创建的,也不用自己去创建。

工厂模式主要分为:简单工厂模式、工厂方法模式、抽象工厂模式,主要功能都是将对象的实例化部分抽离出来,目的是降低系统中代码耦合度,增强了系统拓展性。


简单工厂模式

简单工厂模式,顾名思义,就是一个实现比较简单的方式。简单工厂模式只是将不同对象的创建操作进行了 一层简单的封装,其实也就是把不同对象的创建操作逻辑全部单独放到一个类中,这个类就成为了简单工厂类,但我们需要一个对象的时候,只需要告诉这个简单工厂类,然后由这个简单工厂类根据我们的需要去创建对应的对象即可。

摘抄网上创建电脑的例子.

具体实现:

实现一个电脑工厂,由电脑工厂来根据需求生成不同品牌的电脑。

1、创建一个电脑抽象类

public abstract class Computer {
    public abstract void use();
}

2、创建继承了电脑抽象类的,具体不同品牌的电脑的实体类(也就是不同的对象,但是都是继承或实现了一个抽象类或者接口类)

需要注意的是被创建的实例通常都具有共同的父类。

public class HaseeComputer extends Computer {
    public HaseeComputer() {
        System.out.println("生产了神舟电脑");
    }

    @Override
    public void use() {
        System.out.println("使用了神舟电脑");
    }
}
public class LenovoComputer extends Computer {
    public LenovoComputer() {
        System.out.println("生产了联想电脑");
    }
    @Override
    public void use() {
        System.out.println("使用了联想电脑");
    }
}

3、创建一个电脑工厂(简单工厂类)

public class ComputerFactory {
    public static Computer produceComputer(String computerBrand) {
        if (computerBrand == null) {
            return null;
        }
        if (computerBrand.equalsIgnoreCase("Hasee")) {
            return new HaseeComputer();
        }else if (computerBrand.equalsIgnoreCase("Lenovo")) {
            return new LenovoComputer();
        }
        return null;
    }
}

4、使用工厂创建不同品牌的电脑并使用(实现由工厂创建对象)

public class FactoryTest {
    public static void main(String[] args) {
        ComputerFactory computerFactory = new ComputerFactory();
        Computer haseeComputer = computerFactory.produceComputer("Hasee");
        haseeComputer.use();
        Computer  lenovoComputer = computerFactory.produceComputer("Lenovo");
        lenovoComputer.use();
    }
}

简单工厂可以使动态生成使用者所需的类对象,但是其拓展性差,违背了开闭原则,当需要新增新的电脑品牌产品的时候,就需要修改工厂类。

开闭原则:软件应该实现对拓展开放,对修改关闭。

工厂方法模式

在上述简单工厂方法中,我们只有一个工厂类,由这个工厂类来负责创建动态的创建我们需要的对象,而在工厂方法模式中,我们的工厂类下面还有很多的子工厂类,我们需要的对象由这些子工厂来创建,也就是在简单工厂的模式下进行了改进,因为当我们需要一个新产品的时候,只需要扩展一个新的子工厂类即可,这样就符合了开闭原则。

具体实现

同样以上述电脑为例子,实现一个电脑工厂,再根据不同的品牌实现电脑工厂子工厂类,也就是每一个品牌都有各自的字电脑工厂,再由电脑工厂子类根据需求生成不同品牌的电脑。

1、创建一个电脑抽象类

public abstract class Computer {
    public abstract void use();
}

2、创建继承了电脑抽象类的,具体的不同品牌的电脑的实体类

public class LenovoComputer extends Computer {
    public LenovoComputer() {
        System.out.println("生产了联想电脑");
    }
    @Override
    public void use() {
        System.out.println("使用了联想电脑");
    }
}
public class HaseeComputer extends Computer {
    public HaseeComputer() {
        System.out.println("生产了神舟电脑");
    }
    @Override
    public void use() {
        System.out.println("使用了神舟电脑");
    }
}

3、创建一个电脑抽象工厂类(父工厂)

public abstract class ComputerFactory {
    public abstract Computer produce();
}

4、创建电脑抽象工厂类的子类(子工厂,根据不同的电脑品牌)

public class LenovoFactory extends ComputerFactory {
    @Override
    public Computer produce() {
        return new LenovoComputer();
    }
}
public class HaseeFactory extends ComputerFactory {
    @Override
    public Computer produce() {
        return new HaseeComputer();
    }
}

5、使用不同的子工厂生成不同品牌电脑并使用

创建不同的子工厂,通过不同的子工厂类创建不同的电脑品牌对象。

可以理解成,先选择子工厂,再创建电脑,这样在新增新的电脑品牌的时候就只需要新建一个新的电脑品牌子工厂类继承父工厂类即可达到扩展的目的。

public class FactoryTest {
    public static void main(String[] args) {
        ComputerFactory haseeFactory = new HaseeFactory();
        Computer haseeComputer = haseeFactory.produce();
        haseeComputer.use();

        ComputerFactory lenovoFactory = new LenovoFactory();
        Computer lenovoComputer = lenovoFactory.produce();
        lenovoComputer.use();
    }
}

工厂方法模式扩展性好,符合开闭原则,每个工厂只需要负责一种产品,而不是由一个工厂去生成所有产品(同一个品牌还可以有不同的产品,比如台式电脑,笔记本等)

抽象工厂模式

抽象工厂模式是基于上述工厂方法模式的基础上的升级版,工厂方法模式针对某一种产品,而抽象工厂模式可以针对多种不同的产品,也就是工厂方法模式解决的是生成不同品牌的同一类型的电脑。而抽象工厂模式解决的是生成不同品牌的多种类型的电脑(台式、笔记本等)

具体实现

依旧使用上述电脑例子,实现一个电脑工厂,再根据不同的品牌实现电脑工厂子类,由电脑工厂子类根据需求生成不同品牌的不同种类的电脑。

按照不同的电脑类型区分生产

1、创建各种类型的电脑抽象类(将不同电脑类型[产品]分离出来成不同的独立的电脑类型抽象类)

// 台式电脑抽象类
public abstract class DesktopComputer {
    public abstract void use();
}
// 笔记本电脑抽象类
public abstract class NotebookComputer {
    public abstract void use();
}

2、创建具体的不同品牌的各种类型的电脑实体类(不同的品牌的不同的产品)

public class HaseeDesktopComputer extends DesktopComputer {
    public HaseeDesktopComputer() {
        System.out.println("生产了神舟台式电脑");
    }
    @Override
    public void use() {
        System.out.println("使用了神舟台式电脑");
    }
}
public class HaseeNotebookComputer extends NotebookComputer {
    public HaseeNotebookComputer() {
        System.out.println("生产了神舟笔记本电脑");
    }
    @Override
    public void use() {
        System.out.println("使用了神舟笔记本电脑");
    }
}
public class LenovoDesktopComputer extends DesktopComputer {
    public LenovoDesktopComputer() {
        System.out.println("生产了联想台式电脑");
    }
    @Override
    public void use() {
        System.out.println("使用了联想台式电脑");
    }
}
public class LenovoNotebookComputer extends NotebookComputer {
    public LenovoNotebookComputer() {
        System.out.println("生产了联想笔记本电脑");
    }
    @Override
    public void use() {
        System.out.println("使用了联想笔记本电脑");
    }
}

3、创建一个电脑的抽象工厂类(父工厂)

public abstract class ComputerFactory {
    public abstract DesktopComputer produceDesktopComputer(); // 生产台式电脑
    public abstract NotebookComputer produceNotebookComputer(); // 生产笔记本电脑
}

4、创建电脑抽象工厂类的子类(根据不同的电脑品牌)

public class LenovoFactory extends ComputerFactory {
    @Override
    public DesktopComputer produceDesktopComputer() {
        return new LenovoDesktopComputer();
    }

    @Override
    public NotebookComputer produceNotebookComputer() {
        return new LenovoNotebookComputer();
    }
}
public class HaseeFactory extends ComputerFactory {
    @Override
    public DesktopComputer produceDesktopComputer() {
        return new HaseeDesktopComputer();
    }

    @Override
    public NotebookComputer produceNotebookComputer() {
        return new HaseeNotebookComputer();
    }
}

5、使用不同子工厂创建不同品牌的不同种类的电脑

按照不同类型产品分离工厂,创建不同的品牌工厂子类对象来执行获取生产不同类型产品的方法获得对象。

public class TestFactory {
    public static void main(String[] args) {
        ComputerFactory haseeFactory = new HaseeFactory();
        DesktopComputer haseeDesktopComputer = haseeFactory.produceDesktopComputer();
        haseeDesktopComputer.use();
        NotebookComputer haseeNotebookComputer = haseeFactory.produceNotebookComputer();
        haseeNotebookComputer.use();

        ComputerFactory lenovoFactory = new LenovoFactory();
        DesktopComputer lenovoDesktopComputer = lenovoFactory.produceDesktopComputer();
        lenovoDesktopComputer.use();
        NotebookComputer lenocoFactory  = lenovoFactory.produceNotebookComputer();
        lenocoFactory.use();

    }
}

抽象工厂方法既满足了创建多个类型的产品,也满足了多个品牌的生产,当打算新增一个品牌的时候,创建一个品牌的子类工厂继承父工厂类,当想新增一个类型的产品的时候,比如pad的时候,在父类工厂新增一个品牌的创建即可。

三种工厂模式的优缺点

优点:

简单工厂模式:工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅"消费"产品。简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。                 

 

工厂方法模式:工厂方法模式是为了克服简单工厂模式的缺点(主要是为了满足开闭原则)而设计出来的。简单工厂模式的工厂类随着产品类的增加需要增加很多方法(或代码),而工厂方法模式每个具体工厂类只完成单一任务,代码简洁。工厂方法模式完全满足开闭原则,即它有非常良好的扩展性。        

           

抽象工厂模式:抽象工厂模式分离了具体的类,可以帮助我们控制一个应用创建的对象的类。抽象工厂模式有助于这样的团队的分工,降低了模块间的耦合性,提高了团队开发效率。                     

缺点:

简单工厂模式:因为工厂类集中了所有产品创建逻辑,一旦增加产品或者删除产品,整个系统都要受到影响。系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,有可能造成工厂逻辑过于复杂,违背了"开放--封闭"原则(OCP).另外,简单工厂模式通常使用静态工厂方法,这使得无法由子类继承,造成工厂角色无法形成基于继承的等级结构。

                

工厂方法模式:假如某个具体产品类需要进行一定的修改,很可能需要修改对应的工厂类。当同时需要修改多个产品类的时候,对工厂类的修改会变得相当麻烦。比如说,每增加一个产品,相应的也要增加一个子工厂,会加大了额外的开发量。    

             

抽象工厂模式:抽象工厂模式在于难于应付“新对象”的需求变动。难以支持新种类的产品。难以扩展抽象工厂以生产新种类的产品。这是因为抽象工厂几乎确定了可以被创建的产品集合,支持新种类的产品就需要扩展该工厂接口,这将涉及抽象工厂类及其所有子类的改变。

适用范围:

简单工厂模式:工厂类负责创建的对象比较少,客户只知道传入了工厂类的参数,对于始何创建对象(逻辑)不关心。  

                  

工厂方法模式:当一个类不知道它所必须创建对象的类或一个类希望由子类来指定它所创建的对象时,当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候,可以使用工厂方法,支持多扩展少修改的OCP原则。      

              

抽象工厂模式:一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有形态的工厂模式都是重要的。这个系统有多于一个的产品族,而系统只消费其中某一产品族。同属于同一个产品族的产品是在一起使用的,这一约束必须在系统的设计中体现出来。系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于实现。            

    

无论是简单工厂模式、工厂模式还是抽象工厂模式,它们本质上都是将不变的部分提取出来,将可变的部分留作接口,以达到最大程度上的复用。


三、单例模式与简单工厂模式结合应用

在实际应用中,还可以通过结合工厂模式与单例模式来对对象的创建进行管理和节省对象创建的开销。

这里引用一个例子,我们要实现一个数据生成器的工厂,生成数据的方式有很多种,每一种方式都对应着不同的生成器,在以下实现中,不同的数据生成器都实现了DataGenerator接口类,在数据生成器工厂中,用一个map按照不同的类型来保存不同的数据生成器对象,使用者可以按需获取不同的数据生成器实例。

public interface DataGenerator {

    /**
     * 生成
     *
     * @param field 字段信息
     * @param rowNum 行数
     * @return 生成的数据列表
     */
    List<String> doGenerate(Field field, int rowNum);

}
/**
 * 数据生成器工厂
 */
public class DataGeneratorFactory {

    /**
     * 模拟类型 => 生成器映射
     */
    private static final Map<MockTypeEnum, DataGenerator> mockTypeDataGeneratorMap = new HashMap<MockTypeEnum, DataGenerator>() {{
        put(MockTypeEnum.NONE, new DefaultDataGenerator());
        put(MockTypeEnum.FIXED, new FixedDataGenerator());
        put(MockTypeEnum.RANDOM, new RandomDataGenerator());
        put(MockTypeEnum.RULE, new RuleDataGenerator());
        put(MockTypeEnum.DICT, new DictDataGenerator());
        put(MockTypeEnum.INCREASE, new IncreaseDataGenerator());
    }};

    private DataGeneratorFactory() {
    }

    /**
     * 获取实例
     *
     * @param mockTypeEnum
     * @return
     */
    public static DataGenerator getGenerator(MockTypeEnum mockTypeEnum) {
        mockTypeEnum = Optional.ofNullable(mockTypeEnum).orElse(MockTypeEnum.NONE); // 默认不模拟
        return mockTypeDataGeneratorMap.get(mockTypeEnum); // 返回不同的数据生成器对象
    }
}

我们也可以换一种方式来实现,比如实现一个SQL方言工厂

public interface SQLDialect {

    String wrapFieldName(String name);

    String parseFieldName(String fieldName);

    String wrapTableName(String name);

    String parseTableName(String tableName);
}
public class SQLDialectFactory {

    /**
     * className => 方言实例映射
     */
    private static final Map<String, SQLDialect> DIALECT_POOL = new ConcurrentHashMap<>(); // 线程安全

    private SQLDialectFactory() {
    }

    /**
     * 获取方言实例
     *
     * @param className 类名
     * @return
     */
    public static SQLDialect getDialect(String className) {
        SQLDialect dialect = DIALECT_POOL.get(className); // 获取SQL方言实例
        if (null == dialect) {
            // String.intern():返回字符串在字符串常量池中的引用,没有则将字符串放入常量池中再返回引用(地址)
            // synchronized():加同步锁,有没有intern是两种不同的效果 ==> 可以看成==与eq的区别
            synchronized (className.intern()) {
                // computeIfAbsent:判断Map中key为className是否存在,不存在则重新计算映射一个key为className的value值 -> 这里指的是SQLDialect实例
                dialect = DIALECT_POOL.computeIfAbsent(className,
                        key -> {
                            try {
                                // Class.forName:获得Class的对象(类的全限定名)
                                return (SQLDialect) Class.forName(className).newInstance();
                            } catch (Exception e) {
                                throw new BusinessException(ErrorCode.SYSTEM_ERROR);
                            }
                        });
            }
        }
        return dialect;
    }
}

...究竟要使用哪种设计模式更适合,这要根据具体的业务需求来决定。

 

 

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sinder_小德

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值