Simplest Explanation - Facade, Decorator, Composite, Adapter, Flyweight, Proxy, Module Pattern

In this article I have talked about structural design patterns along with it two other design patterns which are popular in javascript Module pattern and revealing module pattern.

Note - you may read more about the creational pattern in part 1 of this design pattern series

Github - https://github.com/overflowjs-com/design-pattern

Let's start with a brief review of Structural design patterns. Structural Design Patterns are design patterns that ease the design by identifying a simple way to realize relationships between entities or defines a manner for creating relationships between objects. It serves as a blueprint for how different classes and objects are combined to form larger structures.

The patterns that we are going to talk about in this article are Facade Pattern, Decorator Pattern, Composite Pattern, Adapter pattern, Flyweight Pattern, and Proxy pattern.

While the creational pattern is concerned with the creation of object while hiding the creation logic, structural design patterns deal with ways to club classes and object together to form large structures

Facade pattern

A facade is an object that serves as a front-facing interface masking a more complex underlying or structural code. Facade pattern hides the complexities of the system and provides an interface to the client using which the client can access the system. This type of design pattern comes under a structural pattern as this pattern adds an interface to the existing system to hide its complexities. jquery in javascript is a good example of the same.

In this example library is the client system which is using the auto library, Now it(library) doesn't have to know that what inner implementation is needed for lending a book out, that whole functionality is shielded from the client ( in autolibraryFacade).

class Library {

  //lending books from library
  lendBook = (genre, bookName, author, studentId) => {

    const aLibrarian = new autoLibrarianFacade();
    const isSuccess = aLibrarian.extractBook(genre, bookName, author, studentId);
    const {
      book,
      msg
    } = isSuccess;
    book ? console.log('Thats ur book') : console.log(msg);

  }
}


class autoLibrarianFacade {

  constructor() {
    //supposedly there is a mongoDb connections that contain inventory for library
    //const mongo = new Database('mongo');
    //for right now we are making pseudo function as mongo
    this.mongo = {
      isBookAvailable: (genre, bookName, author) => new Promise(resolve => {
        setTimeout(() => {
          resolve('5 seconds');
        }, 50000);
      }),
      addBookToStudentCard: (studentId) => new Promise(resolve => {
        setTimeout(() => {
          resolve('5 seconds');
        }, 50000);
      })
    };
  }

  extractBook(genre, bookName, author, studentId) {
    const isSuccess = this.isStudentEligible(studentId);
    if (!isSuccess) {
      return {
        book: false,
        msg: 'Student not eligible , please contact the admin for same.'
      }
    }
    //a pseudo function which will be connecting to our db
    const isBookAvailable = this.mongo.isBookAvailable(genre, bookName, author);

    if (isBookAvailable) {

      //adding the book to student card

      this.mongo.addBookToStudentCard(studentId);

      return {
        book: true,
        msg: true
      }
    } else return {
      book: false,
      msg: 'book not available'
    };

  }

  isStudentEligible(studentId) {
    //function will get student data from db to check if there are overstanding fine on student or
    //whether this student is elligible for getting books from this library

    return true;
  }
}

One prominent disadvantage with a facade is that it compromises on performance.

For eg: In jQuery library, getElementById("identifier") and $("#identifier") can be used to query an element on a page by its ID. But getElementById() on its own is significantly faster by a high order of magnitude.

So, using the pattern, try to be aware of any performance costs involved and make a call on whether they are worth the level of abstraction offered.

Decorator Pattern

Decorators offered the ability to add behavior to existing classes in a system dynamically. They can be used to modify existing systems where we wish to add additional features to objects without the need to heavily modify the underlying code using them.

The decorator type behavior is very easy to implement in JavaScript because JavaScript allows us to add methods and properties to object dynamically. The simplest approach would be to just add a property to an object, but it will not be efficiently reusable.

