内容简介: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 -Github -
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:
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
破茧成蝶:用户体验设计师的成长之路
刘津、李月 / 人民邮电出版社 / 2014-7 / 69.00
市面上已经有很多专业的用户体验书籍,但解决用户体验设计师在职场中遇到的众多现实问题的图书并不多见。本书从用户体验设计师的角度出发,系统地介绍了其职业生涯中的学习方法、思维方式、工作流程等,覆盖了用户体验设计基础知识、设计师的角色和职业困惑、工作流程、需求分析、设计规划和设计标准、项目跟进和成果检验、设计师职业修养以及需要具备的意识等,力图帮助设计师解决在项目中遇到的一些常见问题,找到自己的职业成长......一起来看看 《破茧成蝶:用户体验设计师的成长之路》 这本书的介绍吧!