《设计模式》12.组合模式(结构型)

【《设计模式》12.组合模式(结构型)】又称“整体—部分”模式,组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构,使用户对单个对象和组合对象具有访问一致性。
角色 抽象构件(Component):抽象类,定义叶子构件和树枝构件的公共接口,可提供默认实现,叶子构件中提供空实现或抛出异常
叶子构件(Leaf):没有子节点,实现抽象构件中的公共接口(空实现或抛出异常)
树枝构件(Composite):包含子节点,子节点可以是叶子构件也可以是树枝构件,实现抽象构件中公共接口
抽象构件 Component

public abstract class Component { protected static final String SPACE = " "; protected static final String hyphen = "-"; private String name; public Component(String name) { this.name = name; }public String getName() { return this.name; }public abstract boolean add(Component component); public abstract Component getChild(int index); public abstract boolean remove(Component c); public abstract void operation(int spaces); }

叶子构件 Leaf
import java.util.Collections; public class Leaf extends Component { public Leaf(String name) { super(name); }@Override public boolean add(Component component) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public Component getChild(int index) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public boolean remove(Component c) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public void operation(int spaces) { StringBuffer line = new StringBuffer(); if (spaces > 0) { line.append(String.join("", Collections.nCopies(spaces, SPACE))); line.append("|- "); } line.append(getName()); System.out.println(line); } }

树枝构件 Composite
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Composite extends Component { private List components = new ArrayList(); public Composite(String name) { super(name); }@Override public boolean add(Component component) { return components.add(component); }@Override public Component getChild(int index) { return components.get(index); }@Override public boolean remove(Component c) { return components.remove(c); }@Override public void operation(int spaces) { StringBuffer line = new StringBuffer(); if (spaces > 0) { line.append(String.join("", Collections.nCopies(spaces, SPACE))); line.append("|- "); } line.append(getName()); System.out.println(line); int length = line.length() - spaces; for (Component component : components) { int mid = (length % 2 == 0) ? (length / 2 - 1) : (length / 2 + 1); component.operation(mid + spaces); } } }

测试类
public class CompositeTester { public static void main(String args[]) { Component root = new Composite("root"); root.add(new Leaf("node#1")); Component branch2 = new Composite("branch#2"); root.add(branch2); root.add(new Leaf("node#2")); branch2.add(new Leaf("node#3")); branch2.add(new Leaf("node#4")); Composite branch3 = new Composite("branch#3"); branch2.add(branch3); branch3.add(new Leaf("node#5")); branch3.add(new Leaf("node#6")); branch3.add(new Leaf("node#7")); root.operation(0); Component leaf = new Leaf("node#8"); leaf.add(new Leaf("node#9")); } }

输出
root |- node#1 |- branch#2 |- node#3 |- node#4 |- branch#3 |- node#5 |- node#6 |- node#7 |- node#2 Exception in thread "main" java.lang.UnsupportedOperationException: Leaf at com.designpatterns.composite.base.Leaf.add(Leaf.java:12) at com.designpatterns.composite.base.CompositeTester.main(CompositeTester.java:23)

举例:文件 & 文件夹 抽象构件 File
import lombok.Getter; import lombok.Setter; import java.util.List; public abstract class File { @Getter @Setter private String pathname; @Getter @Setter private String path; @Getter @Setter private String fileName; public File(String pathname) { if (pathname == null) { throw new IllegalArgumentException(pathname); } pathname = pathname.replace("/", java.io.File.separator); if (!pathname.contains(java.io.File.separator)) { throw new IllegalArgumentException(pathname); } this.pathname = pathname; int indexOfSep = pathname.lastIndexOf(java.io.File.separator); this.path = pathname.substring(0, indexOfSep); this.fileName = pathname.substring(indexOfSep + 1); }public File(File parent, String fileName) { if (parent == null) { throw new IllegalArgumentException("null"); } if (fileName == null || fileName.isEmpty()) { throw new IllegalArgumentException(fileName); }this.path = parent.getPathname(); this.fileName = fileName; this.pathname = parent.getPathname() + java.io.File.separator + fileName; }public static boolean isDirectory(String fileName) { return !fileName.contains("."); }public abstract boolean add(File f); public abstract File getChild(String fileName); public abstract boolean remove(String fileName); public abstract List> list(); }

