javascript design pattern

wony·2022년 8월 31일
0

올해 연말 쯤 런칭을 목표로하고 있는 프로젝트를 시작하기전, 회사프로젝트에서 사용할 디자인패턴에 대한 정립을 하기 위해 공부를 해보았다

목차

  • Singleton pattern
  • Factory pattern
  • Container/Presentational Pattern
  • Proxy pattern

✏️Singleton Pattern


사용될 앱 전체에서 단일 글로벌 인스턴스를 공유 하는 것이다

인스턴스가 필요할 때, 똑같은 인스턴스를 만들지 않고 기존의 인스턴스를 활용하는 것!

생성자가 여러번 호출되도, 실제로 생성되는 객체는 하나이며 최초로 생성된 이후에 호출된 생성자는 이미 생성한 객체를 반환시키도록 만드는 것이다

싱글톤은 한 번 인스턴스화할 수 있고 전역적으로 액세스할 수 있는 클래스입니다.
이 단일 인스턴스 는 응용 프로그램 전체에서 공유할 수 있으므로 Singleton은 응용 프로그램의 전역 상태를 관리하는 데 적합합니다.

let instance;
let counter = 0;

class Counter {
  constructor() {
    if (instance) {
      throw new Error("You can only create one instance!");
    }
    instance = this;
  }

  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const counter1 = new Counter();
const counter2 = new Counter();
// Error: You can only create one instance!

new 를 통해 새로운 두개의 인스턴스를 만들었지만 하나의 인스턴스만 만들 수 있기때문에 오류가 발생한다

그래서 하나의 인스턴스만 만들 수 있도록 동결해주는 메서드가 있는데 바로 Object.freez이다

Object.freez 는 사용하는 코드가 singleton을 수정할 수 없도록 한다
고정된 인스턴스의 속성은 추가하거나 수정할 수 없으므로 실수로 Singleton의 값을 덮어쓸 위험이 줄어든다

let instance;
let counter = 0;

class Counter {
  constructor() {
    if (instance) {
      throw new Error("You can only create one instance!");
    }
    instance = this;
  }

  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;

장점

  • 고정된 메모리 영역을 얻으면서 한번의 new로 인스턴스를 사용하기 때문에 메모리 낭비를 방지할 수 있다
  • 싱글톤으로 만들어진 클래스의 인스턴스는 전역 인스턴스이기 때문에 다른 클래스의 인스턴스들이 데이터를 공유하기 쉽다.
  • DBCP(DataBase Connection Pool)처럼 공통된 객체를 여러개 생성해서 사용해야하는 상황에서 많이 사용.

단점

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다. DIP를 위반한다.
  • 클라이언트가 구체 클래스에 의존해서 OCP 원칙을 위반할 가능성이 높다.
  • 테스트하기 어렵다. 내부 속성을 변경하거나 초기화 하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다. 결론적으로 유연성이 떨어진다.
  • 안티패턴으로 불리기도 한다.

✏️Factory Pattern

팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 만드는 패턴이다.

팩토리 패턴은 객체지향 디자인 패턴이다
팩토리 패턴은 비교적 복잡한 객체를 생성하는 경우에 유용할 수 있다.
key와 value의 값이 특정 환경이나 구성에 따라 달라질 수 있으며 팩토리 패턴을 사용하면 사용자가 정의한 key와 value의 값을 포함하는 새 객체를 쉽게 생성할 수 있다.

const createUser = ({ firstName, lastName, email }) => ({
  firstName,
  lastName,
  email,
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  }
});

const user1 = createUser({
  firstName: "John",
  lastName: "Doe",
  email: "john@doe.com"
});

const user2 = createUser({
  firstName: "Jane",
  lastName: "Doe",
  email: "jane@doe.com"
});

console.log(user1);
console.log(user2);

장점

  • 객체간의 결합도를 낮출 수 있다
  • 단일 책임 원칙을 따른다. 프로그램의 코드에서 생성자 코드를 분리함으로서 코드를 더울 간결하게 만들 수 있다
  • 개방 폐쇄 원칙을 따른다 기존 client의 코드를 파괴하지 않고 새로운 타입을 추가할 수 있다

단점

