一、组合模式
1.1概述
组合模式是一种结构型设计模式,它允许将对象组合成树形结构,以表示“部分-整体”的层次结构。组合模式使得客户端可以一致地对待单个对象和对象组合,从而将复杂的层次结构展现为一个统一的树形结构。
在组合模式中,一般会定义一个抽象类或接口来表示组合中的所有对象,包括叶子对象和容器对象;叶子对象表示树形结构中的最底层节点,容器对象表示树形结构中的分支节点,同时容器对象可以包含叶子对象或其他容器对象。
组合模式能够有效地简化代码结构,提高代码的可维护性和可扩展性,同时也具有良好的透明性和灵活性。在实际编程中,组合模式广泛应用于树形结构的处理、图形绘制、文件系统等领域。
对于这个图片肯定会非常熟悉,上图我们可以看做是一个文件系统,对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点后,就可以对叶子节点进行相关的操作。可以将这颗树理解成一个大的容器,容器里面包含很多的成员对象,这些成员对象即可是容器对象也可以是叶子对象。但是由于容器对象和叶子对象在功能上面的区别,使得我们在使用的过程中必须要区分容器对象和叶子对象,但是这样就会给客户带来不必要的麻烦,作为客户而已,它始终希望能够一致的对待容器对象和叶子对象。
1.2结构
组合模式主要包含三种角色:
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>();
}
//添加菜单项或子菜单
@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();
}
}
}
叶子节点(叶子构件)
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);
}
}
客户端类
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组合模式的分类
1.5使用场景
组合模式为你提供了两种共享公共接口的基本元素类型: 简单叶节点和复杂容器。 容器中可以包含叶节点和其他容器。 这使得你可以构建树状嵌套递归对象结构。
组合模式中定义的所有元素共用同一个接口。 在这一接口的帮助下, 客户端不必在意其所使用的对象的具体类。
组合模式正是应树形结构而生,所以组合模式的使用场景就是出现树形结构的地方。比如:文件目录显示,多级目录呈现等树形结构数据的操作。
二、享元模式
2.1概述
享元模式是一种结构型设计模式,旨在减少内存占用和提高性能。它将对象分解为可共享的和不可共享的部分。可共享的部分是多个对象共有的、不变的部分,它们可以被多个对象共享使用,从而减少内存占用。不可共享的部分是对象不同的、可变的部分,每个对象各自拥有不同的副本。
通过应用享元模式,我们可以大幅度减少系统中的对象数量,提高系统的性能和响应速度。然而,享元模式会增加程序的复杂性和开发难度,因为需要将对象分解为可共享和不可共享的部分,并维护对象的共享池。因此,需要在实际应用中根据具体情况进行权衡和取舍。
定义
运用共享技术来有效地支持大量细粒度对象的复用。它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似对象的开销,从而提高系统资源的利用率。
2.2结构
享元(Flyweight )模式中存在以下两种状态:
享元模式的主要有以下角色:
- 抽象享元角色(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形状的方块。
*/
@Override
public String getShape() {
return "I";
}
}
package com.yanyu.Flyweight;
public class LBox extends AbstractBox {
@Override
public String getShape() {
return "L";
}
}
package com.yanyu.Flyweight;
public class OBox extends AbstractBox {
@Override
public String getShape() {
return "O";
}
}
享元工厂
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对象
}
}
客户端类
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应用场景
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
类图编程模拟实现。实现方式
确保应用的核心模型能够以树状结构表示。 尝试将其分解为简单元素和容器。 记住, 容器必须能够同时包含简单元素和其他容器。
声明组件接口及其一系列方法, 这些方法对简单和复杂元素都有意义。
创建一个叶节点类表示简单元素。 程序中可以有多个不同的叶节点类。
创建一个容器类表示复杂元素。 在该类中, 创建一个数组成员变量来存储对于其子元素的引用。 该数组必须能够同时保存叶节点和容器, 因此请确保将其声明为组合接口类型。实现组件接口方法时, 记住容器应该将大部分工作交给其子元素来完成。
记住,这些操作可在组件接口中声明。 这将会违反接口隔离原则,因为叶节点类中的这些方法为空。 但是,这可以让客户端无差别地访问所有元素, 即使是组成树状结构的元素。
编程要求
根据提示,在右侧编辑器 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;
}
@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);
}
}
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);
}
}
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);
}
}
客户端类
// 定义客户端类
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
类)。请分析“树”的状态中哪些是外在状态,哪些是内部状态,然后补全代码。实现方式
将需要改写为享元的类成员变量拆分为两个部分:内在状态,包含不变的、 可在许多对象中重复使用的数据的成员变量。外在状态, 包含每个对象各自不同的情景数据的成员变量;
你可以有选择地创建工厂类来管理享元缓存池, 它负责在新建享元时检查已有的享元。 如果选择使用工厂, 客户端就只能通过工厂来请求享元, 它们需要将享元的内在状态作为参数传递给工厂;
客户端必须存储和计算外在状态 (情景) 的数值, 因为只有这样才能调用享元对象的方法。 为了使用方便, 外在状态和引用享元的成员变量可以移动到单独的情景类中。
编程提示
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));
}
}
原文地址:https://blog.csdn.net/qq_62377885/article/details/134547712
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_3624.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!