软件设计与体系结构——创建型模式

Posted _瞳孔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了软件设计与体系结构——创建型模式相关的知识,希望对你有一定的参考价值。

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

创建型模式:

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

一:单例模式

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

比如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 "拿铁咖啡";
    创建型设计模式-简单工厂方法设计模式

抽象工厂模式

抽象工厂模式

设计模式之抽象工厂模式

设计模式——创建型模式之抽象工厂模式

软件设计与体系结构——创建型模式