반응형

props drilling을 줄이고 사용하는 입장에서 '조합'해서 사용할 수 있도록 하는 패턴 

 

예를 들어 

아래와 같은 코드가 있고, 내부적으로 2~4단계의 트리 depth단계로 구성된 컴포넌트가 있다면

<Counter text="example" ex1={<div></div>} onchange{()=>{ console.log(change)}} onClick={() => console.log('test1')}>

 

사용하는 입장에서는 내부 구성을 정확하게 알 수 없을것이다.

그래서 사용자 입장에서 컴포넌트를 직접 조합해서 구현하고 props를 직접 내려주는 방식이다.

 

코드 예시)

export default function AppEx() {
  return (
    <div>
      <Counter onClick={() => console.log('test1')}>
        <Counter.Increment />
        <Counter.Decrement />
        <Counter.Label />
      </Counter>

      <Counter onClick={() => console.log('test2')}>
        <Counter.Increment />
        <Counter.Label color="blue" />
        <Counter.Decrement />
      </Counter>

      <Counter onClick={() => console.log('test3')}>
        <Counter.Increment />
        <Counter.Label color="red" />
      </Counter>
    </div>
  );
}

 

구현된 화면 예시

 

장점 

 

매우 높은 유연성

선언적으로 구성 -> 가독성이 좋음 

하나의 giant component를 쪼개 여러개로 분리함으로써 복잡성이 줄어듬 

 

단점

 

매우 높은 유연성(실수 혹은 의도치 않는 코드 삽입 가능성 증가)

코드가 좀 복잡해짐

JSX 크기 증가

 

코드 예시)

import React, { useEffect, useState, Children } from 'react';
import styled from 'styled-components';

import { Decrement, Increment, Label, Reset } from './modules';

const StyledDiv = styled.div`
  display: inline-flex;
  flex-direction: row;
  border: 0.5px solid;
  gap: 0.5px;

  margin: 30px;
`;

function Counter({ children, onClick }) {
  const [counter, setCounter] = useState(0);
  const resetCounter = () => setCounter(0);

  return (
    <StyledDiv onClick={onClick}>
      {Children.map(children, (child) => {
        return React.cloneElement(child, {
          counter,
          resetCounter, // 그냥 첨가 가능하다는 예시용
          setCounter,
        });
      })}
    </StyledDiv>
  );
}

Counter.Reset = Reset;
Counter.Increment = Increment;
Counter.Decrement = Decrement;
Counter.Label = Label;

export default function AppEx() {
  return (
    <div>
      <Counter onClick={() => console.log('test1')}>
        <Counter.Increment />
        <Counter.Decrement />
        <Counter.Label />
      </Counter>

      <Counter onClick={() => console.log('test2')}>
        <Counter.Increment />
        <Counter.Label color="blue" />
        <Counter.Decrement />
      </Counter>

      <Counter onClick={() => console.log('test3')}>
        <Counter.Increment />
        <Counter.Label color="red" />
      </Counter>
    </div>
  );
}

 

구현하는 방식은 Context Api를 사용하는 방식과 Children와 cloneElement를 사용하는 방식 2가지를 발견했는데

 

ex) children & cloneElement 이용

function Counter({ children, onClick }) {
  const [counter, setCounter] = useState(0);
  const resetCounter = () => setCounter(0);

  return (
    <StyledDiv onClick={onClick}>
      {Children.map(children, (child) => {
        return React.cloneElement(child, {
          counter,
          resetCounter, // 그냥 첨가 가능하다는 예시용
          setCounter,
        });
      })}
    </StyledDiv>
  );
}

ex) context api 이용

const CounterContext = createContext(undefined);

function CounterProvider({ children, value }: any) {
  return <CounterContext.Provider value={value}>{children}</CounterContext.Provider>;
}

function useCounterContext() {
  const context = useContext(CounterContext);
  if (context === undefined) {
    throw new Error('useCounterContext must be used within a CounterProvider');
  }
  return context;
}

function Counter2({ children, onClick }) {
  const [counter, setCounter] = useState(0);
  const resetCounter = () => setCounter(0);

  const handleIncrement = () => {
    setCounter(counter + 1);
  };

  const handleDecrement = () => {
    setCounter(counter - 1);
  };

  return (
    <CounterProvider value={{ counter, handleIncrement, handleDecrement }}>
      <StyledDiv onClick={onClick}>{children}</StyledDiv>
    </CounterProvider>
  );
}

children & cloneElement 방식의 경우 cloneElement라길래 복사하는 비용이 크지 않을까? 검색해봤는데 

 

stackoverflow에 따르면 

어차피 JSX가 React.createElement(Object)로 전환되는 과정에서 cloneElement를 쓰면 똑같이 Object로 전환되면서 props만 전달해서 바꿔주는 식으로 작동해서 성능 이슈는 큰 문제가 아니라고 한다.

 

