软件设计与体系结构——创建型模式(代码片段)

_瞳孔 _瞳孔     2022-12-06     786

关键词:

如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:瞳孔的个人空间

创建型模式:

  • 创建型模式抽象了实例化过程
  • 帮助系统独立于如何创建、组合和表示对象
    • 一个类创建型模式使用继承改变被实例化的类
    • 类创建型模式使用继承改变被实例化的类
    • 对象创建型模式将实例化委托给另一个对象
  • 系统演化越来越依赖于对象复合而非类继承
    • 从对一组固定行为的硬编码
    • 定义一个较小的基本行为集
    • 可以被组合成任意数目的更复杂的行为
  • 两个不断出现的主旋律
    • 一、将系统使用哪些具体的类的信息封装起来
    • 二、隐藏这些类的实例是如何被创建和放在一起的
  • 创建型模式的灵活性
    • 什么被创建, 谁创建它、怎样被创建、何时创建
    • 允许用结构和功能差别很大的“产品”对象配置一个系统
  • 符合单一职责原则
    • 能够将软件模块中对象的创建和对象的使用分离
    • 为使软件结构更加清晰,外界只需要知道对象共同的接口
    • 无需清楚其具体的实现细节

一:单例模式

所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

比如Hibernate的SessionFactory,它充当数据存储源的代理,并负责创建Session对象。SessionFactory并不是轻量级的,一般情况下,一个项目通常只需要一个SessionFactory就够,这是就会使用到单例模式。

1.1:八种单例模式

单例模式有八种:

  • 饿汉式
    • 静态常量
    • 静态代码块
  • 懒汉式
    • 线程不安全
    • 线程安全,同步方法
    • 线程安全,同步代码块
  • 双重检查
  • 静态内部类
  • 枚举

1.1.1:饿汉式——静态常量

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费

这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果

结论:这种单例模式可用,可能造成内存浪费

public class Singleton1 
    public static void main(String[] args) 
        SingletonTest1 instance1 = SingletonTest1.getInstance();
        SingletonTest1 instance2 = SingletonTest1.getInstance();
        System.out.println(instance1 == instance2);  // true
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    


class SingletonTest1 
    // 构造器私有化,不能从外部通过new创建对象
    private SingletonTest1()  

    // 本类内部创建对象实例
    private final static SingletonTest1 instance = new SingletonTest1();

    // 提供一个公有的静态方法,返回实例对象
    public static SingletonTest1 getInstance() 
        return instance;
    

1.1.2:饿汉式——静态代码块

优缺点说明:这种方式和静态常量方式类似,只不过将类实例化过程放在了静态代码快中,也是在类装载的时候,就执行静态代码块中的代码,初始化类的实例。优缺点和静态常量方式一样

结论:这种单例模式可用,可能造成内存浪费

class SingletonTest2 
    private static SingletonTest2 instance;

    static 
        instance = new SingletonTest2();
    

    private SingletonTest2() 

    public static SingletonTest2 getInstance() 
        return instance;
    

1.1.3:懒汉式——线程不安全

