組合模式(學習筆記)

  1. 意圖

  講對象組合成樹形結構以表示「部分——總體」的層次結構。Composite使得用戶對單個對象和組合對象的使用具備一致性html

  2. 動機

  若是應用的核心模型能用樹狀結構表示,在應用中使用組合模式纔有價值java

  假如,有兩類對象:產品和盒子。一個盒子能夠包含幾個產品或多個較小的盒子。這些小盒子也能夠包含一些產品或更小的盒子,以此類推canvas

 

 

   在此基礎上開發一個訂購系統。訂單中可能包含無包裝的產品,也可能包含裝滿產品的盒子....該如何計算每張訂單的總價格呢?安全

  組合模式使用一個通用的接口來與產品和盒子進行交互,而且在接口中聲明一個計算總價的方法。對於一個產品 該方法直接返回其價格 對於一個盒子 該方法遍歷盒子中的全部項目 詢問每一個項目的價格 而後返回該盒子的總價格 若是其中某個項目是小一號的盒子 那麼當前盒子也會遍歷其中的全部項目 以此類推 直到計算出全部內部組成部分的價格 該方式的最大優勢在於你無需瞭解構成樹狀結構的對象的具體類 你也無需瞭解對象是簡單的產品仍是複雜的盒子 你只需調用通用接口以相同的方式對其進行處理便可 當你調用該方法後 對象會將請求沿着樹結構傳遞下去編輯器

  3. 適用性

  • 若是但願實現樹狀對象結構
  • 但願客戶端代碼以相同的方式處理簡單和複雜元素

  4. 結構

 

 

  

  5. 效果

  1) 能夠利用多態和遞歸機制更方便地使用複雜樹結構ide

  2) 開閉原則。 無需更改現有代碼, 你就能夠在應用中添加新元素, 使其成爲對象樹的一部分this

  3) 簡化客戶代碼spa

  4) 對於功能差別較大的類, 提供公共接口或許會有困難。 在特定狀況下, 你須要過分通常化組件接口, 使其變得使人難以理解設計

  6. 代碼實現

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

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

  執行結果

  

  7. 與其餘模式的關係

  • 能夠在建立複雜組合樹時使用生成器模式, 由於這可以使其構造步驟以遞歸的方式運行
  • 責任鏈模式一般和組合模式結合使用。 在這種狀況下, 葉組件接收到請求後, 能夠將請求沿包含全體父組件的鏈一直傳遞至對象樹的底部
  • 可使用迭代器模式來遍歷組合樹
  • 可使用訪問者模式對整個組合樹執行操做
  • 可使用享元模式實現組合樹的共享葉節點以節省內存
  • 組合和裝飾模式的結構圖很類似, 由於二者都依賴遞歸組合來組織無限數量的對象
    裝飾相似於組合, 但其只有一個子組件。 此外還有一個明顯不一樣: 裝飾爲被封裝對象添加了額外的職責, 組合僅對其子節點的結果進行了 「求和」
    可是, 模式也能夠相互合做: 你可使用裝飾來擴展組合樹中特定對象的行爲

  •  

    大量使用組合和裝飾的設計一般可從對於原型模式的使用中獲益。 你能夠經過該模式來複制複雜結構, 而非從零開始從新構造  

  8. 已知應用

  一些 Java 標準程序庫中的組合示例:

  java.awt.Container#add(Component) (幾乎普遍存在於 Swing 組件中)

  javax.faces.component.UIComponent#getChildren() (幾乎普遍存在於 JSF UI 組件中)

  識別方法: 組合能夠經過將同一抽象或接口類型的實例放入樹狀結構的行爲方法來輕鬆識別

  9. 關於組合模式安全性和透明性的討論

  • 在類層次結構的根部定義子節點管理接口的方法具備良好的透明性,由於能夠一致的使用全部的組件,可是這一方法是以安全性爲代價的,由於客戶可能會作一些無心義的事情,例如在leaf中增長和刪除對象
  • 在composite類中定義和管理子部件的方法具備良好的安全性。可是這樣又損失了透明性,由於leaf和composite具備不一樣的接口
相關文章
相關標籤/搜索