组合模式
定义
-
也称为
- 整体局部模式
- 也叫合成模式
主旨是通过单个对象 (叶子节点) 和组合对象 (树枝节点) 用雷同的接口进行示意
-
作用
- 让客户端对单个对象和组合对象保持一致的形式解决
属于结构型模式
组合和聚合的区别
如果是心在一起就是一个团队,人在一起只是团伙
头和身材就是组合
-
学生和老师就是聚合
- 没有互相的生命周期
生存中的案例
- 公司组织架构
- 电脑的文件治理
实用场景
- 如果咱们心愿客户端代码以雷同形式解决简略和简单元素,能够应用该模式
-
对象档次具备整体和局部,呈树形构造
- 树形菜单
- 操作系统目录构造
- 公司组织架构
通用构造
通明写法
public abstract class Component {
protected String name;
public Component(String name) {this.name = name;}
public abstract String operation();
public boolean addChild(Component component) {throw new UnsupportedOperationException("addChild not supported!");
}
public boolean removeChild(Component component) {throw new UnsupportedOperationException("removeChild not supported!");
}
public Component getChild(int index) {throw new UnsupportedOperationException("getChild not supported!");
}
}
在形象办法中属于根的办法都抛出异样,而后具体的根重写这个办法
public class Composite extends Component {
private List<Component> mComponents;
public Composite(String name) {super(name);
this.mComponents = new ArrayList<Component>();}
@Override
public String operation() {StringBuilder builder = new StringBuilder(this.name);
for (Component component : this.mComponents) {builder.append("\n");
builder.append(component.operation());
}
return builder.toString();}
@Override
public boolean addChild(Component component) {return this.mComponents.add(component);
}
@Override
public boolean removeChild(Component component) {return this.mComponents.remove(component);
}
@Override
public Component getChild(int index) {return this.mComponents.get(index);
}
}
子节点不须要重写这个办法
public class Leaf extends Component {public Leaf(String name) {super(name);
}
@Override
public String operation() {return this.name;}
}
测试
public class Test {public static void main(String[] args) {
// 来一个根节点
Component root = new Composite("root");
// 来一个树枝节点
Component branchA = new Composite("---branchA");
Component branchB = new Composite("------branchB");
// 来一个叶子节点
Component leafA = new Leaf("------leafA");
Component leafB = new Leaf("---------leafB");
Component leafC = new Leaf("---leafC");
root.addChild(branchA);
root.addChild(leafC);
branchA.addChild(leafA);
branchA.addChild(branchB);
branchB.addChild(leafB);
String result = root.operation();
System.out.println(result);
}
}
弊病
这种形式有个弊病,子节点领有了本人并不需要关怀的办法,并且一旦调用就会抛出异样,这个违反接口隔离准则和历史替换准则
长处
客户端无须要判断是根节点还是数值节点,他们具备完全一致的接口
平安组合模式的写法
public class Composite extends Component {
private List<Component> mComponents;
public Composite(String name) {super(name);
this.mComponents = new ArrayList<Component>();}
@Override
public String operation() {StringBuilder builder = new StringBuilder(this.name);
for (Component component : this.mComponents) {builder.append("\n");
builder.append(component.operation());
}
return builder.toString();}
public boolean addChild(Component component) {return this.mComponents.add(component);
}
public boolean removeChild(Component component) {return this.mComponents.remove(component);
}
public Component getChild(int index) {return this.mComponents.get(index);
}
}
能够看到通过依赖的形式,来获取根节点和页节点, 然而要求咱们辨别根节点和叶子节点
平安写法的案例
在咱们的电脑文件系统中有文件和文件夹所以咱们能够应用平安组合模式来实现目录提供。
咱们能够先定义顶层抽象类
public abstract class Directory {
protected String name;
public Directory(String name) {this.name = name;}
public abstract void show();}
别离创立文件和目录
public class File extends Directory {public File(String name) {super(name);
}
@Override
public void show() {System.out.println(this.name);
}
}
public class Folder extends Directory {
private List<Directory> dirs;
private Integer level;
public Folder(String name,Integer level) {super(name);
this.level = level;
this.dirs = new ArrayList<Directory>();}
@Override
public void show() {System.out.println(this.name);
for (Directory dir : this.dirs) {
// 管制显示格局
if(this.level != null){for(int i = 0; i < this.level; i ++){
// 打印空格管制格局
System.out.print(" ");
}
for(int i = 0; i < this.level; i ++){
// 每一行开始打印一个 + 号
if(i == 0){System.out.print("+"); }
System.out.print("-");
}
}
// 打印名称
dir.show();}
}
public boolean add(Directory dir) {return this.dirs.add(dir);
}
public boolean remove(Directory dir) {return this.dirs.remove(dir);
}
public Directory get(int index) {return this.dirs.get(index);
}
public void list(){for (Directory dir : this.dirs) {System.out.println(dir.name);
}
}
}
目录还减少了 list 办法,并且实现了对应的 show 办法
测试
class Test {public static void main(String[] args) {System.out.println("============ 平安组合模式 ===========");
File qq = new File("抖音.exe");
File wx = new File("QQ.exe");
Folder office = new Folder("办公软件",2);
File word = new File("Word.exe");
File ppt = new File("PowerPoint.exe");
File excel = new File("Excel.exe");
office.add(word);
office.add(ppt);
office.add(excel);
Folder wps = new Folder("金山软件",3);
wps.add(new File("WPS.exe"));
office.add(wps);
Folder root = new Folder("根目录",1);
root.add(qq);
root.add(wx);
root.add(office);
System.out.println("----------show()办法成果 -----------");
root.show();
System.out.println("----------list()办法成果 -----------");
root.list();
/**
* Folder 是汇合,增加了汇合独特的办法,而这个 File 就不具备这个办法 <br>
*/
}
}
能够看到平安模式的益处就是借口定义职责清晰。
合乎繁多职责准则,借口隔离准则
然而客户须要辨别根节点和叶子节点。
然而客户端无奈依赖形象,违反了依赖导致准则
源码中的应用
HashMap
public void putAll(Map<? extends K, ? extends V> m) {for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
putAll 中传入的是 Map 对象,Map 就是一个形象组件,HashMap 中的 Node 节点就是对应的叶子节点
ArrayList
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
ArrayList 中也合乎局部和整体的关系。
Mybatis
SqlNode
org.apache.ibatis.scripting.xmltags.XMLScriptBuilder#parseScriptNode
能够看到
public SqlSource parseScriptNode() {List<SqlNode> contents = parseDynamicTags(context);
MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
SqlSource sqlSource = null;
if (isDynamic) {sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
在解析
节点的时候
List<SqlNode> parseDynamicTags(XNode node) {List<SqlNode> contents = new ArrayList<SqlNode>();
NodeList children = node.getNode().getChildNodes();
for (int i = 0; i < children.getLength(); i++) {XNode child = node.newXNode(children.item(i));
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
if (textSqlNode.isDynamic()) {contents.add(textSqlNode);
isDynamic = true;
} else {contents.add(new StaticTextSqlNode(data));
}
} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlers(nodeName);
if (handler == null) {throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
handler.handleNode(child, contents);
isDynamic = true;
}
}
return contents;
}
public class MixedSqlNode implements SqlNode {
private List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {this.contents = contents;}
@Override
public boolean apply(DynamicContext context) {for (SqlNode sqlNode : contents) {sqlNode.apply(context);
}
return true;
}
}
能够看到 MixedSqlNode 就是对应的树枝构建,SqlNode 就是形象构建角色
总结
长处
- 分明的定义分档次的简单对象,示意对象的整体或者局部
-
让客户端疏忽了档次的差别,不便对整个构造档次进行管制
- 一棵树中的所有节点都是
Component
,部分和整体对调用者来说没有任何区别 - 高层不须要关怀本人解决的是单个对象还是整个组合构造
- 一棵树中的所有节点都是
- 简化客户端代码
- 节点可能自在减少
- 合乎开闭准则
毛病
- 限度类型时会较为简单
- 让设计变得更加形象
-
间接应用了实现类
- 违反了依赖导致准则
问题
怎么了解客户端对单个对象和组合对象保持一致的形式解决
-
都是面向形象编程,所有的根节点和子节点都有独特的形象角色构建
- 所以操作形式都是雷同的形式
组合模式通明形式是怎么违反起码晓得准则的
组合模式通明形式因为是通过继承,所以子类其实也取得了很多原本本人所不须要关怀的属性。
我的笔记仓库地址 gitee 快来给我点个 Star 吧