优缺点说明:

  • 起到了Lazy Loading的效果,即使用时才完成实例化。但只能在单线程下使用
  • 如果在多线程下,一个线程进入了if(singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可以使用这种方式

结论:在实际开发中不要使用这种方式

class SingletonTest3 
    private static SingletonTest3 singleton;

    private SingletonTest3() 

    public static SingletonTest3 getInstance() 
        if (singleton == null) 
            singleton = new SingletonTest3();
        
        return singleton;
    

1.1.4:懒汉式——线程安全

优缺点:

  • 解决了线程不安全问题
  • 效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低

结论:在实际开发中,不推荐使用这种方式

class SingletonTest4 
    private static SingletonTest4 singleton;

    private SingletonTest4() 

    // 加入同步代码,解决线程不安全问题
    public static synchronized SingletonTest4 getInstance() 
        if (singleton == null) 
            singleton = new SingletonTest4();
        
        return singleton;
    

1.1.5:懒汉式——同步代码块

这种方式本意是想对第四种实现方式的改进,因为前面同步方法效率太低,改为同步产生实例化的的代码块。但是这种同步并不能起到线程同步的作用。跟第3种实现方式遇到的情形一致,假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例

结论:在实际开发中,不能使用这种方式

class SingletonTest5 
    private static SingletonTest5 singleton;

    private SingletonTest5() 

    public static SingletonTest5 getInstance() 
        if (singleton == null) 
            synchronized(SingletonTest5.class) 
                singleton = new SingletonTest5();
            
        
        return singleton;
    

1.1.6:DoubleCheck

双重检查的概念在多线程开发中常常使用,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。并且实例化代码只用执行一次,后面再次访问时,判断if (singleton == null)直接return实例化对象,也避免反复进行方法同步

结论:线程安全,延迟加载,效率较高。在实际开发中推荐使用这种单例设计模式

class SingletonTest6 
    private static volatile SingletonTest6 singleton;

    private SingletonTest6() 

    public static SingletonTest6 getInstance() 
        if (singleton == null) 
            synchronized(SingletonTest6.class) 
                if (singleton == null) 
                    singleton = new SingletonTest6();
                
            
        
        return singleton;
    

1.1.7:静态内部类

  • 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
  • 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getlnstance方法,才会装载SingletonInstance类,从而完成singleton的实例化。
  • 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

结论:避免了线程不安全,利用静态内部类特点实现延迟加载,效率高,推荐使用。

class SingletonTest7 
    private SingletonTest7() 
    
    private static class SingletonInstance 
        private static final SingletonTest7 INSTANCE = new SingletonTest7();
    

    public static SingletonTest7 getInstance() 
        return SingletonInstance.INSTANCE;
    

1.1.8:枚举方式

这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。这种方式也是Effective Java作者Josh Bloch提倡的方式

结论:推荐使用

public class Singleton8 
    public static void main(String[] args) 
        SingletonTest8 instance1 = SingletonTest8.INSTANCE;
        SingletonTest8 instance2 = SingletonTest8.INSTANCE;

        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    


enum SingletonTest8 
    INSTANCE;

    public void sayOK() 
        System.out.println("ok");
    

1.2:JDK中的单例模式

在JDK中,java.lang.Runtime就是经典的饿汉式单例模式:

public class Runtime 
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() 
        return currentRuntime;
    

    /** Don't let anyone else instantiate this class */
    private Runtime() 

    public void exit(int status) 
        SecurityManager security = System.getSecurityManager();
        if (security != null) 
            security.checkExit(status);
        
        Shutdown.exit(status);
    
    
	// 省略无数代码
	// ...


1.3:单例模式注意事项

单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new

单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)

二:工厂模式

工厂模式:专门负责实例化有共同父类(接口)的类的实例;工厂模式可以动态决定实例化哪一个子类对象,不必事先知道要实例化哪个类;

在Java中,万物皆对象,这些对象都需要创建,如果创建的时候直接new该对象,就会对该对象耦合严重,假如我们要更换对象,所有new对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的;所以说,工厂模式最大的优点就是:解耦。

工厂模式分为以下几种形态:

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

下面这个案例是没有使用工厂设计模式的,大致描述了案例的目的。

public class NoFactory 
    public static void main(String[] args) 
        CoffeeStore store = new CoffeeStore();

        Coffee coffee1 = store.orderCoffee("latte");
        System.out.println(coffee1.getName());

        Coffee coffee2 = store.orderCoffee("american");
        System.out.println(coffee2.getName());

        Coffee coffee3 = store.orderCoffee("瑞幸");
    


// 咖啡抽象类
abstract class Coffee 
    public abstract String getName();

    public void addSugar() 
        System.out.println("加糖");
    

    public void addMilk() 
        System.out.println("加奶");
    


// 美式咖啡
class AmericanCoffee extends Coffee 

    @Override
    public String getName() 
        return "美式咖啡";
    


// 拿铁咖啡
class LatteCoffee extends Coffee 

    @Override
    public String getName() 
        return "拿铁咖啡";
    


// 咖啡店
class CoffeeStore 
    public Coffee orderCoffee(String type) 
        // 声明Coffee类型的变量,根据不同类型创建不同的coffee子类对象
        Coffee coffee = null;
        if("american".equals(type)) 
            coffee = new AmericanCoffee();
         else if("latte".equals(type)) 
            coffee = new LatteCoffee();
         else 
            throw new RuntimeException("暂无此咖啡种类");
        
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    

2.1:简单工厂模式

简单工厂模式不在23种设计模式之内,相较于设计模式,它更像是一种编程习惯。

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

模式动机:考虑一个简单的软件应用场景,一个软件系统可以提供多个外观不同的按钮(如圆形按钮、矩形按钮、菱形按钮等),这些按钮都源自同一个基类,不过在继承基类后不同的子类修改了部分属性从而使得它们可以呈现不同的外观,如果我们希望在使用这些按钮时,不需要知道这些具体按钮类的名字,只需要知道表示该按钮类的一个参数,并提供一个调用方便的方法,把该参数传入方法即可返回一个相应的按钮对象,此时,就可以使用简单工厂模式。

