当前位置: 首页 > news >正文

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

一、组合模式

1.1概述

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

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

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

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

 1.2结构

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

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

1.3实现

软件菜单

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

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

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

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();}
}

 树枝节点(组合部件)

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>();}//添加菜单项或子菜单@Overridepublic void add(MenuComponent menuComponent) {menuComponentList.add(menuComponent);}//移除菜单项或子菜单@Overridepublic void remove(MenuComponent menuComponent) {menuComponentList.remove(menuComponent);}//获取指定的子菜单@Overridepublic MenuComponent getChild(int i) {return menuComponentList.get(i);}//打印菜单及子菜单@Overridepublic void print() {for (int i = 1; i < level; i++) {System.out.print("--");}System.out.println(name);for (MenuComponent menuComponent : menuComponentList) {menuComponent.print();}}
}

叶子节点(叶子构件)

package com.yanyu.Component;//菜单项类,继承自菜单组件
public class MenuItem extends MenuComponent {//构造函数public MenuItem(String name, int level) {this.name = name;this.level = level;}//打印菜单项@Overridepublic void print() {for (int i = 1; i < level; i++) {System.out.print("--");}System.out.println(name);}
}

客户端类

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 声明了 addremove 、getChild 方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。

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

  • 安全组合模式

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

1.5使用场景

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

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

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

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

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

二、享元模式 

2.1概述

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

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

定义

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

2.2结构

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

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

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

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

2.3实现

【例】俄罗斯方块

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

先来看类图:

抽象享元

package com.yanyu.Flyweight;// 抽象享元类
public abstract class AbstractBox {// 声明一个抽象方法,用于获取方块形状public abstract String getShape();// 实现一个公共方法,用于显示方块的形状和颜色public void display(String color) {System.out.println("方块形状:" + this.getShape() + " 颜色:" + color);}
}

具体亨元

package com.yanyu.Flyweight;/*** IBox是一个继承自AbstractBox的类。* 它代表一个I形状的方块。*/
public class IBox extends AbstractBox {/*** 重写了AbstractBox中的getShape方法。* 返回"I"表示这个方块是一个I形状的方块。*/@Overridepublic String getShape() {return "I";}
}
package com.yanyu.Flyweight;public class LBox extends AbstractBox {@Overridepublic String getShape() {return "L";}
}
package com.yanyu.Flyweight;public class OBox extends AbstractBox {@Overridepublic String getShape() {return "O";}
}

享元工厂

package com.yanyu.Flyweight;import java.util.HashMap;// 创建一个BoxFactory类
public class BoxFactory {// 创建一个静态的HashMap用于存储不同类型的AbstractBoxprivate 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对象放入HashMapmap.put("L", lBox); // 将LBox对象放入HashMapmap.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获取对应的AbstractBoxpublic AbstractBox getBox(String key) {return map.get(key); // 根据key从HashMap中获取对应的AbstractBox对象}
}

客户端类

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类使用了享元模式。我们先看下面的例子:


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" 文件代码,其它文件不需要修

抽象构建

package step1;
//抽象文件类:抽象构件
/********** Begin *********/public abstract class AbstractFile {public abstract void add(AbstractFile file);public abstract void remove(AbstractFile file);public abstract void scan();
}/********** End *********/

组合部件

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(); // 调用子文件或子文件夹的扫描方法}}
}

叶子构建

package step1;//图片文件类:叶子构件
public class ImageFile extends AbstractFile{private String fileName;public ImageFile(String fileName){this.fileName = fileName;}@Overridepublic void add(AbstractFile element) {System.out.println("对不起,不支持该方法");}@Overridepublic void remove(AbstractFile element) {System.out.println("对不起,不支持该方法");}@Overridepublic void scan() {System.out.println("扫描图片文件:"+fileName);}
}
package step1;//文本文件类:叶子构件
public class TextFile extends  AbstractFile{private String fileName;public TextFile(String fileName){this.fileName = fileName;}@Overridepublic void add(AbstractFile element) {System.out.println("对不起,不支持该方法");}@Overridepublic void remove(AbstractFile element) {System.out.println("对不起,不支持该方法");}@Overridepublic void scan() {System.out.println("扫描文本文件:"+fileName);}
}
package step1;//视频文件类:叶子构件
public class VideoFile extends AbstractFile{private String fileName;public VideoFile(String fileName){this.fileName = fileName;}@Overridepublic void add(AbstractFile element) {System.out.println("对不起,不支持该方法");}@Overridepublic void remove(AbstractFile element) {System.out.println("对不起,不支持该方法");}@Overridepublic void scan() {System.out.println("扫描视频文件:"+fileName);}
}