https://stackoverflow.com/questions/54922160/react-cloneelement-in-list-performance

 

React.cloneElement in List performance

I have doubts about React.cloneElement in List. Is that something that we should avoid doing or not, if we have lots of elements in list? Does React.cloneElement makes unnecessary re-renders that c...

stackoverflow.com

 

cloneElement 방식의 경우 depth가 깊어지면 다루기 좀 힘들어질꺼 같지만 간편하고

Context API 방식은 boilerplate?를 까는게 번거롭긴 하지만 depth에 상관없이 사용 가능할꺼 같다.

 

headlessui 란 오픈소스는 context 방식을 사용한것으로 보인다.

 

그외 비슷한 패턴들

 

쓸만한게 있을까 빠르게 훑고 넘어가자

 

Control Props Pattern

 

import React, { useState } from "react";
import { Counter } from "./Counter";

function Usage() {
  const [count, setCount] = useState(0);

  const handleChangeCounter = (newCount) => {
    setCount(newCount);
  };
  return (
    <Counter value={count} onChange={handleChangeCounter}>
      <Counter.Decrement icon={"minus"} />
      <Counter.Label>Counter</Counter.Label>
      <Counter.Count max={10} />
      <Counter.Increment icon={"plus"} />
    </Counter>
  );
}

export { Usage };

Example

 

Github: https://github.com/alexis-regnaud/advanced-react-patterns/tree/main/src/patterns/control-props

 

컴포넌트를 제어 컴포넌트로 사용 

하나의 'single source of truth' 단일 상태 사용 -> base component에 custom logic과 상태 부여

 

장점 

직접적 제어권 부여

 

단점

(compound 패턴보다) 구현의 복잡성 -> state, function, component 제어가 필요

 

 

Custom Hook Pattern

import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";

function Usage() {
  const { count, handleIncrement, handleDecrement } = useCounter(0);
  const MAX_COUNT = 10;

  const handleClickIncrement = () => {
    //Put your custom logic
    if (count < MAX_COUNT) {
      handleIncrement();
    }
  };

  return (
    <>
      <Counter value={count}>
        <Counter.Decrement
          icon={"minus"}
          onClick={handleDecrement}
          disabled={count === 0}
        />
        <Counter.Label>Counter</Counter.Label>
        <Counter.Count />
        <Counter.Increment
          icon={"plus"}
          onClick={handleClickIncrement}
          disabled={count === MAX_COUNT}
        />
      </Counter>
      <button onClick={handleClickIncrement} disabled={count === MAX_COUNT}>
        Custom increment btn 1
      </button>
    </>
  );
}

export { Usage }

Example

Github: https://github.com/alexis-regnaud/advanced-react-patterns/tree/main/src/patterns/custom-hooks

 

장점
제어의 역전 -> 메인 로직이 custom hook으로 전환

 

단점

 

로직과 렌더링UI의 분리 -> (남이 보기에) 가독성 하락

 

 

Props Getters Pattern

 

import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";

const MAX_COUNT = 10;

function Usage() {
  const {
    count,
    getCounterProps,
    getIncrementProps,
    getDecrementProps
  } = useCounter({
    initial: 0,
    max: MAX_COUNT
  });

  const handleBtn1Clicked = () => {
    console.log("btn 1 clicked");
  };

  return (
    <>
      <Counter {...getCounterProps()}>
        <Counter.Decrement icon={"minus"} {...getDecrementProps()} />
        <Counter.Label>Counter</Counter.Label>
        <Counter.Count />
        <Counter.Increment icon={"plus"} {...getIncrementProps()} />
      </Counter>
      <button {...getIncrementProps({ onClick: handleBtn1Clicked })}>
        Custom increment btn 1
      </button>
      <button {...getIncrementProps({ disabled: count > MAX_COUNT - 2 })}>
        Custom increment btn 2
      </button>
    </>
  );
}

export { Usage };

 

장점

 

getter를 통한 쉬운 사용 및 통합

복잡성을 getter에 감춤으로써 복잡성을 숨김

 

단점

 

Lack of visibility ->  getter가 어떻게 구현되어 있는지 모르는 사람들에게는 가독성이 하락한다

  //props getter for 'Counter'
  const getCounterProps = ({ ...otherProps } = {}) => ({
    value: count,
    "aria-valuemax": max,
    "aria-valuemin": 0,
    "aria-valuenow": count,
    ...otherProps
  });

getter는 이런식으로 구성되어있는데 spread 연산자로 뿌려주는 식이다.

 

State reducer pattern

 

custom hook 패턴에 reducer를 조합한 형태 

import React from "react";
import { Counter } from "./Counter";
import { useCounter } from "./useCounter";

