又称“整体—部分”模式,组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构,使用户对单个对象和组合对象具有访问一致性。
角色
抽象构件(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<Component> components = new ArrayList<Component>();
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<String> 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<String> 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<String> 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<String> 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<File> 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<File> 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<File> iter = files.iterator();
while (iter.hasNext()) {
File f = iter.next();
if (f.getFileName().equals(fileName)) {
iter.remove();
return true;
}
}
return false;
}
@Override
public List<String> list() {
List<String> 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<String> 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<String, 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<String, 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("</").append(getTagName()).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<String, 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("</").append(getTagName()).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<Element> 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<String, 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 + "</" + getTagName() + ">");
}
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);
}
}
<div id="content" style="display:block">
<span id="title" style="display:inline">设计模式之组合模式</span>
<a id="goBaidu" style="display:inline"></a>
<input id="clickMe" style="display:inline-block;position:absolute;float:left"> </input>
<div id="description" style="display:block">
<ul id="ul" style="display:block">
<li id="li-1" style="display:block">
</li>
<li id="li-2" style="display:block">
</li>
</ul>
</div>
</div>