文章目录
本篇博客主要是学习 韩顺平_Java设计模式 做一个学习笔记使用
10. 组合模式
10.1 需求的引入
编写程序展示一个学校院系结构:需求是这样,要在一个页面中展示出学校的院系组成,一个学校有多个学院, 一个学院有多个系。
-----清华大学--
----计算机学院---
计算机科学与技术
软件工程
网络工程
----信息工程学院------
通信工程
信息工程
解决方案:把学校、院、系都看做是组织结构,他们之间没有继承的关系,而是一个树形结构,可以更好的实 现管理操作。 => 组合模式
10.2 基本介绍
- 组合模式(Composite Pattern),又叫部分整体模式,它创建了对象组的树形结构,将对象组合成树状结构以 表示“整体-部分”的层次关系。
- 组合模式依据树形结构来组合对象,用来表示部分以及整体层次。
- 这种类型的设计模式属于结构型模式。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以 及组合对象
10.3 角色以及职责介绍
- Component :这是组合中对象声明接口,在适当情况下,实现所有类共有的接口默认行为,用于访问和管理 Component 子部件, Component 可以是抽象类或者接口
- Leaf : 在组合中表示叶子节点,叶子节点没有子节点
- Composite :非叶子节点, 用于存储子部件, 在 Component 接口中实现 子部件的相关操作,比如增加(add), 删除。
10.4 应用实例
import java.util.ArrayList;
import java.util.List;
/**
* 组合模式
*
* @author houyu
* @createTime 2019/12/28 12:50
*/
public class Demo {
public static void main(String[] args) {
OrganizationComponent university = new University("清华大学", "中国顶级大学");
//
OrganizationComponent computerCollege = new College("计算机学院", "计算机学院");
OrganizationComponent infoEngineerCollege = new College("信息工程学院", "信息工程学院");
//
computerCollege.add(new Department("软件工程", " 软件工程不错 "));
computerCollege.add(new Department("网络工程", " 网络工程不错 "));
computerCollege.add(new Department("计算机科学与技术", " 计算机科学与技术是老牌的专业 "));
//
infoEngineerCollege.add(new Department("通信工程", " 通信工程不好学 "));
infoEngineerCollege.add(new Department("信息工程", " 信息工程好学 "));
//
university.add(computerCollege);
university.add(infoEngineerCollege);
// 按需打印即可
university.print();
// computerCollege.print();
// infoEngineerCollege.print();
}
}
abstract class OrganizationComponent {
public OrganizationComponent(String name, String des) {
this.name = name;
this.des = des;
}
/** 名字 */
private String name;
/** 描述 */
private String des;
/** 组合子元素(children属性, add(), print()也可以交给 继承 OrganizationComponent 的实现, ) */
List<OrganizationComponent> children = new ArrayList<>(8);
public void add(OrganizationComponent component) {
children.add(component);
}
public void print() {
System.out.println("-------------- " + name + " --------------");
// 遍历 organizationComponents
for(OrganizationComponent organizationComponent : children) {
organizationComponent.print();
}
}
}
/**
* 大学
*/
class University extends OrganizationComponent {
public University(String name, String des) {
super(name, des);
}
}
/**
* 学院
*/
class College extends OrganizationComponent {
public College(String name, String des) {
super(name, des);
}
}
/**
* 专业
*/
class Department extends OrganizationComponent {
public Department(String name, String des) {
super(name, des);
}
}
10.5 组合模式在 JDK 集合的源码分析(HashMap)
- Map.put(K key, V value)
Map<String, String> map1 = new HashMap<>();
map1.put("计算机学院", "计算机学院");
map1.put("信息工程学院", "信息工程学院");
Map<String, Map<String, String>> map = new HashMap<>();
map.put("清华大学", map1);
System.out.println("map = " + map);
- Map.putAll(Map<? extends K, ? extends V> m)
Map<String, String> temp1 = new HashMap<>();
temp1.putAll(new HashMap<>());
- HashMap.Node<K, V>
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
// ...
}
10.6 组合模式的注意事项和细节
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题。
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的 树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式.
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
10.7 组合模式实际应用
import cn.shaines.fastboot.common.modules.sys.entity.TreeNodeEntity;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* public class TreeNodeEntity<T extends TreeNodeEntity<T>> {
* private String id;
* private String parentId;
* @TableField(exist = false)
* private List<T> children = new ArrayList<>(8);
* }
*
* @author houyu
* @createTime 2019/12/25 10:28
*/
public abstract class TreeUtil {
public static <Node extends TreeNodeEntity<Node>> List<Node> build(List<Node> list) {
Map<String, Node> treeMap = list.stream().collect(Collectors.toMap(TreeNodeEntity::getId, v -> v));
//
List<Node> target = new ArrayList<>(16);
for(Node node : list) {
Node parent = treeMap.get(node.getParentId());
if(parent != null && !parent.getId().equals(node.getId())) {
parent.getChildren().add(node);
} else {
target.add(node);
}
}
return target;
}
public static <Node extends TreeNodeEntity<Node>> List<Node> build(List<Node> list, String parentId) {
List<Node> target = new ArrayList<>(16);
for(Node node : list) {
if(parentId.equals(node.getParentId())) {
target.add(node);
node.setChildren(build(list, node.getId()));
}
}
return target;
}
public static <Node extends TreeNodeEntity<Node>> List<Node> parse(List<Node> out, List<Node> treeList, String parentId) {
for(Node node : treeList) {
if(parentId.equals(node.getParentId())) {
out.add(node);
parse(out, node.getChildren(), node.getId());
node.setChildren(new ArrayList<>(2));
}
}
return out;
}
public static <Node extends TreeNodeEntity<Node>> Node findNode(List<Node> treeList, String id) {
for(Node node : treeList) {
if(id.equals(node.getId())) {
return node;
} else if(node.getChildren() != null && !node.getChildren().isEmpty()) {
return findNode(node.getChildren(), id);
}
}
return null;
}
}
11. 外观模式(门面模式)
11.1 需求的引入
组建一个家庭影院: DVD 播放器、投影仪、自动屏幕、环绕立体声、爆米花机,要求完成使用家庭影院的功能,其过程为: 直接用遥控器:统筹各设备开关 开爆米花机 放下屏幕 开投影仪 开音响 开 DVD,选 dvd 去拿爆米花 调暗灯光 播放观影结束后,关闭各种设备
解决思路:定义一个高层接口,给子系统中的一组接口提供一个一致的界面(比如在高层接口提供四个方法 ready, play, pause, end ),用来访问子系统中的一群接口, 也就是说 就是通过定义一个一致的接口(界面类),用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发 生调用,而无需关心这个子系统的内部细节 => 外观模式
11.2 基本介绍
- 外观模式(Facade),也叫“过程模式”, “门面”:外观模式为子系统中的一组接口提供一个一致的界面,此模式定义了 一个高层接口,这个接口使得这一子系统更加容易使用
- 外观模式通过定义一个一致的接口,用以屏蔽内部子系统的细节,使得调用端只需跟这个接口发生调用,而无 需关心这个子系统的内部细节
11.3 应用实例
/**
* 外观模式(门面模式)
*
* @author houyu
* @createTime 2019/12/28 14:09
*/
public class Demo {
public static void main(String[] args) {
HomeTheaterFacade homeTheaterFacade = new HomeTheaterFacade();
homeTheaterFacade.ready();
homeTheaterFacade.play();
homeTheaterFacade.end();
}
}
class HomeTheaterFacade {
/** 定义各个子系统对象 */
private TheaterLight theaterLight = new TheaterLight();
private Popcorn popcorn = new Popcorn();
private Stereo stereo = new Stereo();
private Projector projector = new Projector();
private Screen screen = new Screen();
private DVDPlayer dVDPlayer = new DVDPlayer();
public void ready(){
popcorn.take();
screen.down();
projector.on();
stereo.on();
dVDPlayer.on();
}
public void play() {
dVDPlayer.play();
}
public void end() {
screen.up();
projector.off();
stereo.off();
dVDPlayer.off();
}
}
/**
* 灯
*/
class TheaterLight {
public void on() {
System.out.println("灯 ON");
}
public void off() {
System.out.println("灯 OFF");
}
}
/**
* 爆米花
*/
class Popcorn {
public void take() {
System.out.println("爆米花 TAKE");
}
}
/**
* 播放器
*/
class Stereo {
public void on() {
System.out.println("播放器 ON");
}
public void off() {
System.out.println("播放器 OFF");
}
}
/**
* 投影机
*/
class Projector {
public void on() {
System.out.println("投影机 ON");
}
public void off() {
System.out.println("投影机 OFF");
}
}
/**
* 屏幕
*/
class Screen {
public void up() {
System.out.println("屏幕 UP");
}
public void down() {
System.out.println("屏幕 DOWN");
}
}
/**
* DVD播放机
*/
class DVDPlayer {
public void on() {
System.out.println("DVD播放机 ON");
}
public void off() {
System.out.println("DVD播放机 OFF");
}
public void play() {
System.out.println("DVD播放机 PLAY");
}
}
11.4 外观模式在 Mybatis 的源码分析
- Configuration 作为外观类对外提供接口;
- Configuration 中关联了很多其他的类(子系统),Configuration 的接口对这些类的功能进行封装,对外屏蔽对这些成员的组合调用;
public class Configuration {
// ...
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
// ...
public MetaObject newMetaObject(Object object) {
return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
}
}
11.5 外观模式的注意事项和细节
- 外观模式对外屏蔽了子系统的细节,因此外观模式降低了客户端对子系统使用的复杂性
- 外观模式对客户端与子系统的耦合关系-解耦,让子系统内部的模块更易维护和扩展
- 通过合理的使用外观模式,可以帮我们更好的划分访问的层次
- 当系统需要进行分层设计时,可以考虑使用Facade模式
- 在维护一个遗留的大型系统时,可能这个系统已经变得非常难以维护和扩展,此时可以考虑为新系统开发一个Facade类,来提供遗留系统的比较清晰简单的接口,让新系统与Facade类交互,提高复用性
- 不能过多的或者不合理的使用外观模式,使用外观模式好,还是直接调用模块好。要以让系统有层次,利于维护为目的。
11.6 外观模式总结
- 何时使用:客户端不需要知道系统内部的复杂联系,整个系统只提供一个“接待员”即可定义系统的入口
- 方法:客户端不与系统耦合,外观类与系统耦合
- 优点:减少了系统的相互依赖吗提高了灵活性。不管系统内部如何变化,只要不影响到外观对象,任你自由活动, 提高了安全性。想让你访问子系统的哪些业务就开通哪些逻辑,不在外观上开通的方法,你就访问不到。非常符合设计模式的“依赖倒转”和“迪米特法则(最少知道原则)”
- 缺点:不符合开不原则,修改很麻烦
- 使用场景:为一个复杂的模块或子系统提供一个外界访问的接口子系统相对独立,外界对子系统的访问只要黑箱操作即可预防低水平人员带来的风险扩散
- 应用实例:基金(用户只和基金打交道,实际操作为基金经理人与股票和其它投资品打交道)