const MAX_COUNT = 10;
function Usage() {
  const reducer = (state, action) => {
    switch (action.type) {
      case "decrement":
        return {
          count: Math.max(0, state.count - 2) //The decrement delta was changed for 2 (Default is 1)
        };
      default:
        return useCounter.reducer(state, action);
    }
  };

  const { count, handleDecrement, handleIncrement } = useCounter(
    { initial: 0, max: 10 },
    reducer
  );

  return (
    <>
      <Counter value={count}>
        <Counter.Decrement icon={"minus"} onClick={handleDecrement} />
        <Counter.Label>Counter</Counter.Label>
        <Counter.Count />
        <Counter.Increment icon={"plus"} onClick={handleIncrement} />
      </Counter>
      <button onClick={handleIncrement} disabled={count === MAX_COUNT}>
        Custom increment btn 1
      </button>
    </>
  );
}

export { Usage };

Example

Github: https://github.com/alexis-regnaud/advanced-react-patterns/tree/main/src/patterns/state-reducer

 

가장 파워풀하고 가장 Lack of visibility한 패턴이지 싶다.

 

느낀점?

 

협업시 가독성 문제 때문에 느낌상 1 ~ 3번 패턴이 젤 무난하지 않을까 싶다.

그 외로는 공통으로 사용할 수 있는 로직을 가지고(추상화) UI만 갈아끼우는 식으로 사용 가능할꺼 같다

 

reference 

 

https://javascript.plainenglish.io/5-advanced-react-patterns-a6b7624267a6

 

5 Advanced React Patterns

An overview of 5 modern advanced React patterns, including integration codes, pros and cons, and concrete usage within public libraries.

javascript.plainenglish.io

 

반응형

'소프트웨어공학 > 디자인패턴' 카테고리의 다른 글

메모  (0) 2022.03.25
프론트엔드에서의 레포지토리 패턴?  (0) 2022.03.18
[행위] 책임 연쇄 패턴  (0) 2022.03.13
[행위] 전략 패턴 & 커맨드 패턴  (0) 2022.03.07
[행동] 중재자 패턴  (1) 2022.03.01

반응형

https://blog.logrocket.com/implementing-repository-pattern-flutter/

* 레포지토리 패턴은 대부분 백엔드 내용이 많은데 프론트에도 쓰일 수 있나 해서 찾아본 글입니다!

듣기로는 백엔드에도 repository-service-controller 3계층으로 나누고 repository 계층에서 data source(DB)에 접근하는것으로 알고 있다.

 

이 글은 https://medium.com/backenders-club/consuming-apis-using-the-repository-pattern-in-vue-js-e64671b27b09를 개인 공부용으로 요약한 내용이고 틀린 오역이 있을수도 있습니다.

 

 

레포지토리 패턴이란?

 

데이터 출처에 관계없이 동일한 인터페이스로 접속할 수 있는 디자인 패턴 

보통 코드를 짜면 네트워크 요청을 할때 fetch/axios와 강한 결합도를 가지고 변경에 유연하지 않은데

 

중간에 인터페이스를 둬 

 

1. data acess code 재사용

2. (도메인별로 repositiory를 나눴을때) domain logic 사용이 쉬워짐 

3. logic의 결합도 감소

4. data access logic 과 business logic이 분리되어 테스트가 더 쉬워짐

5. DI의 개념이 추가됨 -> 테스트가 더 쉬워짐 

 

5개의 장점을 가질 수 있다.

 

4, 5번은 클린코드에서 말한

아직 존재하지 않는 코드를 사용하기
경계와 관련해 또 다른 유형은 아는 코드와 모르는 코드를 분리하는 경계다.
ex) 저쪽 팀이 아직 API를 설계하지 않았으므로 구체적인 방법은 모를때 구현을 미루고 이쪽 코드를 진행하기 위해 자체적으로 인터페이스를 정의한다. - 경계 part

경계를 구현하다보니 부수적으로 얻어지는 효과인거 같다. 

테스트시 or 백엔드에서 아직 구현이 안되었을시엔 프론트에서 mocking api를 사용하다 백엔드에서 구현이 완료되었을경우 손쉽게 갈아치울 수 있을걸로 보인다. 

 

// 03-28 추가

 

서비스워커에서 api 요청을 가로채서 프론트단에서 dummy data를 구현 안해도 되는 msw 라는걸 알게 되었다.

https://tech.kakao.com/2021/09/29/mocking-fe/

 

Mocking으로 생산성까지 챙기는 FE 개발

안녕하세요. 카카오엔터프라이즈 검색플랫폼프론트파트의 Lawrence.net입니다. 프론트엔드 개발 업무의 효율성을 높이기 위한 방법의 하나로 고민해 본 Mocking에 대해 설명하고 이를 적용했던 사례

tech.kakao.com

 

예제 코드

 

axiosclient.js

import axios from "axios";

