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


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

    创建型模式:

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

    一:单例模式

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

    比如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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

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

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

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

    class SingletonTest2 {
        private static SingletonTest2 instance;
    
        static {
            instance = new SingletonTest2();
        }
    
        private SingletonTest2() {}
    
        public static SingletonTest2 getInstance() {
            return instance;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    模式分析:

    • 将对象的创建和对象本身业务处理分离可以降低系统的耦合度,使得两者修改起来都相对容易。
    • 在调用工厂类的工厂方法时,由于工厂方法是静态方法,使用起来很方便,可通过类名直接调用,而且只需要传入一个简单的参数即可,在实际开发中,还可以在调用时将所传入的参数保存在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 "拿铁咖啡";
        }
    }
    
    // 咖啡工厂接口
    interface CoffeeFactory2 {
        Coffee2 createCoffee();
    }
    
    
    // 美式咖啡工厂
    class AmericanCoffeeFactory2 implements CoffeeFactory2 {
        @Override
        public Coffee2 createCoffee() {
            return new AmericanCoffee2();
        }
    }
    
    // 拿铁咖啡工厂
    class LatteCoffeeFactory2 implements CoffeeFactory2 {
        @Override
        public Coffee2 createCoffee() {
            return new LatteCoffee2();
        }
    }
    
    // 咖啡店
    class CoffeeStore2 {
        private CoffeeFactory2 factory;
    
        public void setFactory(CoffeeFactory2 factory) {
            this.factory = factory;
        }
    
        public Coffee2 orderCoffee() {
            Coffee2 coffee = factory.createCoffee();
            coffee.addMilk();
            coffee.addSugar();
            return coffee;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    工厂方法模式的优点:

    • 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程;
    • 在系统增加新的产品时只需要添加具体产品类和对应的具体工厂类,无须对原工厂进行任何修改,满足开闭原则;

    工厂方法模式的缺点:

    • 每增加一个产品就要增加一个具体产品类和一个对应的具体工厂类,这增加了系统的复杂度。

    工厂方法模式适用情况包括:

    • 一个类不知道它所需要的对象的类
    • 一个类通过其子类来指定创建哪个对象

    2.3:抽象工厂模式

    抽象工厂模式是一种为访问类提供一个创建一组相关或相互依赖对象的接口,且访问类无须指定所要产品的具体类就能得到同族的不同等级的产品的模式结构。抽象工厂模式是工厂方法模式的升级版本,工厂方法模式只生产一个等级的产品,而抽象工厂模式可生产多个等级的产品。

    抽象工厂模式的主要角色如下:

    • 抽象工厂:提供了创建产品的接口,它包含多个创建产品的方法,可以创建多个不同等级的产品
    • 具体工厂:主要是实现抽象工厂中的多个抽象方法,完成具体产品的创建
    • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能,抽象工厂有多个抽象产品
    • 具体产品:实现了抽象产品角色所定义的接口,由具体工厂来创建,它同具体工厂之间是多对一的关系。

    此时咖啡店不只是生产咖啡,还生产甜点,使用抽象工厂方法模式改进上述案例:

    public class AbstractFactory {
        public static void main(String[] args) {
            AmericanProductFactory americanProductFactory = new AmericanProductFactory();
    
            Coffee3 coffee = americanProductFactory.createCoffee();
            System.out.println(coffee.getName());
    
            Dessert3 dessert = americanProductFactory.createDessert();
            dessert.show();
        }
    }
    
    // 咖啡抽象类
    abstract class Coffee3 {
        public abstract String getName();
    }
    
    // 美式咖啡
    class AmericanCoffee3 extends Coffee3 {
        @Override
        public String getName() {
            return "美式咖啡";
        }
    }
    
    // 拿铁咖啡
    class LatteCoffee3 extends Coffee3 {
        @Override
        public String getName() {
            return "拿铁咖啡";
        }
    }
    
    // 甜品抽象类
    abstract class Dessert3 {
        public abstract void show();
    }
    
    // 提拉米苏
    class Trimisu3 extends Dessert3 {
        @Override
        public void show() {
            System.out.println("提拉米苏");
        }
    }
    
    // 抹茶慕斯
    class MatchMousse3 extends Dessert3 {
        @Override
        public void show() {
            System.out.println("抹茶慕斯");
        }
    }
    
    // 产品工厂接口
    interface ProductFactory {
        Coffee3 createCoffee();
    
        Dessert3 createDessert();
    }
    
    // 美式产品工厂
    class AmericanProductFactory implements ProductFactory {
        @Override
        public Coffee3 createCoffee() {
            return new AmericanCoffee3();
        }
    
        @Override
        public Dessert3 createDessert() {
            return new MatchMousse3();
        }
    }
    
    // 意大利风味产品工厂
    class ItalyProductFactory implements ProductFactory {
        @Override
        public Coffee3 createCoffee() {
            return new LatteCoffee3();
        }
    
        @Override
        public Dessert3 createDessert() {
            return new Trimisu3();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    可以看到,如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

    优点:

    • 抽象工厂模式隔离了具体类的生成,使得客户并不需要知道什么被创建。由于这种隔离,更换一个具体工厂就变得相对容易。所有的具体工厂都实现了抽象工厂中定义的那些公共接口,因此只需改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。另外,应用抽象工厂模式可以实现高内聚低耦合的设计目的,因此抽象工厂模式得到了广泛的应用
    • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。这对一些需要根据当前环境来决定其行为的软件系统来说,是一种非常实用的设计模式。
    • 增加新的具体工厂和产品族很方便,无需修改已有系统,符合“开闭原则”。

    缺点:

    • 在添加新的产品对象时,难以扩展抽象工厂以便生产新种类的产品,这是因为在抽象工厂角色中规定了所有可能被创建的产品集合,要支持新种类的产品就意味着要对该接口进行扩展,而这将涉及到对抽象工厂角色及其所有子类的修改,显然会带来较大的不便
    • 开闭原则的倾斜性(增加新的工厂和产品族容易,增加新的产品等级结构麻烦)

    使用场景:

    • 当需要创建的对象是一系列相互关联或相互依赖的产品族时,如电器工厂中的电视机、洗衣机、空调等。
    • 系统中有多个产品族,但每次只使用其中的某一族产品。如有人只喜欢穿某一个品牌的衣服和鞋。
    • 系统中提供了产品的类库,且所有产品的接口相同,客户端不依赖产品实例的创建细节和内部结构。

    如:

    • 输入法换皮肤,一整套一起换。
    • 生成不同操作系统的程序

    三:原型模式

    原型模式,即用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型对象相同的新对象。

    原型模式包含如下角色:

    • 抽象原型类:规定了具体原型对象必须实现的clone方法
    • 具体原型类:实现抽象原型类的clone方法,它是可被复制的对象
    • 访问类:使用具体原型类中的clone方法来复制新的对象

    原型模式的克隆分为浅克隆和深克隆:

    • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
    • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址

    Java中的Object类中提供了clone方法来实现浅克隆。Cloneable接口是上面的类图中的抽象原型类,而实现了cloneable接口的子实现类就是具体的原型类。代码如下:

    public class Prototype1 {
        public static void main(String[] args) {
            RealizeType1 realizeType = new RealizeType1();
            try {
                RealizeType1 clone = realizeType.clone();
                System.out.println(realizeType == clone);  // false
            } catch (CloneNotSupportedException e) {
                System.out.println("克隆失败!");
            }
        }
    }
    
    class RealizeType1 implements Cloneable {
        public RealizeType1() {
            System.out.println("构造方法被调用");
        }
    
        @Override
        public RealizeType1 clone() throws CloneNotSupportedException {
            System.out.println("具体原型复制成功");
            return (RealizeType1) super.clone();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    从运行结果可以看出,克隆出来的对象并非原对象。值得注意的是,构造方法只执行了一次,可见clone方法底层并不是通过new创建对象的。

    在这里插入图片描述

    以下是原型模式的使用案例。用原型模式生成“三好学生奖状”:

    public class Prototype2 {
        public static void main(String[] args) {
            Citation citation = new Citation();
            try {
                Citation clone = citation.clone();
    
                citation.setName("张三");
                citation.show();
    
                clone.setName("李四");
                clone.show();
            } catch (CloneNotSupportedException e) {
                System.out.println("克隆出错!");
            }
        }
    }
    
    class Citation implements Cloneable {
        private String name;
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void show() {
            System.out.println(name + "同学荣获三好学生!");
        }
    
        @Override
        public Citation clone() throws CloneNotSupportedException {
            return (Citation) super.clone();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    原型模式的使用场景:

    • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗。
    • 通过new获得一个对象需要非常繁琐的数据准备或访问权限时,可以使用原型模式。
    • 一个对象需要提供给其他对象访问,而且各个调用者可能需要修改其值,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。

    以下是深拷贝代码:

    public class Prototype3 {
        public static void main(String[] args) throws CloneNotSupportedException {
            Student2 student1 = new Student2();
            student1.setName("张三");
    
            Citation2 citation1 = new Citation2();
            citation1.setStudent(student1);
    
            Citation2 citation2 = citation1.clone();
            citation2.getStudent().setName("李四");
    
            citation1.show();
            citation2.show();
        }
    }
    
    class Citation2 implements Cloneable {
        private Student2 student;
    
        public Student2 getStudent() {
            return student;
        }
    
        public void setStudent(Student2 student) {
            this.student = student;
        }
    
        public void show() {
            System.out.println(student.getName() + "同学荣获三好学生!");
        }
    
        @Override
        public Citation2 clone() throws CloneNotSupportedException {
            Citation2 clone = (Citation2) super.clone();
            clone.setStudent(new Student2());
            return clone;
        }
    }
    
    class Student2 {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    当然一般还是通过对象流进行深拷贝的,代码如下:

    public class Prototype4 {
        public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
            Student3 student1 = new Student3();
            student1.setName("张三");
    
            Citation3 citation1 = new Citation3();
            citation1.setStudent(student1);
    
            // 创建对象输出流对象
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("C:\\Users\\eyes\\Desktop\\test.txt"));
            // 写对象
            objectOutputStream.writeObject(citation1);
            // 释放资源
            objectOutputStream.close();
    
            // 创建对象输入流对象
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("C:\\Users\\eyes\\Desktop\\test.txt"));
            // 读取对象
            Citation3 citation2 = (Citation3) objectInputStream.readObject();
            // 释放资源
            objectInputStream.close();
    
            citation2.getStudent().setName("李四");
            citation1.show();
            citation2.show();
        }
    }
    
    class Citation3 implements Cloneable, Serializable {
        private Student3 student;
    
        public Student3 getStudent() {
            return student;
        }
    
        public void setStudent(Student3 student) {
            this.student = student;
        }
    
        public void show() {
            System.out.println(student.getName() + "同学荣获三好学生!");
        }
    
        @Override
        public Citation3 clone() throws CloneNotSupportedException {
            return (Citation3) super.clone();
        }
    }
    
    class Student3 implements Serializable {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60

    四:建造者模式

    在软件系统中,有时面临着“一个复杂对象”的创建工作,该复杂对象通常由各个部分的子对象用一定的算法构成。这个复杂对象的各个部分经常面临着剧烈变化,但是将它们组合在一起的算法却相对稳定,那如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?这时候建造者模式就出现了。

    建造者模式概述:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示

    • 分离了部件的构造(由Builder来负责)和装配(由pirector负责)。从而可以构造出复杂的对象。这个模式适用于某个对象的构建过程复杂的情况。
    • 由于实现了构建和装配的解耦。不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法、装配算法的解耦,实现了更好的复用。
    • 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

    在这里插入图片描述

    建造者模式包含如下角色:

    • 抽象建造者类:这个接口规定要实现复杂对象的那些部分的创建,并不涉及具体的对象部件的创建。
    • 具体建造者类:实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
    • 产品类:要创建的复杂对象
    • 指挥者类:调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品信息,只负责保证对象各部分完整创建或按某种顺序创建

    以下是代码实例:

    public class BuilderDemo1 {
        public static void main(String[] args) {
            // 创建指挥者对象
            Director1 director = new Director1(new MobileBuilder1());
            // 让指挥者组装自行车
            Bike1 bike = director.construct();
    
            System.out.println(bike.getFrame());
            System.out.println(bike.getSeat());
        }
    }
    
    class Bike1 {
        private String frame;
        private String seat;
    
        public String getFrame() {
            return frame;
        }
    
        public void setFrame(String frame) {
            this.frame = frame;
        }
    
        public String getSeat() {
            return seat;
        }
    
        public void setSeat(String seat) {
            this.seat = seat;
        }
    }
    
    abstract class Builder1 {
        protected Bike1 bike = new Bike1();
    
        public abstract void buildFrame();
    
        public abstract void buildSeat();
    
        public abstract Bike1 createBike();
    }
    
    class MobileBuilder1 extends Builder1 {
    
        @Override
        public void buildFrame() {
            bike.setFrame("碳纤维车架");
        }
    
        @Override
        public void buildSeat() {
            bike.setSeat("真皮车座");
        }
    
        @Override
        public Bike1 createBike() {
            return bike;
        }
    }
    
    class OfoBuilder1 extends Builder1 {
    
        @Override
        public void buildFrame() {
            bike.setFrame("铝合金车架");
        }
    
        @Override
        public void buildSeat() {
            bike.setSeat("橡胶车座");
        }
    
        @Override
        public Bike1 createBike() {
            return bike;
        }
    }
    
    class Director1 {
        private Builder1 builder;
    
        public Director1(Builder1 builder) {
            this.builder = builder;
        }
    
        public Bike1 construct() {
            builder.buildFrame();
            builder.buildSeat();
            return builder.createBike();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    建造者模式的优点:

    • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
    • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
    • 可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
    • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。

    建造者模式的缺点:

    • 创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。

    建造者模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

    • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
    • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。
    • 构造过程允许被构造的对象有不同的表示
  • 相关阅读:
    Apache HTTP服务器配置syslog
    Springboot考研教室管理系统 毕业设计-附源码221757
    SpringCloud微服务之SpringCloud Gateway实现服务网关
    【Rust日报】2023-10-02 改进 Rust 宏中的自动完成功能
    关于input的一些事件和属性
    Vue3 + ts +vite + elementplus
    只问耕耘,不问收获,其实收获却在耕耘中
    文献认证!Kamiya艾美捷抗酒石酸酸性磷酸酶TRAP染色试剂盒
    没想到还有这种骚操作~如何使用Golang实现无头浏览器截图?
    详解C++静态多态和动态多态的区别
  • 原文地址:https://blog.csdn.net/tongkongyu/article/details/127189018