叶子构件 Text
import java.util.List; public class Text extends File { public Text(String pathname) { super(pathname); }public Text(File parent, String fileName) { super(parent, fileName); }@Override public boolean add(File f) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public File getChild(String fileName) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public boolean remove(String fileName) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public List> list() { throw new UnsupportedOperationException(getClass().getSimpleName()); } }

叶子构件 Audio
import java.util.List; public class Audio extends File { public Audio(String pathname) { super(pathname); }public Audio(File parent, String fileName) { super(parent, fileName); }@Override public boolean add(File f) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public File getChild(String fileName) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public boolean remove(String fileName) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public List> list() { throw new UnsupportedOperationException(getClass().getSimpleName()); } }

叶子构件 Video
import java.util.List; public class Video extends File { public Video(String pathname) { super(pathname); }public Video(File parent, String fileName) { super(parent, fileName); }@Override public boolean add(File f) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public File getChild(String fileName) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public boolean remove(String fileName) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public List> list() { throw new UnsupportedOperationException(getClass().getSimpleName()); } }

树枝构件 Directory
import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class Directory extends File { private List files = new ArrayList<>(); public Directory(String pathname) { super(pathname); }public Directory(File parent, String fileName) { super(parent, fileName); }@Override public boolean add(File f) { return files.add(f); }@Override public File getChild(String fileName) { Iterator iter = files.iterator(); while (iter.hasNext()) { File f = iter.next(); if (f.getFileName().equals(fileName)) { return f; } } return null; }@Override public boolean remove(String fileName) { Iterator iter = files.iterator(); while (iter.hasNext()) { File f = iter.next(); if (f.getFileName().equals(fileName)) { iter.remove(); return true; } } return false; }@Override public List> list() { List> pathnames = new ArrayList<>(); for (File f : files) { pathnames.add(f.getPathname()); if (isDirectory(f.getFileName())) { pathnames.addAll(f.list()); } } return pathnames; } }

测试类
import java.util.List; public class FileTester { public static void main(String args[]) { File root = new Directory("D:/root"); File readme = new Text(root, "readme.txt"); root.add(readme); File musicListTxt = new Text(root, "music-list.txt"); root.add(musicListTxt); File musicDir = new Directory(root, "musics"); File giveMeYourHeart = new Audio(musicDir, "give you my heart.mp3"); File heyJude = new Audio(musicDir, "hey judy.wma"); musicDir.add(giveMeYourHeart); musicDir.add(heyJude); root.add(musicDir); File videoListTxt = new Text(root, "video-list.txt"); root.add(videoListTxt); File videoDir = new Directory(root, "videos"); File superMan = new Video(videoDir, "超人.rmvb"); videoDir.add(superMan); root.add(videoDir); List> pathnames = root.list(); for (String pathname : pathnames) { System.out.println(pathname); }File file = root.getChild("readme.txt"); System.out.println(file.getPathname()); } }

D:\root\readme.txt D:\root\music-list.txt D:\root\musics D:\root\musics\give you my heart.mp3 D:\root\musics\hey judy.wma D:\root\video-list.txt D:\root\videos D:\root\videos\超人.rmvb D:\root\readme.txt