模式结构:

简单工厂包含如下角色:

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现或者继承抽象产品的子类
  • 具体工厂:提供了创建产品的方法,调用者通过该方法来获取产品

使用简单对上述咖啡店代码进行改进:

public class SimpleFactory 
    public static void main(String[] args) 
        CoffeeStore1 store = new CoffeeStore1();

        Coffee1 coffee1 = store.orderCoffee("latte");
        System.out.println(coffee1.getName());

        Coffee1 coffee2 = store.orderCoffee("american");
        System.out.println(coffee2.getName());

        Coffee1 coffee3 = store.orderCoffee("瑞幸");
    


// 咖啡抽象类
abstract class Coffee1 
    public abstract String getName();

    public void addSugar() 
        System.out.println("加糖");
    

    public void addMilk() 
        System.out.println("加奶");
    


// 美式咖啡
class AmericanCoffee1 extends Coffee1 

    @Override
    public String getName() 
        return "美式咖啡";
    


// 拿铁咖啡
class LatteCoffee1 extends Coffee1 

    @Override
    public String getName() 
        return "拿铁咖啡";
    


// 咖啡工厂
class CoffeeFactory1 
    public static Coffee1 createCoffee(String type) 
        Coffee1 coffee = null;
        if("american".equals(type)) 
            coffee = new AmericanCoffee1();
         else if("latte".equals(type)) 
            coffee = new LatteCoffee1();
         else 
            throw new RuntimeException("暂无此咖啡种类");
        
        return coffee;
    


// 咖啡店
class CoffeeStore1 
    public Coffee1 orderCoffee(String type) 
        Coffee1 coffee = CoffeeFactory1.createCoffee(type);
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    

模式分析:

  • 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
  • 在调用工厂类的工厂方法时,由于工厂方法是静态方法,使用起来很方便,可通过类名直接调用,而且只需要传入一个简单的参数即可,在实际开发中,还可以在调用时将所传入的参数保存在XML等格式的配置文件中,修改参数时无须修改任何Java源代码。
  • 简单工厂模式最大的问题在于工厂类的职责相对过重,增加新的产品需要修改工厂类的判断逻辑,这一点与开闭原则是相违背的。
  • 简单工厂模式的要点在于:当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。

简单工厂模式的优点:

  • 工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性

简单工厂模式的缺点:

  • 由于工厂类集中了所有产品创建逻辑,一旦不能正常工作,整个系统都要受到影响。
  • 使用简单工厂模式将会增加系统中类的个数,在一定程序上增加了系统的复杂度和理解难度。
  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,违反了开闭原则。在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。

模式适用环境:

  • 工厂类负责创建的对象比较少:由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  • 客户端只知道传入工厂类的参数,对于如何创建对象不关心:客户端既不需要关心创建细节,甚至连类名都不需要记住,只需要知道类型所对应的参数。

2.2:工厂方法模式

工厂方法模式可以完美解决简单工厂模式的问题,工厂方法遵循开闭原则。

工厂方法模式也叫虚拟构造器模式(Virtual Constructor Pattern),或者多态工厂模式(Polymorphic Factory Pattern)。该模式定义一个用于创建对象的接口,让子类决定实例化哪个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类

工厂方法模式的主要角色:

  • 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法来创建产品
  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建
  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间一一对应

使用工厂方法模式改进上述案例:

public class MethodFactory 
    public static void main(String[] args) 
        CoffeeStore2 coffeeStore = new CoffeeStore2();

        coffeeStore.setFactory(new AmericanCoffeeFactory2());
        Coffee2 coffee1 = coffeeStore.orderCoffee();
        System.out.println(coffee1.getName());

        coffeeStore.setFactory(new LatteCoffeeFactory2());
        Coffee2 coffee2 = coffeeStore.orderCoffee();
        System.out.println(coffee2.getName());
    


// 咖啡抽象类
abstract class Coffee2 
    public abstract String getName();

    public void addSugar() 
        System.out.println("加糖");
    

    public void addMilk() 
        System.out.println("加奶");
    


// 美式咖啡
class AmericanCoffee2 extends Coffee2 
    @Override
    public String getName() 
        return "美式咖啡";
    


// 拿铁咖啡
class LatteCoffee2 extends Coffee2 
    @Override
    public String getName() 
        return "拿铁咖啡";
    查看详情  

63.display:none与visibility:hidden的区别?(代码片段)