const baseDomain = "https://jsonplaceholder.typicode.com";
const baseURL = `${baseDomain}`; // Incase of /api/v1;

// ALL DEFUALT CONFIGURATION HERE

export default axios.create({
  baseURL,
  headers: {
    // "Authorization": "Bearer xxxxx"
  }
});

 

우선 기본 axios를 정의한다.

 

repository.js

import Client from './Clients/AxiosClient';
const resource = '/posts';

export default class Repository {

	constructor({resource}){
		this.resource = resource;
    }

    get() {
        return Client.get(`${this.resource}`);
    },
    getPost(id) {
        return Client.get(`${this.resource}/${id}`);
    },
    create(payload) {
        return Client.post(`${this.resource}`, payload);
    },
    update(payload, id) {
        return Client.put(`${this.resource}/${id}`, payload);
    },
    delete(id) {
        return Client.delete(`${this.resource}/${id}`)
    },

    // MANY OTHER ENDPOINT RELATED STUFFS
};

기본 repository에 CRUD를 구현하고 필요시 상속 or 확장으로 메소드를 추가한다.

 

예를 들어 /user, /item, /car 등 여러 경로(도메인?)마다 기본 CRUD가 필요하다면 Repository를 상속만으로 각각 UserRepository, ItemRepositoy, CarRepository 를 사용해 기본 CRUD를 빠르게 구현할 수 있고 추가 메소드를 유연하게 붙여줄 수 있다.  

 

repositoryFactory.js

import PostRepository from './PostRepository'; // 만들어서 export?
import UserRepository from './UserRepository';

const repositories = {
    'posts': PostRepository,
    'users': UserRepository
}
export default {
    get: name => repositories[name]
};

레포지토리 생성은 팩토리 메소드 패턴을 이용한다.

각 도메인별로 이름을 붙이면 될 듯 하다.

 

Posts.js

<template>
  <div class="row">
    <Post v-for="(post, i) in posts" :key="i" :posts="post" />
    <div class="col-lg-8 col-md-10 mx-auto">
      <div class="clearfix">
        <a class="btn btn-primary float-right" href="#">Older Posts &rarr;</a>
      </div>
    </div>
  </div>
</template>

<script>
import Repository from "../repositories/RepositoryFactory";
const PostRepository = Repository.get("posts");

import Post from "./Post";
export default {
  name: "Posts",
  components: {
    Post
  },
  data() {
    return {
      posts: []
    };
  },
  created() {
    this.getPosts();
  },
  methods: {
    getPosts: async function() {
      const { data } = await PostRepository.get();
      this.posts = data;
    }
  }
};
</script>

Vue.js 예제인데 코드 흐름을 이해하는건 어렵진 않다.

사용하는 입장(Posts.js)에서는 레포지토리의 세부 사항을 알 필요가 없으므로 data access logic과 business logic이 분리되었다.

 

reference

 

https://blog.hanlee.io/2019/do-front-ends-dream-of-back-ends

 

프론트엔드는 백엔드의 꿈을 꾸는가?

점점 더 복잡해지는 프론트엔드와 백엔드의 유사점을 알아봅시다

blog.hanlee.io

 

https://blog.logrocket.com/implementing-repository-pattern-flutter/

 

Implementing a repository pattern In Flutter - LogRocket Blog

Write more manageable codebases for your Flutter apps by decoupling data access and business logic according to the repository design pattern.

blog.logrocket.com

https://medium.com/backenders-club/consuming-apis-using-the-repository-pattern-in-vue-js-e64671b27b09

 

Consuming APIs Using the Repository Pattern in Vue.js

in this article, we will be discussing how to consume API data with Vue.js using the Repository Pattern.

medium.com

 

반응형

'소프트웨어공학 > 디자인패턴' 카테고리의 다른 글

React Compound Component 패턴  (0) 2022.06.15
메모  (0) 2022.03.25
[행위] 책임 연쇄 패턴  (0) 2022.03.13
[행위] 전략 패턴 & 커맨드 패턴  (0) 2022.03.07
[행동] 중재자 패턴  (1) 2022.03.01
반응형

책임 연쇄 패턴(Chain of Responsibility)

 

클라이언트의 요청을 수신해서 처리할 수 있는 객체를 찾을때까지 집합 내부에서 요청을 전달하는 

패턴

 

'파이프 라인'이라고 비유하면 이해가 쉽습니다. 

파이프라인을 따라 가다가 요청을 처리 가능한 그 handler가 이벤트를 처리하고,

처리불가능하면 다음 handler에 요청을 넘깁니다.

 

장점)

 

결합도를 낮추고 요청의 발신자, 수신자 분리

클라이언트는 내부를 알 필요가 없다

집합 내 순서를 유연하게 처리 가능

새로운 요청을 처리하기 매우 쉬워진다(계속 붙여주면 됨) 

 

단점)

