设计模式学习笔记(自用)

设计模式学习笔记(自用)

1. 概述

软件设计模式(Software Design Pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说,它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的普遍性,可以反复使用。

分类:

创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。

  • GoF 书中提供了:单例、原型、工厂方法、抽象工厂、建造者 5 种创建型模式。

结构型模式:用于描述如何将类或对象按某种布局组成更大的结构。

  • GoF 书中提供了:代理、适配器、桥接、装饰、外观、享元、组合 7 种结构型模式。

行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象无法单独完成的任务,以及怎样分配职责。

  • GoF 书中提供了:模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器 11 种行为型模式。

2. UML

统一建模语言 (Unified Modeling Language,UML) 是用来设计软件的可视化建模语言。

它的特点是简单、统一、图形化、能表达软件设计中的动态与静态信息。

定义了:用例图、类图、对象图、状态图、活动图、时序图、协作图、构件图、部署图 9 种图。

2.1 类图

类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及它们与其他类的关系等。

类图不显示暂时性的信息。类图是面向对象建模的主要组成部分。

作用:

  • 在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化了人们对系统的理解。
  • 类图是系统分析和设计阶段的重要产物,是系统编码和测试的重要模型。

表示方法:

在 UML 类图中,类使用包含类名、属性(field)和方法(method)且带有分割线的矩形来表示。

属性/方法名称前的 +- 表示了这个属性/方法的可见性,UML 类图中表示可见性的符号有三种:

  • +:表示 public
  • -:表示 private
  • #:表示 protected

属性的完整表示方式是: 可见性 名称 :类型 [ = 缺省值]

方法的完整表示方式是: 可见性 名称(参数列表) [ : 返回类型]

1,中括号中的内容表示是可选的

2,也有将类型放在变量名前面,返回值类型放在方法名前面

2.2 类与类的关系

关联关系是类与类之间最常用的一种关系,分为一般关联关系、聚合关系和组合关系。

一般关联又可以分为:单向关联,双向关联,自关联。

单向关联

在 UML 类图中,单向关联用一个带箭头的实线表示。

双向关联

双方各自持有对方类型的成员变量。

在 UML 类图中,双向关联用一个不带箭头的直线表示。

自关联

在 UML 类图中,自关联用一个带有箭头且指向自身的线表示,

聚合关系是关联关系的一种,是强关联关系,是整体和部分之间的关系。

聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。

在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。

组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。

在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。

在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。

区分聚合关系与组合关系,它们的主要区别就是:部分能否脱离整体而存在

依赖关系是一种使用关系,它是对象之间耦合度最弱的一种关联方式,是临时性的关联。

在代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成一些职责。

在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。

继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系。

在代码实现时,使用面向对象的继承机制来实现泛化关系。

在 UML 类图中,泛化关系用带空心三角箭头的实线来表示,箭头从子类指向父类。

实现关系是接口与实现类之间的关系。

在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。

在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。

3. 软件设计原则

在软件开发中,为了提高软件系统的可维护性和可复用性,增加软件的可扩展性和灵活性,程序员要尽量根据 6 条原则来开发程序,从而提高软件开发效率、节约软件开发成本和维护成本。

  • 开闭原则:对拓展开放,对修改封闭。
  • 里式代换原则:任何基类可以出现的地方,子类一定可以出现,反之不一定。
  • 依赖倒转原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象。
  • 接口隔离原则:客户端不应该被迫依赖于它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上。
  • 迪米特法则:只和你的直接朋友交谈,不跟 “陌生人” 说话(Talk only to your immediate friends and not to strangers)。
  • 合成复用原则:尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

3.1 开闭原则

对扩展开放,对修改关闭

在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。这是为了使程序的扩展性好,易于维护和升级。

想要达到这样的效果,我们需要使用接口和抽象类。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节可以从抽象派生来的实现类来进行扩展,当软件需要发生变化时,只需要根据需求重新派生一个实现类来扩展就可以了。

eg:

3.2 里式代换原则

任何基类可以出现的地方,子类一定可以出现,反之不一定

子类可以扩展父类的功能,但不能改变父类原有的功能。

换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

如果必须重写,更适合在父类中定义成抽象方法

如果通过重写父类的方法来完成新的功能,这样写起来虽然简单,但是整个继承体系的可复用性会比较差,特别是运用多态比较频繁时,程序运行出错的概率会非常大。

eg:

3.3 依赖倒转原则

高层模块不应该依赖低层模块,两者都应该依赖其抽象

抽象不应该依赖细节,细节应该依赖抽象。简单的说就是要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。

eg:

3.4 接口隔离原则

客户端不应该被迫依赖于它不使用的方法

一个类对另一个类的依赖应该建立在最小的接口上。

eg:

3.5 迪米特法则(最少知识原则)

只和你的直接朋友交谈,不跟“陌生人”说话

含义:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。

目的:降低类之间的耦合度,提高模块的相对独立性。

“朋友”是指:当前对象本身、当前对象的成员对象、当前对象所创建的对象、当前对象的方法参数等,这些对象同当前对象存在关联、聚合或组合关系,可以直接访问这些对象的方法。

eg:

3.6 合成复用原则

尽量先使用组合或者聚合等关联关系来实现,其次才考虑使用继承关系来实现

通常类的复用分为:继承复用和合成复用两种

继承复用虽然有简单和易实现的优点,但它也存在以下缺点:

  1. 继承复用破坏了类的封装性。因为继承会将父类的实现细节暴露给子类,父类对子类是透明的,所以这种复用又称为“白箱”复用。
  2. 子类与父类的耦合度高。父类的实现的任何改变都会导致子类的实现发生变化,这不利于类的扩展与维护。
  3. 它限制了复用的灵活性。从父类继承而来的实现是静态的,在编译时已经定义,所以在运行时不可能发生变化。

采用组合或聚合复用时,可以将已有对象纳入新对象中,使之成为新对象的一部分,新对象可以调用已有对象的功能,它有以下优点:

  1. 它维持了类的封装性。因为成分对象的内部细节是新对象看不见的,所以这种复用又称为“黑箱”复用。
  2. 对象间的耦合度低。可以在类的成员位置声明抽象。
  3. 复用的灵活性高。这种复用可以在运行时动态进行,新对象可以动态地引用与成分对象类型相同的对象。

eg:

4. 单例模式

创建型模式的主要关注点是:怎样创建对象?

主要特点是:将对象的创建与使用分离

这样可以降低系统的耦合度,使用者不需要关注对象的创建细节。

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

4.1 结构与实现

结构:

  • 单例类:只能创建一个实例的类
  • 访问类:使用单例类

实现:

  • 饿汉式:类加载就会导致该单实例对象被创建
/**
 * 饿汉式:静态成员变量
 */
public class Singleton {
    // 1.私有构造方法
    private Singleton() {}
    // 2.在本类中创建本类对象
    private static Singleton instance = new Singleton();
    // 3.提供一个公共的访问方式,让外界获取该对象
    public static Singleton getInstance() {
        return instance;
    }
}

//instance 对象是随着类的加载而创建的,如果该对象很大,却一直没有使用就会造成内存的浪费。



/**
 * 饿汉式: 静态代码块
 */
public class Singleton {
    // 声明Singleton类型的变量
    private static Singleton instance; // null
    //在静态代码块中进行赋值
    static {
        instance = new Singleton();
    }
    // 私有构造方法
    private Singleton() {}
    //对外提供获取该类对象的方法
    public static Singleton getInstance() {
        return instance;
    }
}

/**
 * 饿汉式:枚举实现
 */
public enum Singleton {
    INSTANCE
}
//因为枚举类型是线程安全的,并且只会装载一次,设计者充分的利用了枚举的这个特性来实现单例模式。
//枚举的写法非常简单,而且枚举方式是所用单例实现中唯一一种不会被破坏的单例实现模式。
  • 懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
/**
 * 懒汉式: 线程不安全(如果是多线程环境,会出现线程安全问题)
 */
public class Singleton {
    // 声明Singleton类型的变量instance, 没有赋值
    private static Singleton instance;
    // 私有构造方法
    private Singleton() {}
    // 对外提供访问方式
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

/**
 * 懒汉式: 线程不安全
 */
public class Singleton {
    // 使用 synchronized 关键字解决了线程安全问题,但是该方法的执行效率特别低
    private synchronized static Singleton instance;
    // 私有构造方法
    private Singleton() {}
    // 对外提供访问方式
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

/**
 * 双重检查锁方式(有风险)
 */
public class Singleton {
    // 私有构造方法
    private Singleton() {}
    // 声明Singleton类型的变量
    private static Singleton instance;
    // 对外提供公共的访问方式
    public static Singleton getInstance() {
        // 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次判断,抢占到锁以后再次判断
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return  instance;
    }
}

//在多线程的情况下,可能会出现空指针问题,原因是 JVM 在实例化对象的时候会进行优化和指令重排序操作。

//要解决双重检查锁带来空指针的问题,只需要使用 volatile 关键字, volatile 关键字可以保证可见性和有序性。

/**
 * 双重检查锁方式(标准)
 */
public class Singleton {
    // 私有构造方法
    private Singleton() {}
    // 声明Singleton类型的变量,使用volatile保证可见性和有序性
    private static volatile Singleton instance;
    // 对外提供公共的访问方式
    public static Singleton getInstance() {
        // 第一次判断,如果instance不为null,不需要抢占锁,直接返回对象
        if (instance == null) {
            synchronized (Singleton.class) {
                // 第二次判断,抢占到锁以后再次判断
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return  instance;
    }
}

/**
 * 静态内部类方式
 */
public class Singleton {
    // 私有构造方法
    private Singleton() {}
    // 定义一个静态内部类
    private static class SingletonHolder {
        // 在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }
    // 提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

4.2 存在的问题

有两种方式可以使上面定义的单例类可以创建多个对象(枚举方式除外),分别是序列化反射

枚举方式是利用了 Java 特性实现的单例模式,不会被破坏,其他实现方式都有可能会被破坏

序列化破坏:

/**
 * 测试使用序列化破坏单例模式
 */
public class Client {
    public static void main(String[] args) throws Exception {
        Singleton s1 = readObjectFromFile();
        Singleton s2 = readObjectFromFile();
        System.out.println(s1 == s2); // false
    }
    // 从文件读取数据(对象)
    public static Singleton readObjectFromFile() throws Exception {
        // 1.创建对象输入流对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("C:\\Users\\Think\\Desktop\\a.txt"));
        // 2.读取对象
        Singleton instance = (Singleton) ois.readObject();
        // 3.释放资源
        ois.close();
        return instance;
    }
    // 向文件中写数据(对象)
    public static void writeObject2File() throws Exception {
        // 1.获取Singleton对象
        Singleton instance = Singleton.getInstance();
        // 2.创建对象输出流对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("C:\\Users\\Think\\Desktop\\a.txt"));
        // 3.写对象
        oos.writeObject(instance);
        // 4.释放资源
        oos.close();
    }
}

解决方案:在 Singleton 类中添加readResolve()方法。

在反序列化时被反射调用,如果定义了这个方法,就返回这个方法的值,如果没有定义,则返回新 new 出来的对象。

/**
 * 静态内部类方式(解决序列化破解单例模式)
 */
public class Singleton implements Serializable {
    // 私有构造方法
    private Singleton() {}
    // 提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /**
     * 下面是为了解决序列化反序列化破解单例模式
     * 当进行反序列化时,会自动调用该方法,将该方法的返回值直接返回
     */
    private Object readResolve() {
        return SingletonHolder.INSTANCE;
    }
    // 定义一个静态内部类
    private static class SingletonHolder {
        // 在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }
}

反射破坏单例

/**
 * 测试使用反射破坏单例模式
 */
public class Client {
    public static void main(String[] args) throws Exception {
        // 1.获取Singleton的字节码对象
        Class clazz = Singleton.class;
        // 2.获取无参构造方法对象
        Constructor cons = clazz.getDeclaredConstructor();
        // 3.取消访问检查
        cons.setAccessible(true);
        // 4.创建Singleton对象
        Singleton s1 = (Singleton) cons.newInstance();
        Singleton s2 = (Singleton) cons.newInstance();
        System.out.println(s1 == s2); // false
    }
}

解决方案:当通过反射方式调用构造方法进行创建时,直接抛异常

/**
 * 静态内部类方式(解决反射破坏单例模式)
 */
public class Singleton {
    private static boolean flag = false;
    // 私有构造方法
    private Singleton() {
        synchronized (Singleton.class) {
            // 如果是true,说明非第一次访问,直接抛一个异常,如果是false,说明第一次访问
            if (flag) {
                throw new RuntimeException("不能创建多个对象");
            }
            flag = true;
        }
    }
    // 提供公共的访问方式
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    // 定义一个静态内部类
    private static class SingletonHolder {
        // 在内部类中声明并初始化外部类的对象
        private static final Singleton INSTANCE = new Singleton();
    }
}

4.3 JDK 源码 - Runtime 类

Runtime 类使用的就是单例设计模式。

源码查看:下面是 Runtime 类的源码,是静态变量方式的饿汉单例模式。

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

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

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

/**
 *  RuntimeDemo
 */
public class RuntimeDemo {
    public static void main(String[] args) throws IOException {
        // 获取Runtime类的对象
        Runtime runtime = Runtime.getRuntime();
        // 调用runtime的方法exec,参数要的是一个命令
        Process process = runtime.exec("ifconfig");
        // 调用process对象的获取输入流的方法
        InputStream is =  process.getInputStream();
        byte[] arr = new byte[1024 * 1024 * 100];
        // 读取数据
        int len = is.read(arr); // 返回读到的字节的个数
        // 将字节数组转换为字符串输出到控制台
        System.out.println(new String(arr, 0, len, "GBK"));
    }
}

5. 工厂模式

在 Java 中,万物皆对象,这些对象都需要创建,如果创建的时候直接 new 该对象,就会对该对象耦合严重。假如我们要更换对象,所有 new 对象的地方都需要修改一遍,这显然违背了软件设计的开闭原则。

如果我们使用工厂来生产对象,我们就只和工厂打交道就可以了,彻底和对象解耦,如果要更换对象,直接在工厂里更换该对象即可,达到了与对象解耦的目的。

所以说,工厂模式最大的优点就是:解耦

5.1 简单工厂模式

结构:

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

实现:

/**
 * 咖啡类
 */
public abstract class Coffee {
    // 获取咖啡名称
    public abstract String getName();
    // 加糖
    public void addSugar() {
        System.out.println("加糖");
    }
    // 加奶
    public void addMilk() {
        System.out.println("加奶");
    }
}

/**
 * 美式咖啡
 */
public class AmericanCoffee extends Coffee {
    public String getName() {
        return "美式咖啡";
    }
}

/**
 * 拿铁咖啡
 */
public class LatteCoffee extends Coffee {
    public String getName() {
        return "拿铁咖啡";
    }
}

/**
 * 简单咖啡工厂类,用来生产咖啡
 */
public class SimpleCoffeeFactory {
    public Coffee createCoffee(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("对不起,您所点的咖啡没有");
        }
        return coffee;
    }
}

/**
 * 咖啡店
 */
public class CoffeeStore {
    public Coffee orderCoffee(String type) {
        // 生产咖啡的简单工厂
        SimpleCoffeeFactory factory = new SimpleCoffeeFactory();
        // 调用生产咖啡的方法
        Coffee coffee = factory.createCoffee(type);
        // 加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

在以上代码中,工厂处理创建对象的细节,一旦有了 SimpleCoffeeFactory,CoffeeStore 类中的 orderCoffee() 就变成此对象的客户,后期如果需要 Coffee 对象直接从工厂中获取即可,这样也就解除了和 Coffee 实现类的耦合。

但是又产生了新的耦合,CoffeeStore 对象和 SimpleCoffeeFactory 工厂对象的耦合,工厂对象和商品对象的耦合。

后期如果再加新品种的咖啡,我们需要修改 SimpleCoffeeFactory 的代码,违反了开闭原则。

优点:

封装了创建对象的过程,可以通过参数直接获取对象。

把对象的创建和业务逻辑层分开,这样以后就避免了修改客户代码,如果要实现新产品直接修改工厂类,而不需要在原代码中修改,这样就降低了客户代码修改的可能性,更加容易扩展。

缺点:增加新产品时还是需要修改工厂类的代码,违背了 “开闭原则”

拓展:静态工厂

在开发中也有一部分人将工厂类中的创建对象的功能定义为静态的,这就是静态工厂模式,它也不属于 23 种设计模式。

public class SimpleCoffeeFactory {
    public static Coffee createCoffee(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("对不起,您所点的咖啡没有");
        }
        return coffee;
    }
}

5.2 工厂方法模式

简单工厂模式违背了开闭原则,使用工厂方法模式可以完全遵循开闭原则。

定义一个用于创建对象的接口(工厂),让其子类决定实例化哪个产品类对象。

工厂方法模式使一个产品类的实例化延迟到其工厂的子类。

结构:

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

实现:

/**
 * 抽象工厂
 */
public interface CoffeeFactory {
    // 创建咖啡对象的方法
    Coffee createCoffee();
}

/**
 * 美式咖啡工厂,专门用来生产美式咖啡
 */
public class AmericanCoffeeFactory implements CoffeeFactory {
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}

/**
 * 拿铁咖啡工厂,专门用来生产拿铁咖啡
 */
public class LatteCoffeeFactory implements CoffeeFactory {
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}

/**
 * 咖啡店
 */
public class CoffeeStore {
    private CoffeeFactory factory;
    public void setFactory(CoffeeFactory factory) {
        this.factory = factory;
    }
    // 点咖啡功能
    public Coffee orderCoffee() {
        Coffee coffee = factory.createCoffee();
        // 加配料
        coffee.addMilk();
        coffee.addSugar();
        return coffee;
    }
}

public class Client {
    public static void main(String[] args) {
        // 创建咖啡店对象
        CoffeeStore store = new CoffeeStore();
        // 创建对象, 使用不同的工厂创建不同的产品
        // CoffeeFactory factory = new AmericanCoffeeFactory();
        CoffeeFactory factory = new LatteCoffeeFactory();
        store.setFactory(factory);
        // 点咖啡
        Coffee coffee = store.orderCoffee();
        System.out.println(coffee.getName());
    }
}

要增加产品类时也要增加相应地工厂类,从而不需要修改工厂类的代码,这样就满足了开闭原则。

优点:

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

缺点:

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

5.3 抽象工厂模式

工厂方法模式:一个工厂生产一种类对象的模式。

抽象工厂模式:一个工厂可以生产多种类对象的模式。

抽象工厂模式将考虑多等级产品的生产,将同一个具体工厂所生产的位于不同等级的一组产品称为一个产品族

名词总结:

  • 同等级产品:就是同类别的产品,如:美式咖啡、拿铁咖啡。
  • 同一产品族:就是一个工厂生产的不同类别的一系列产品,如:拿铁咖啡、意大利甜品。

结构:

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

实现:

现咖啡店业务发生改变,不仅要生产咖啡还要生产甜点,如提拉米苏、抹茶慕斯等。

是按照工厂方法模式,需要定义提拉米苏类、抹茶慕斯类、提拉米苏工厂、抹茶慕斯工厂、甜点工厂类,很容易发生类爆炸情况。

  • 拿铁咖啡、美式咖啡是一个产品等级,都是咖啡;提拉米苏、抹茶慕斯也是一个产品等级,都是甜品;
  • 拿铁咖啡、提拉米苏是同一产品族,都属于意大利风味;美式咖啡、抹茶慕斯是同一产品族,都属于美式风味;
/**
 * 甜品工厂
 */
public interface DessertFactory {
    // 生产咖啡的功能
    Coffee createCoffee();
    // 生产甜品的功能
    Dessert createDessert();
}

/**
 * 意大利风味的甜品工厂
 * 生产拿铁咖啡和提拉米苏甜品
 */
public class ItalyDessertFactory implements DessertFactory {
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
    public Dessert createDessert() {
        return new Trimisu();
    }
}

/**
 * 美式风味的甜品工厂
 * 生产美式咖啡和抹茶慕斯
 */
public class AmericanDessertFactory implements DessertFactory {
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
    public Dessert createDessert() {
        return new MatchaMousse();
    }
}

public class Client {
    public static void main(String[] args) {
        // 创建的是意大利风味甜品工厂对象
        ItalyDessertFactory factory = new ItalyDessertFactory();
        // 获取拿铁咖啡和提拉米苏甜品
        Coffee coffee = factory.createCoffee();
        Dessert dessert = factory.createDessert();
        dessert.show();
    }
}
//如果要加同一个产品族的话,只需要再加一个对应的工厂类即可,不需要修改其他的类。

优点:

当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

想要生产一个产品族的产品,只需要一个接口或类。

缺点:

当产品族中需要增加一个新的产品时,所有的工厂类都需要进行修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值