浅谈设计模式之结构型模式前言通过学习设计模式,我们知道根据目的、用途的不同,把设计模式分为创建型模式、结构型模式、行为型模式。创建型模式主要用于创建对象;结构型模式主要用于处理类和对象的组合;行为型模... 查看详情

java设计模式-创建型设计模式-原型模式(代码片段)

Java设计模式-创建型设计模式-原型模式从这一专栏开始将学习设计模式,上课学习和自己总结归纳的笔记将总结出来供大家参考。参考书籍:《设计模式就该这样学》其他文章:Java设计模式-UML类图Java设计模式-七大... 查看详情

23种设计模式介绍----创建型模式(代码片段)

由于设计模式篇幅比较大,如果在一篇文章讲完所有的设计模式的话不利于阅读。于是我把它分为三篇文章23种设计模式介绍(一)----创建型模式23种设计模式介绍(二)----结构型模式23种设计模式介绍(三)----行为型模式 ... 查看详情

java设计模式之工厂模式(三种工厂模式)(代码片段)

...(CreationalPattern)对类的实例化过程进行了抽象,能够将软件模块中对象的创建和对象的使用分离。为了使软件的结构更加清晰,外界对于这些对象只需要知道它们共同的接口,而不清楚其具体的实现细节,使整个系... 查看详情

设计模式-创建型模式-模板方法(代码片段)

设计模式-创建型模式-模板方法定义一个操作中的算法框架,将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义一个算法的步骤。protected对于本包及其子类可见。类图代码代码如下publicabstractclassAbstractClas... 查看详情

设计模式之创建者模式(代码片段)

...人总结的最佳实践,也是一种态度,一种进步。软件过程里面设计模式一共有23种,一般分为3类。即创建型,结构性型,行为型。其中:****创建型5种:****解决了创建对象的烦恼单例模式,工厂模... 查看详情

设计模式(代码片段)

...是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一... 查看详情

设计模式_创建型模式_单例模式_案例(代码片段)

...自:http://blog.csdn.net/lovelion 作者:刘伟负载均衡器的设计与实现 Sunny公司开发人员通过分析和权衡,决定使用单例模式来设计该负载均衡器,结构图如图3-3所示:在图中,将负载均衡器LoadBalancer设计为单例类,其中包含... 查看详情

设计模式创建型设计模式(代码片段)

设计模式(一)创建型设计模式创建型设计模式:作用于对象的创建,将对象的创建与使用分离一、单例设计模式用途:保证该类在系统之中只有一个实例,用以确保运行逻辑的正确性与使用效率。饿汉单例模式(在没有调用之... 查看详情

设计模式(代码片段)

一、常见的设计模式分类设计模式可以根据创建型、结构型、行为型三种结构进行划分。1.1  创建型创建对象时,不再由我们直接实例化对象;而是根据特定场景,由程序来确定创建对象的方式,从而保证更大的性能、更... 查看详情

设计模式_单例模式(代码片段)

...2.对象模式:对象之间的动态关系,运行在内存之间。3.设计模式(1)单例模式动机:Motivation.保证在系统中,只有一个实例,才能保证逻辑正 查看详情

javascript设计模式读书笔记=;创建型设计模式(代码片段)

全系列目录JavaScript设计模式读书笔记(一)=>创建型设计模式JavaScript设计模式读书笔记(二)=>结构型设计模式JavaScript设计模式读书笔记(三)=>行为型设计模式JavaScript设计模式读书笔记&#... 查看详情

设计模式创建型模式之单例模式(代码片段)

单例模式单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。简单的理解:保证这一个类... 查看详情

设计模式分类

设计模式分类*创建型模式*结构性模式*行为型模式———————————————————————————————————————创建型模式:他们的运行机制基于对象的创建方式将对象创建的细节隔离开来代码与所创... 查看详情

python常用的设计模式(代码片段)

...式5.3.策略模式5.4.模板方法模式1.设计模式设计模式是对软件设计中普遍存在(反复出 查看详情

python之常用设计模式(创建型模式篇)(代码片段)

Python之常用设计模式一、设计模式分类1.创建型模式工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式2.结构型模式适配器模式、桥模式、组合模式、装饰模式、外观模式、享元模式、代理模式3.行为型模式解释器... 查看详情

python之常用设计模式(创建型模式篇)(代码片段)

Python之常用设计模式一、设计模式分类1.创建型模式工厂方法模式、抽象工厂模式、建造者模式、原型模式、单例模式2.结构型模式适配器模式、桥模式、组合模式、装饰模式、外观模式、享元模式、代理模式3.行为型模式解释器... 查看详情