Strategy Pattern benefits

Take advantage of strategy pattern 

One of the ways to improve your code and become better developer in general one should familiarize himself with design patterns. There is amazing book from gang of four called "Design patterns. Elements of Reusable Object Oriented Software". It is one of the best known books. Today I hope to explain the concept of one of the design patterns, Strategy pattern to be specific. 

I will try to avoid technical definitions and instead will use simple language to my best ability.  Basically it is an ability to multiple ways to accomplish a problem dependent on specific criteria. if you still feel confused don't worry things should clear by the end of article. 

Imagine the scenario where we have e-commerce platform, once customer buys a product, it can be shipped by only one company. Let's dive into the code. 

const productInfo = {
    productName: "Blender", 
    location: "Malmö", 
    postCode: 12345
    weight: 2
}

// our PostNord delivery class 

class Delivery {

  constructor(product) {
    this.product = product
  }
  
  // shipments are usually calculated based on few factors
  calculateShipping() {
    return this.calculateBasedOnWeight + this.calculateBasedOnLocation
   }
  
  calculateBasedOnWeight() {
      return this.product.weight * 1.25
  }
  
  calculateBasedOnLocation() {
      return this.product.location * 0.23
  }
}

// to create delivery we just simple instantiate delivery class

const delivery = new Delivery(productInfo);
const shipmentPrice = delivery.calculateShipping()

Now, even this simple example looks ok, however our management comes and tells us that we should add to our business logic another shipment company. So what do we do in that situation. We add another parameter to our Delivery class. 

class Delivery {

  constructor(product, company) {
    this.product = product
    this.company = company
  }
  
  // shipments are usually calculated based on few factors
  // for simplicity let's assume that it is calculated based on weight
  calculateShipping() {
    return this.calculateBasedOnWeight + this.calculateBasedOnLocation
  }
  
  calculateBasedOnWeight() {
      // DHL has additional fees to be accounted
      if(this.company === "DHL") {
      
        return this.product.weight * 2.24 + this.additionalFess()
      }
      
      if(this.company === "POST_NORD") {
      
          return this.product.weight * 1.25
      }
  }
  
  // the same if logic must be implement here as well
  calculateBasedOnLocation() {
     
      if(this.company === "DHL") {
      
        return this.product.weight * 2.22
      }
      
      if(this.company === "POST_NORD") {
      
          return this.product.location * 0.23 + this.polutionTaxation()
      }
      
  }
  
  // for simplicity let's assume it calculates tax rate
  additionalFees() {
    return 1.2
  }
  
  // same goes here
  polutionTaxation() {
    return 0.32
  }
  
}

// let's instantiate our new class

const dhlDelivery = new Delivery(product, "DHL")
const dhpPrice = dhlDelivery.calculateShipping()

if we add more delivery methods and different shipment price calculation methods  our class will soon get messy and it would be hard to maintain code. The hint for strategy pattern is that our shipment price calculation is dependent on shipment company. Thus we can create different strategies for shipment calculation. Let's extract price calculation methods to a strategy. 

class PostNordDelivery {

  constructor(product) {
    this.product = product
  }
  
  calculateShipping() {
    return this.calculateBasedOnWeight() + this.calculateBasedOnLocation()
  }
  
  calculateBasedOnWeight() {
    return this.product.weight * 1.25
  }
  
  calculateBasedOnLocation() {
    return this.product.location * 0.23 + this.polutionTaxation()
  }
  
  polutionTaxation() {
    return 0.32
  }
      
}

// Let's extract DHL logic to seperate class

class DHLDelivery {

  constructor(product) {
    this.product = product
  }
  
  calculateShipping() {
    return this.calculateBasedOnWeight() + this.calculateBasedOnLocation()
  }
  
  calculateBasedOnWeight() {
    return this.product.weight * 2.24 + this.additionalFess()
  }
  
  calculateBasedOnLocation() {
    return this.product.weight * 2.22
  }
  
  additionalFess() {
    return 1.2
  }
      
}

We successfully extract logic. As you can see we do not have second argument in our class. So how are we supposed to figure out which class should be implemented. Let create a class with sole purpose of return specific strategy based on our input. (aka Factory pattern)

class ShippingFactory {
  constructor(company, product) {
    this.company = company
    this.product = product
  }
  
  getStrategy() {
    if(this.company ==="DHL") {
      const dhlShipping = new DHLDelivery(this.product)
      return dhlShipping.calculateShipping()
    }
    
    if(this.company ==="POST_NORD") {
      const postNordShipping = new PostNordShipping(this.product)
      return postNordShipping.calculateShipping(this.product)
    }
  }
}

As a final step, let actually use our patterns.

const productInfo = {
    productName: "Blender", 
    location: "Malmö", 
    postCode: 12345,
    weight: 2
}

let shippingCompany = new ShippingFactory("DHL", productInfo)
const shippingPrice = shippingCompany.getStrategy()

// => 10.12
console.log(shippingPrice)

I hope it will help you to understand this pattern and use it effectively.