In this a pattern rather than just relying on prototypal inheritance, we work with a single base object and progressively add decorator objects which provide the additional capabilities(this will be clear with the example below). Developers enjoy using this pattern as it can be used transparently and is also fairly flexible, objects can be wrapped with new behavior and then continue to be used without needing to worry about the base object being modified. In a broader context, this pattern also avoids us needing to rely on large numbers of subclasses to get the same benefits.

In this example addFurnitures() function is a decorator which override the getCost method to add a new parameter, we could have used extend functionality too but then we have to create two classes and in the same way, if there are more options later we have to keep extending classes to add all those options which are a redundant code.

class Flat {

  constructor(size, address) {
    this.size = size;
    this.address = address;
  }

  getCost = () => {
    return 997; //default value
  }

}


//decorator 1
function addFurnitures(flat) {

  flat.furnished = true;
  const v = flat.getCost();

  flat.getCost = () => {
    return parseInt(v) + 1000;
  }
}

//decorator 2
function addBalcony(flat) {
  flat.hasBalcony = true;
  const v = flat.getCost();
  flat.getCost = () => {
    return parseInt(v) + 1000;
  }
}


const flat = new Flat('800sq ft', "gurgaon");

console.log(flat.getCost());
addFurnitures(flat);
console.log(flat);
console.log(flat.getCost());

Composite Pattern

The Composite Pattern describes a group of objects that can be treated in the same way a single instance of an object may be. This allows us to treat both individual objects and compositions in a uniform manner, meaning that the same behavior will be applied regardless of whether we're working with one item or a thousand.

It’s like a binary tree in data structure each node can either be a leaf node or can be a parent which can contain more leaf node within it. A good example of this could be a multi-level menu. Each node can be a distinct option, or it can be a menu itself, which has multiple options as its child.

A node component with children is a composite component, while a node component without any child is a leaf component. Popular libraries like React and Vue make extensive use of the composite pattern to build robust, reusable interfaces. This is a powerful concept as it helps make development much easier for consumers of the library, in addition to making it highly convenient to build scalable applications that utilize many objects.

While developing an application and you come across a situation where you're dealing with objects that have a tree structure(like directory system), it could end up being a very good decision to adopt this pattern into your code.

In this example I create two class document and document composite, documentComposit econtain collection of document so now whether you do document.setWatermark() something or documentComposite.setWatermark it will behave same ways i.e all the document will have watermark sign.

class Document {

  constructor(name) {
    this.name = name;
    this.watermark = false;
  }

  setWaterMark(isWaterMark) {
    this.watermark = isWaterMark;
  }
}

class DocumentComposit {

  constructor(name) {
    this.collection = [];
    if (name) {
      this.collection.push(new Document(name));
    }
  }

  add(name) {
    this.collection.push(name);
  }

  setWaterMark(isWaterMark) {

    this.collection.map(doc => {
      doc.setWaterMark(isWaterMark);
    })
  }
}

const forms = new DocumentComposit()
const pr2Form = new Document(
  'Primary Treating Physicians Progress Report (PR2)',
)
const w2Form = new DocumentComposit('Internal Revenue Service Tax Form (W2)');
forms.add(pr2Form);
forms.add(w2Form);

forms.setWaterMark('Namaste');

console.log(forms);

Adapter Pattern

The Adapter Pattern translates an interface for an object or class into an interface compatible with a specific system. This pattern is often used to create wrappers for new refactored APIs so that other existing old APIs can still work with them. This is usually done when new implementations or code refactoring result in a different public API, while the other parts of the system/clients are still using the old API and need to be adapted to work together.

In this example, there is an API getInfo which gives info about a user (its name, height, occupation, etc) but in new API we divide in into diff functionality so an adapter class is created so that this new API could still work with a system that uses old API.

class UserApi {

  getUserInfo = (userId) => {
    //a pseudo function that symbolises getting info from db
    const userInfo = (function() {
      return {
        name: 'Mantra',
        address: 'Gurgaon',
        occupation: 'SDE'
      }
    }());

    return [{
        name: userInfo.name
      },
      {
        addr: userInfo.address
      },
      {
        occ: userInfo.occupation
      }
    ]

  }
}


//New Class

class Users {

