【设计模式】结构型模式(四):组合模式、享元模式

设计模式之结构型模式》系列,共包含以下文章:

😊 如果您觉得这篇文章有用 ✔️ 的话,请给博主一个一键三连 🚀🚀🚀 吧 (点赞 🧡、关注 💛、收藏 💚)!!!您的支持 💖💖💖 将激励 🔥 博主输出更多优质内容!!!

结构型模式(四):组合模式、享元模式

  • 6.组合模式(Composite)
    • [6.1 案例](#6.1 案例)
      • [6.1.1 定义统一接口](#6.1.1 定义统一接口)
      • [6.1.2 实现叶子节点(文件)](#6.1.2 实现叶子节点(文件))
      • [6.1.3 实现组合节点(文件夹)](#6.1.3 实现组合节点(文件夹))
      • [6.1.4 客户端](#6.1.4 客户端)
      • [6.1.5 输出](#6.1.5 输出)
  • 7.享元模式(Flyweight)
    • [7.1 问题](#7.1 问题)
    • [7.2 解决方案](#7.2 解决方案)
    • [7.3 代码实现](#7.3 代码实现)
      • [7.3.1 享元接口](#7.3.1 享元接口)
      • [7.3.2 享元对象](#7.3.2 享元对象)
      • [7.3.3 享元工厂](#7.3.3 享元工厂)
      • [7.3.4 客户端](#7.3.4 客户端)
      • [7.3.5 输出结果](#7.3.5 输出结果)

6.组合模式(Composite)

组合模式Composite Pattern)是一种设计模式,用于处理树形结构的数据。它的主要目的是将对象组合成树形结构来表示 "部分 - 整体" 的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

具体来说,组合模式有以下几个特点:

  • 统一接口:无论是单个对象(叶子节点)还是组合对象(树枝节点),都提供相同的接口,这样客户端代码可以一致地处理它们,而不需要关心它们是单个对象还是组合对象。
  • 递归结构:组合模式通过 递归 的方式构建树形结构,每个组合对象可以包含多个子对象,这些子对象可以是叶子节点或更深层次的组合对象。
  • 透明性:客户端代码不需要关心对象的具体类型,只需要通过统一的接口进行操作。

6.1 案例

假设你正在开发一个文件系统,文件系统中包含文件(File)和文件夹(Folder)。文件夹可以包含多个文件和其他文件夹,文件夹和文件都有一些共同的操作,比如显示内容。

6.1.1 定义统一接口

java 复制代码
public interface Component {
 void display();
}

6.1.2 实现叶子节点(文件)

java 复制代码
public class File implements Component {
    private String name;

    public File(String name) {
        this.name = name;
    }

    @Override
    public void display() {
        System.out.println("文件: " + name);
    }
}

6.1.3 实现组合节点(文件夹)

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Folder implements Component {
   private String name;
   private List<Component> children = new ArrayList<>();

   public Folder(String name) {
       this.name = name;
   }

   public void add(Component component) {
       children.add(component);
   }

   public void remove(Component component) {
       children.remove(component);
   }

   @Override
   public void display() {
       System.out.println("文件夹: " + name);
       for (Component child : children) {
           child.display();
       }
   }
}

6.1.4 客户端

java 复制代码
public class CompositePatternExample {
   public static void main(String[] args) {
       // 创建文件和文件夹
       File file1 = new File("文件1");
       File file2 = new File("文件2");
       Folder folder1 = new Folder("文件夹1");
       Folder folder2 = new Folder("文件夹2");

       // 组合文件和文件夹
       folder1.add(file1);
       folder1.add(file2);
       folder2.add(folder1);
       folder2.add(new File("文件3"));

       // 显示文件系统结构
       folder2.display();
   }
}

6.1.5 输出

plain 复制代码
文件夹: 文件夹2
文件夹: 文件夹1
文件: 文件1
文件: 文件2
文件: 文件3

在这个例子中,Component 接口定义了所有组件的共同操作,FileFolder 都实现了这个接口。Folder 可以包含多个 Component,从而形成树形结构。客户端代码通过 Component 接口操作文件和文件夹,而不需要关心它们的具体类型。

7.享元模式(Flyweight)

享元模式Flyweight Pattern) 是一种用于性能优化的设计模式,主要目的是通过共享已经存在的对象来减少内存使用和提高性能。简单来说,享元模式 通过共享对象来减少对象的数量,从而节省内存

假设你正在开发一个文本编辑器,用户可以在编辑器中输入大量的文本。为了显示这些文本,你需要为每个字符创建一个对象。如果每个字符都创建一个独立的对象,那么当文本非常大时,会占用大量的内存。

7.1 问题

  • 内存消耗大:如果每个字符都创建一个独立的对象,内存消耗会非常大。
  • 性能问题:创建和管理大量对象会导致性能下降。

7.2 解决方案

使用享元模式,你可以共享字符对象,而不是为每个字符都创建一个独立的对象。具体来说:

  • 内部状态Intrinsic State):这些是共享的、不变的状态,例如字符的字形信息。
  • 外部状态Extrinsic State):这些是不共享的、变化的状态,例如字符在文档中的位置。

🚀 计算机世界中无穷无尽的可能,其本质都是由 10 两个 "元" 的组合变化而产生的。

🚀 ,顾名思义,始也,有本初、根源的意思。"享元" 则是 共享元件 的意思。享元模式的英文 Flyweight轻量级 的意思,这就意味着享元模式能使程序变得更加轻量化。当系统存在大量的对象,并且这些对象又具有相同的内部状态时,我们就可以用享元模式共享相同的元件对象,以避免对象泛滥造成资源浪费。

7.3 代码实现

假设你有一个文本编辑器,需要显示大量的字符。你可以使用享元模式来减少内存使用。

7.3.1 享元接口

java 复制代码
public interface CharacterFlyweight {
   void display(int position);
}
  • 定义了一个接口,用于显示字符及其位置。
  • 方法:display(int position),用于显示字符在文本中的位置。

7.3.2 享元对象

java 复制代码
public class CharacterObject implements CharacterFlyweight {
    private final char value;
    private final String font;

    public CharacterObject(char value, String font) {
        this.value = value;
        this.font = font;
    }

    @Override
    public void display(int position) {
        System.out.println("Character: " + value + " at position: " + position + " with font: " + font);
    }
}

Character 类实现了 CharacterFlyweight 接口,包含字符的值和字体信息。这些信息是内部状态,是共享的。

7.3.3 享元工厂

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class CharacterFactory {
    private static final Map<Character, CharacterFlyweight> pool = new HashMap<>();

    public static CharacterFlyweight getCharacter(char value, String font) {
        CharacterFlyweight character = pool.get(value);
        if (character == null) {
            // 创建新的 CharacterFlyweight 对象
            System.out.println("===============: " + value + " 加入共享");
            character = new CharacterObject(value, font);
            // 将新创建的对象添加到 pool 中
            pool.put(value, character);
        }
        return character;
    }
}

CharacterFactory 类是一个工厂类,用于管理共享的字符对象 。通过 getCharacter 方法,根据字符值和字体信息从池中获取或创建字符对象。

为什么 CharacterFactory 不需要实现 CharacterFlyweight 接口?

  • 职责分离CharacterFactory 的职责是创建和管理 CharacterFlyweight 对象,而不是实现 CharacterFlyweight 接口。实现接口的类应该是具体的字符对象类,如 CharacterObject
  • 灵活性:通过工厂类创建对象,可以在不改变调用代码的情况下,轻松地更换不同的实现类。例如,如果将来需要添加一个新的字符对象实现类 CharacterObject2,只需要在 CharacterFactory 中创建 CharacterObject2 的实例即可。
  • 解耦:调用者只需要知道 CharacterFlyweight 接口,而不需要知道具体的实现类。这有助于降低代码的耦合度,提高代码的可维护性和扩展性。

CharacterFactory 类中,getCharacter 方法负责创建 CharacterFlyweight 对象。具体来说,当 pool 中没有指定字符的 CharacterFlyweight 对象时,getCharacter 方法会创建一个新的 CharacterObject 实例,并将其添加到 pool 中。

7.3.4 客户端

java 复制代码
public class FlyweightPatternExample {
   public static void main(String[] args) {
       // 模拟输入文本
       String text = "Hello, World!";

       for (int i = 0; i < text.length(); i++) {
           char c = text.charAt(i);
           CharacterFlyweight character = CharacterFactory.getCharacter(c, "Arial");
           character.display(i);
       }
   }
}

客户端代码通过 CharacterFactory 获取字符对象,并调用 display 方法显示字符。每个字符对象在池中只创建一次,多次使用时直接从池中获取,从而减少了内存使用。

7.3.5 输出结果

plain 复制代码
===============: H 加入共享
Character: H at position: 0 with font: Arial
===============: e 加入共享
Character: e at position: 1 with font: Arial
===============: l 加入共享
Character: l at position: 2 with font: Arial
Character: l at position: 3 with font: Arial
===============: o 加入共享
Character: o at position: 4 with font: Arial
===============: , 加入共享
Character: , at position: 5 with font: Arial
===============:   加入共享
Character:   at position: 6 with font: Arial
===============: W 加入共享
Character: W at position: 7 with font: Arial
Character: o at position: 8 with font: Arial
===============: r 加入共享
Character: r at position: 9 with font: Arial
Character: l at position: 10 with font: Arial
===============: d 加入共享
Character: d at position: 11 with font: Arial
===============: ! 加入共享
Character: ! at position: 12 with font: Arial
相关推荐
腥臭腐朽的日子熠熠生辉31 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian33 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之38 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen1 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
绝顶少年2 小时前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端