裴大头-秦可爱

裴大头-秦可爱

Java 23种设计模式——组合模式(Composite Pattern)

发表于 2023-12-01
裴大头
阅读量 1167
更新于 2023-12-01

什么是组合模式?

组合模式是一种结构型设计模式,它允许我们将对象组织成树状结构,并以统一的方式处理这些对象。组合模式通过将对象分为两个主要角色来实现这一目标:叶节点和容器节点。叶节点表示树结构中的最底层对象,而容器节点则表示由叶节点或其他容器节点组成的对象。

组合模式的结构

组合模式的结构包含以下几个核心组件:

  • Component(组件):定义叶节点和容器节点的共同接口,可以在该接口上定义默认行为和访问子节点的方法。
  • Leaf(叶节点):表示树结构中的最底层对象,没有子节点。
  • Composite(容器节点):由叶节点或其他容器节点组成的对象,可以包含子节点,并实现在Component接口中定义的方法。

下面是一个简化的类图,展示了组合模式的结构:

        +------------------+
        |     Component    |
        +------------------+
        | +operation()     |
        | +add(Component)  |
        | +remove(Component) |
        | +getChild(int)   |
        +------------------+
                 / \
                /   \
               /     \
              /       \
             /         \
+--------------+   +--------------+
|     Leaf     |   |   Composite  |
+--------------+   +--------------+
| +operation() |   | +operation() |
+--------------+   +--------------+

组合模式的应用场景

组合模式适用于以下情况:

  1. 当你希望以统一的方式处理对象集合时,可以使用组合模式。这种方式使得客户端可以将单个对象和组合对象一视同仁。
  2. 当你有一个对象树,且对象之间具有层次关系时,组合模式可以帮助你更好地管理和操作这些对象。
  3. 当你希望添加或移除树中的对象时,组合模式能够提供一种一致的方式来执行这些操作。

示例代码

shapes

shapes/Shape.java:

通用形状接口

package com.pnkx.designpatterns.composite.shapes;

import java.awt.*;

public interface Shape {
    int getX();
    int getY();
    int getWidth();
    int getHeight();
    void move(int x, int y);
    boolean isInsideBounds(int x, int y);
    void select();
    void unSelect();
    boolean isSelected();
    void paint(Graphics graphics);
}
java

shapes/BaseShape.java:

提供基本功能的抽象形状

package com.pnkx.designpatterns.composite.shapes;

import java.awt.*;

abstract class BaseShape implements Shape {
    public int x;
    public int y;
    public Color color;
    private boolean selected = false;

    BaseShape(int x, int y, Color color) {
        this.x = x;
        this.y = y;
        this.color = color;
    }

    @Override
    public int getX() {
        return x;
    }

    @Override
    public int getY() {
        return y;
    }

    @Override
    public int getWidth() {
        return 0;
    }

    @Override
    public int getHeight() {
        return 0;
    }

    @Override
    public void move(int x, int y) {
        this.x += x;
        this.y += y;
    }

    @Override
    public boolean isInsideBounds(int x, int y) {
        return x > getX() && x < (getX() + getWidth()) &&
                y > getY() && y < (getY() + getHeight());
    }

    @Override
    public void select() {
        selected = true;
    }

    @Override
    public void unSelect() {
        selected = false;
    }

    @Override
    public boolean isSelected() {
        return selected;
    }

    void enableSelectionStyle(Graphics graphics) {
        graphics.setColor(Color.LIGHT_GRAY);

        Graphics2D g2 = (Graphics2D) graphics;
        float[] dash1 = {2.0f};
        g2.setStroke(new BasicStroke(1.0f,
                BasicStroke.CAP_BUTT,
                BasicStroke.JOIN_MITER,
                2.0f, dash1, 0.0f));
    }

    void disableSelectionStyle(Graphics graphics) {
        graphics.setColor(color);
        Graphics2D g2 = (Graphics2D) graphics;
        g2.setStroke(new BasicStroke());
    }


    @Override
    public void paint(Graphics graphics) {
        if (isSelected()) {
            enableSelectionStyle(graphics);
        }
        else {
            disableSelectionStyle(graphics);
        }

        // ...
    }
}
java