  getUsername = (id) => {
    // a pseudoFunctio
    const userName = (function() {
      return {
        name: 'Mantra'
      }
    }());

    return userName;
  }

  getUserAddr = (id) => {
    // a pseudoFunction
    const userAddr = (function() {
      return {
        address: 'Gurgaon'
      }
    }());

    return {
      addr: userAddr.address
    };
  }

  getUserOccupation = (id) => {
    // a pseudoFunction
    const userOcc = (function() {
      return {
        occupation: 'SDE'
      }
    }());

    return {
      occ: userOcc.occupation
    };
  }

}

//adapter class
class UserAdapter {

  constructor() {
    this.user = new Users();
  }

  getUserInfo = (id) => {
    const userName = this.user.getUsername(id);
    const userAddr = this.user.getUserAddr(id);
    const userOcc = this.user.getUserOccupation(id);

    return [
      userName,
      userAddr,
      userOcc
    ]
  }

}


const oldUser = new UserApi();
const userInfo = oldUser.getUserInfo();

const newUser = new UserAdapter();
const newUserInfo = newUser.getUserInfo();

Flyweight pattern

The Flyweight pattern is used for optimizing code that is repetitive, slow, and inefficiently shares data. It aims to minimize the use of memory in an application by sharing as much data as possible with related objects.

Each "flyweight" object is divided into two pieces: the extrinsic part, and the intrinsic part. The intrinsic state is stored in the Flyweight object. The extrinsic state is stored or computed by client objects and passed to the Flyweight when its operations are invoked. Modern web browsers use this technique to prevent loading the same images twice. When a browser loads a web page, it traverses through all images on that page. The browser loads all new images from the Internet and places them on the internal cache. For already loaded images, a flyweight object is created, which has some unique data like position within the page, but everything else is referenced to the cached one.

The main use cases of this technique are:

  • Used in caching

  • Used to optimize the object so that less memory usage is taken as the properties which are not intrinsic to the object could be pruned out.

In this example, we create flyweight class coffee for sharing data regarding the type of coffees and a factory class coffee factory to create those flyweight objects. For memory conservation, the objects are recycled if the same object is instantiated twice. This is a simple example of flyweight implementation.

// caching suppose there is cofee machine which if have already created a
//type of cofee we provide taht or we else we provide a new one.
class coffee {
  constructor(type) {
    this.type = type;
  }
}

class coffeeFactory {
  constructor() {
    this.coffeeCollection = [];
  }

  createCoffee(type) {
    let coffee = this.getCoffee(type);
    if (coffee) {
      return coffee;
    }

    const newCoffe = new coffee(type);
    this.coffeeCollection.push(newCoffe);
    return newCoffe;
  }

  getCoffee(type) {
    return this.coffeeCollection.find(item => item.type = type)
  }

}

Proxy Pattern

n the proxy pattern, one object acts as an interface to another object. The proxy sits between the client of an object and the object itself and protects the access to that object.This pattern help in performance optimization.The proxy pattern can be very useful when working with network request-heavy applications to avoid unnecessary or redundant network requests.

In the real world it’s useful when we do an HTTP operation.Now suppose

You have a list of videos on the page. When the user clicks a video title, the area below the title expands to show more information about the video and also enables the video to be played. The detailed video information and the URL of the video are not part of the page; they need to be retrieved by making a web service call.

The web service can accept multiple video IDs, so we can speed up the application by making fewer HTTP requests whenever possible and retrieving data for several videos at one time. The videos object doesn't call the HTTP service directly but calls the proxy instead. The proxy then waits before forwarding the request. If other calls from videos come in the 50ms waiting period, they will be merged into one.

A delay of 50ms is pretty imperceptible for the user but can help combine requests and speed up the experience when clicking “toggle” and expanding more than one video at once. It also reduces the server load significantly since the webserver has to handle a smaller number of requests.

In this example for the user API a UserProxy is created so that for each network call we don't have to directly access the network we can check in the cache of proxy class for information.

