万字解析设计模式之组合模式、亨元模式

一、组合模式

1.1概述

组合模式是一种结构型设计模式,它允许将对象组合成树形结构,以表示"部分-整体"的层次结构。组合模式使得客户端可以一致地对待单个对象和对象组合,从而将复杂的层次结构展现为一个统一的树形结构。

在组合模式中,一般会定义一个抽象类或接口来表示组合中的所有对象,包括叶子对象和容器对象;叶子对象表示树形结构中的最底层节点,容器对象表示树形结构中的分支节点,同时容器对象可以包含叶子对象或其他容器对象。

组合模式能够有效地简化代码结构,提高代码的可维护性和可扩展性,同时也具有良好的透明性和灵活性。在实际编程中,组合模式广泛应用于树形结构的处理、图形绘制、文件系统等领域。

对于这个图片肯定会非常熟悉,上图我们可以看做是一个文件系统,对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象即可是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象和叶子对象。

1.2结构

组合模式主要包含三种角色:

  • 抽象根节点(Component):定义系统各层次对象的共有方法和属性,定义组合中所有对象的通用接口,可以是抽象类或接口,包含了组合对象和叶子对象的公共操作。
  • 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。表示组合对象,包含了一个子对象的集合,并实现了抽象构件中的公共操作。
  • 叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。

1.3实现

软件菜单

如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。

要实现该案例,我们先画出类图:

抽象构件角色(抽象根节点)

java 复制代码
package com.yanyu.Component;


//菜单组件  不管是菜单还是菜单项,都应该继承该类
public abstract class MenuComponent {

    protected String name;
    protected int level;

    //添加菜单
    public void add(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }

    //移除菜单
    public void remove(MenuComponent menuComponent){
        throw new UnsupportedOperationException();
    }

    //获取指定的子菜单
    public MenuComponent getChild(int i){
        throw new UnsupportedOperationException();
    }

    //获取菜单名称
    public String getName(){
        return name;
    }

    //打印菜单
    public void print(){
        throw new UnsupportedOperationException();
    }
}

树枝节点(组合部件)

java 复制代码
package com.yanyu.Component;

import java.util.ArrayList;
import java.util.List;

//菜单类,继承自菜单组件
public class Menu extends MenuComponent {

    private List<MenuComponent> menuComponentList;

    //构造函数
    public Menu(String name, int level){
        this.level = level;
        this.name = name;
        menuComponentList = new ArrayList<MenuComponent>();
    }

    //添加菜单项或子菜单
    @Override
    public void add(MenuComponent menuComponent) {
        menuComponentList.add(menuComponent);
    }

    //移除菜单项或子菜单
    @Override
    public void remove(MenuComponent menuComponent) {
        menuComponentList.remove(menuComponent);
    }

    //获取指定的子菜单
    @Override
    public MenuComponent getChild(int i) {
        return menuComponentList.get(i);
    }

    //打印菜单及子菜单
    @Override
    public void print() {

        for (int i = 1; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);
        for (MenuComponent menuComponent : menuComponentList) {
            menuComponent.print();
        }
    }
}

叶子节点(叶子构件)

java 复制代码
package com.yanyu.Component;

//菜单项类,继承自菜单组件
public class MenuItem extends MenuComponent {

    //构造函数
    public MenuItem(String name, int level) {
        this.name = name;
        this.level = level;
    }

    //打印菜单项
    @Override
    public void print() {
        for (int i = 1; i < level; i++) {
            System.out.print("--");
        }
        System.out.println(name);
    }
}

客户端类

java 复制代码
package com.yanyu.Component;

public class Client {
    public static void main(String[] args) {
        //创建菜单树
        MenuComponent menu1 = new Menu("菜单管理", 2);
        menu1.add(new MenuItem("页面访问", 3));
        menu1.add(new MenuItem("展开菜单", 3));
        menu1.add(new MenuItem("编辑菜单", 3));
        menu1.add(new MenuItem("删除菜单", 3));
        menu1.add(new MenuItem("新增菜单", 3));
        MenuComponent menu2 = new Menu("权限管理", 2);
        menu2.add(new MenuItem("页面访问", 3));
        menu2.add(new MenuItem("提交保存", 3));
        MenuComponent menu3 = new Menu("角色管理", 2);
        menu3.add(new MenuItem("页面访问", 3));
        menu3.add(new MenuItem("新增角色", 3));
        menu3.add(new MenuItem("修改角色", 3));
        //创建一级菜单
         MenuComponent component = new Menu("系统管理", 1);

         component.add(menu1);
         component.add(menu2);
         component.add(menu3);
         component.print();
    }
}
  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
  • 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合"开闭原则"。
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

