Guten Tag, Freunde!Strukturelle Entwurfsmuster werden verwendet, um große Beziehungssysteme zwischen Objekten aufzubauen, um Flexibilität und Effizienz aufrechtzuerhalten. Schauen wir uns einige davon mit Verweisen auf das Game of Thrones an.In der Softwareentwicklung werden Entwurfsmuster verwendet, um die häufigsten Probleme zu lösen. Sie stellen die Best Practices dar, die Entwickler seit langem beim Testen von Anwendungen und Beheben von Fehlern entwickelt haben.In diesem Artikel werden wir über strukturelle Muster sprechen. Sie dienen zum Entwerfen von Anwendungen, indem sie eine einfache Möglichkeit zur Interaktion mit Instanzen definieren.Am häufigsten sind die folgenden Muster:- Adapter
- Dekorateur
- Fassade
- Adaptiv (Leichtgewicht (Element), Fliegengewicht)
- Proxy
Adapter
Ein Adapter ist eine Vorlage, mit der Sie die Schnittstelle einer Klasse in eine andere übersetzen (übertragen) können. Dadurch können Klassen zusammenarbeiten, was aufgrund inkompatibler Strukturen häufig nicht möglich ist.Stellen Sie sich vor, die Targaryen haben beschlossen, mit allen ihnen zur Verfügung stehenden Kräften (Flawless and Dragons) zu kämpfen, und Daenerys sucht nach einer Möglichkeit, mit jedem von ihnen zu interagieren.Wir werden folgendes brauchen:- Unbeschmutzte Klasse
- Drachenklasse
- Adapterklasse zum Übergeben der Brennmethode der Dragon-Klasse an die allgemeine Kill-Methode
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())
})()
Anwendungsfälle
- Wenn neue Komponenten oder Module mit in der Anwendung vorhandenen Komponenten zusammenarbeiten sollen oder wenn ein Teil des Codes infolge von Refactoring verbessert wurde, aber mit den alten Teilen interagieren soll.
- Wenn Sie eine Bibliothek zur Lösung sekundärer Probleme gefunden haben und diese in Ihre Anwendung integrieren möchten.
Dekorateur
Ein Dekorator ist eine Vorlage, mit der vorhandenen Klassen dynamisch Verhalten oder Funktionen hinzugefügt werden können. Dies ist eine Alternative zur Unterklassifizierung.Angenommen, wir möchten überprüfen, ob das Getränk, das König Joffrey serviert wird, vergiftet ist.Wir werden folgendes brauchen:- Königsklasse
- König Joffrey Instanz
- Getränkeklasse
- Klasse Vergiftetes Getränk
- isNotPoisoned-Funktion, um das Leben des Königs zu retten
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)
})()
Anwendungsfälle für Dekorateure
- Wenn wir einer großen Anzahl von Klassen oder Methoden mit unterschiedlichen Kontexten eine Funktion hinzufügen möchten
- Wenn wir eine zuvor erstellte Klasse verbessern möchten, aber keine Zeit für ein vollständiges Refactoring haben
Fassade
Fassade - eine in Bibliotheken weit verbreitete Vorlage. Es wird verwendet, um eine einheitliche und einfache Schnittstelle bereitzustellen und die Komplexität seiner Subsysteme oder Unterklassen zu verbergen.Stellen Sie sich vor, wir wollen eine Armee in einem Kampf mit Barbaren kontrollieren. Instanzen unserer Armee haben Methoden, um Kavallerie, Soldaten und Riesen zu bewegen. Wir sind jedoch gezwungen, diese Methoden separat aufzurufen, was viel Zeit in Anspruch nimmt. Wie können wir das Armeemanagement vereinfachen?Wir werden folgendes brauchen:- Armeeproben
- ArmyFacade-Klasse
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
})
})()
Anwendungsfälle
- Wenn wir viele Codezeilen, die sich möglicherweise wiederholen, in eine einfache Funktion konvertieren möchten.
Opportunist
Adaptiv - eine Vorlage für eine effiziente Datenübertragung zwischen vielen kleinen Objekten. Es wird verwendet, um die Leistung zu verbessern und Speicherplatz zu sparen.Angenommen, wir wollen eine Armee weißer Wanderer kontrollieren. Gleichzeitig können unsere Wanderer drei Zustände haben:Wir werden folgendes brauchen:- WhiteWalker-Klasse
- Klasse WhiteWalkerFlyweight
- Klient für die Auferstehung der weißen Wanderer
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())
})()
Anwendungsfälle
- Wenn wir vermeiden möchten, eine große Anzahl von Objekten zu erstellen
- Wenn wir Objekte erstellen möchten, die viel Speicher verbrauchen
- Wenn wir Objekte brauchen, deren Erstellung komplexe Berechnungen erfordert
- Wenn wir nur über begrenzte Ressourcen verfügen: Rechenleistung, Speicher, Speicherplatz usw.
Proxies
Die Proxy-Vorlage wird, wie der Name schon sagt, als Add-In oder Ersatz für ein anderes Objekt verwendet, um den Zugriff auf dieses Objekt zu steuern.Stellen Sie sich vor, Königin Cersei hat ein Dekret erlassen, das die Rekrutierung von mehr als 100 Soldaten ohne ihre Erlaubnis verbietet. Wie setzen wir das um?Wir werden folgendes brauchen:- Klassensoldat
- ArmyProxy-Klasse zur Prozesssteuerung
- Instanzen der Cercei-Klasse zur Erlaubnis
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`)
})()
Anwendungsfälle
- Wenn das Objekt, das wir verwenden möchten, weit entfernt ist (tief verschachtelt), speichern Sie die Logik im Proxy, um den Client nicht zu beeinträchtigen
- Wenn wir in Erwartung eines realen Ergebnisses ein ungefähres Ergebnis angeben möchten, dessen Berechnung viel Zeit in Anspruch nimmt
- Wenn wir den Zugriff oder die Erstellung eines Objekts steuern möchten, ohne dessen Logik zu beeinträchtigen
Github-
Code .Hinweis trans: hier ist ein tolles Video über Designmuster.Vielen Dank für Ihre Aufmerksamkeit.