客户端类

// 定义客户端类
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” 文件的代码(其它文件不需要更改),计算对比,优化前后分别占用的内存数。

抽象享元

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); // 调用享元对象的绘制方法}
}
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);*/}
}

享元工厂

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)`: 这行代码用于将新创建的享元对象放入对象池中。如果对象池中不存在具有相同内在状态的享元对象,那么我们需要将新创建的享元对象放入对象池中,以便后续重复使用。

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

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);}}*/
}

客户端类

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));}
}

相关文章:

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

一、组合模式 1.1概述 组合模式是一种结构型设计模式&#xff0c;它允许将对象组合成树形结构&#xff0c;以表示“部分-整体”的层次结构。组合模式使得客户端可以一致地对待单个对象和对象组合&#xff0c;从而将复杂的层次结构展现为一个统一的树形结构。 在组合模式中&…...

HTTP之常见问答

1&#xff1a;HTTP/1.1 如何优化&#xff1f; &#xff1a;尽量避免发送 HTTP 请求&#xff1b;通过缓存技术&#xff0c;使用请求的 Etag 参数来处理判断缓存过期等问题&#xff0c;类似304状态码就是告诉客户端&#xff0c;缓存有效还能继续使用 &#xff1a;在需要发送 HTTP…...

java伪共享问题

参考文章 https://blog.csdn.net/qq_45443475/article/details/131417090 产生原因 cpu 与内核数据交换的单位是 cache 行&#xff0c;多核 cpu 的高速缓存在对同一个变量进行修改时由于缓存一致性协议导致对应的缓存失效。 缓存行的大小 cpu 架构有关系&#xff0c;如果是 …...

【Ubuntu】Ubuntu arm64 部署 Blazor Server 应用

部署步骤 发布安装运行环境&#xff1a;dotnet-sdk&#xff08;必装&#xff09;、aspnetcore-runtime、dotnet-runtime安装证书设置环境变量&#xff1a;临时变量、当前用户永久变量、所有用户的永久变量运行&#xff1a;终端运行、后台运行 基本情况 开发系统环境 系统&am…...

Android加固为何重要?很多人不学

为什么要加固&#xff1f; APP加固是对APP代码逻辑的一种保护。原理是将应用文件进行某种形式的转换&#xff0c;包括不限于隐藏&#xff0c;混淆&#xff0c;加密等操作&#xff0c;进一步保护软件的利益不受损坏。总结主要有以下三方面预期效果&#xff1a; 1.防篡改&#x…...

【C/PTA】函数专项练习(一)

本文结合PTA专项练习带领读者掌握函数&#xff0c;刷题为主注释为辅&#xff0c;在代码中理解思路&#xff0c;其它不做过多叙述。 目录 6-1 输出星期名6-2 三整数最大值6-3 数据排序6-4 多项式求值 6-1 输出星期名 请编写函数&#xff0c;根据星期数输出对应的星期名。 函数原…...

SUDS: Scalable Urban Dynamic Scenes

SUDS: Scalable Urban Dynamic Scenes&#xff1a;可扩展的城市动态场景 创新点 1.将场景分解为三个单独的哈希表数据结构&#xff0c;以高效地编码静态、动态和远场辐射场 2.利用无标签的目标信号&#xff0c;包括RGB图像、稀疏LiDAR、现成的自监督2D描述符&#xff0c;以及…...

蓝桥杯算法双周赛心得——迷宫逃脱(记忆化搜索)

大家好&#xff0c;我是晴天学长&#xff0c;非常经典实用的记忆化搜索题&#xff0c;当然也可以用dp做&#xff0c;我也会发dp的题解&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .迷宫逃脱 迷官逃脱…...

nodejs+vue线上生活超市购物商城系统w2c42

超市管理系统的开发流程包括对超市管理系统的需求分析&#xff0c;软件的设计建模以及编写程序实现系统所需功能这三个阶段。对超市管理系统的需求分析。在这个阶段&#xff0c;通过查阅书籍&#xff0c;走访商场搜集相关资料&#xff0c;了解经营者对软件功能的具体所需和建议…...

飞翔的小鸟

运行游戏如下&#xff1a; 碰到柱子就结束游戏 App GameApp类 package App;import main.GameFrame;public class GameApp {public static void main(String[] args) {//游戏的入口new GameFrame();} } main Barrier 类 package main;import util.Constant; import util.Ga…...

浅析OKR的敏捷性

前言 OKR对于工作的提升有着一定的不可替代的作用。特别在敏捷方面。 OKR的敏捷性 OKR&#xff08;Objectives and Key Results&#xff09;是一种目标设定框架&#xff0c;它的敏捷性主要体现在以下几个方面&#xff1a; 公开透明 OKR要求完全公开透明&#xff0c;让每个员…...

Linux+qt:创建动态库so,以及如何使用(详细步骤)

目录 1、根据安装Qt Creator的向导进行创建 2、开发动态库注意的一些细节 3、给动态库添加一个对外开放的接口文件 4、了解下Qt的 .pri文件&#xff08;非常实用&#xff09; 5、如何调用动态库.so 1、根据安装Qt Creator的向导进行创建 &#xff08;1&#xff09;选择“…...

如何将Docker的构建时间减少40%

与许多公司类似&#xff0c;我们为产品中使用的所有组件构建docker映像。随着时间的推移&#xff0c;其中一些映像变得越来越大&#xff0c;我们的CI构建花费的时间也越来越长。我的目标是CI构建不超过5分钟——差不多是喝杯咖啡休息的理想时间。如果构建花费的时间超过这个时间…...

二分查找——经典题目合集

文章目录 &#x1f99c;69. x 的平方根&#x1f33c;题目&#x1f33b;算法原理&#x1f337;代码实现 &#x1f433;35. 搜索插入位置&#x1f33c;题目&#x1f33b;算法原理&#x1f337;代码实现 &#x1f9ad;852. 山脉数组的峰顶索引&#x1f33c;题目&#x1f33b;算法原…...

在Jupyter Lab中使用多个环境,及魔法命令简介

一、Jupyter Lab使用conda虚拟环境 1、给虚拟环境添加 ipykernel 方法一: 创建环境时直接添加ipykernel 方法&#xff1a;conda create -n 【虚拟环境名称】python3.8 ipykernel实例如下&#xff1a; conda create -n tensorflow_cpu python3.8 ipykernel 方法二&#xff…...

知虾数据软件:电商人必备知虾数据软件,轻松掌握市场趋势

在当今数字化时代&#xff0c;数据已经成为了企业决策的重要依据。对于电商行业来说&#xff0c;数据更是至关重要。如果你想在电商领域中脱颖而出&#xff0c;那么你需要一款强大的数据分析工具来帮助你更好地了解市场、分析竞争对手、优化运营策略。而知虾数据软件就是这样一…...

c语言中*p1++和p1++有啥区别

在C语言中&#xff0c;*p1和p1是两个不同的表达式&#xff0c;有以下区别&#xff1a; *p1&#xff1a;这是一个后缀递增运算符的组合。首先&#xff0c;*p1会取出指针p1所指向的值&#xff0c;并且对p1进行递增操作。简而言之&#xff0c;这个表达式会先取出p1指向的值&#x…...

2

【任务 2】私有云服务运维[10 分] 【适用平台】私有云 【题目 1】OpenStack 开放镜像权限[0.5 分] 使 用 OpenStack 私 有 云 平 台 &#xff0c; 在 OpenStack 平台的 admin 项 目 中 使 用 cirros-0.3.4-x86_64-disk.img 镜像文件创建名为 glance-cirros 的镜像&#xff0c;通…...

SELinux零知识学习二十二、SELinux策略语言之类型强制(7)

接前一篇文章&#xff1a;SELinux零知识学习二十一、SELinux策略语言之类型强制&#xff08;6&#xff09; 二、SELinux策略语言之类型强制 3. 访问向量规则 AV规则就是按照对客体类别的访问许可指定具体含义的规则&#xff0c;SELinux策略语言目前支持四类AV规则&#xff1a…...

cadence layout lvs时出现error

Error&#xff1a;Schematic export failed or was cancelled.Please consult the transcript in the viewer window. 解决办法同下&#xff1a; cadence layout lvs时出现error-CSDN博客...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

Leetcode 3577. Count the Number of Computer Unlocking Permutations

Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接&#xff1a;3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯&#xff0c;要想要能够将所有的电脑解锁&#x…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

(二)原型模式

原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...

关于 WASM:1. WASM 基础原理

一、WASM 简介 1.1 WebAssembly 是什么&#xff1f; WebAssembly&#xff08;WASM&#xff09; 是一种能在现代浏览器中高效运行的二进制指令格式&#xff0c;它不是传统的编程语言&#xff0c;而是一种 低级字节码格式&#xff0c;可由高级语言&#xff08;如 C、C、Rust&am…...

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别

OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

蓝桥杯 冶炼金属

原题目链接 &#x1f527; 冶炼金属转换率推测题解 &#x1f4dc; 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V&#xff0c;是一个正整数&#xff0c;表示每 V V V 个普通金属 O O O 可以冶炼出 …...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...