shapes/Dot.java:

package com.pnkx.designpatterns.composite.shapes;

import java.awt.*;

public class Dot extends BaseShape {
    private final int DOT_SIZE = 3;

    public Dot(int x, int y, Color color) {
        super(x, y, color);
    }

    @Override
    public int getWidth() {
        return DOT_SIZE;
    }

    @Override
    public int getHeight() {
        return DOT_SIZE;
    }

    @Override
    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.fillRect(x - 1, y - 1, getWidth(), getHeight());
    }
}
java

shapes/Circle.java:

圆形

package com.pnkx.designpatterns.composite.shapes;

import java.awt.*;

public class Circle extends BaseShape {
    public int radius;

    public Circle(int x, int y, int radius, Color color) {
        super(x, y, color);
        this.radius = radius;
    }

    @Override
    public int getWidth() {
        return radius * 2;
    }

    @Override
    public int getHeight() {
        return radius * 2;
    }

    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1);
    }
}
java

shapes/Rectangle.java:

三角形

package com.pnkx.designpatterns.composite.shapes;

import java.awt.*;

public class Rectangle extends BaseShape {
    public int width;
    public int height;

    public Rectangle(int x, int y, int width, int height, Color color) {
        super(x, y, color);
        this.width = width;
        this.height = height;
    }

    @Override
    public int getWidth() {
        return width;
    }

    @Override
    public int getHeight() {
        return height;
    }

    @Override
    public void paint(Graphics graphics) {
        super.paint(graphics);
        graphics.drawRect(x, y, getWidth() - 1, getHeight() - 1);
    }
}
java

shapes/CompoundShape.java:

由其他形状对象组成的复合形状