举例2:html元素
import lombok.Data; import lombok.Getter; import lombok.Setter; import java.util.HashMap; import java.util.Map; public abstract class Element { @Getter private String tagName; protected long height; protected long lineHeight; protected long width; protected Margin margin = new Margin(); private Border border = new Border(); protected Padding padding = new Padding(); protected Map, Object> styleCache = new HashMap<>(); @Getter @Setter private String id; public Element(String tagName) { this.tagName = tagName; }public Element(String tagName, String id) { this.tagName = tagName; this.id = id; }public abstract boolean add(Element e); public abstract Element getChild(String id); public abstract boolean remove(Element e); public abstract boolean empty(); public abstract void print(int tabs); public void addStyle(String name, Object value) { this.styleCache.put(name, value); }public void removeStyle(String name) { this.styleCache.remove(name); }public void setBorderTop(long top) { this.border.setTop(top); this.styleCache.put("border-top", top); }public void setBorderRight(long right) { this.border.setRight(right); this.styleCache.put("border-right", right); }public void setBorderBottom(long bottom) { this.border.setBottom(bottom); this.styleCache.put("border-bottom", bottom); }public void setBorderLeft(long left) { this.border.setLeft(left); this.styleCache.put("border-left", left); }public void setBorder(long topAndBottom, long leftAndRight) { this.border.setTop(topAndBottom); this.border.setRight(leftAndRight); this.border.setBottom(topAndBottom); this.border.setLeft(leftAndRight); this.styleCache.put("border-top", topAndBottom); this.styleCache.put("border-right", leftAndRight); this.styleCache.put("border-bottom", topAndBottom); this.styleCache.put("border-left", leftAndRight); }public void setBorder(long top, long right, long bottom, long left) { this.border.setTop(top); this.border.setRight(right); this.border.setBottom(bottom); this.border.setLeft(left); this.styleCache.put("border-top", top); this.styleCache.put("border-right", right); this.styleCache.put("border-bottom", bottom); this.styleCache.put("border-left", left); }public void setMarginTop(long top) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setMarginRight(long right) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setMarginBottom(long bottom) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setMarginLeft(long left) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setMargin(long topAndBottom, long leftAndRight) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setMargin(long top, long right, long bottom, long left) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setPaddingTop(long top) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setPaddingRight(long right) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setPaddingBottom(long bottom) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setPaddingLeft(long left) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setPadding(long topAndBottom, long leftAndRight) { throw new UnsupportedOperationException(getClass().getSimpleName()); }public void setPadding(long top, long right, long bottom, long left) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Data public class Margin { private long top; private long left; private long right; private long bottom; }@Data public class Border { private long top; private long left; private long right; private long bottom; }@Data public class Padding { private long top; private long left; private long right; private long bottom; } }

import java.util.Collections; import java.util.Map; public class InlineElement extends Element { private String text; public InlineElement(String tagName) { super(tagName); addStyle("display", "inline"); }public InlineElement(String tagName, String id) { super(tagName, id); addStyle("display", "inline"); }public String getText() { return text; }public void setText(String text) { this.text = text; }@Override public boolean add(Element e) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public Element getChild(String id) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public boolean remove(Element e) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public boolean empty() { text = ""; return true; }@Override public void print(int tabs) { String prefix = String.join("", Collections.nCopies(tabs, "\t")); StringBuffer styles = new StringBuffer(); for (Map.Entry, Object> style : styleCache.entrySet()) { if (styles.length() > 0) { styles.append("; "); } styles.append(style.getKey()).append(":").append(style.getValue()); } StringBuffer content = new StringBuffer(); content.append(prefix).append("<").append(getTagName()); if (getId() != null && !getId().isEmpty()) { content.append(" id=\"").append(getId()); } content.append("\" style=\"").append(styles).append("\">"); if (text != null && !text.isEmpty()) { content.append(text); } content.append(""); System.out.println(content); } }

