组合模式和访问者模式:
组合模式注重树形结构数据 的包装
访问者模式注重对不同层次数据 的操作
二者可以完美搭配,除此之外,二者的UML类图中部分元素是重叠的。
简介
组合模式,主要作用是将对象组合成树形结构,以表示”部分-整体“的层次结构。
实现
类图
组合模式通用的UML类图:
从上图来看,组合模式主要分为三个组件:
1)Component抽象对象 :所有树形结构的叶子节点和非叶子节点都需要继承该抽象对象
2)Composite树枝构件对象 :非叶子节点,非叶子节点必定会存在下属子节点
3)Leaf叶子构件对象 :叶子节点,其下没有其他的分支,同时也无法直接添加子节点(UML图中可以看出Leaf构件没有Add()
等方法)
思考
在通用的UML类图中叶子构件对象无法直接添加子节点 :
为什么?
能确保永远不需要添加子节点吗?
这个特点对于通用UML类图下的组合模式是致命的,因此业界更推荐的方式:统一Leaf叶子构件对象和Composite树枝对象,于是乎,修改之后的类图:
可以看出,我们直接删除了Leaf叶子构件,直接使用Composite作为节点对象(思想同二叉树构建)。
实战
需求:实现商品类目的展示,注意:后续会有商品类目的变更
SQL脚本:
CREATE TABLE IF NOT EXISTS product_item ( id INT PRIMARY KEY, name VARCHAR (8 ) NOT NULL , pid INT NOT NULL ); INSERT INTO product_item(id, name, pid) VALUES (1 , '商城' , 0 ), (2 , '电脑' , 1 ), (3 , '书籍' , 1 ), (4 , '台式电脑' , 2 ), (5 , '笔记本电脑' , 2 ), (6 , '游戏电脑' , 4 ), (7 , '办公电脑' , 4 ), (8 , '教育类' , 3 ), (9 , '科普类' , 3 ), (10 , '九年义务教育书籍' , 8 );
上述数据可以划归为如下的树形结构:
接下来就可以基于UML类图来进行结构定义和方法定义了。
1)Component抽象组件
根据需求,需要支持商品类目的变更。因此,我们创建AbstractProductItem
抽象类,并创建添加和删除的方法:
public abstract class AbstractProductItem { protected void addProductItem (AbstractProductItem item) { throw new UnsupportedOperationException ("Not Support child add!" ); } protected void delProductItem (AbstractProductItem item) { throw new UnsupportedOperationException ("Not Support child delete!" ); } }
上述的两个方法都不强制子类实现,并且传入的参数都是AbstractProductItem
,遵循里氏替换原则。
2)Composite树枝构件
从类图中可以看出,Composite树枝构件作为AbstractProductItem
的直接子类
@EqualsAndHashCode(callSuper = true) @Data @Builder @NoArgsConstructor @AllArgsConstructor public class ProductComposite extends AbstractProductItem { private Integer id; private Integer pid; private String name; private List<AbstractProductItem> children = new ArrayList <>(); @Override protected void addProductItem (AbstractProductItem item) { this .children.add(item); } @Override protected void delProductItem (AbstractProductItem item) { ProductComposite removeItem = (ProductComposite) item; Iterator<AbstractProductItem> iterator = children.iterator(); while (iterator.hasNext()) { ProductComposite composite = (ProductComposite) iterator.next(); if (composite.getId().equals(removeItem.id)) { iterator.remove(); break ; } } } }
上述代码需要关注的是children
的类型是List<AbstractProductItem>
,使用AbstractProductItem
主要是为了后续扩展考虑。
PS:
虽然children
属性的元素使用了AbstractProductItem
作为类型能够提高扩展性,但是由于业务场景下商品类目管理模块单一,后续扩展的可能性小,因此可以直接使用ProductComposite
作为类型。
=> 设计模式的使用,需要因地制宜。
最终实现的构建树形结构的方法:
@Service public class ProductItemService { @Autowired private RedisCommonProcessor redisCommonProcessor; @Autowired private ProductItemRepository productItemRepository; public ProductComposite queryAllItems () { Object cacheItems = redisCommonProcessor.get("product:items" ); if (cacheItems != null ) { return (ProductComposite) cacheItems; } List<ProductItem> productItemsFromDb = productItemRepository.findAll(); ProductComposite composite = generateProductTree(productItemsFromDb); if (composite == null ) { throw new RuntimeException ("Product items should not be null!" ); } redisCommonProcessor.set("product:items" , composite); return composite; } private ProductComposite generateProductTree (List<ProductItem> productItems) { List<ProductComposite> composites = new ArrayList <>(productItems.size()); productItems.forEach(productItem -> composites.add(ProductComposite.builder() .id(productItem.getId()) .pid(productItem.getPid()) .name(productItem.getName()) .build())); Map<Integer, List<ProductComposite>> compositeGroups = composites.stream().collect(Collectors.groupingBy(ProductComposite::getPid)); composites.forEach(item -> { List<ProductComposite> compositeList = compositeGroups.get(item.getId()); item.setChildren( compositeList == null ? new ArrayList <>() : compositeList.stream() .map(composite -> (AbstractProductItem) composite) .collect(Collectors.toList()) ); }); return composites.size() == 0 ? null : composites.get(0 ); } }
接口调用的结果:
{ "id" : 1 , "pid" : 0 , "name" : "商城" , "children" : [ { "id" : 2 , "pid" : 1 , "name" : "电脑" , "children" : [ { "id" : 4 , "pid" : 2 , "name" : "台式电脑" , "children" : [ { "id" : 6 , "pid" : 4 , "name" : "游戏电脑" , "children" : [ ] } , { "id" : 7 , "pid" : 4 , "name" : "办公电脑" , "children" : [ ] } ] } , { "id" : 5 , "pid" : 2 , "name" : "笔记本电脑" , "children" : [ ] } ] } , { "id" : 3 , "pid" : 1 , "name" : "书籍" , "children" : [ { "id" : 8 , "pid" : 3 , "name" : "教育类" , "children" : [ { "id" : 10 , "pid" : 8 , "name" : "九年义务教育书籍" , "children" : [ ] } ] } , { "id" : 9 , "pid" : 3 , "name" : "科普类" , "children" : [ ] } ] } ] }
具体的数据层和控制层的实现就交给各自自己实现啦~