디버깅이 쉽지 않다

사이클이 발생하지 않게 주의

 

코드 예시

 

숫자를 넣었을때 10, 100, 1000이하인지 처리하는 handler를 만들어봅시다.

 

class COR {
    
    constructor(){
        this.setLog();
    }

    setLog(){
        this.log = 0;
    }

    setNextChain(nextChain){
        this.chain = nextChain;
        return nextChain;
    }

    support(number){
        if(this.resolve(number)){
            this.done(number);
        }
        else if(this.chain){
            this.chain.support(number);
        }
        else{
            this.fail(number);
        }
    }

    resolve(number){
        return false;
    }

    done(number){
        console.log(`${number} less than ${this.log} `);
    }

    fail(number){
        console.log(`${number} bigger than ${this.log} `);
    }
}

기본 interface는 위의 코드와 같습니다.

 

support 라는 메소드로 이벤트를 처리하는데,

지금 이벤트 핸들링이 가능하면 this.done 메소드를 호출해서 이벤트 체인을 끝내고 자신의 이벤트를 실행합니다.

그렇지 않으면 체인을 걸어준 다음 handler로 넘기고,

끝까지 도착했으면 이벤트 핸들링이 불가능한 뜻이므로 this.fail 메소드를 실행해 체이닝을 종료합니다.

 

class TenChain extends COR{
    setLog(){
        this.log = 10;
    }


    resolve(number){
        if(number<10){
            return true;
        }
        return false;
    }
}

class HundredChain extends COR{
    setLog(){
        this.log = 100;
    }

    resolve(number){
        if(number<100){
            return true;
        }
        return false;
    }
}

class ThousandChain extends COR{
    setLog(){
        this.log = 1000;
    }
    resolve(number){
        if(number<1000){
            return true;
        }
        return false;
    }
}

각각 10, 100, 1000 이하인지 확인하는 간단한 예시 이벤트 핸들러입니다. 

실제 책임 연쇄 패턴 사용 예시는 이보다 복잡하지만 전체 구조를 훑어보기에 알맞은 예시인거 같아서 작성했습니다.

 

let PC = new TenChain();
let cursor = PC;

cursor = cursor.setNextChain(new HundredChain());
cursor = cursor.setNextChain(new ThousandChain());

PC.support(1);
PC.support(30);
PC.support(210);
PC.support(220);
PC.support(3100);

/*
1 less than 10 
30 less than 100 
210 less than 1000 
220 less than 1000 
3100 bigger than 1000 
*/

10 handler -> 100 handler -> 1000 handler 로 체인이 걸려있고, 실제 테스트해보니

잘 작동하는것을 볼 수 있습니다.

 

 

 

 

ref

 

https://medium.com/geekculture/understanding-the-chain-of-responsibility-pattern-d729ef84621c

 

Understanding The Chain of Responsibility Pattern

Chain of responsibility pattern is a behavioral design pattern that we use to achieve loose coupling in our software design, where a…

medium.com

 

반응형

'소프트웨어공학 > 디자인패턴' 카테고리의 다른 글

메모  (0) 2022.03.25
프론트엔드에서의 레포지토리 패턴?  (0) 2022.03.18
[행위] 전략 패턴 & 커맨드 패턴  (0) 2022.03.07
[행동] 중재자 패턴  (1) 2022.03.01
[구조] 데코레이터 패턴  (0) 2022.02.28
반응형

전략 패턴

-> 

 

1. 동일 계열의 알고리즘을 정의하고

동적으로 행위의 수정이 필요한 경우

전략을 바꾸는것으로 행위의 수정이 가능하게 만드는 패턴 

 

https://ko.wikipedia.org/wiki/%EC%A0%84%EB%9E%B5_%ED%8C%A8%ED%84%B4

 

example

public class StrategyPatternWiki
{
    public static void Main(String[] args)
    {
        Customer firstCustomer = new Customer(new NormalStrategy());

        // Normal billing
        firstCustomer.Add(1.0, 1);

        // Start Happy Hour
        firstCustomer.Strategy = new HappyHourStrategy();
        firstCustomer.Add(1.0, 2);

        // New Customer
        Customer secondCustomer = new Customer(new HappyHourStrategy());
        secondCustomer.Add(0.8, 1);
        // The Customer pays
        firstCustomer.PrintBill();

        // End Happy Hour
        secondCustomer.Strategy = new NormalStrategy();
        secondCustomer.Add(1.3, 2);
        secondCustomer.Add(2.5, 1);
        secondCustomer.PrintBill();
    }
}

명령 패턴

-> 

 

요청 자체를 캡슐화

실행될 기능을 캡슐화해 기능 실행을 요구하는 호출자와 실제 기능을 실행하는 수신자 클래스 사이의 의존성 제거

 