1.4组合模式的分类

在使用组合模式时,根据抽象构件类的定义形式,我们可将组合模式分为透明组合模式和安全组合模式两种形式。

  • 透明组合模式

    透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中 MenuComponent 声明了 addremovegetChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。

    透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)

  • 安全组合模式

    在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。

1.5使用场景

  • 如果你需要实现树状对象结构, 可以使用组合模式。

组合模式为你提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。 容器中可以包含叶节点和其他容器。 这使得你可以构建树状嵌套递归对象结构。

  • 如果你希望客户端代码以相同方式处理简单和复杂元素, 可以使用该模式。

组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。

组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作。

二、享元模式

2.1概述

享元模式是一种结构型设计模式,旨在减少内存占用和提高性能。它将对象分解为可共享的和不可共享的部分。可共享的部分是多个对象共有的、不变的部分,它们可以被多个对象共享使用,从而减少内存占用。不可共享的部分是对象不同的、可变的部分,每个对象各自拥有不同的副本。

通过应用享元模式,我们可以大幅度减少系统中的对象数量,提高系统的性能和响应速度。然而,享元模式会增加程序的复杂性和开发难度,因为需要将对象分解为可共享和不可共享的部分,并维护对象的共享池。因此,需要在实际应用中根据具体情况进行权衡和取舍。
定义

运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。

2.2结构

享元(Flyweight )模式中存在以下两种状态:

  1. 内部状态,即不会随着环境的改变而改变的可共享部分。
  2. 外部状态,指随环境改变而改变的不可以共享的部分。享元模式的实现要领就是区分应用中的这两种状态,并将外部状态外部化。

享元模式的主要有以下角色:

  • 抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
  • 具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
  • 非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
  • 享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。

2.3实现

【例】俄罗斯方块

下面的图片是众所周知的俄罗斯方块中的一个个方块,如果在俄罗斯方块这个游戏中,每个不同的方块都是一个实例对象,这些对象就要占用很多的内存空间,下面利用享元模式进行实现。

先来看类图:

抽象享元

java 复制代码
package com.yanyu.Flyweight;

// 抽象享元类
public abstract class AbstractBox {
    // 声明一个抽象方法,用于获取方块形状
    public abstract String getShape();

    // 实现一个公共方法,用于显示方块的形状和颜色
    public void display(String color) {
        System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);
    }
}

具体亨元

java 复制代码
package com.yanyu.Flyweight;

/**
 * IBox是一个继承自AbstractBox的类。
 * 它代表一个I形状的方块。
 */
public class IBox extends AbstractBox {

    /**
     * 重写了AbstractBox中的getShape方法。
     * 返回"I"表示这个方块是一个I形状的方块。
     */
    @Override
    public String getShape() {
        return "I";
    }
}
java 复制代码
package com.yanyu.Flyweight;

public class LBox extends AbstractBox {

    @Override
    public String getShape() {
        return "L";
    }
}
java 复制代码
package com.yanyu.Flyweight;

public class OBox extends AbstractBox {

    @Override
    public String getShape() {
        return "O";
    }
}

享元工厂

java 复制代码
package com.yanyu.Flyweight;

import java.util.HashMap;

// 创建一个BoxFactory类
public class BoxFactory {

    // 创建一个静态的HashMap用于存储不同类型的AbstractBox
    private static HashMap<String, AbstractBox> map;

    // 私有构造函数,初始化HashMap并将不同类型的AbstractBox放入其中
    private BoxFactory() {
        map = new HashMap<String, AbstractBox>();
        AbstractBox iBox = new IBox(); // 创建IBox对象
        AbstractBox lBox = new LBox(); // 创建LBox对象
        AbstractBox oBox = new OBox(); // 创建OBox对象
        map.put("I", iBox); // 将IBox对象放入HashMap
        map.put("L", lBox); // 将LBox对象放入HashMap
        map.put("O", oBox); // 将OBox对象放入HashMap
    }

