设计模式-模板模式 + 工厂模式

参考资料

  1. 3 个绝招 解决 代码重复,你会么?

一. 需求

需要开发一个购物车下单的功能,针对不同用户进行不同处理

  • 普通用户需要收取运费,运费是商品价格的 10%,无商品折扣;
  • VIP 用户同样需要收取商品价格 10% 的快递费,但购买两件以上相同商品时,第三件开始享受一定折扣;
  • 内部用户可以免运费,无商品折扣。

⏹分析需求

  • 所有类型的用户都需要用到购物车,且购物车的初始化,统计总价、总运费、总优惠和支付价格的逻辑都是一样的。
  • 不同用户的运费计算和优惠的方式不同

⏹得到如下设计
设计模式-模板模式 + 工厂模式_第1张图片


二. 前期准备

⏹商品实体类

import lombok.Data;

import java.math.BigDecimal;

@Data
public class Item {

    // 商品ID
    private long id;

    // 商品数量
    private int quantity;

    // 商品单价
    private BigDecimal price;

    // 商品优惠
    private BigDecimal couponPrice;

    // 商品运费
    private BigDecimal deliveryPrice;
}

⏹购物车实体类

import lombok.Data;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

@Data
public class Cart {

    // 商品清单
    private List<Item> items = new ArrayList<>();

    // 总优惠
    private BigDecimal totalDiscount;

    // 商品总价
    private BigDecimal totalItemPrice;

    // 总运费
    private BigDecimal totalDeliveryPrice;

    // 应付总价
    private BigDecimal payPrice;
}