package com.pnkx.designpatterns.composite.shapes;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class CompoundShape extends BaseShape {
    protected List<Shape> children = new ArrayList<>();

    public CompoundShape(Shape... components) {
        super(0, 0, Color.BLACK);
        add(components);
    }

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

    public void add(Shape... components) {
        children.addAll(Arrays.asList(components));
    }

    public void remove(Shape child) {
        children.remove(child);
    }

    public void remove(Shape... components) {
        children.removeAll(Arrays.asList(components));
    }

    public void clear() {
        children.clear();
    }

    @Override
    public int getX() {
        if (children.size() == 0) {
            return 0;
        }
        int x = children.get(0).getX();
        for (Shape child : children) {
            if (child.getX() < x) {
                x = child.getX();
            }
        }
        return x;
    }

    @Override
    public int getY() {
        if (children.size() == 0) {
            return 0;
        }
        int y = children.get(0).getY();
        for (Shape child : children) {
            if (child.getY() < y) {
                y = child.getY();
            }
        }
        return y;
    }

    @Override
    public int getWidth() {
        int maxWidth = 0;
        int x = getX();
        for (Shape child : children) {
            int childsRelativeX = child.getX() - x;
            int childWidth = childsRelativeX + child.getWidth();
            if (childWidth > maxWidth) {
                maxWidth = childWidth;
            }
        }
        return maxWidth;
    }

    @Override
    public int getHeight() {
        int maxHeight = 0;
        int y = getY();
        for (Shape child : children) {
            int childsRelativeY = child.getY() - y;
            int childHeight = childsRelativeY + child.getHeight();
            if (childHeight > maxHeight) {
                maxHeight = childHeight;
            }
        }
        return maxHeight;
    }

    @Override
    public void move(int x, int y) {
        for (Shape child : children) {
            child.move(x, y);
        }
    }

    @Override
    public boolean isInsideBounds(int x, int y) {
        for (Shape child : children) {
            if (child.isInsideBounds(x, y)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void unSelect() {
        super.unSelect();
        for (Shape child : children) {
            child.unSelect();
        }
    }

    public boolean selectChildAt(int x, int y) {
        for (Shape child : children) {
            if (child.isInsideBounds(x, y)) {
                child.select();
                return true;
            }
        }
        return false;
    }

    @Override
    public void paint(Graphics graphics) {
        if (isSelected()) {
            enableSelectionStyle(graphics);
            graphics.drawRect(getX() - 1, getY() - 1, getWidth() + 1, getHeight() + 1);
            disableSelectionStyle(graphics);
        }

        for (Shape child : children) {
            child.paint(graphics);
        }
    }
}
java

editor

editor/ImageEditor.java:

形状编辑器

package com.pnkx.designpatterns.composite.editor;

import com.pnkx.designpatterns.composite.shapes.CompoundShape;
import com.pnkx.designpatterns.composite.shapes.Shape;

import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class ImageEditor {
    private EditorCanvas canvas;
    private CompoundShape allShapes = new CompoundShape();

    public ImageEditor() {
        canvas = new EditorCanvas();
    }

    public void loadShapes(Shape... shapes) {
        allShapes.clear();
        allShapes.add(shapes);
        canvas.refresh();
    }

    private class EditorCanvas extends Canvas {
        JFrame frame;

        private static final int PADDING = 10;

        EditorCanvas() {
            createFrame();
            refresh();
            addMouseListener(new MouseAdapter() {
                @Override
                public void mousePressed(MouseEvent e) {
                    allShapes.unSelect();
                    allShapes.selectChildAt(e.getX(), e.getY());
                    e.getComponent().repaint();
                }
            });
        }

        void createFrame() {
            frame = new JFrame();
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setLocationRelativeTo(null);

            JPanel contentPanel = new JPanel();
            Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING);
            contentPanel.setBorder(padding);
            frame.setContentPane(contentPanel);

            frame.add(this);
            frame.setVisible(true);
            frame.getContentPane().setBackground(Color.LIGHT_GRAY);
        }

        public int getWidth() {
            return allShapes.getX() + allShapes.getWidth() + PADDING;
        }

        public int getHeight() {
            return allShapes.getY() + allShapes.getHeight() + PADDING;
        }

        void refresh() {
            this.setSize(getWidth(), getHeight());
            frame.pack();
        }

        public void paint(Graphics graphics) {
            allShapes.paint(graphics);
        }
    }
}
java

Main.java:

客户端代码

package com.pnkx.designpatterns.composite;

import com.pnkx.designpatterns.composite.editor.ImageEditor;
import com.pnkx.designpatterns.composite.shapes.Circle;
import com.pnkx.designpatterns.composite.shapes.CompoundShape;
import com.pnkx.designpatterns.composite.shapes.Dot;
import com.pnkx.designpatterns.composite.shapes.Rectangle;

import java.awt.*;

public class Main {
    public static void main(String[] args) {
        ImageEditor editor = new ImageEditor();

        editor.loadShapes(
                new Circle(10, 10, 10, Color.BLUE),

                new CompoundShape(
                    new Circle(110, 110, 50, Color.RED),
                    new Dot(160, 160, Color.RED)
                ),

                new CompoundShape(
                        new Rectangle(250, 250, 100, 100, Color.GREEN),
                        new Dot(240, 240, Color.GREEN),
                        new Dot(240, 360, Color.GREEN),
                        new Dot(360, 360, Color.GREEN),
                        new Dot(360, 240, Color.GREEN)
                )
        );
    }
}
java

执行结果

image.png

总结

组合模式是一种强大且灵活的设计模式,它允许我们以统一的方式处理对象集合,并将对象组织成树状结构。通过定义共同接口和使用叶节点和容器节点,组合模式使得处理复杂对象结构变得简单而一致。在开发具有层次结构的系统时,组合模式是一个有用的工具。

源码

组合模式源码下载

Java 23种设计模式(含源码)

评论
来发一针见血的评论吧!
表情

快来发表评论吧~

推荐文章
  • Vue项目代码规范

    1点赞1评论

  • JavaScript 的事件循环机制

    1点赞1评论

  • 聊一聊我的文本编辑器

    1点赞11评论

  • JavaScript的常用遍历方法整理

    1点赞8评论

  • 前端面试精选-基础篇

    1点赞0评论

Crafted with by Pei你看雪

小破站居然运行了 865 天访客 23357

© 2023 Pei你看雪鲁ICP备19037910号-2