https://ko.wikipedia.org/wiki/%EC%BB%A4%EB%A7%A8%EB%93%9C_%ED%8C%A8%ED%84%B4

 

/*the Invoker class*/
public class Switch {
    private Command flipUpCommand;
    private Command flipDownCommand;

    public Switch(Command flipUpCmd,Command flipDownCmd){
        this.flipUpCommand=flipUpCmd;
        this.flipDownCommand=flipDownCmd;
    }

    public void flipUp(){
         flipUpCommand.execute();
    }

    public void flipDown(){
         flipDownCommand.execute();
    }
}

/*Receiver class*/

public class Light{
     public Light(){  }

     public void turnOn(){
        System.out.println("The light is on");
     }

     public void turnOff(){
        System.out.println("The light is off");
     }
}


/*the Command interface*/

public interface Command{
    void execute();
}


/*the Command for turning on the light*/

public class TurnOnLightCommand implements Command{
   private Light theLight;

   public TurnOnLightCommand(Light light){
        this.theLight=light;
   }

   public void execute(){
      theLight.turnOn();
   }
}

/*the Command for turning off the light*/

public class TurnOffLightCommand implements Command{
   private Light theLight;

   public TurnOffLightCommand(Light light){
        this.theLight=light;
   }

   public void execute(){
      theLight.turnOff();
   }
}

/*The test class*/
public class TestCommand{
   public static void main(String[] args){
       Light light=new Light();
       Command switchUp=new TurnOnLightCommand(light);
       Command switchDown=new TurnOffLightCommand(light);

       Switch s=new Switch(switchUp,switchDown);

       s.flipUp();
       s.flipDown();
   }
}

 

둘의 차이점?

 

개인적으로 둘의 쓰임새는 비슷하다고 느껴서 엄청난 혼동이 왔습니다 

 

스텍오버플로우 형님들에 따르면 

 

Command - Open or Close [action change]

Typically the Command pattern is used to make an object out of what needs to be done

Strategy - Quicksort or Mergesort [algo change]

The Strategy pattern, on the other hand, is used to specify how something should be done

 

라는데 요약해보자면

 

커맨드 패턴 -> 다양한 action이 있고 어떤 action'(what)을 사용하냐에 중점

 

전략 패턴 -> 다양한 algorithm이 있고 '어떻게'(how) 해결하냐에 중점

 

인거 같아요.?

 

 

reference

 

 

https://gmlwjd9405.github.io/2018/07/07/command-pattern.html

 

[Design Pattern] 커맨드 패턴이란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

https://stackoverflow.com/questions/4834979/difference-between-strategy-pattern-and-command-pattern

 

Difference between Strategy pattern and Command pattern

What is the difference between the Strategy pattern and the Command pattern? I am also looking for some examples in Java.

stackoverflow.com

 

반응형
반응형

출처 https://ko.wikipedia.org/wiki/%EC%A4%91%EC%9E%AC%EC%9E%90_%ED%8C%A8%ED%84%B4

 


객체지향 모델에서는 객체를 구성할때 행동을 여러 객체로 분산시켜 처리하도록 권하고 있습니다(SRP)
그러다보니 시스템의 객체 분할이 객체 간 상호종속을 유발하고 이때문에 재사용성이 저하되거나 결합도가 증가할 수 있습니다.

그러면 객체는 독립적이야한다는 객체의 특성이 유명무실해질 수 있는데 객체들간의 직접적 통신을 제한하고
중재자 객체를 통해 서로 양방향 통신하게 하는 디자인 패턴입니다.

예를 들어 숫자를 increment하는 단순한 board를 만든다고 해봅시다.
text class, InputBar class, button class가 있고 서로 상호작용하지 않고 mediator 패턴으로 구현해봅시다.

역할은 단순하게

text : 지금 숫자가 얼만지 보여줍니다(show)

button : 누를때마다 숫자가 1씩 증가합니다
혹은 reset 가능합니다

InputBar : input으로 넣은 숫자로 갱신됩니다.

class Button {
  constructor(mediator) {
    this.mediator = mediator;
    this.points = 0;
  }

  increment() {
    this.mediator.increment();
  }

  reset() {
    this.mediator.reset();
  }
}

class InputBar {
  constructor(mediator) {
    this.mediator = mediator;
  }

  update(val) {
    this.mediator.update(val);
  }
}

class TextBar {
  constructor(mediator) {
    this.text = 0;
    this.mediator = mediator;
  }

  setText(val) {
    this.text = val;
  }

  show() {
    this.mediator.show(this.text);
  }
}


mediator 객체는 다음과 같습니다.

class BoardMediator extends Mediator {
  constructor() {
    this.button = new Button(this);
    this.inputBar = new InputBar(this);
    this.textBar = new TextBar(this);
    this.points = 0;
  }

  increment() {
    this.points += 1;
    this.textBar.setText(this.points);
    console.log(`increment ${this.points}`);
  }

