Service Scenario Introduction

Currently the following pages are available:

  1. General merchandise details page
  2. New exclusive product details page
  3. Seconds kill product details page
  4. Group purchase product details page
  5. Full reduction of product details page
  6. Credits product details page
  7. Live product details page
  8. Internal purchase product details page

These pages have common business logic, such as displaying product information, adding a shopping cart, buying now, selecting specifications, sharing, and so on.

There is a problem

These pages are common commodity data fields, but there are different data fields, but also because of different interfaces respectively by different backend implementation classmate, returns are not uniform field, the same commodity name field, for example, may be A backend data returned by the field is prodName, B is productName back-end data returned by the field, C The data field returned by the backend is name. For example, the data structure returned by group purchase products looks like this:

{
	startTime: ' '.// Group purchase start time
	endTime: ' '.// Group purchase end time
	prodName: ' ' // Product name. }Copy the code

Full reduction goods:

{
	startTime: ' '.// Activity start time
	endTime: ' '.// The end of the activity
	productVO: { // Product details
		prodName: ' '}... }Copy the code

For example, the data of integral commodities are returned by two interfaces respectively. Interface A returns the CONFIGURATION information of SKU and integral activity information of integral commodities. Interface B returns other information of the commodities

Traditional solutions

Respectively establish their own page module, each to achieve their own business logic

Disadvantages:

A lot of redundant code is generated, and the maintenance cost is very high

Mid-tier policy pattern solution

What is the middle layer

The traditional front-end middle layer refers to the Node layer. The Node layer can obtain data from the Server layer, and then integrate the data into the data format that meets the requirements of the front-end UI. In addition, Node layer can also do proxy forwarding, interface aggregation, data cache, interface traffic limiting, log operation and so on

We do not have Node layer at present, but we can realize a Service module of Model layer, which is used to deal with data exclusively. After unifying the data format, it is handed over to the View layer, so that the View layer can focus more on UI, namely, efficient separation of concerns and improvement of maintainability.

What are strategic patterns

The core idea of the policy pattern is the same as if else, which dynamically finds different business logic based on different keys.

Advantages:

  • The policy pattern separates the use of the algorithm into the environment class and the implementation of the algorithm into the concrete policy class.
  • The policy pattern provides perfect support for the open closed principle, allowing for the flexibility of adding new algorithms without modifying the original code.

The specific implementation

The flow chart

Define a commodity detail data transformation service layer class middle layer

/** * Commodity detail data conversion service layer */
class GoodsDetailService {
    constructor(goodsDetailPolicy, goodsType, priceKey) {
        this.goodsDetailPolicy = goodsDetailPolicy
        this.goodsType = goodsType
        this.priceKey = priceKey
    }
	
    /** * start *@param {Object} Params API request parameter */
    start(params) {
        this.setNavigationBarTitle()
        return new Promise((resolve, reject) = > {
            this.goodsDetailPolicy.detailApi(params).then(r= > {
                this.goodsInfo = this.goodsDetailPolicy.initData ? this.goodsDetailPolicy.initData(r) : r.data
                this.transform(this.goodsInfo)
                resolve(this.goodsInfo)
            }).catch(err= > reject(err))
        })
    }
	
    /** * sets the title */
    setNavigationBarTitle() {
        uni.setNavigationBarTitle({
            title: this.goodsDetailPolicy.navigationBarTitle || 'Merchandise Details'})}/** * Data conversion *@param {Object} GoodsInfo product details */
    transform(goodsInfo) {
        this.goodsDetailPolicy.transform(goodsInfo)
        // Set SKU data for multiple attributes if not single
        if (!this.setIsSingleSpecification(goodsInfo)) {
            goodsInfo['productAttr'] = this.normalizeSkus(goodsInfo.skus)
        }
        // Get the SKU item with the smallest price
        let minHeap = util.minHeap(goodsInfo.skus, this.priceKey)
        minHeap['price'] = minHeap[this.priceKey]
        // If there is a quota, then the inventory is a quota
        minHeap.actualStocks = minHeap.limits ? minHeap.limits : minHeap.actualStocks
        goodsInfo['minHeap'] = minHeap
        goodsInfo['price'] = minHeap.price
        goodsInfo['actualStocks'] = minHeap.actualStocks
        goodsInfo['marketPrice'] = minHeap.marketPrice
    }

    /** * Set no Single specifications *@param {Object} GoodsInfo product details */
    setIsSingleSpecification(goodsInfo) {
        goodsInfo['isSingleSpecification'] = goodsInfo.skus.length === 1 && !goodsInfo.skus[0].properties
        return goodsInfo.isSingleSpecification
    }

    /** * SKU data structure initialization *@param {Object} skus* /
    normalizeSkus(skus) {
        let temp = []
        NormalizeSkus reassembles all attributes into a one-dimensional array, and then normalizeSkus reassembles all attributes into a two-dimensional array
        skus.forEach(o= > {
            const properties = JSON.parse(o.properties)
            Object.keys(properties).forEach(v= > {
                temp.push({
                    attrName: v,
                    attrVal: properties[v]
                })
            })
        })
        // The SKU data structure is initialized
        return this.normalizeSkusToTwoDimensionalArray(temp)
    }

    /** * Commodity SKU data structure construction *@param {Array} data 
	 */
    normalizeSkusToTwoDimensionalArray(data) {
        let temp = []
        data.forEach(o= > {
            const attrName = o.attrName
            const skuVal = o.attrVal
            if (temp.length > 0) {
                if (temp.some(val= > val.attrName === attrName)) {
                    temp.forEach((v, j) = > {
                        if (v.attrName === attrName && v.attrValues.every(sv= >sv.val ! == o .attrVal)) { temp[j].attrValues.push({val: skuVal,
                                isSelect: false})}})}else {
                    temp.push({
                        attrName,
                        attrId: attrName,
                        attrValues: [{
                            val: skuVal,
                            isSelect: false}}}}])else {
                temp.push({
                    attrName,
                    attrid: attrName,
                    attrValues: [{
                        val: skuVal,
                        isSelect: false}]})}})return temp
    }
}
Copy the code

Each module needs to perform common operations, such as setting headers, setting single specifications, SKU data structure initialization, and commodity SKU data structure construction, so all these operations are carried out in the common Service module, and each data transformation is carried out in its own policy.

Define the transformation strategy

The realization of their different policy conversion operations, can also be said to achieve different algorithm calculation.

/** * Switch policy */
const goodsDetailPolicy = {
    [goodsTypes.GROUP_BUYING]: {
        detailApi: fetchGroupBuyingGoodsDetail,
        navigationBarTitle: 'Group Purchase Details'.transform: goodsInfo= > {
            / / round figure
            goodsInfo['sliderImage'] = JSON.parse(goodsInfo.imgs)
            goodsInfo['prodName'] = goodsInfo.productName
            goodsInfo['skus'] = goodsInfo.skuList.map(o= > {
                o['prodName'] = goodsInfo.productName
                // Group purchase limit one
                o['limits'] = 1
                return o
            })
        }
    },
    [goodsTypes.FLASH_SALE]: {
        detailApi: fetchFlashSaleGoodsDetail,
        navigationBarTitle: 'Seckill Product Details'.transform: goodsInfo= > {
            / / round figure
            goodsInfo['sliderImage'] = goodsInfo.imgs.split(', ')
            goodsInfo['prodName'] = goodsInfo.title
            goodsInfo['skus'] = goodsInfo.seckillSkuVO
        }
    },
    ...
}
Copy the code

Call the implementation

const service = new GoodsDetailService(goodsDetailPolicy[this.goodsType], this.goodsType, this.priceKey)
			
service.start(params).then(r= > {
    
    this.goodsInfo = r
    
}).catch(err= > console.log(err))
Copy the code

The goodsDetailPolicy[this.goodstype] implementation calls different conversion policies. This.goodstype can be item types: normal, seckill, Live, full subtraction, Integral, etc.

Specific case analysis

The following is an analysis of the implementation of the integral commodity detail, because the integral commodity detail includes almost all kinds of situations. First of all, we need to return the relevant data through two interfaces for the details of the integrated product. Api-a returns the information of the integrated product configuration, such as the start time of the activity and the SKU information of the integrated product configuration. Api-b returns all the information of the product, so the information we need should be the data in API-A. If apI-A does not have the information in API-B after the requirements are clear, we start to implement

Define a transformation strategy

{
    [goodsTypes.POINTS_MALLS]: {
        detailApi: fetchPointsMallsGoodsDetail,
        navigationBarTitle: 'Bonus Point Product Details'.initData: data= >{},transform: goodsInfo= >{}}},Copy the code

Although two apis are required to provide the credits product details data, we still use one API as the entry point in the transformation strategy

Then the API layer handles the requests of the two API interfaces concurrently. Note that the API layer does not handle the data, so that the API layer only focuses on the WORK of THE API request

/** ** To obtain points for merchandise details ** /
export function fetchPointsMallsGoodsDetail(data) {
    const pointsGoodsDetailApi = request.get(`/smsPointsMalls/${data.pointsMallsId}`, {})
    const plainGoodsDetailApi = request.get('/smsFullMinus/product', { ids: data.ids })
    return Promise.all([
        pointsGoodsDetailApi,
        plainGoodsDetailApi
    ])
}
Copy the code

The requested data is first initialized in the policy

    [goodsTypes.POINTS_MALLS]: {
	    detailApi: fetchPointsMallsGoodsDetail,
	    navigationBarTitle: 'Bonus Point Product Details'.initData: data= > {
			const pointsGoodsDetail = data[0].data
			const plainGoodsDetail = data[1].data[0]
			
			const pointSkus = JSON.parse(pointsGoodsDetail.sku)
			// Set minimum credits and minimum prices
			let integral = ' '
			let price = ' '
			pointSkus.forEach(o= > {
				if (o.integral > integral && o.integral) integral = o.integral
				if (o.price > price && o.price) price = o.price
			})
			
			const skus = pointSkus.map(o= > {
				o['pic'] = o.img 
				// Each person can change the amount
				o['actualStocks'] = pointsGoodsDetail.personExchange
				/ / purchase
				o['limits'] = pointsGoodsDetail.personExchange
				o['prodName'] = plainGoodsDetail.prodName
				return o
			})
			// return data[0]
			return {
				...plainGoodsDetail,
				pointsGoodsDetail,
				skus,
				integral,
				price
			}
		},
	    transform: goodsInfo= > {console.log('goodsInfo', goodsInfo)
	        / / round figure
	        goodsInfo['sliderImage'] = JSON.parse(goodsInfo.imgs)
			// goodsInfo['skus'] = JSON.parse(goodsInfo.skus)}}Copy the code

After the initialization has processed the data, the respective transformations and the common layer data transformations take place.

Summary: When to use policy patterns

According to Ali development specification – programming specification – control statement – article 6: more than 3 layers of if-else logic judgment code can use guard statement, policy mode, state mode to achieve.

You’ve all seen code like this:

if(conditionA) {logic1
} else if(conditionB) {logic2
} else if(conditionC) {logic3
} else{logic4
}
Copy the code

This code, while simple to write, violates the open-closed principle (open for extension, closed for modification) : if you need to add (remove) some logic, you will inevitably have to change the original code.

Especially if there is a large amount of code in the if-else block, subsequent code extension and maintenance will gradually become very difficult and error-prone, using the guard statement also cannot avoid these two problems.

According to the Ali Development Statute:

  • If-else no more than 2 layers, 1~5 lines of code in the block, directly written to the block, otherwise encapsulated as a method
  • If-else more than 2 layers, but no more than 3 lines of code in the block, try to use guard statements
  • If-else more than 2 levels and more than 3 lines of code in the block, use policy mode if possible

This article is about the implementation of the back-end strategy mode, but the core idea is also the use of the front end.