Что такое шаблоны проектирования и зачем их использовать?
Шаблоны проектирования - проверенный способ для решения проблем. Они не включают в себя такие очевидные вещи, как использование for loop для перебора элементов массива. Их используют для решения более сложных проблем, с которыми мы сталкиваемся при разработке больших приложений.
Типы шаблонов и примеры некоторых из них:
- Порождающие (Creational design patterns): создание новых объектов.
- Структурные (Structural design patterns): упорядочивают объекты.
- Поведенческие (Behavioral design patterns): как объекты соотносятся друг с другом.
- Constructor
// ES5 function Person(name, age, favFood) { this.name = name this.age = age this.favFood = favFood } Person.prototype.greet = function() { console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old, and my favorite food is ${this.favFood}`) } const bob = new Person('Bob', 22, 'Chicken') bob.greet() // Hello, my name is Bob, I'm 22 years old, and my favorite food is Chicken
// ES6 class Person { constructor(name, age, favFood) { this.name = name this.age = age this.favFood = favFood } greet() { console.log(`Hello, my name is ${this.name}, I'm ${this.age} years old, and my favorite food is ${this.favFood}`) } } const bob = new Person('Bob', 22, 'Chicken') bob.greet() // Hello, my name is Bob, I'm 22 years old, and my favorite food is Chicken
- Factory
class SimpleMembership { constructor(name) { this.name = name this.cost = 50 } } class StandardMembership { constructor(name) { this.name = name this.cost = 100 } } class PremiumMembership { constructor(name) { this.name = name this.cost = 200 } } class MemberFactory { static list = { simple: SimpleMembership, standard: StandardMembership, premium: PremiumMembership } create(name, type = 'simple') { const Membership = MemberFactory.list[type] || MemberFactory.list.simple const member = new Membership(name) member.type = type member.define = function() { console.log(`${this.name} (${this.type}): ${this.cost}`) } return member } } const factory = new MemberFactory() const members = [ factory.create('John', 'simple'), factory.create('Doe', 'standard'), factory.create('Mike', 'premium'), factory.create('Foo') ] members.forEach(member => { member.define() }) /* John (simple): 50 Doe (standard): 100 Mike (premium): 200 Foo (simple): 50 */
- Prototype
const car = { wheels: 4, init() { console.log(`Wheels: ${this.wheels} / Owner: ${this.owner}`) } } const carWithOwner = Object.create(car, { owner: { value: 'John' } }) carWithOwner.init() // Wheels: 4 / Owner: John console.log(carWithOwner.__proto__ === car) // true
- Singletion
class Database { constructor(data) { if (Database.exists) { return Database.instance } Database.instance = this Database.exists = true this.data = data } getData() { return this.data } } const mongo = new Database('MongoDB') console.log(mongo.getData()) // MongoDB const mysql = new Database('MySQL') console.log(mysql.getData()) // MongoDB
- Adapter
class OldCalc { operations(t1, t2, operation) { switch (operation) { case 'add': return t1 + t2 case 'sub': return t1 - t2 default: return NaN } } } class NewCalc { add(t1, t2) { return t1 + t2 } sub(t1, t2) { return t1 - t2 } } class CalcAdapter { constructor() { this.calc = new NewCalc() } operations(t1, t2, operation) { switch (operation) { case 'add': return t1 + t2 case 'sub': return t1 - t2 default: return NaN } } } const oldCalc = new OldCalc() console.log(oldCalc.operations(10, 5, 'add')) // 15 const newCalc = new NewCalc() console.log(newCalc.add(10, 5)) // 15 const adapter = new CalcAdapter() console.log(adapter.operations(25, 10, 'sub')) // 15 console.log(adapter.calc.add(10, 5)) // 15
- Decorator
class Server { constructor(ip, port) { this.ip = ip this.port = port } get url() { return `https://${this.ip}:${this.port}` } } function decoratorAws(server) { server.isAws = true server.awsInfo = function() { return server.url } return server } function decoratorAzure(server) { server.isAzure = true server.port = 22 return server } const s1 = decoratorAws(new Server('12.34.56.78', 8080)) console.log(s1.isAws) // true console.log(s1.awsInfo()) // https://12.34.56.78:8080 const s2 = decoratorAzure(new Server('87.65.43.21', 36)) console.log(s2.isAzure) // true console.log(s2.port) // 22 console.log(s2.url) // https://87.65.43.21:22
- Facade
class Complaints { constructor() { this.complaints = [] } reply(complaint) {} add(complaint) { this.complaints.push(complaint) return this.reply(complaint) } } class ProductComplaints extends Complaints { reply({id, customer, details}) { return `Product: ${id}: ${customer} (${details})` } } class ServiceComplaints extends Complaints { reply({id, customer, details}) { return `Service: ${id}: ${customer} (${details})` } } class ComplaintRegistry { register(customer, type, details) { const id = Date.now() let complaint if (type === 'service') { complaint = new ServiceComplaints() } else { complaint = new ProductComplaints() } return complaint.add({id, customer, details}) } } const registry = new ComplaintRegistry() console.log(registry.register('John', 'service', 'Blocked')) // Service: 1595708989374: John (Blocked) console.log(registry.register('Doe', 'product', 'Denied')) // Product: 1595708989374: Doe (Denied)
- Chain of Responsibility
class MySum { constructor(initialValue = 5) { this.sum = initialValue } add(value) { this.sum += value return this } } const sum1 = new MySum() console.log(sum1.add(5).add(10).add(30).sum) // 50 const sum2 = new MySum(0) console.log(sum2.add(1).add(2).add(3).sum) // 6
- Command
class MyMath { constructor(initialValue = 0) { this.num = initialValue } square() { return this.num ** 2 } cube() { return this.num ** 3 } } class Command { constructor(subject) { this.subject = subject this.commandsExecuted = [] } execute(command) { this.commandsExecuted.push(command) return this.subject[command]() } } const x = new Command(new MyMath(2)) console.log(x.execute('square')) // 4 console.log(x.execute('cube')) // 8 console.log(x.commandsExecuted) // (2) ["square", "cube"]
- Mediator
class User { constructor(name) { this.name = name this.room = null } send(message, to) { this.room.send(message, this, to) } receive(message, from) { console.log(`${from.name} => ${this.name}: ${message}`) } } class ChatRoom { constructor() { this.users = {} } register(user) { this.users[user.name] = user user.room = this } send(message, from, to) { if (to) { to.receive(message, from) } else { Object.keys(this.users).forEach(key => { if (this.users[key] !== from) { this.users[key].receive(message, from) } }) } } } const john = new User('John') const doe = new User('Doe') const foo = new User('Foo') const room = new ChatRoom() room.register(john) room.register(doe) room.register(foo) john.send('Hello!', doe) doe.send('Hello!', john) foo.send('Hello!') /* John => Doe: Hello! Doe => John: Hello! Foo => John: Hello! Foo => Doe: Hello! */
- Strategy
class Vehicle { travelTime() { return this.timeTaken } } class Bus extends Vehicle { constructor() { super() this.timeTaken = 10 } } class Taxi extends Vehicle { constructor() { super() this.timeTaken = 5 } } class Car extends Vehicle { constructor() { super() this.timeTaken = 3 } } class Commute { travel(transport) { return transport.travelTime() } } const commute = new Commute() console.log(commute.travel(new Bus())) // 10 console.log(commute.travel(new Taxi())) // 5 console.log(commute.travel(new Car())) // 3