  update(val) {
    this.points = val;
    this.textBar.setText(this.points);
    console.log(`update ${this.points}`);
  }

  reset() {
    this.points = 0;
    this.textBar.setText(this.points);
    console.log(`reset ${this.points}`);
  }

  show(val) {
    console.log(`show ${val}`);
  }
}


실행을 시뮬레이션 해보았을때

const med = new BoardMediator();

// click mocking event

med.button.increment();
med.button.increment();
med.button.reset();
med.button.increment();
med.inputBar.update(10);
med.button.increment();
med.textBar.show();

(인터넷을 뒤져보니 mediator 구조에 emit 처럼 observer pattern을 하나 더 끼워넣은 구조도 많이 보이던데 그런 방식도 괜찮은거 같아요)


객체간 통신(N:M)을 객체끼리 직접 상호작용하는게 아니라 mediator 객체를 통해서 실행시키므로 결합도를 여러개의 1:N 정도로 크게 줄였습니다.

 

단점으로는 

중재자 객체가 엄청 비대해질 가능성이 있겠죠. 

또한 비대해지면 유지보수가 힘들어질 느낌이 듭니다. 


느낌상
observer pattern은 1:N이고 단방향
퍼사드 패턴은 N:M이고 중재자 패턴이랑 비슷한 용도로 쓰이기는 하는데

단방향이고 관계를 단순화하고 통합된 구조에 좀 더 관심이 있는 구조

(한 이벤트가 발생하면 내부 객체들이 정해진 구조에 따라서 알맞게 제어되고 실행되는 통합된 구조를 만드는데 관심이 있는 구조)


그리고 mediator 패턴은 N:M이고 양방향을 가질 수 있는 구조 느낌?

reference

GOF의 디자인 패턴

반응형
반응형

 
데코레이터 패턴이란 구조 패턴중의 하나로 상속(inheritance)과 합성(Composition)을 통해
기존 객체에 동적으로 동작을 추가하는 구조적인 패턴입니다.
 

새로운 서비스의 추가가 필요할때 일반적으로 상속을 이용합니다.

하지만 상속을 사용한다면 다음과 같은 문제점이 발생할 수 있습니다.

 

1. 나중 추가사항이 생긴다면 superclass를 수정하고 기능을 집어넣어야 합니다.

 

2. 1번으로 인하여 superclass는 비대해지고

SOLID의 SRP를 위반합니다. (어떤 특정 subClass A에서만 사용하는 메소드가 B, C에도 상속됩니다.)

 

그래서 OCP의 컨셉을 활용하여 해결하려고 시도하는 디자인 패턴입니다.

 

예를 들어 라면집을 하기 위해서 라면을 구현한다고 해봅시다.
라면엔 면과 다시마가 들어간다고 볼 수 있습니다.

 

class Food {
  add() {
    //재료 추가
  }
}

class Ramen extends Food {
  addKelp() {}

  addNoodle() {}

  add() {
    this.addNoodle();
    this.addKelp();
  }
}

 

그런데 라면의 인기가 높아져서 라면에 온갖 고추장, 치즈, 짜장, 곰탕등 소스를 집어넣기 시작했습니다.

그리고 각 소스를 그냥 넣는것이 아니라, 1개 이상 중복해서 넣어서 섞어먹는 이상한 사람들이 등장하기 시작했습니다.

 

이때 상속으로 구현하게 된다면 중복으로 쓰이는 메소드들은 상위 클래스인 Ramen class 혹은 어딘가에 구현이 되고, 상
속을 시켜야할것입니다.
예를 들어 치즈짜장라면과 치즈곰탕라면은 addCheese라는 공통 함수를 Ramen Class에 넣거나 CheeseRamen Class에 넣어야할것인데 그렇다면
 
1. 공통 class인 Ramen에 쓰이면 치즈를 안넣는 class도 상속을 받아서 문제가 발생
2. CheeseRamen Class에 넣는다면 조합에 따라 만들어지는 class의 개수가 기하급수적으로 증가하게 될것

 

입니다. 

 

그래서 데코레이터를 사용해서 치즈짜장라면과 치즈곰탕라면을..

상속을 사용하지 않고 데코레이터를 사용해서 구현해봅시다...

(글 쓰면서 뭔가 내용이 산으로 가는 느낌인데..?)

 

class Food {
  add() {
    //재료 추가
  }
}

class Ramen extends Food {
  addKelp() {
    return 'add Kelp ';
  }

  addNoodle() {
    return 'add noodle ';
  }

  assemble() {
    return this.addNoodle() + this.addKelp();
  }
}

class RamenDecorator extends Food {
  constructor(Ramen) {
    super();
    this.Ramen = Ramen;
  }

  assemble() {
    return this.Ramen.assemble();
  }
}

 

