Padrões de design - maneiras de resolver os problemas mais comuns no desenvolvimento de software. Neste artigo, veremos padrões generativos com referências ao Game of Thrones.Leia sobre padrões estruturais aqui .Os padrões de geração são projetados para trabalhar com mecanismos de construção de objetos, a fim de criar um objeto da maneira mais apropriada nessa situação.Os padrões generativos mais comuns são os seguintes:- Fábrica (Tecido)
- Abstração (Resumo)
- Construtor (Construtor)
- Protótipo (Protótipo)
- Singleton (Singleton)
Fábrica
Uma fábrica é um padrão que usa os chamados métodos de fábrica para criar objetos sem a necessidade de determinar a classe do objeto criado. O que isto significa?Imagine que queremos ser capazes de criar soldados. Esses soldados podem ser da casa Targaryen ou da casa Lannister.Para isso, precisamos:- Interface para definir soldados
- Uma classe para cada tipo de soldado para capacitar todos os lares
- Classe para solicitar a criação de um novo soldado, independentemente da casa à qual ele pertence
class Soldier {
constructor(name) {
this.name = name
this.attack = this.attack.bind(this)
}
attack() {}
}
class LannisterSoldier extends Soldier {
attack() {
return 'Lannister always pays his debts'
}
}
class TargaryenSoldier extends Soldier {
attack() {
return 'Fire and blond'
}
}
class Spawn {
constructor(type, name) {
if (type === 'lannister') return new LannisterSoldier(name)
else return new TargaryenSoldier(name)
}
}
(() => {
const lannister = new Spawn('lannister', 'soldier1')
const targaryen = new Spawn('targaryen', 'soldier2')
console.log(lannister.attack())
console.log(targaryen.attack())
})()
Quando é usado?
Isenção de responsabilidade: um modelo abstrato com base em objetos. É extremamente difícil de usar na programação funcional.Este modelo permite encapsular um grupo de fábricas individuais que executam tarefas semelhantes, sem a necessidade de definir classes específicas. No uso padrão, o software cliente cria uma implementação específica da fábrica abstrata e, em seguida, usa a interface geral da fábrica para criar objetos específicos como parte de um único sistema. O cliente não sabe (ou não importa para ele) quais objetos específicos recebe de cada fábrica interna, pois uma interface comum é usada para isso.Imagine que queremos gerenciar cada casa individualmente. Cada casa deve ter a oportunidade de determinar o herdeiro do trono e sua amada.Para isso, precisamos:
class HeirToTheThrone {
conctructor(name, isActualOnTheThrone) {
this.name = name
this.isActualOnTheThrone = isActualOnTheThrone
this.getTheThrone = this.getTheThrone.bind(this)
}
getTheThrone() {}
}
class HeirToTheThroneLannister extends HeirToTheThrone {
getTheThrone(){
console.log('kill all')
}
}
class HeirToTheThroneTargaryen extends HeirToTheThrone {
getTheThrone() {
console.log('burn all')
}
}
class Subject {
constructor(name) {
this.name = name
this.speak = this.speak.bind(this)
}
speak() {}
}
class SubjectLannister extends Subject {
speak(){
console.log('i love Cersei')
}
}
class SubjectTargaryen extends Subject {
speak(){
console.log('i love Daenerys')
}
}
class House {
constructor() {
this.getHeir = this.getHeir.bind(this)
this.getSubject = this.getSubject.bind(this)
}
getHeir(){}
getSubject(){}
}
class Lannister extends House {
getHeir() {
return new HeirToTheThroneLannister('Cersei', true)
}
getSubject(name) {
return new SubjectLannister(name)
}
}
class Targaryen extends House {
getHeir() {
return new HeirToTheThroneTargaryen('Daenerys', true)
}
getSubject(name) {
return new SubjectTargaryen(name)
}
}
(()=>{
const lannister = new Lannister()
const targaryen = new Targaryen()
lannister.getHeir().getTheThrone()
lannister.getSubject().speak()
targaryen.getHeir().getTheThrone()
targaryen.getSubject().speak()
})()
?
O objetivo do designer é separar um objeto complexo de suas representações. Com a complexidade do objeto, este modelo permite separar o processo de criação de um novo objeto por outro objeto (construtor).Imagine que queremos criar uma frota para cada casa. Cada família terá um certo número de navios e guerreiros. Para facilitar a criação, poderemos chamar o método makeNavy, que criará automaticamente tudo o que precisamos através das configurações.Para isso, precisamos:- Classe para criar uma frota
- Interface para definição de construtor
- A classe responsável por criar os objetos do nosso exército
- Classes responsáveis pela criação de soldados e navios
class Lannister {
constructor() {
this.soldiers = []
this.ships = []
this.makeNavy = this.makeNavy.bind(this)
}
makeNavy(soldiers, ships) {
const Build = new ConcreteBuilder()
for (let i = 0; i < soldiers; i++) {
this.soldiers.push(Build.createSoldier())
}
for (let i = 0; i < ships; i++) {
this.ships.push(Build.createShip())
}
}
}
class Builder {
createSoldier() {}
createShip() {}
}
class ConcreteBuilder extends Builder {
createSoldier() {
const soldier = new Soldier()
return soldier
}
createShip() {
const ship = new Ship()
return ship
}
}
class Soldier {
constructor() {
console.log('soldier created')
}
}
class Ship {
constructor() {
console.log('ship created')
}
}
(() => {
const lannister = new Lannister()
lannister.makeNavy(100, 10)
})()
Quando é usado?
- Quando o processo de criação de um objeto é muito complexo, envolve um grande número de parâmetros obrigatórios e opcionais
- Quando um aumento no número de parâmetros do construtor leva a um aumento no número de construtores
- Quando o cliente espera visualizações diferentes para o objeto construído
Protótipo
O protótipo permite determinar os tipos de objetos criados por meio da herança do protótipo e criar novos objetos usando o esquema de um objeto existente. Isso melhora o desempenho e minimiza a perda de memória.Imagine que queremos criar um exército de caminhantes brancos. Não temos requisitos especiais para este exército. Nós apenas queremos muitos deles, que tenham as mesmas características e que não ocupem muito espaço na memória.Para isso, precisamos:- Classe para armazenar informações sobre caminhantes brancos
- Método para clonar uma instância retornando o mesmo método
class WhiteWalker {
constructor(force, weight) {
this.force = force
this.weight = weight
}
clone() {
console.log('cloned')
return new WhiteWalker(this.name, this.weight)
}
}
(()=>{
const firstWalker = new WhiteWalker()
const walkers = [firstWalker]
for(let i=0;i<100;i++){
walkers.push(firstWalker.clone())
}
})()
Vantagens e desvantagens do protótipo
Prós:- Ajuda a economizar custo, tempo e produtividade, eliminando a necessidade de usar um novo operador para criar novos objetos
- Reduz a complexidade de inicializar um objeto: cada classe usa seu próprio método de clonagem
- Não há necessidade de classificar e criar muitas subclasses para inicializar objetos, como ao usar um modelo abstrato
- Aumenta a flexibilidade do sistema criando novos objetos, alterando algumas propriedades do objeto copiado
Minuses:- A clonagem de objetos complexos com referências circulares é uma tarefa não trivial
Singleton
O Singleton permite garantir que o objeto criado seja a única instância de uma classe específica. Esse objeto geralmente é usado para controlar várias operações no sistema.Imagine que queremos ter certeza de ter uma única mãe-dragão.Para isso, precisamos:- Classe para armazenar informações sobre a Mãe dos Dragões
- Instância salva de Mãe Dragão
let INSTANCE = null
class MotherOfDragons {
constructor(name, dragons) {
if(!!INSTANCE) return INSTANCE
this.dragons = dragons
this.name = name
this.getMyDragons = this.getMyDragons.bind(this)
INSTANCE = this
}
getMyDragons(){
console.log(`I'm ${this.name} and my dragons are`, this.dragons)
}
}
(()=>{
const dragonMother = new MotherOfDragons('Daenerys Targaryen', [
'Drogon',
'Rhaegal',
'Viserion'
])
const dragonMother2 = new MotherOfDragons('Cercei Targaryen', [
'Tirion',
'Jennifer',
'Goblin'
])
dragonMother.getMyDragons()
dragonMother2.getMyDragons()
console.log(dragonMother instanceof MotherOfDragons)
console.log(dragonMother2 instanceof MotherOfDragons)
console.log(dragonMother2 === dragonMother)
})()
Quando é usado?
- Quando os recursos usados para criar o objeto são limitados (por exemplo, objetos de conexão com o banco de dados)
- É uma boa prática criar autorização com um singleton para melhorar o desempenho.
- Quando você precisa criar uma classe para configurar o aplicativo
- Quando você precisa criar uma classe para alocação de recursos
Código do Github .Nota trans: aqui está um ótimo vídeo sobre padrões de design.Obrigado pela atenção.