    // 创建一个静态内部类SingletonHolder用于实现单例模式
    private static class SingletonHolder {
        private static final BoxFactory INSTANCE = new BoxFactory(); // 创建BoxFactory的单例实例
    }

    // 获取BoxFactory的实例
    public static final BoxFactory getInstance() {
        return SingletonHolder.INSTANCE; // 返回BoxFactory的单例实例
    }

    // 根据key获取对应的AbstractBox
    public AbstractBox getBox(String key) {
        return map.get(key); // 根据key从HashMap中获取对应的AbstractBox对象
    }
}

客户端类

java 复制代码
package com.yanyu.Flyweight;

public class Client {
    public static void main(String[] args) {
        AbstractBox box1 = BoxFactory.getInstance().getBox("I");
        box1.display("灰色");
        AbstractBox box2 = BoxFactory.getInstance().getBox("L");
        box2.display("绿色");
        AbstractBox box3 = BoxFactory.getInstance().getBox("O");
        box3.display("灰色");
        // 再次获取相同的图形对象
        AbstractBox box4 = BoxFactory.getInstance().getBox("O");
        box4.display("红色");
        System.out.println("再次获取到的图形对象是否是同一个对象: " + (box3 == box4));
    }
}

2.4 优缺点

1,优点

  • 极大减少内存中相似或相同对象数量,节约系统资源,提供系统性能
  • 享元模式中的外部状态相对独立,且不影响内部状态

2,缺点:

为了使对象可以共享,需要将享元对象的部分状态外部化,分离内部状态和外部状态,使程序逻辑复杂

2.5应用场景

  1. 仅在程序必须支持大量对象且没有足够的内存容量时使用享元模式。

  2. 应用该模式所获的收益大小取决于使用它的方式和情景。 它在下列情况中最有效:

  • 程序需要生成数量巨大的相似对象
  • 这将耗尽目标设备的所有内存
  • 对象中包含可抽取且能在多个对象间共享的重复状态。

2.6 JDK源码解析

Integer类使用了享元模式。我们先看下面的例子:

java 复制代码
public class Demo {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;
​
        System.out.println("i1和i2对象是否是同一个对象?" + (i1 == i2));
​
        Integer i3 = 128;
        Integer i4 = 128;
​
        System.out.println("i3和i4对象是否是同一个对象?" + (i3 == i4));
    }
}

运行上面代码,结果如下:

三、组合模式实验

任务描述

使用组合模式设计一个杀毒软件(AntiVirus)的框架,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒,文件种类包括文本文件 TextFile,图片文件 ImageFile、视频文件 VideoFile

本关任务:根据 UML 类图编程模拟实现。

实现方式

  1. 确保应用的核心模型能够以树状结构表示。 尝试将其分解为简单元素和容器。 记住, 容器必须能够同时包含简单元素和其他容器。

  2. 声明组件接口及其一系列方法, 这些方法对简单和复杂元素都有意义。

  3. 创建一个叶节点类表示简单元素。 程序中可以有多个不同的叶节点类。

  4. 创建一个容器类表示复杂元素。 在该类中, 创建一个数组成员变量来存储对于其子元素的引用。 该数组必须能够同时保存叶节点和容器, 因此请确保将其声明为组合接口类型。实现组件接口方法时, 记住容器应该将大部分工作交给其子元素来完成。

  5. 最后, 在容器中定义添加和删除子元素的方法。

记住,这些操作可在组件接口中声明。 这将会违反接口隔离原则,因为叶节点类中的这些方法为空。 但是,这可以让客户端无差别地访问所有元素, 即使是组成树状结构的元素。

编程要求

根据提示,在右侧编辑器 Begin-End 内 补充 "AbstractFile.java" 和 "Folder.java" 文件代码,其它文件不需要修

抽象构建

java 复制代码
package step1;
//抽象文件类:抽象构件
/********** Begin *********/

public abstract class AbstractFile {
    public abstract void add(AbstractFile file);
    public abstract void remove(AbstractFile file);
    
