讲对象组合成树形结构以表示“部分——总体”的层次结构。Composite使得用户对单个对象和组合对象的使用具备一致性html
若是应用的核心模型能用树状结构表示,在应用中使用组合模式才有价值java
假如,有两类对象:产品和盒子。一个盒子能够包含几个产品或多个较小的盒子。这些小盒子也能够包含一些产品或更小的盒子,以此类推canvas
在此基础上开发一个订购系统。订单中可能包含无包装的产品,也可能包含装满产品的盒子....该如何计算每张订单的总价格呢?安全
组合模式使用一个通用的接口来与产品和盒子进行交互,而且在接口中声明一个计算总价的方法。对于一个产品, 该方法直接返回其价格; 对于一个盒子, 该方法遍历盒子中的全部项目, 询问每一个项目的价格, 而后返回该盒子的总价格。 若是其中某个项目是小一号的盒子, 那么当前盒子也会遍历其中的全部项目, 以此类推, 直到计算出全部内部组成部分的价格。 该方式的最大优势在于你无需了解构成树状结构的对象的具体类。 你也无需了解对象是简单的产品仍是复杂的盒子。 你只需调用通用接口以相同的方式对其进行处理便可。 当你调用该方法后, 对象会将请求沿着树结构传递下去编辑器
1) 能够利用多态和递归机制更方便地使用复杂树结构ide
2) 开闭原则。 无需更改现有代码, 你就能够在应用中添加新元素, 使其成为对象树的一部分this
3) 简化客户代码spa
4) 对于功能差别较大的类, 提供公共接口或许会有困难。 在特定状况下, 你须要过分通常化组件接口, 使其变得使人难以理解设计
shapes/Shape.java: 通用形状接口3d
package composite.shapes; import java.awt.*; /** * @author GaoMing * @date 2021/7/12 - 14:16 */ 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); }
shapes/BaseShape.java: 提供基本功能的抽象形状
package composite.shapes; import java.awt.*; /** * @author GaoMing * @date 2021/7/12 - 14:17 */ public 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); } // ... } }
shapes/Dot.java: 点
package composite.shapes; import java.awt.*; /** * @author GaoMing * @date 2021/7/12 - 14:35 */ 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()); } }
shapes/Circle.java: 圆形
package composite.shapes; import java.awt.*; /** * @author GaoMing * @date 2021/7/12 - 14:38 */ 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); } }
shapes/Rectangle.java: 长方形
package composite.shapes; import java.awt.*; /** * @author GaoMing * @date 2021/7/12 - 14:39 */ 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); } }
shapes/CompoundShape.java: 由其余形状对象组成的复合形状
package composite.shapes; import java.awt.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @author GaoMing * @date 2021/7/12 - 14:40 */ 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 (composite.shapes.Shape child : children) { child.paint(graphics); } } }
editor/ImageEditor.java: 形状编辑器
package composite.editor; import composite.shapes.CompoundShape; import composite.shapes.Shape; import javax.swing.*; import javax.swing.border.Border; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; /** * @author GaoMing * @date 2021/7/12 - 15:10 */ public class ImageEditor { private EditorCanvas canvas; private CompoundShape allShapes = new CompoundShape(); public ImageEditor() { canvas = new EditorCanvas(); } // 使用shapes.shape 类型的对象 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); } } }
Demo.java: 客户端代码
package composite; import composite.editor.ImageEditor; import composite.shapes.CompoundShape; import composite.shapes.Dot; import composite.shapes.Circle; import composite.shapes.Rectangle; import java.awt.*; /** * @author GaoMing * @date 2021/7/12 - 14:16 */ public class Demo { 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 标准程序库中的组合示例:
java.awt.Container#add(Component) (几乎普遍存在于 Swing 组件中)
javax.faces.component.UIComponent#getChildren() (几乎普遍存在于 JSF UI 组件中)
识别方法: 组合能够经过将同一抽象或接口类型的实例放入树状结构的行为方法来轻松识别