1, Foreword
Appearance mode is A very simple mode. For example, for classes A and B, if they need to interact and go through A certain processing process to realize A specific function, we can define the processing process as A new class, and then integrate the processing steps of classes A and B in this class, For the outside world, we only expose the interface in the new class, so the reusability of the code is very good. These codes can be used as components for other programs, which is very common in our development. Even we can take the interface of creating products in the abstract factory pattern as an application of appearance pattern, which is also an integration. For template methods, in fact, we also face Abstract Programming in the parent class and integrate some relationships, but the difference is very obvious. There is no inheritance relationship in the appearance mode. Instead, we create A new class to integrate the complex interdependencies and calls between other classes. Therefore, the appearance mode is more intuitive.
Two, mode intent
Appearance mode is mainly used to provide a consistent interface for a group of interfaces. Thus, the complex subsystem is decoupled from the client.
It is a bit similar to the one button switch commonly used in families. As long as you press one button, the lamps in the table lamp, bedroom and living room will be on. Although they have their own switches, they are controlled by one external switch
3, Application scenario (report system)
1 provide simple interfaces for complex systems.
2 when building a hierarchical system, it is used as an entry.
3. The client program is separated from the implementation part of the abstract class (which can use the abstract factory pattern).
4, Practice
1 first define an abstract template (for unified encapsulation)
public abstract class AbstractReportManagerTemplate<E,R extends JpaRepository> { JpaRepository repository; public AbstractReportManagerTemplate(R repository){ this.repository = repository; } public Class getEntityClazz(){ ParameterizedType type = (ParameterizedType)getClass().getGenericSuperclass(); return (Class<E>)type.getActualTypeArguments()[0]; } public void generate(Long shopId, String timeZone,String date,ReportDataSource dataSource){ List<E> entityList = buildEntity(shopId,timeZone,date,dataSource); repository.save(entityList); } abstract List<E> query(Long shopId, String from,String to); abstract List<E> realTimeQuery(Long shopId,String timeZone, String date, ReportDataSource dataSource); abstract List<E> buildEntity(Long shopId,String timeZone, String date, ReportDataSource dataSource); }
2 implementation class (implementation of one type)
@Component public class MemberPointsDailyReportManager extends AbstractReportManagerTemplate<MemberPointsDailyReportEntity, MemberPointsDailyReportRepository> { public MemberPointsDailyReportManager(MemberPointsDailyReportRepository repository) { super(repository); } @Override List<MemberPointsDailyReportEntity> query(Long shopId, String from, String to) { return ((MemberPointsDailyReportRepository) repository).findAllByShopIdAndDateBetween(shopId, from, to); } @Override List<MemberPointsDailyReportEntity> realTimeQuery(Long shopId, String timeZone, String date, ReportDataSource dataSource) { return buildEntity(shopId, timeZone, date, dataSource); } @Override List<MemberPointsDailyReportEntity> buildEntity(Long shopId, String timeZone, String date, ReportDataSource dataSource) { return MemberPointsDailyReportEntity.create(shopId, date, dataSource.getPointsLogs()); } }
3. Define the ReportManagerFacade
@Component public class ReportManagerFacade { private Map<Class, AbstractReportManagerTemplate> managerMap = new HashMap<>(); @Autowired private ApplicationContext applicationContext; @Autowired private RemoteShopService remoteShopService; private ReportManagerFacade proxySelf; /** * 1 Initialize data carrier */ @PostConstruct public void init() { Map<String, AbstractReportManagerTemplate> beanMap = applicationContext.getBeansOfType(AbstractReportManagerTemplate.class); beanMap.values().forEach(manager -> managerMap.put(manager.getEntityClazz(), manager)); proxySelf = applicationContext.getBean(ReportManagerFacade.class); } /** * 2 Initialize this type of data */ public void initializeReport(Long orgId) { initializeReport(null, remoteShopService.getShopIds(orgId)); } /** * 3 Generate yesterday's report data (there are scheduled tasks to trigger) */ public void generateYesterdayReport(List<Long> shopIds) { shopIds.forEach(shopId -> { Shop shop = findShop(shopId); String yesterday = DateUtil.standardDateString(getShopLocalDate(shop, -1)); proxySelf.generateReport(shop, yesterday); }); } /** * 4 Generate report data on the specified date */ public void generateReport(Long shopId, String date) { Shop shop = findShop(shopId); proxySelf.generateReport(shop, date); } /** * 5 Specific report generation method (you can define a data source separately to simplify the operation) */ @Transactional(rollbackFor = Exception.class) public void generateReport(Shop shop, String date) { ReportDataSource dataSource = ReportDataSource.builder(shop.getOrganization().getId(), shop.getId(), date, shop.getTimeZone()).setAll().build(); managerMap.values().forEach(manager -> manager.generate(shop.getId(), shop.getTimeZone(), date, dataSource)); } /** * 6 Report data query */ public ReportEntityHolder query(Long orgId, List<Class> entityList, String from, String to, Long... shopIds) { ReportEntityHolder holder = new ReportEntityHolder(); for (Long shopId : shopIds) { doQuery(holder, entityList, from, to, shopId); doRealTimeQuery(holder, orgId, shopId, entityList, from, to); } return holder; } private void doQuery(ReportEntityHolder holder, List<Class> entityList, String from, String to, Long shopId) { entityList.forEach(clazz -> { if (managerMap.containsKey(clazz)) { holder.addEntity(clazz, managerMap.get(clazz).query(shopId, from, to)); } }); } private void doRealTimeQuery(ReportEntityHolder holder, Long orgId, Long shopId, List<Class> entityList, String from, String to) { Shop shop = findShop(shopId); LocalDate today = getShopLocalDate(shop, 0); LocalDate toDateLocalDate = null; LocalDate fromDateLocalDate = null; try { toDateLocalDate = DateUtil.reportDateFormat(to); fromDateLocalDate = DateUtil.reportDateFormat(from); } catch (Exception e) { throw new DateTimeParamException(""); } if (!fromDateLocalDate.isAfter(today) && !toDateLocalDate.isBefore(today)) { proxySelf.doRealTimeQuery(holder, entityList, DateUtil.standardDateString(today), orgId, shopId, shop.getTimeZone()); } LocalDate yesterday = today.minusDays(1L); if (!fromDateLocalDate.isAfter(yesterday) && !toDateLocalDate.isBefore(yesterday)) { LocalDateTime time = DateUtil.getNowLocalDateTimeByTimeZoneId(shop.getTimeZone()); if (time.getHour() == 0 && shop.getId() != 0) { proxySelf.doRealTimeQuery(holder, entityList, DateUtil.standardDateString(yesterday), orgId, shopId, shop.getTimeZone()); } } }
4, Building storage data sources
@Getter public class ReportDataSource { private static final Set<Class> NEED_REFUND_SET = Sets.newHashSet(DailyReportEntity.class); private List<Order> orders; private List<PointsLogEntity> pointsLogs; private ReportDataSource() { } private ReportDataSource(List<Order> orders, List<OrderHour> orderHours, List<OrderDish> orderDishes, List<OrderAddOn> orderAddOns, List<OrderPayment> orderPayments, List<PointsLogEntity> pointsLogs) { this.orders = orders; this.pointsLogs = pointsLogs; } public static ReportContextBuilder builder(Long orgId, Long shopId, String date, String timeZone) { return new ReportContextBuilder(orgId, shopId, date, timeZone); } @AllArgsConstructor public static class Order { public final Long channelId; public final String channelName; public final BigDecimal salesAmount; public final BigDecimal subTotalAmount; public final BigDecimal taxAmount; public final BigDecimal platformServiceFeeAmount; public final BigDecimal tipAmount; public final BigDecimal pointAmount; public final BigDecimal promotionAmount; public final BigDecimal discountAmount; public final BigDecimal itemsDiscountAmount; public final Long orderCount; } public static class ReportContextBuilder { private JPAQueryFactory queryFactory; private MemberPointsReportService memberPointsReportService; private Long orgId; private Long shopId; private String timeZone; private String date; private List<Order> orders; private List<PointsLogEntity> pointsLogs; private ReportContextBuilder(Long orgId, Long shopId, String date, String shopTimeZone) { this.orgId = orgId; this.shopId = shopId; this.timeZone = shopTimeZone; this.date = date; this.queryFactory = SpringUtil.getBean(JPAQueryFactory.class); this.memberPointsReportService = SpringUtil.getBean(MemberPointsReportService.class); } public ReportContextBuilder setAll() { this.orders = initOrders(); this.pointsLogs = initPointLogs(); return this; } public ReportContextBuilder setOrders() { this.orders = orders; return this; } public ReportDataSource build() { return new ReportDataSource(orders, orderHours, orderDishes, orderAddOns, orderPayments, pointsLogs); } private List<Order> initOrders() { QChannelTemplateEntity channelTemplate = QChannelTemplateEntity.channelTemplateEntity; QOrderEntity orderModel = QOrderEntity.orderEntity; return queryFactory.select( Projections.constructor( Order.class, orderModel.channelId, orderModel.channel.min(), orderModel.payAmount.sum(), orderModel.subTotalAmount.sum(), orderModel.taxAmount.sum(), orderModel.platformServiceFeeAmount.sum(), orderModel.tipAmount.sum(), orderModel.pointAmount.sum(), orderModel.promotionAmount.sum(), orderModel.discountAmount.sum(), orderModel.itemDiscountAmount.sum(), orderModel.id.count() ) ).from(orderModel) .leftJoin(orderModel.channelTemplateEntity, channelTemplate) .where(orderModel.shopId.eq(this.shopId) .and(orderModel.businessDate.eq(this.date)) .and(orderModel.payState.in(DAILY_REPORT_ORDER_PAY_STATE)) .and(channelTemplate.type.eq(ChannelType.SELF_OPERATED_SALES))) .groupBy(orderModel.shopId, orderModel.businessDate, orderModel.channelId) .fetch(); } private List<PointsLogEntity> initPointLogs() { Long startOfToday = DateUtil.getStartOfToday(date, timeZone); Long endOfDay = DateUtil.getEndOfDay(date, timeZone); return memberPointsReportService.findStoreLssuedAndUsedPoints(orgId, shopId, startOfToday, endOfDay); } } }
V. constructing query data source
@Getter public class ReportEntityHolder { private List<DailyReportEntity> dailyReports = new ArrayList<>(); private List<PayTypeDailyReportEntity> payTypeDailyReports = new ArrayList<>(); private List<ChannelDailyReportEntity> channelDailyReports = new ArrayList<>(); private List<DishCategoryDailyReportEntity> categoryDailyReports = new ArrayList<>(); private List<DishDailyReportEntity> dishDailyReports = new ArrayList<>(); private List<DishAddOnsDailyReportEntity> dishAddOnsDailyReports = new ArrayList<>(); private List<HourDailyReportEntity> hourDailyReports = new ArrayList<>(); private List<MemberPointsDailyReportEntity> memberPointsDailyReports = new ArrayList<>(); public <T> void addEntity(Class<T> clazz, List<T> list) { if (clazz.equals(DailyReportEntity.class)) { this.dailyReports.addAll((List<DailyReportEntity>) list); } else if (clazz.equals(PayTypeDailyReportEntity.class)) { this.payTypeDailyReports.addAll((List<PayTypeDailyReportEntity>) list); } else if (clazz.equals(ChannelDailyReportEntity.class)) { this.channelDailyReports.addAll((List<ChannelDailyReportEntity>) list); } else if (clazz.equals(DishCategoryDailyReportEntity.class)) { this.categoryDailyReports.addAll((List<DishCategoryDailyReportEntity>) list); } else if (clazz.equals(DishDailyReportEntity.class)) { this.dishDailyReports.addAll((List<DishDailyReportEntity>) list); } else if (clazz.equals(DishAddOnsDailyReportEntity.class)) { this.dishAddOnsDailyReports.addAll((List<DishAddOnsDailyReportEntity>) list); } else if (clazz.equals(HourDailyReportEntity.class)) { this.hourDailyReports.addAll((List<HourDailyReportEntity>) list); } else if (clazz.equals(MemberPointsDailyReportEntity.class)) { this.memberPointsDailyReports.addAll((List<MemberPointsDailyReportEntity>) list); } } }