    public abstract void scan();
}

/********** End *********/

组合部件

java 复制代码
package step1;

import java.util.ArrayList;
import java.util.List;

// 文件夹类,属于组合模式中的容器构件
public class Folder extends AbstractFile {
    private String name; // 文件夹名称
    private List<AbstractFile> files = new ArrayList<>(); // 存储文件夹中的文件或子文件夹
    
    public Folder(String name) {
        this.name = name;
    }
    
    // 添加文件或子文件夹
    public void add(AbstractFile file) {
        files.add(file);
    }
    
    // 移除文件或子文件夹
    public void remove(AbstractFile file) {
        files.remove(file);
    }
    
    // 获取子文件或子文件夹
    public AbstractFile getChild(int i) {
        return files.get(i);
    }
    
    // 扫描文件夹
    public void scan() {
        System.out.println("开始扫描文件夹:" + name); // 打印扫描文件夹的信息
        for (AbstractFile file : files) {
            file.scan(); // 调用子文件或子文件夹的扫描方法
        }
    }
}

叶子构建

java 复制代码
package step1;

//图片文件类:叶子构件
public class ImageFile extends AbstractFile{
    private String fileName;
    public ImageFile(String fileName){
        this.fileName = fileName;
    }

    @Override
    public void add(AbstractFile element) {
        System.out.println("对不起,不支持该方法");
    }

    @Override
    public void remove(AbstractFile element) {
        System.out.println("对不起,不支持该方法");
    }

    @Override
    public void scan() {
        System.out.println("扫描图片文件:"+fileName);
    }
}
java 复制代码
package step1;

//文本文件类:叶子构件
public class TextFile extends  AbstractFile{
    private String fileName;
    public TextFile(String fileName){
        this.fileName = fileName;
    }

    @Override
    public void add(AbstractFile element) {
        System.out.println("对不起,不支持该方法");
    }

    @Override
    public void remove(AbstractFile element) {
        System.out.println("对不起,不支持该方法");
    }

    @Override
    public void scan() {
        System.out.println("扫描文本文件:"+fileName);
    }
}
java 复制代码
package step1;

//视频文件类:叶子构件
public class VideoFile extends AbstractFile{
    private String fileName;
    public VideoFile(String fileName){
        this.fileName = fileName;
    }

    @Override
    public void add(AbstractFile element) {
        System.out.println("对不起,不支持该方法");
    }

    @Override
    public void remove(AbstractFile element) {
        System.out.println("对不起,不支持该方法");
    }

    @Override
    public void scan() {
        System.out.println("扫描视频文件:"+fileName);
    }
}

客户端类

java 复制代码
// 定义客户端类
package step1;

public class Client {
    public static void main(String[] args) {
        // 创建文件和文件夹对象
        AbstractFile file1, file2, file3, file4, file5, folder1, folder2, folder3;
        
        // 创建各种类型的文件对象
        file1 = new ImageFile("pic1.gif");
        file2 = new ImageFile("pic2.jpg");
        file3 = new TextFile("txt1.txt");
        file4 = new TextFile("txt2.doc");
        file5 = new VideoFile("video1.rmvb");
        
        // 创建文件夹对象
        folder1 = new Folder("图片文件夹");
        folder1.add(file1); // 将图片文件添加到图片文件夹中
        folder1.add(file2);
        
        folder2 = new Folder("文本文件夹");
        folder2.add(file3); // 将文本文件添加到文本文件夹中
        folder2.add(file4);
        
        folder3 = new Folder("个人资料");
        folder3.add(file5); // 将视频文件添加到个人资料文件夹中
        folder3.add(folder1); // 将图片文件夹添加到个人资料文件夹中
        folder3.add(folder2); // 将文本文件夹添加到个人资料文件夹中
        
        // 扫描文件夹
        folder3.scan(); // 调用文件夹的扫描方法,实现对整个文件夹结构的扫描
    }
}

四、享元模式 实验

任务描述

在一个虚拟仿真程序中需要渲染一片森林 (1,000,000 棵树)! 每棵树对象都包含一些状态(品种名 name,屏幕坐标 x,屏幕坐标 y,颜色 color,其它附带数据 otherTreeData)。