  • 패턴을 구현할 많은 서브 클래스를 도입함으로서 코드가 복잡해 질 수 있다

✏️Container/Presentational Pattern

개인적으로는 이 패턴을 주로 사용해서 가장 익숙한 패턴이다

로직을 수행하는 컴포넌트와, markup을 통해 ui를 보여주는 컴포넌트가 분리된 패턴이다

container component

  • 어떻게 동작하는지, 어떤 로직을 수행하는지에 관련이 있다
  • markup을 사용하지 않는다
  • 스타일을 사용하지 않는다
  • 데이터와 데이터 조작에 관한 함수를 만들고 present component에 제공한다
export default class DogImagesContainer extends React.Component {
  constructor() {
    super();
    this.state = {
      dogs: []
    };
  }

  componentDidMount() {
    fetch("https://dog.ceo/api/breed/labrador/images/random/6")
      .then(res => res.json())
      .then(({ message }) => this.setState({ dogs: message }));
  }

  render() {
    return <DogImages dogs={this.state.dogs} />;
  }
}

presentational component

  • 사용자가 직접 보고, 조작하는 컴포넌트 (ui와 관련)
  • state를 직접 조작하지 않으며, container component가 내려준 prop의 함수에 의해 state를 변경한다
  • 그에 따라 useState, useCallback 등 state관련된 훅이 하나도 없다.
  • 상태를 거의 가지지 않으며, 상태를 가진다면 데이터에 관한것이 아닌 ui 상태에 관한 것이다
import React from "react";

export default function DogImages({ dogs }) {
  return dogs.map((dog, i) => <img src={dog} key={i} alt="Dog" />);
}

장점

  • 관심사의 분리를 더 잘할 수 있다
  • 이 패턴 방식으로 Component 를 작성하면 작업을 하는 앱 과 UI 를 보다 이해하기 쉽게 만들 수 있다
  • 재 사용성을 높일 수 있다
  • 완전히 다른 곳으로부터 온 여러 상태(state) 라 할지라도, 이를 표현하기 위해 같은 Presentational Component 사용할 수 있는데 이때, 각 상태를 나타내는 Container Component 를 만들어 이를 또 재사용 할 수 있다
  • Markup 작업이 편하다

단점

  • 파일의 갯수 증가 (Container 컴포넌트를 분리했을 시)
  • React-Redux 초보자가 바로바로 배우기 어렵다
  • props를 단번에 파악하기 어려움

✏️Proxy Pattern

프록시(Proxy)를 번역하면 대리자, 대변인의 의미를 갖고 있다.
즉, 프록시에게 어떤 일을 대신 시키는 것이다.

어떤 객체를 사용하고자 할때, 객체를 직접적으로 참조하는 것이 아닌 해당 객체를 대항하는 객체를 통해 대상 객체에 접근하는 방식을 사용하면 해당 객체가 메모리에 존재하지 않아도 기본적인 정보를 참조하거나 설정할 수 있고, 실제 객체의 기능이 필요한 시점까지 객체의 생성을 미룰 수 있다.

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${obj[prop]}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    obj[prop] = value;
    return true;
  }
});

personProxy.name;
personProxy.age = 43;

// The value of name is John Doe 
// Changed age from 42 to 43 

proxy 처리하는 메서드 중 가장 많이 사용하는것은 getset이 있다

get 은 값을 가져올때 사용한다
set 은 값을 수정할때 사용한다

장점

  • 사이즈가 큰 객체가 로딩되기 전에도 프록시를 통해 참조를 할 수 있다.
  • 실제 객체의 public, protected 메소드를 숨기고 인터페이스를 통해 노출시킬 수 있다.
  • 로컬에 있지 않고 떨어져있는 객체를 사용할 수 있다.
  • 원래 객체에 접근에 대해 사전처리를 할 수 있다.

단점

  • 객체를 생성할 때 한 단계를 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있다.
  • 프록시 내부에서 객체 생성을 위해 스레드가 생성, 동기화가 구현되어야 하는 경우 성능이 저하될 수 있다.
  • 로직이 난해해져 가독성이 떨어질 수 있다.

참조
https://www.patterns.dev/

https://cokeworld.tistory.com/26

https://velog.io/@newtownboy/%EB%94%94%EC%9E%90%EC%9D%B8%ED%8C%A8%ED%84%B4-%ED%94%84%EB%A1%9D%EC%8B%9C%ED%8C%A8%ED%84%B4Proxy-Pattern

profile
무럭무럭 성장중🌿

0개의 댓글