Что такое шаблоны проектирования и зачем их использовать?
Шаблоны проектирования - проверенный способ для решения проблем. Они не включают в себя такие очевидные вещи, как использование 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