youyichannel

志于道,据于德,依于仁,游于艺!

0%

组合模式

组合模式和访问者模式:

  • 组合模式注重树形结构数据包装
  • 访问者模式注重对不同层次数据操作

二者可以完美搭配,除此之外,二者的UML类图中部分元素是重叠的。

简介

组合模式,主要作用是将对象组合成树形结构,以表示”部分-整体“的层次结构。

实现

类图

组合模式通用的UML类图:

从上图来看,组合模式主要分为三个组件:

1)Component抽象对象:所有树形结构的叶子节点和非叶子节点都需要继承该抽象对象

2)Composite树枝构件对象:非叶子节点,非叶子节点必定会存在下属子节点

3)Leaf叶子构件对象:叶子节点,其下没有其他的分支,同时也无法直接添加子节点(UML图中可以看出Leaf构件没有Add()等方法)

思考

在通用的UML类图中叶子构件对象无法直接添加子节点

  1. 为什么?
  2. 能确保永远不需要添加子节点吗?

这个特点对于通用UML类图下的组合模式是致命的,因此业界更推荐的方式:统一Leaf叶子构件对象和Composite树枝对象,于是乎,修改之后的类图:

可以看出,我们直接删除了Leaf叶子构件,直接使用Composite作为节点对象(思想同二叉树构建)。

实战

需求:实现商品类目的展示,注意:后续会有商品类目的变更

SQL脚本:

CREATE TABLE IF NOT EXISTS product_item (
id INT PRIMARY KEY, -- ID
name VARCHAR(8) NOT NULL, -- 商品类目名称
pid INT NOT NULL -- 父级类目SQL
);

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) {
// convert ProductItem to ProductComposite
List<ProductComposite> composites = new ArrayList<>(productItems.size());
productItems.forEach(productItem -> composites.add(ProductComposite.builder()
.id(productItem.getId())
.pid(productItem.getPid())
.name(productItem.getName())
.build()));

// Group By pid
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": []
}
]
}
]
}

具体的数据层和控制层的实现就交给各自自己实现啦~