Bom dia amigosPadrões de design estrutural são usados para criar grandes sistemas de relacionamentos entre objetos, a fim de manter a flexibilidade e a eficiência. Vejamos alguns deles com referências ao Game of Thrones.No desenvolvimento de software, os padrões de design são usados para resolver os problemas mais comuns. Eles representam as melhores práticas desenvolvidas por um longo tempo pelos desenvolvedores no processo de teste de aplicativos e correção de bugs.Neste artigo, falaremos sobre padrões estruturais. Eles foram projetados para projetar aplicativos, definindo uma maneira simples de interagir com instâncias.Os mais comuns são os seguintes padrões:- Adaptador
- Decorador
- Fachada
- Adaptável (peso leve (elemento), peso mosca)
- Proxy
Adaptador
Um adaptador é um modelo que permite traduzir (transferir) a interface de uma classe para outra. Ele permite que as classes trabalhem juntas, o que geralmente é impossível devido a estruturas incompatíveis.Imagine que os Targaryen decidiram lutar com todas as forças à sua disposição (Flawless e Dragons), e Daenerys está procurando uma maneira de interagir com cada uma delas.Vamos precisar do seguinte:- Classe imaculada
- Classe dragão
- Classe do adaptador para passar o método de gravação da classe Dragon para o método comum de eliminação
class Unsullied {
constructor(name) {
this.name = name
this.kill = this.kill.bind(this)
}
kill() {
console.log(`Unsullied ${this.name} kill`)
}
}
class Dragon {
constructor(name) {
this.name = name
this.burn = this.burn.bind(this)
}
burn() {
console.log(`Dragon ${this.name} burn`)
}
}
class DragonAdapter extends Dragon {
kill() {
this.burn()
}
}
(() => {
const Army = [
new DragonAdapter('Rhaegal'),
new Unsullied('Grey worm'),
new DragonAdapter('Drogon')
]
Army.forEach(soldier => soldier.kill())
})()
Casos de uso
- Quando novos componentes ou módulos devem funcionar com os existentes no aplicativo ou quando parte do código foi aprimorada como resultado da refatoração, mas devem interagir com as partes antigas.
- Quando você encontrou uma biblioteca para resolver problemas secundários e deseja integrá-la ao seu aplicativo.
Decorador
Um decorador é um modelo projetado para adicionar dinamicamente comportamento ou funcionalidade às classes existentes. Essa é uma alternativa à subclassificação.Suponha que queremos verificar se a bebida servida ao rei Joffrey está envenenada.Vamos precisar do seguinte:- Classe King
- Instância do rei Joffrey
- Aula de bebida
- classe Bebida Envenenada
- Função isNotPoisoned para salvar a vida do rei
function isNotPoisoned(t, n, descriptor) {
const original = descriptor.value
if(typeof original === 'function') {
descriptor.value = function(...args) {
const [drink] = args
if(drink.constructor.name === 'poisonedDrink') throw new Error('Someone wants to kill the king')
return original.apply(this, args)
}
}
return descriptor
}
class PoisonedDrink {
constructor(name) {
this.name = name
}
}
class Drink {
constructor(name) {
this.name = name
}
}
class King {
constructor(name) {
this.name = name
}
@isNotPoisoned
drink(drink) {
console.log(`The king drank ${drink}`)
}
}
(() => {
const joffrey = new King('Joffrey Baratheon')
const normalDrink = new Drink('water')
const poisonedDrink = new Drink('poisoned water')
joffrey.drink(normalDrink)
joffrey.drink(poisonedDrink)
})()
Casos de uso do decorador
- Quando queremos adicionar uma função a um grande número de classes ou métodos com diferentes contextos
- Quando queremos melhorar uma aula criada anteriormente, mas não temos tempo para uma refatoração completa
Fachada
Fachada - um modelo amplamente usado em bibliotecas. É usado para fornecer uma interface unificada e simples e ocultar a complexidade de seus subsistemas ou subclasses.Imagine que queremos controlar um exército em uma batalha com bárbaros. Instâncias de nosso exército têm métodos para mover cavalaria, soldados e gigantes. Mas somos forçados a chamar esses métodos separadamente, o que leva muito tempo. Como podemos facilitar a administração do exército?Vamos precisar do seguinte:- espécimes do exército
- Classe ArmyFacade
class Horse {
constructor(name) {
this.name = name
}
attack() {
console.log(`Infantry ${this.name} attack`)
}
}
class Soldier {
constructor(name) {
this.name = name
}
attack() {
console.log(`Soldier ${this.name} attack`)
}
}
class Giant {
constructor(name) {
this.name = name
}
attack() {
console.log(`Giant ${this.name} attack`)
}
}
class ArmyFacade {
constructor() {
this.army = [];
(new Array(10)).fill().forEach((_, i) => this.army.push(new Horse(i + 1)));
(new Array(10)).fill().forEach((_, i) => this.army.push(new Soldier(i + 1)));
(new Array(1)).fill().forEach((_, i) => this.army.push(new Giant(i + 1)));
this.getByType = this.getByType.bind(this)
}
getByType(type, occurency) {
return this.army.filter(el => {
return el.constructor.name === type && occurency-- > 0
})
}
attack(armyInfo = {}) {
const keys = Object.keys(armyInfo)
let subArmy = []
keys.forEach(soldier => {
switch(soldier) {
case 'horse': {
subArmy = [...subArmy, ...this.getByType('Horse', armyInfo.horse)]
break;
}
case 'soldier': {
subArmy = [...subArmy, ...this.getByType('Soldier', armyInfo.soldier)]
break;
}
case 'giant': {
subArmy = [...subArmy, ...this.getByType('Giant', armyInfo.giant)]
break;
}
}
})
subArmy.forEach(soldier => soldier.attack())
}
}
(() => {
const army = new ArmyFacade()
army.attack({
horse: 3,
soldier: 5,
giant: 1
})
})()
Casos de uso
- Quando queremos converter muitas linhas de código, possivelmente repetidas, em uma função simples.
Oportunista
Adaptável - um modelo projetado para transferência eficiente de dados entre muitos objetos pequenos. É usado para melhorar o desempenho e economizar memória.Suponha que queremos controlar um exército de caminhantes brancos. Ao mesmo tempo, nossos caminhantes podem ter três estados:Vamos precisar do seguinte:- Classe WhiteWalker
- classe WhiteWalkerFlyweight
- cliente para a ressurreição de caminhantes brancos
class WhiteWalker {
constructor({
sprite,
someOtherBigInformation
}) {
this.sprite = sprite
this.someOtherBigInformation = someOtherBigInformation
this.state = 'alive'
this.resurrect = this.resurrect.bind(this)
this.kill = this.kill.bind(this)
this.getState = this.getState.bind(this)
}
kill() {
this.state = 'dead'
}
resurrect() {
this.state = 'resurrected'
}
getState() {
return this.state
}
}
const whiteWalker = new WhiteWalker({
sprite: Date.now()
})
class WhiteWalkerFlyweight {
constructor(position, name) {
this.position = position
this.name = name
this.whiteWalker = whiteWalker
}
getInfo() {
console.log(`The White Walker ${this.name} whit sprite ${this.whiteWalker.sprite} is ${this.whiteWalker.state}`)
}
getFatherInstance() {
return this.whiteWalker
}
}
(() => {
const myArmy = []
for(let i = 0; i < 10; i++) {
myArmy.push(new WhiteWalkerFlyweight({
x: Math.floor(Math.random() * 200),
y: Math.floor(Math.random() * 200),
}, i + 1))
}
myArmy.forEach(soldier => soldier.getInfo())
console.log('KILL ALL')
const [onOffWhiteWalker] = myArmy
onOffWhiteWalker.getFatherInstance().kill()
myArmy.forEach(soldier => soldier.getInfo())
console.log('RESURRECT ALL')
onOffWhiteWalker.getFatherInstance().resurrect()
myArmy.forEach(soldier => soldier.getInfo())
})()
Casos de uso
- Quando queremos evitar criar um grande número de objetos
- Quando queremos criar objetos que consomem uma grande quantidade de memória
- Quando precisamos de objetos cuja criação requer cálculos complexos
- Quando temos um suprimento limitado de recursos: poder de computação, memória, espaço etc.
Proxies
O modelo de proxy, como o nome indica, é usado como um suplemento ou um substituto para outro objeto para controlar o acesso a esse objeto.Imagine que a rainha Cersei emitiu um decreto proibindo o recrutamento de mais de cem soldados sem a permissão dela. Como implementamos isso?Vamos precisar do seguinte:- soldado de classe
- Classe ArmyProxy para controle de processos
- instâncias da classe Cercei para permissão
class Soldier {
constructor(name) {
this.name = name
}
attack() {
console.log(`Soldier ${this.name} attack`)
}
}
class Queen {
constructor(name) {
this.name = name
}
getConsent(casualNumber) {
return casualNumber %2 === 0
}
}
class ArmyProxy {
constructor() {
this.army = []
this.available = 0
this.queen = new Queen('Cercei')
this.getQueenConsent = this.getQueenConsent.bind(this)
}
getQueenConsent() {
return this.queen.getConsent(Math.floor(Math.random() * 200))
}
enrollSoldier() {
if(!this.available) {
const consent = this.getQueenConsent()
if(!consent) {
console.error(`The queen ${this.queen.name} deny the consent`)
return
}
this.available = 100
}
this.army.push(new Soldier(this.army.length))
this.available--
}
}
(() => {
const myArmy = new ArmyProxy()
for(let i = 0; i < 1000; i++) {
myArmy.enrollSoldier()
}
console.log(`I have ${myArmy.army.length} soldiers`)
})()
Casos de uso
- Quando o objeto que queremos usar está longe (profundamente aninhado) e salva a lógica no proxy para não afetar o cliente
- Quando queremos fornecer um resultado aproximado em antecipação a um resultado real, cujo cálculo leva muito tempo
- Quando queremos controlar o acesso ou a criação de um objeto sem interferir com sua lógica
Código do Github .Nota trans: aqui está um ótimo vídeo sobre padrões de design.Obrigado pela atenção.