本关任务:太多树对象包含重复数据,因此我们可用享元模式来将这些数值存储在单独的享元对象中(TreeType 类)。请分析"树"的状态中哪些是外在状态,哪些是内部状态,然后补全代码。

实现方式

  1. 将需要改写为享元的类成员变量拆分为两个部分:内在状态,包含不变的、 可在许多对象中重复使用的数据的成员变量。外在状态, 包含每个对象各自不同的情景数据的成员变量;

  2. 保留类中表示内在状态的成员变量, 并将其属性设置为不可修改。 这些变量仅可在构造函数中获得初始数值;

  3. 找到所有使用外在状态成员变量的方法, 为在方法中所用的每个成员变量新建一个参数, 并使用该参数代替成员变量;

  4. 你可以有选择地创建工厂类来管理享元缓存池, 它负责在新建享元时检查已有的享元。 如果选择使用工厂, 客户端就只能通过工厂来请求享元, 它们需要将享元的内在状态作为参数传递给工厂;

  5. 客户端必须存储和计算外在状态 (情景) 的数值, 因为只有这样才能调用享元对象的方法。 为了使用方便, 外在状态和引用享元的成员变量可以移动到单独的情景类中。

编程提示

TreeType.java :内在状态享元类; Tree.java:树对象类; TreeFactory.java:内在状态享元工厂类; Forest.java:森林对象类。

本地 windows 系统调式时,可将图形代码恢复,观看实际效果。提交测试时,务必屏蔽图形代码

编程要求

根据提示,在右侧编辑器 Begin-End 内 补充 "Tree.java","TreeType.java" 和 "TreeFactory.java" 文件的代码(其它文件不需要更改),计算对比,优化前后分别占用的内存数。

抽象享元

java 复制代码
import java.awt.*;

public class Tree {
    // 享元对象和外在状态
    private int x; // 外在状态:树的x坐标
    private int y; // 外在状态:树的y坐标
    private TreeType type; // 享元对象:树的类型

    // 构造方法
    public Tree(int x, int y, TreeType type) {
        this.x = x;
        this.y = y;
        this.type = type;
    }

    public void draw(Graphics g) {
        type.draw(g, x, y); // 调用享元对象的绘制方法
    }
}
java 复制代码
import java.awt.*;

public class TreeType {
    /********** 内在状态 *********/
    // 内在状态
    private String name; // 树的名称
    private Color color; // 树的颜色
    private String texture; // 树的纹理


    /********** End *********/

    /********** 构造方法 *********/
   public TreeType(String name, Color color, String texture) {
        this.name = name;
        this.color = color;
        this.texture = texture;
    }

    /********** End *********/

    public void draw(Graphics g, int x, int y) {
        //本地测试时,可使用图形化代码
        /*g.setColor(Color.BLACK);
        g.fillRect(x - 1, y, 3, 5);
        g.setColor(color);
        g.fillOval(x - 5, y - 10, 10, 10);*/
    }
}

享元工厂

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

public class TreeFactory {
    private Map<String, TreeType> treeTypes = new HashMap<>();

    public TreeType getTreeType(String name, Color color, String texture) {
        String key = name + color + texture; // 内在状态的唯一关键字参数
        TreeType result = treeTypes.get(key);
        if (result == null) {
            // 创建一个享元对象
            result = new TreeType(name, color, texture);
            // 将新的享元对象放入对象池中
            treeTypes.put(key, result);
        }
        return result;
    }

    public int getTotalTreetype() {
        return treeTypes.size();
    }
}

在上述代码中,`treeTypes.get(key)`和`treeTypes.put(key, result)`是享元模式中的关键操作。具体解释如下:

  • `treeTypes.get(key)`: 这行代码用于从对象池中获取具有特定内在状态的享元对象。在这里,我们使用`key`作为唯一的标识符来检索享元对象。如果对象池中已经存在具有相同内在状态的享元对象,则直接返回该对象;否则返回null。

  • `treeTypes.put(key, result)`: 这行代码用于将新创建的享元对象放入对象池中。如果对象池中不存在具有相同内在状态的享元对象,那么我们需要将新创建的享元对象放入对象池中,以便后续重复使用。