import java.util.Collections; import java.util.Map; public class InlineBlockElement extends Element { private String text; public InlineBlockElement(String tagName) { super(tagName); addStyle("display", "inline-block"); addStyle("float", "left"); // or right addStyle("position", "absolute"); // or fixed }public InlineBlockElement(String tagName, String id) { super(tagName, id); addStyle("display", "inline-block"); addStyle("float", "left"); // or right addStyle("position", "absolute"); // or fixed }public String getText() { return text; }public void setText(String text) { this.text = text; }@Override public boolean add(Element e) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public Element getChild(String id) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public boolean remove(Element e) { throw new UnsupportedOperationException(getClass().getSimpleName()); }@Override public boolean empty() { text = ""; return true; }@Override public void print(int tabs) { String prefix = String.join("", Collections.nCopies(tabs, "\t")); StringBuffer styles = new StringBuffer(); for (Map.Entry, Object> style : styleCache.entrySet()) { if (styles.length() > 0) { styles.append("; "); } styles.append(style.getKey()).append(":").append(style.getValue()); } StringBuffer content = new StringBuffer(); content.append(prefix).append("<").append(getTagName()); if (getId() != null && !getId().isEmpty()) { content.append(" id=\"").append(getId()); } content.append("\" style=\"").append(styles).append("\">"); if (text != null && !text.isEmpty()) { content.append(text); } content.append(prefix).append(""); System.out.println(content); }public void setMarginTop(long top) { this.margin.setTop(top); this.styleCache.put("margin-top", top); }public void setMarginRight(long right) { this.margin.setRight(right); this.styleCache.put("margin-right", right); }public void setMarginBottom(long bottom) { this.margin.setBottom(bottom); this.styleCache.put("margin-bottom", bottom); }public void setMarginLeft(long left) { this.margin.setLeft(left); this.styleCache.put("margin-left", left); }public void setMargin(long topAndBottom, long leftAndRight) { this.margin.setTop(topAndBottom); this.margin.setRight(leftAndRight); this.margin.setBottom(topAndBottom); this.margin.setLeft(leftAndRight); this.styleCache.put("margin-top", topAndBottom); this.styleCache.put("margin-right", leftAndRight); this.styleCache.put("margin-bottom", topAndBottom); this.styleCache.put("margin-left", leftAndRight); }public void setMargin(long top, long right, long bottom, long left) { this.margin.setTop(top); this.margin.setRight(right); this.margin.setBottom(bottom); this.margin.setLeft(left); this.styleCache.put("margin-top", top); this.styleCache.put("margin-right", right); this.styleCache.put("margin-bottom", bottom); this.styleCache.put("margin-left", right); }public void setPaddingTop(long top) { this.padding.setTop(top); this.styleCache.put("padding-top", top); }public void setPaddingRight(long right) { this.padding.setRight(right); this.styleCache.put("padding-right", right); }public void setPaddingBottom(long bottom) { this.padding.setBottom(bottom); this.styleCache.put("padding-bottom", bottom); }public void setPaddingLeft(long left) { this.padding.setLeft(left); this.styleCache.put("padding-left", left); }public void setPadding(long topAndBottom, long leftAndRight) { this.padding.setTop(topAndBottom); this.padding.setRight(leftAndRight); this.padding.setBottom(topAndBottom); this.padding.setLeft(leftAndRight); this.styleCache.put("padding-top", topAndBottom); this.styleCache.put("padding-right", leftAndRight); this.styleCache.put("padding-bottom", topAndBottom); this.styleCache.put("padding-left", leftAndRight); }public void setPadding(long top, long right, long bottom, long left) { this.padding.setTop(top); this.padding.setRight(right); this.padding.setBottom(bottom); this.padding.setLeft(left); this.styleCache.put("padding-top", top); this.styleCache.put("padding-right", right); this.styleCache.put("padding-bottom", bottom); this.styleCache.put("padding-left", left); } }

import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; public class BlockElement extends Element { private String text; private Element parent; private List children = new ArrayList<>(); public BlockElement(String tagName) { super(tagName); addStyle("display", "block"); }public BlockElement(String tagName, String id) { super(tagName, id); addStyle("display", "block"); }public String getText() { return text; }public void setText(String text) { this.text = text; }public long getHeight() { return this.height; }public void setHeight(long height) { this.height = height; this.styleCache.put("height", height); }public long getLineHeight() { return this.lineHeight; }public void setLineHeight(long lineHeight) { this.lineHeight = lineHeight; this.styleCache.put("line-height", lineHeight); }public long getWidth() { return this.width; }public void setWidth(long width) { this.width = width; this.styleCache.put("width", width); }@Override public boolean add(Element e) { return children.add(e); }@Override public Element getChild(String id) { for (Element child : children) { if (child.getId().equals(id)) { return child; } } return null; }@Override public boolean remove(Element e) { return children.remove(e); }@Override public boolean empty() { children.clear(); return true; }@Override public void print(int tabs) { StringBuffer styles = new StringBuffer(); for (Map.Entry, Object> style : styleCache.entrySet()) { if (styles.length() > 0) { styles.append("; "); } styles.append(style.getKey()).append(":").append(style.getValue()); } String prefix = String.join("", Collections.nCopies(tabs, "\t")); StringBuffer startTag = new StringBuffer(); startTag.append(prefix).append("<").append(getTagName()); if (getId() != null && !getId().isEmpty()) { startTag.append(" id=\"").append(getId()); } startTag.append("\" style=\"").append(styles).append("\">" ); System.out.println(startTag); for (Element child : children) { child.print(tabs + 1); } System.out.println(prefix + ""); }public void setMarginTop(long top) { this.margin.setTop(top); this.styleCache.put("margin-top", top); }public void setMarginRight(long right) { this.margin.setRight(right); this.styleCache.put("margin-right", right); }public void setMarginBottom(long bottom) { this.margin.setBottom(bottom); this.styleCache.put("margin-bottom", bottom); }public void setMarginLeft(long left) { this.margin.setLeft(left); this.styleCache.put("margin-left", left); }public void setMargin(long topAndBottom, long leftAndRight) { this.margin.setTop(topAndBottom); this.margin.setRight(leftAndRight); this.margin.setBottom(topAndBottom); this.margin.setLeft(leftAndRight); this.styleCache.put("margin-top", topAndBottom); this.styleCache.put("margin-right", leftAndRight); this.styleCache.put("margin-bottom", topAndBottom); this.styleCache.put("margin-left", leftAndRight); }public void setMargin(long top, long right, long bottom, long left) { this.margin.setTop(top); this.margin.setRight(right); this.margin.setBottom(bottom); this.margin.setLeft(left); this.styleCache.put("margin-top", top); this.styleCache.put("margin-right", right); this.styleCache.put("margin-bottom", bottom); this.styleCache.put("margin-left", right); }public void setPaddingTop(long top) { this.padding.setTop(top); this.styleCache.put("padding-top", top); }public void setPaddingRight(long right) { this.padding.setRight(right); this.styleCache.put("padding-right", right); }public void setPaddingBottom(long bottom) { this.padding.setBottom(bottom); this.styleCache.put("padding-bottom", bottom); }public void setPaddingLeft(long left) { this.padding.setLeft(left); this.styleCache.put("padding-left", left); }public void setPadding(long topAndBottom, long leftAndRight) { this.padding.setTop(topAndBottom); this.padding.setRight(leftAndRight); this.padding.setBottom(topAndBottom); this.padding.setLeft(leftAndRight); this.styleCache.put("padding-top", topAndBottom); this.styleCache.put("padding-right", leftAndRight); this.styleCache.put("padding-bottom", topAndBottom); this.styleCache.put("padding-left", leftAndRight); }public void setPadding(long top, long right, long bottom, long left) { this.padding.setTop(top); this.padding.setRight(right); this.padding.setBottom(bottom); this.padding.setLeft(left); this.styleCache.put("padding-top", top); this.styleCache.put("padding-right", right); this.styleCache.put("padding-bottom", bottom); this.styleCache.put("padding-left", left); } }

public class ElementTester { public static void main(String args[]) { Element div = new BlockElement("div", "content"); InlineElement span = new InlineElement("span", "title"); span.setText("设计模式之组合模式"); Element a = new InlineElement("a", "goBaidu"); Element input = new InlineBlockElement("input", "clickMe"); Element content = new BlockElement("div", "description"); Element ul = new BlockElement("ul", "ul"); BlockElement li1 = new BlockElement("li", "li-1"); li1.setText("1. 通过 a 标签跳到百度首页"); BlockElement li2 = new BlockElement("li", "li-2"); li1.setText("2. 这是一个点击按钮"); ul.add(li1); ul.add(li2); content.add(ul); div.add(span); div.add(a); div.add(input); div.add(content); div.print(0); } }

设计模式之组合模式

    推荐阅读