// A pseudo function that is working as a api for now
function User() {

  this.getUserInfo = (id) => {

    switch (id) {
      case 1:
        return "apple";
      case 2:
        return "ball";
      case 3:
        return "cat";
      case 4:
        return "nayna";
      case 5:
        return "mantra";
      default:
        return "Unknown"
    }
  }
}

//a proxy class , will give cached info if present else will call a api saving network call
class UserProxy {

  constructor() {
    cache = [];
  }

  getUserInformation = (id) => {
    if (cache[id]) {
      return cache[id];
    }

    //get info from api
    const user = new User();
    return user.getUserInfo(id);

  }
}

There are two more patterns we have to talk about which are common in modern javascript Module pattern and revealing module pattern -

Module pattern

The module pattern is a pattern that provides a way of wrapping a mix of public and private methods and variables, protecting pieces from leaking into the global scope, and accidentally colliding with another developer's interface. With this pattern, only a public API is returned, keeping everything else within the closure private. It encapsulates our code containing various methods and functions in a single object/function so as to provide data privacy. This gives us a clean solution for shielding logic doing the heavy lifting whilst only exposing an interface we wish other parts of our application to use. The pattern utilizes an immediately-invoked function expression (IIFE - see the section on namespacing patterns for more on this) where an object is returned.

In this example, you can access any function of shoppingBasket but not the main basket object, coz its encapsulated by the module

const shoppingBasket = (function() {

  //private
  const basket = [];

  const calculateTotprice = () => {
    return basket.reduce((tot, item) => tot + item.price, 0);
  }
  return {
    addItem: (item) => {
      basket.push(item);
    },

    totalItem: () => {
      return basket.length;
    },

    totalPrice: () => {
      const totalVal = calculateTotprice();
      return totalVal;
    },
    clearAll: () => {
      basket.size = 0;
    }
  }

})();

shoppingBasket.addItem({
  item: "bread",
  price: 5
});

shoppingBasket.addItem({
  item: "butter",
  price: 10
});

Revealing Module pattern

The revealing module pattern is the same as module pattern except that here we would simply define all of our functions and variables in the private scope and return an anonymous object with pointers to the private functionality we wished to reveal as public.

This means that instead of returning an object in which we define our different function, we can define our variables and methods right up in the area we normally consider private. This will still return an object, but that object will have simple key/value pair references to anything you want to make public to a consumer of the module. Its advantage is that this pattern allows the syntax of our scripts to be more consistent. It also makes it more clear at the end of the module which of our functions and variables may be accessed publicly which eases readability.

A disadvantage of this pattern is that if a private function refers to a public function, that public function can't be overridden if a patch is necessary. This is because the private function will continue to refer to the private implementation and the pattern doesn't apply to public members, only to functions. Public object members which refer to private variables are also subject to the no-patch rule notes above. As a result of this, modules created with the Revealing Module pattern may be more fragile than those created with the original Module pattern, so care should be taken during usage.

I am using the same example as I have used in module pattern so that its easy to see the difference in both pattern

const shoppingBasket = (function() {

  //private
  const basket = [];

  const calculateTotprice = () => {
    return basket.reduce((tot, item) => tot + item.price, 0);
  }

  addItem = (item) => {
      basket.push(item);
    },

    totalItem = () => {
      return basket.length;
    },

    totalPrice = () => {
      const totalVal = calculateTotprice();
      return totalVal;
    },
    clearAll = () => {
      basket.size = 0;
    }

  return {
    addItem,
    totalItem,
    totalPrice,
    clearAll
  }

})();

shoppingBasket.addItem({
  item: "bread",
  price: 5
});

shoppingBasket.addItem({
  item: "butter",
  price: 10
});

All the code discussed here is available on my GitHub link ( https://github.com/lazy-sunshine/design-pattern )

Get yourself added to our 2500+ people subscriber family to learn and grow more and please hit the share button on this article to share with your co-workers, friends, and others.

Check out articles onJavascript, Angular , Node.js , Vue.js

For more articles stay tuned tooverflowjs.com

Happy coding:heart:

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章