JavaScript 마스터하기: 구조 디자인 패턴
"좋은 코드는 그 자체로 최고의 문서입니다." - Steve McConnell, 'Code Complete'의 저자
디자인 패턴은 모두 관계에 관한 것이지만 Creational Design Patterns 에서는 개체의 인스턴스를 만드는 데 중점을 두기 때문에 많은 관계를 찾지 못합니다. 이를 처리하는 유일한 패턴은 다른 객체를 생성하는 팩토리 패턴입니다.
구조 디자인 패턴은 개체 관계 및 구성을 단순화하여 변화하는 요구 사항에 적응할 수 있는 유연하고 유지 관리 가능한 소프트웨어 시스템을 만듭니다. 따라서 고품질 코드를 생성하는 데 필수적입니다.
구조 디자인 패턴에서는 모든 것이 관계를 중심으로 이루어집니다. 각각의 경우에 객체가 있고 그것이 다른 것과 어떻게 관련되는지 확인합니다. 관계를 다루는 것은 두 가지 방법으로 수행됩니다.
- 기능 확장,
- 기능 단순화.
데코레이터 패턴
데코레이터 패턴은 눈에 거슬리지 않고 기존 객체에 새로운 기능을 추가하는 데 사용됩니다. 데코레이터 패턴으로 해결하려는 문제는 생성자를 엉망으로 만드는 것이 아닙니다 . 철저히 테스트되고 작동하는 코드에 버그를 도입하고 싶지 않습니다.
예를 들어 생성자 자체의 동작을 변경하지 않고 생성자의 속성을 확장하려고 합니다. 다음 예제에서는 sportsCar라는 속성 hp과 다음 메서드 로 데코레이션합니다 calculateSpeed.
var Car = function(name) {
this.name = name;
this.active = false;
}
Car.prototype.activate = function() {
console.log("Activating:", this.name);
this.active = true;
}
Car.prototype.save = function() {
console.log("Saving car:", this.name);
}
var car = new Car("BMW");
car.activate();
car.save();
var sportsCar = new Car("M Performance");
// decorate with property 'hp'
sportsCar.hp = 355;
// decorate with method 'calculateSpeed'
sportsCar.calculateSpeed = function() {
console.log("Max speed is 250 km/h!");
}
// calling 'activate' method attached to a construtor 'Car'
sportsCar.activate();
// decorating (overriding) with new 'save' method
sportsCar.save = function() {
this.calculateSpeed();
// bind the save execution to the 'this' scope
// calling original save from Car
Car.prototype.save.call(this);
}
// calling 'save' method attached to an object 'sportsCar'
sportsCar.save();
var Car = function(name) {
this.name = name;
this.active = false;
}
Car.prototype.activate = function() {
console.log("Activating:", this.name)
this.active = true;
}
Car.prototype.save = function() {
console.log("Saving car:", this.name);
}
var bmw = new Car("BMW");
bmw.activate();
bmw.save();
// sub-classing (wrapping 'Car')
var SportsCar = function(name, hp) {
// call super-class
Car.call(this, name);
this.hp = hp;
}
// wire up the prototype object
// do not assign it to 'Car.prototype', as they are both going to change together
// create new instance of 'Car.prototype'
SportsCar.prototype = Object.create(Car.prototype);
// add new method 'calculateSpeed' on prototype
SportsCar.prototype.calculateSpeed = function() {
console.log("Max speed is 250 km/h!");
}
// decorating 'save' method
SportsCar.prototype.save = function() {
this.calculateSpeed();
console.log("Tuning car...")
// bind the 'save' execution to the 'this' scope
// calling original 'save' from 'Car'
Car.prototype.save.call(this);
}
var m3 = new SportsCar("M3", 543);
var m5 = new SportsCar("M5", 600);
// calling 'activate' methods attached to a constructor 'Car'
m3.activate();
m5.activate();
// calling 'save' methods attached to a constructor 'SportsCar'
m3.save();
m5.save();
console.log({ m3, m5 });
파사드 패턴
잠재적으로 복잡한 하위 시스템에 대한 단순화된 인터페이스를 제공하기 위해 이를 사용합니다 . 그것은 우리에게 무언가를 그것 앞에 놓을 수 있는 방법을 제공하고 시스템과 매우 간단하고 쉽게 상호 작용하게 합니다. 이해하기 어려운 것 뒤에 있는 지저분한 논리를 덮는 역할을 합니다.
jQuery가 이 패턴의 훌륭한 예가 되는 이유는 웹 페이지와 상호 작용하려고 할 때 간단하고 깨끗한 인터페이스를 제공하여 DOM 위에 위치할 수 있는 기능입니다 . jQuery를 사용하면 몇 줄의 코드만으로 쉽게 요소를 조작하고 DOM을 탐색하고 이벤트를 처리할 수 있습니다.
CarService다음 예제에는 메서드에 자동차 개체를 처리하는 데 도움이 되는 서비스 역할을 하는 여러 함수가 있습니다 . 보시다시피 코드가 지저분하고 무슨 일이 일어나고 있는지 보기가 어렵습니다. 그러나 이것이 이러한 유형의 패턴의 요점입니다.
CarService다음 예제에는 자동차 개체를 처리하는 데 도움이 되는 많은 함수가 있습니다 . 보시다시피 코드가 지저분하고 무슨 일이 일어나고 있는지 이해하기 어렵습니다. 그러나 이것이 이러한 유형의 패턴의 요점입니다.
var Car = function(data) {
this.name = data.name;
this.model = data.model;
this.series = data.series;
this.active = data.active;
this.user = data.user;
}
var CarService = function() {
return {
activate: function(car) {
car.active = true;
console.log("Activating car:", car.name);
},
setProductionYear: function(car) {
car.productionYear = "2020-10-18T00:00:00Z";
console.log(car.name + " is released in " + car.productionYear);
},
notifyActivation: function(car, user) {
console.log("Notifying " + user.first + " of the activation!");
},
save: function(car) {
console.log("Saving car:", car.name);
}
}
}();
var car = new Car({
name: "BMW",
model: "M850i xDrive Coupé",
series: "8",
active: false,
user: {
first: "John",
last: "Smith"
}
})
CarService.activate(car);
if (car.active) {
CarService.setProductionYear(car);
CarService.notifyActivation(car, car.user);
CarService.save(car);
}
console.log(car);
var Car = function(data) {
this.name = data.name;
this.model = data.model;
this.series = data.series;
this.active = data.active;
this.user = data.user;
}
// acts as a Module Pattern
// not the Revealing Module Pattern
var CarService = function() {
return {
activate: function(car) {
car.active = true;
console.log("Activating car:", car.name);
},
setProductionYear: function(car) {
car.productionYear = "2020-10-18T00:00:00Z";
console.log(car.name + " is released in " + car.productionYear);
},
notifyActivation: function(car, user) {
console.log("Notifying " + user.first + " of the activation!");
},
save: function(car) {
console.log("Saving car:", car.name);
}
}
}();
// Façade
var CarServiceWrapper = function (){
var setProductionYearAndNotify = function(car){
CarService.activate(car);
if (car.active) {
CarService.setProductionYear(car);
CarService.notifyActivation(car, car.user);
CarService.save(car);
}
}
// Revealing Module Pattern
return {setProductionYearAndNotify};
// If this were a CommonJS, it will be exported as an executed function (singleton)
}();
var car = new Car({
name: "BMW",
model: "M850i xDrive Coupé",
series: "8",
active: false,
user: {
first: "John",
last: "Smith"
}
});
// Only one line of code in the main exectution context
CarServiceWrapper.setProductionYearAndNotify(car);
console.log(car);
플라이급 패턴
플라이웨이트 패턴은 객체 간에 객체의 일부를 공유하여 메모리를 절약하는 데 사용됩니다 . 속성과 메서드를 여러 번 복제하는 대신 패턴을 사용하면 모든 개체의 인스턴스에서 공유할 수 있습니다.
객체를 사용하는 것과 유사합니다 prototype. 모든 개체 인스턴스에 대해 함수를 여러 번 만드는 대신 prototype. 이 패턴은 비슷한 솔루션을 제공하지만 약간 다른 방식으로 제공됩니다. 전체적인 결과는 더 작은 메모리 공간입니다 .
브라우저에서 메모리는 모바일 장치만큼 중요하지 않습니다. 이 경우 메모리 사용량을 가능한 한 작게 유지해야 하며 이 패턴을 통해 이를 달성할 수 있습니다.
주목해야 할 한 가지 중요한 점은 객체의 인스턴스가 많을 때 이 패턴이 유용하다는 것입니다. 적은 수의 개체(500개 미만)만 있는 경우 구현하지 마십시오. 메모리 관리와 관련하여 멋진 결과를 얻지 못할 수 있습니다.
// you have to run the code in NodeJS environment
var Car = function(data) {
this.name = data.name;
this.model = data.model;
this.series = data.series;
this.active = data.active;
this.user = data.user;
}
var CarCollection = function() {
var cars = {};
var count = 0;
var add = function(data) {
cars[data.name] = new Car(data);
count++;
}
var get = function(name) {
return cars[name];
}
var getCount = function() {
return count;
}
return {
add,
get,
getCount
}
}
var cars = new CarCollection();
var series = [1, 2, 3, 4, 5, 6, 7, 8];
var models = ["M3", "M5", "M Performance", "xDrive Coupé", "X7"];
var users = ["John", "Adam", "Eva", "Emma"];
var active = [true, false];
var initMemory = process.memoryUsage().heapUsed;
// one million iterations
for (var i = 0; i < 1000000; i++) {
cars.add({
name: "BMW" + i,
model: models[Math.floor((Math.random() * 5))],
series: series[Math.floor((Math.random() * 8))],
active: active[Math.floor((Math.random() * 2))],
user: users[Math.floor((Math.random() * 4))],
});
}
var afterMemory = process.memoryUsage().heapUsed;
// Used Memory 196.444432
console.log("Used Memory", (afterMemory - initMemory) / 1000000);
// you have to run the code in NodeJS environment
var Flyweight = function(data) {
this.model = data.model;
this.series = data.series;
this.active = data.active;
this.user = data.user;
}
var FlyweightFactory = function() {
// it's a subset of cars
// only unique property for each 'car' is a 'name'
// everything else is shared (random values)
var flyweights = {};
var get = function(model, series, active, user) {
if (!flyweights[model + series + active + user]) {
flyweights[model + series + active + user] = new Flyweight(model, series, active, user);
}
return flyweights[model + series + active + user];
}
var getCount = function() {
var count = 0;
for (var f in flyweights) {
count++;
}
return count;
}
return {
get,
getCount
};
}();
var Car = function(data) {
this.flyweight = FlyweightFactory.get(data.model, data.series, data.active, data.user);
this.name = data.name;
}
// use a setter without exposing the fact that the Flyweight Pattern is used
Car.prototype.getModel = function() {
this.flyweight.model;
}
var CarCollection = function() {
var cars = {};
var count = 0;
var add = function(data) {
cars[data.name] = new Car(data);
count++;
}
var get = function(name) {
return cars[name];
}
var getCount = function() {
return count;
}
return {
add,
get,
getCount
}
}
var cars = new CarCollection();
var series = [1, 2, 3, 4, 5, 6, 7, 8];
var models = ["M3", "M5", "M Performance", "xDrive Coupé", "X7"];
var users = ["John", "Adam", "Eva", "Emma"];
var active = [true, false];
var initMemory = process.memoryUsage().heapUsed;
// one million iterations
for (var i = 0; i < 1000000; i++) {
cars.add({
name: "BMW" + i,
model: models[Math.floor((Math.random() * 5))],
series: series[Math.floor((Math.random() * 8))],
active: active[Math.floor((Math.random() * 2))],
user: users[Math.floor((Math.random() * 4))],
});
}
var afterMemory = process.memoryUsage().heapUsed;
// Used Memory 171.417472
console.log("Used Memory", (afterMemory - initMemory) / 1000000);
// Cars: 1000000
console.log("Cars:", cars.getCount());
// Flyweights: 320
console.log("Flyweights:", FlyweightFactory.getCount());
결론
결론적으로, 우리는 구조 디자인 패턴의 매혹적인 세계와 물체의 구성을 변경하는 능력을 탐구했습니다. 다음을 포함하여 이러한 패턴을 선택했습니다.
- 데코레이터 패턴 ( 개체 자체를 변경하지 않고 추가 기능 추가 ),
- 파사드 패턴 ( 깨끗한 래퍼 뒤에 복잡한 API 숨기기 ),
- 플라이웨이트 패턴 ( 메모리 사용량을 줄이기 위해 개체 조각 공유 ).

![연결된 목록이란 무엇입니까? [1 부]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































