講對象組合成樹形結構以表示「部分——總體」的層次結構。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 組件中)
識別方法: 組合能夠經過將同一抽象或接口類型的實例放入樹狀結構的行爲方法來輕鬆識別