우선 Base가 되는 Ramen과 Ramen Decorator입니다.

RamenDecorator는 만들고 싶은 Ramen을 주입받아 조립해 사용합니다.

 

치즈라면을 일단 만들어 봅시다.

 

class CheeseRamenDecorator extends RamenDecorator {
  addCheese() {
    return 'add cheese';
  }

  assemble() {
    return super.assemble() + this.addCheese();
  }
}


const CheeseRamen = new CheeseRamenDecorator(new Ramen());
console.log(CheeseRamen.assemble());

 

그림으로 보면 이런 상태입니다.

$ node test.js
add noodle add Kelp add cheese

코드를 실제로 돌려보면 잘 적용된걸 확인할 수 있습니다.

 

이제 아까 말했던 치즈짜장라면과 치즈곰탕라면을 구현해봅시다.
class BlackbeanSauceRamenDecorator extends RamenDecorator {
  addBlackbeanSauce() {
    return 'add BlackbeanSauce';
  }

  assemble() {
    return super.assemble() + this.addBlackbeanSauce();
  }
}

class BeefBoneSoupRamenDecorator extends RamenDecorator {
  addBeefBoneSoup() {
    return 'add BeefBoneSoup';
  }

  assemble() {
    return super.assemble() + this.addBeefBoneSoup();
  }
}


const CheeseRamen = new CheeseRamenDecorator(new Ramen());
console.log(CheeseRamen.assemble());

const CheeseBlackbeanSauceRamen = new BlackbeanSauceRamenDecorator(
  new CheeseRamenDecorator(new Ramen()),
);
const CheeseBeefBoneSoupRamen = new BeefBoneSoupRamenDecorator(
  new CheeseRamenDecorator(new Ramen()),
);

console.log(CheeseBlackbeanSauceRamen.assemble());
console.log(CheeseBeefBoneSoupRamen.assemble());

아.. 라면 이름 예시를 잘못들었는데 지금 돌이킬순 없는거 같아요..

 

그림으로 보면 이런상태입니다. 

 

add noodle add Kelp add cheeseadd BlackbeanSauce
add noodle add Kelp add cheeseadd BeefBoneSoup

코드를 실제로 돌려보면 잘 적용된걸 확인할 수 있습니다.

그리고 상속으로 구현했을때보다 훨씬 깨끗해진걸 볼 수 있습니다.

 

지금은 조합 예시가 2~3개이지만 프로그램이 발전되며 조합이 수십~수백가지로 늘어났을때에도 상속보다 유연하게 처리 가능해진걸 확인할 수 있었습니다.

 

 

tmi 하나 하자면.. K사 기술면접에서 super class에 공통 함수를 놔둔다면 안쓰이는 subclass에서는 이것을 어떻게 처리해야할것인가라는 질문이 나왔었는데 그 당시에는 답변을 못했다가 오늘 알게됬네요 ㅠㅠ

 

reference 

 

https://gmlwjd9405.github.io/2018/07/09/decorator-pattern.html

 

[Design Pattern] 데코레이터 패턴이란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

https://steady-coding.tistory.com/391

 

[디자인 패턴] 데코레이터(Decorater) 패턴이란?

안녕하세요? 제이온입니다. 저번 시간에는 상태 패턴에 대해서 알아 보았습니다. 오늘은 데코레이터 패턴을 설명하겠습니다. 데코레이터(Decorater) 패턴 데코레이터 패턴은 객체에 추가적인 요건

steady-coding.tistory.com

 

반응형
반응형

1. 생성(Creational) 패턴

 

객체 생성 방식에 대한 패턴

생성 & 조합을 캡슐화해 특정 객체가 생성-변경되도 프로그램 구조에 영향을 

크게 받지 않게해서 유연성 제공

 

2. 구조(Structural) 패턴

클래스 & 객체를 조합해 더 큰 구조를 만드는 패턴

 

3. 행위(Behavioural) 패턴

객체 사이 상호작용하는 방식이나 관심사를 분리하는 방법

 

 

 

'바퀴를 재발명하지 말자'라는 말이 있는데 구현때 소프트웨어의 복잡성을 줄이려면 아는만큼 보이는거 같아서

앞으로 조금씩이라도 디자인패턴에 대해 공부하고 기록해놓으려고 한다.

 

tmi로 디자인 패턴을 몰라도 경험적으로 체득하고 사용한 경우도 있기는 있었다.

 

reference

 

https://medium.com/@nitinmuteja/part-1-gang-of-four-gof-software-design-patterns-9a2d1abe4dba

 

Part-1 Gang of Four (GOF) Software Design Patterns

Gang Of Four Design Patterns

medium.com

 

https://gmlwjd9405.github.io/2018/07/06/design-pattern.html

 

[Design Pattern] 디자인 패턴 종류 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

반응형

+ Recent posts