Decorator 模式是一个比较常用的模式,它可以在不改变原类文件和不使用继承的情况下动态地扩展一个对象的功能。
对于超市、百货商店等这种需要货架来摆放商品的门店,商品在货架上的陈列方式是非常重要的,不同的陈列方式会直接影响到整个门店的销量、利润等关键指标。现在我们要做一个智能货架系统,它可以根据门店的销量、坪效、商品客单价等一些数据来展现一个智能的商品陈列方案,目的是门店可以通过这种陈列方式来提升商品的销量、门店的坪效等指标。
针对于某一节货架来说,它的品类一般是固定的,我们用一个摆放纯净水的货架为例,假如我们通过上面的一些数据经过算法计算分析过后为这节货架选择了娃哈哈、康师傅、今麦郎三个品牌的纯净水,根据净含量的不同一个选择了6个sku,分别为康师傅1L、娃哈哈2L、康师傅2L、今麦郎1L、玩哈哈1L、今麦郎2L。
陈列要求:
- 我们要按照品牌分组,将品牌相同的商品陈列在一起
- 按照净含量从小到大排序
- 货架必须摆满
- …
这是一个很典型的动态扩展功能的案例,需要扩展的就是货架陈列的规则,示例中只给出3个,实际要多很多,而且后续会扩展更多,所以 Decorator 很适合来解决这个场景,我们来看设计类图:
这是一个简单的设计类图,其中ShelfDisplayRule是我们定义的货架陈列规则接口,而GroupShelfDisplayRule就是具体的被装饰对象,我们要为它动态的扩展功能,OrderShelfDisplayRuleDecorator和CopyShelfDisplayRuleDecorator是两个具体的 Decorator。
/**
* 商品
*/
@Getter
@Setter
@ToString
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() {
}
}
/**
* 货架陈列规则
*/
public interface ShelfDisplayRule {
/**
* 执行规则
*
* @param selectedItems 选品列表
* @return 规则执行后的商品陈列列表
*/
List<List<Item>> execute(List<Item> selectedItems);
}
/**
* 货架陈列规则(按品牌分组)
*/
public class GroupShelfDisplayRule implements ShelfDisplayRule {
@Override
public List<List<Item>> execute(List<Item> selectedItems) {
Map<Long, List<Item>> brandGroup = selectedItems.stream().collect(Collectors.groupingBy(Item::getBrandId));
List<List<Item>> displayItems = Lists.newArrayList();
brandGroup.forEach((brandId, itemGroupList) -> displayItems.add(itemGroupList));
return displayItems;
}
}
/**
* 货架陈列规则 Decorator 抽象类
*/
public abstract class AbstractShelfDisplayRuleDecorator implements ShelfDisplayRule {
private ShelfDisplayRule rule;
public AbstractShelfDisplayRuleDecorator(ShelfDisplayRule rule) {
this.rule = rule;
}
@Override
public List<List<Item>> execute(List<Item> selectedItems) {
return rule.execute(selectedItems);
}
}
/**
* 货架陈列规则(按净含量排序)
*/
public class OrderShelfDisplayRuleDecorator extends AbstractShelfDisplayRuleDecorator {
public OrderShelfDisplayRuleDecorator(ShelfDisplayRule rule) {
super(rule);
}
@Override
public List<List<Item>> execute(List<Item> selectedItems) {
List<List<Item>> displayItems = super.execute(selectedItems);
displayItems.forEach(
itemList -> itemList.sort((o1, o2) -> o1.getNetWeight().equals(o2.getNetWeight()) ? 0
: o1.getNetWeight() > o2.getNetWeight() ? 1 : -1));
return displayItems;
}
}
/**
* 货架陈列规则(复制商品)
*/
public class CopyShelfDisplayRuleDecorator extends AbstractShelfDisplayRuleDecorator {
private static final int COPY_ITEM_NUM = 3;
public CopyShelfDisplayRuleDecorator(ShelfDisplayRule rule) {
super(rule);
}
@Override
public List<List<Item>> execute(List<Item> selectedItems) {
List<List<Item>> displayItems = super.execute(selectedItems);
List<List<Item>> copiedDisplayItems = Lists.newArrayList();
displayItems.forEach(itemList -> {
List<Item> rowItems = Lists.newArrayList();
itemList.forEach(item -> {
for (int i = 0; i < COPY_ITEM_NUM; i++) {
rowItems.add(item);
}
});
copiedDisplayItems.add(rowItems);
});
return copiedDisplayItems;
}
}
/**
* 测试类
*/
public class DecoratorTest {
private static final String ORDER_CMD = "-o";
private static final String COPY_CMD = "-c";
public static void main(String[] args) {
List<String> commands = Lists.newArrayList(args);
// 选品列表
List<Item> selectedItems = 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));
// 根据命令确定陈列规则
ShelfDisplayRule rule = new GroupShelfDisplayRule();
if (commands.contains(ORDER_CMD)) {
rule = new OrderShelfDisplayRuleDecorator(rule);
}
if (commands.contains(COPY_CMD)) {
rule = new CopyShelfDisplayRuleDecorator(rule);
}
// 执行陈列规则
List<List<Item>> displayItems = rule.execute(selectedItems);
// 打印陈列结果
displayItems.forEach(itemList -> {
StringBuilder printString = new StringBuilder();
itemList.forEach(item -> printString.append(item.getName()).append(" "));
System.err.println(printString.toString());
});
}
}
我们通过 main 方法的参数接收外部传入的命令,然后根据命令来确定使用哪些 Decorator,所以调用时我们需要指定一下参数:
运行结果如下:
通过使用 Decorator 模式,我们可以在程序运行时动态的选择需要的功能,而且后续再扩展其他功能时就不会对原有的功能有任何的影响,是一个既简单又实用的设计模式。在我们日常接触到的一些框架的内部实现中,也有很多关于 Decorator 模式的应用,例如JDK的流、Mybatis的Cache等,大家可以参考这些源码进行更进一步的学习。