这两行代码共同实现了享元模式中的对象共享和复用,通过对象池管理和重复利用享元对象,以节省内存和提高性能。

java 复制代码
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;

public class Forest /*extends JFrame*/ {
    private List<Tree> trees = new ArrayList<>(); // 存储森林中的树
    private TreeFactory treeFactory; // 树工厂对象,用于创建和管理树的类型

    public Forest(TreeFactory treeFactory) {
        this.treeFactory = treeFactory;
    }

    // 添加树到森林中
    public void plantTree(int x, int y, String name, Color color, String otherTreeData) {
        // 从树工厂中获取树的类型
        TreeType type = treeFactory.getTreeType(name, color, otherTreeData);
        // 创建树对象并添加到森林中
        Tree tree = new Tree(x, y, type);
        trees.add(tree);
    }

    // 获取森林中的树的数量
    public int getTotalTree() {
        return trees.size();
    }

    // 获取树工厂中的树的类型数量
    public int getTotalTreetype() {
        return treeFactory.getTotalTreetype();
    }

    //@Override
    // 本地测试时,可继承JFrame类,并恢复以下图形化代码
    /*public void paint(Graphics graphics) {
        for (Tree tree : trees) {
            tree.draw(graphics);
        }
    }*/
}

客户端类

java 复制代码
import java.awt.*;

// 客户端类,用于测试和演示
public class Client {
    // 画布大小
    static int CANVAS_SIZE = 500;
    // 需要绘制的树的数量
    static int TREES_TO_DRAW = 1000000;
    // 树的类型数量
    static int TREE_TYPES = 2;

    public static void main(String[] args) {
        // 创建树工厂
        TreeFactory factory = new TreeFactory();
        // 创建森林
        Forest forest = new Forest(factory);

        // 根据树的类型数量和需要绘制的树的数量,随机生成不同类型的树并添加到森林中
        for (int i = 0; i < Math.floor(TREES_TO_DRAW / TREE_TYPES); i++) {
            // 随机生成夏季橡树并添加到森林中
            forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE),
                    "Summer Oak", Color.GREEN, "Oak texture stub");
            // 随机生成秋季橡树并添加到森林中
            forest.plantTree(random(0, CANVAS_SIZE), random(0, CANVAS_SIZE),
                    "Autumn Oak", Color.ORANGE, "Autumn Oak texture stub");
        }
        
        // 输出绘制的树的数量
        System.out.println(forest.getTotalTree() + " trees drawn");
        // 输出内存使用情况
        System.out.println("Memory usage:");
        System.out.println("Tree size (8 bytes) * " + forest.getTotalTree());
        System.out.println("+ TreeTypes size (~30 bytes) * " + forest.getTotalTreetype() + "");
        System.out.println("Total: " + ((forest.getTotalTree() * 8 + forest.getTotalTreetype() * 30) / 1024 / 1024) +
                "MB (instead of " + ((forest.getTotalTree() * 38) / 1024 / 1024) + "MB)");
    }

    // 生成指定范围内的随机数
    private static int random(int min, int max) {
        return min + (int) (Math.random() * ((max - min) + 1));
    }
}
相关推荐
蜡笔小新..19 小时前
【设计模式】软件设计原则——开闭原则&里氏替换&单一职责
java·设计模式·开闭原则·单一职责原则
性感博主在线瞎搞21 小时前
【面向对象】设计模式概念和分类
设计模式·面向对象·中级软件设计师·设计方法
lucifer31121 小时前
JavaScript 中的组合模式(十)
javascript·设计模式
lucifer31121 小时前
JavaScript 中的装饰器模式(十一)
javascript·设计模式
蜡笔小新..21 小时前
【设计模式】软件设计原则——依赖倒置&合成复用
设计模式·依赖倒置原则·合成复用原则
刷帅耍帅21 小时前
设计模式-代理模式
设计模式·代理模式
神的孩子都在歌唱1 天前
行为设计模式 -观察者模式- JAVA
java·观察者模式·设计模式
刷帅耍帅1 天前
设计模式-解释器模式
设计模式·解释器模式
刷帅耍帅1 天前
设计模式-备忘录模式
设计模式·备忘录模式