三. 购物车抽象类

  • 因为所有的购物车都需要统计商品的总价和各种费用明细,此部分的逻辑是共通的,因此使用模板模式,将所有共通的部分模板化为process方法放在抽象类中。
  • 而不同的部分processCouponPriceprocessDeliveryPrice则由继承抽象类的子类去实现,然后抽象类父类使用。
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public abstract class CartAbstract {

    // 处理购物车的大量重复逻辑在父类实现
    public Cart process(Map<Long, Integer> items) {

        // 商品List
        List<Item> itemList = new ArrayList<>();
        items.forEach((itemId, itemCount) -> {
            Item item = new Item();
            // 设置商品的id
            item.setId(itemId);
            // 根据商品id,从数据库中获取商品的价格,※此处指定所有的商品价格都是10元
            item.setPrice(new BigDecimal(10));
            // 设置商品的数量
            item.setQuantity(itemCount);
            itemList.add(item);
        });

        // 让子类处理每一个商品的优惠
        itemList.forEach(item -> {

            // 处理商品优惠的逻辑
            processCouponPrice(item);

            // 处理配送费的逻辑
            processDeliveryPrice(item);
        });

        // 将处理之后的商品逻辑添加到购物车中
        Cart cart = new Cart();
        cart.setItems(itemList);

        // 计算商品总价
        BigDecimal totalItemPrice = cart.getItems().stream()
            .map(
                item -> item
                .getPrice()
                .multiply(BigDecimal.valueOf(item.getQuantity()))
            )
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        cart.setTotalItemPrice(totalItemPrice);

        // 计算总运费
        BigDecimal totalDeliveryPrice = cart.getItems().stream()
            .map(Item::getDeliveryPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        cart.setTotalDeliveryPrice(totalDeliveryPrice);

        // 计算总折扣
        BigDecimal totalDiscount = cart.getItems().stream()
            .map(Item::getCouponPrice)
            .reduce(BigDecimal.ZERO, BigDecimal::add);
        cart.setTotalDiscount(totalDiscount);

        // 计算应付价格
        BigDecimal payPrice = cart.getTotalItemPrice()
            .add(cart.getTotalDeliveryPrice())
            .subtract(cart.getTotalDiscount());
        cart.setPayPrice(payPrice);

        return cart;
    }

    // 处理商品优惠的逻辑留给子类实现
    protected abstract void processCouponPrice(Item item);

    // 处理配送费的逻辑留给子类实现
    protected abstract void processDeliveryPrice(Item item);
}

四. 各用户实现类

  • 因为存在各种类型的用户,针对不同的用户依次写if else分支去处理很不好维护,因此将每一种类型的用户单独创建一个类,然后通过Spring来统一管理。
  • 最终通过applicationContext.getBean来使用工厂模式来动态的生成购物车的处理对象,从而完成了解耦,提高了可维护性。

4.1 普通用户

import org.springframework.stereotype.Service;
import java.math.BigDecimal;

// 普通用户
@Service("NormalUserCart")
public class NormalUserCart extends CartAbstract {

    // 计算优惠价格
    @Override
    protected void processCouponPrice(Item item) {
        // 普通用户无优惠价格
        item.setCouponPrice(BigDecimal.ZERO);
    }

    // 计算运费
    @Override
    protected void processDeliveryPrice(Item item) {

        // 普通用户的运费为商品总价的10%
        BigDecimal deliveryPrice = item
            .getPrice()
            .multiply(BigDecimal.valueOf(item.getQuantity()))
            .multiply(new BigDecimal("0.1"));
        item.setDeliveryPrice(deliveryPrice);
    }
}

4.2 VIP用户

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

// VIP用户
@Service("VipUserCart")
public class VipUserCart extends CartAbstract implements InitializingBean {

    private static BigDecimal discountPercent = BigDecimal.ONE;

    @Override
    public void afterPropertiesSet() {
        // 模拟从数据库根据用户ID查询打折力度
        discountPercent = new BigDecimal("80").divide(new BigDecimal("100"));
    }

    // 计算优惠价格
    @Override
    protected void processCouponPrice(Item item) {

        // VIP用户购买两件以下的商品,不享受优惠价
        if (item.getQuantity() <= 2) {
            item.setCouponPrice(BigDecimal.ZERO);
            return;
        }

        // VIP用户购买两件以以上的商品,优惠价(模拟优惠价为8折)
        BigDecimal couponPrice = item.getPrice()
            .multiply(discountPercent)
            .multiply(BigDecimal.valueOf(item.getQuantity() - 2));
        item.setCouponPrice(couponPrice);
    }

    // 计算运费
    @Override
    protected void processDeliveryPrice(Item item) {

        // VIP用户的运费为商品总价的10%
        BigDecimal deliveryPrice = item
                .getPrice()
                .multiply(BigDecimal.valueOf(item.getQuantity()))
                .multiply(new BigDecimal("0.1"));
        item.setDeliveryPrice(deliveryPrice);
    }
}

4.3 内部用户

import org.springframework.stereotype.Service;

import java.math.BigDecimal;

// 内部用户
@Service("InternalUserCart")
public class InternalUserCart extends CartAbstract {

    // 计算优惠价格
    @Override
    protected void processCouponPrice(Item item) {
        // 内部用户无优惠价格
        item.setCouponPrice(BigDecimal.ZERO);
    }

    // 计算运费
    @Override
    protected void processDeliveryPrice(Item item) {
        // 内部用户无运费
        item.setDeliveryPrice(BigDecimal.ZERO);
    }
}

五. 调用

  • @RequiredArgsConstructor配合@NonNull来实现注入
  • 通过Spring的applicationContext.getBean(bean名称)来使用工厂模式,动态的生成购物车处理对象
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class ZTestController implements CommandLineRunner {
	
	// 通过lombok注入ApplicationContext
    @NonNull
    private ApplicationContext applicationContext;

    @Override
    public void run(String... args) throws Exception {

        // 商品数量
        Map<Long, Integer> cartItem = new HashMap<>(){
            {
                // key: 商品id, value: 商品数量
                put(1L, 2);
                put(2L, 10);
                put(3L, 4);
            }
        };

        Map<Integer, String> userCategoryMap = new HashMap<>(){
            {
                // key: 用户id, value: 用户身份标识
                put(1, "Internal");
                put(2, "Normal");
                put(3, "Vip");
            }
        };

        userCategoryMap.forEach((userId, userCategory) -> {

            CartAbstract cartInfo = (CartAbstract) applicationContext.getBean(userCategory + "UserCart");
            Cart cart = cartInfo.process(cartItem);

            System.out.println("当前用户身份为: " + userCategory);
            System.out.println("商品总优惠为: " + cart.getTotalDiscount());
            System.out.println("商品总价格为: " + cart.getTotalItemPrice());
            System.out.println("商品总运费为: " + cart.getTotalDeliveryPrice());
            System.out.println("商品应付总价为: " + cart.getPayPrice());
            System.out.println("----------------------------------------");
        });
    }
}

六. 效果

设计模式-模板模式 + 工厂模式_第2张图片

你可能感兴趣的:(设计模式,设计模式,java,spring)