装饰者模式是一个比较常用的模式,它可以在不改变原类文件和不使用继承的情况下动态地扩展一个对象的功能。现在我们遇到这样一个需求,我们要做一个智能货架系统,它可以根据门店的销量、坪效、客单价等等一些数据来展现一个智能的陈列方案,目的是门店可以通过这种陈列方式来提升商品的销量、坪效等指标。
一个货架的品类是固定的,我们拿纯净水来举例,假如我们通过上面的一些数据经过算法计算分析过后为这节货架选择了娃哈哈、康师傅、今麦郎三个品牌的纯净水,根据净含量的不同一个选择了6个sku,分别为康师傅1L、娃哈哈2L、康师傅2L、今麦郎1L、玩哈哈1L、今麦郎2L。
陈列要求:
- 我们要按照品牌分组,将品牌相同的商品陈列在一起
- 按照净含量从小到大排序
- 货架必须摆满
- …
这是一个很典型的动态扩展功能的案例,需要扩展的就是货架陈列的规则,示例中只给出3个,实际要多很多,而且后续会扩展更多,所以装饰者模式很适合来解决这个场景,我们来看设计类图:
这是一个简单的设计类图,其中ShelfDisplayRule是我们定义的货架陈列规则接口,而GroupShelfDisplayRule就是具体的被装饰对象,我们要为它动态的扩展功能,OrderShelfDisplayRuleDecorator和CopyShelfDisplayRuleDecorator是两个具体的装饰者对象,我们来看代码实现:
/**
* 商品
*/
@Data
public class Item {
/**
* 商品名称
*/
private String name;
/**
* 品牌id
*/
private Long brandId;
/**
* 净含量
*/
private Long netWeight;
public Item(String name, Long brandId, Long netWeight) {
this.name = name;
this.brandId = brandId;
this.netWeight = netWeight;
}
public Item() {
}
}
/**
* 获取陈列上下文
*/
@Data
public class ShelfDisplayContext {
/**
* 选品列表
*/
private List<Item> selectItemList;
/**
* 陈列列表
*/
private List<List<Item>> displayItemList;
}
/**
* 货架陈列规则
*/
public interface ShelfDisplayRule {
/**
* 执行规则
*
* @param ctx 货架陈列上下文
*/
void execute(ShelfDisplayContext ctx);
}
/**
* 货架陈列规则(按品牌分组)
*/
public class GroupShelfDisplayRule implements ShelfDisplayRule {
@Override
public void execute(ShelfDisplayContext ctx) {
Map<Long, List<Item>> brandGroup =
ctx.getSelectItemList().stream().collect(Collectors.groupingBy(Item::getBrandId));
List<List<Item>> displayItemList = Lists.newArrayList();
brandGroup.forEach((brandId, itemGroupList) -> displayItemList.add(itemGroupList));
ctx.setDisplayItemList(displayItemList);
}
}
/**
* 货架陈列规则装饰者抽象类
*/
public abstract class AbstractShelfDisplayRuleDecorator implements ShelfDisplayRule {
private ShelfDisplayRule rule;
public AbstractShelfDisplayRuleDecorator(ShelfDisplayRule rule) {
this.rule = rule;
}
@Override
public void execute(ShelfDisplayContext ctx) {
rule.execute(ctx);
}
}
/**
* 货架陈列规则(按净含量排序)
*/
public class OrderShelfDisplayRuleDecorator extends AbstractShelfDisplayRuleDecorator {
public OrderShelfDisplayRuleDecorator(ShelfDisplayRule rule) {
super(rule);
}
@Override
public void execute(ShelfDisplayContext ctx) {
super.execute(ctx);
ctx.getDisplayItemList().forEach(
itemList -> itemList.sort((o1, o2) -> o1.getNetWeight().equals(o2.getNetWeight()) ? 0
: o1.getNetWeight() > o2.getNetWeight() ? 1 : -1));
}
}
/**
* 货架陈列规则(复制商品)
*/
public class CopyShelfDisplayRuleDecorator extends AbstractShelfDisplayRuleDecorator {
private static final int COPY_ITEM_NUM = 3;
public CopyShelfDisplayRuleDecorator(ShelfDisplayRule rule) {
super(rule);
}
@Override
public void execute(ShelfDisplayContext ctx) {
super.execute(ctx);
List<List<Item>> displayItemList = Lists.newArrayList();
ctx.getDisplayItemList().forEach(itemList -> {
List<Item> rowItemList = Lists.newArrayList();
itemList.forEach(item -> {
for (int i = 0; i < COPY_ITEM_NUM; i++) {
rowItemList.add(item);
}
});
displayItemList.add(rowItemList);
});
ctx.setDisplayItemList(displayItemList);
}
}
/**
* 测试类
*/
public class Test {
public static void main(String[] args) {
ShelfDisplayContext ctx = new ShelfDisplayContext();
ctx.setSelectItemList(Lists.newArrayList(
new Item("康师傅1L", 3L, 1L),
new Item("娃哈哈2L", 2L, 2L),
new Item("康师傅2L", 3L, 2L),
new Item("今麦郎1L", 1L, 1L),
new Item("玩哈哈1L", 2L, 1L),
new Item("今麦郎2L", 1L, 2L)
));
System.out.println();
ShelfDisplayRule rule = new GroupShelfDisplayRule();
rule = new OrderShelfDisplayRuleDecorator(rule);
rule = new CopyShelfDisplayRuleDecorator(rule);
rule.execute(ctx);
ctx.getDisplayItemList().forEach(itemList -> {
StringBuilder printString = new StringBuilder();
itemList.forEach(item -> printString.append(item.getName()).append(" "));
System.err.println(printString.toString());
});
}
}
执行结果如下:
当然实际场景肯定要比示例给出的要复杂的多,不过思想是一样的,这样后续我们在扩展其他陈列规则的时候就不用修改原来的规则类。在我们日常接触到的一些框架的内部实现中,也有很多关于装饰者模式的应用,例如JDK的流、Mybatis的Cache等。还有一种方案能够解决示例中的问题,就是将所有的陈列规则放入一个List中,当然需要定义一下顺序,然后遍历执行每一个规则,同样也能实现动态扩展功能的需求,两者各有优缺点,这里就不重复说明了。