포스트

[Behavioral Pattern] 인터프리터 패턴 (Interpreter Pattern)

Design Pattern / Behavioral Pattern

인터프리터 패턴의 정의와 해당 디자인 패턴의 예제 코드를 통한 이해 및 설명 정리

개념

  • 인터프리터의 의미 자체는 보통 해석해주거나 통역해주는 역할을 가진 사람 혹은 물건을 의미

    • 악보를 음악으로 변환하는 것도 이러한 역할의 하나이기 때문에 연주자라는 의미도 있음
  • 가장 쉽게 볼 수 있는 예로는 정규표현식이 있음

  • 자주 등장하는 문제를 별개의 언어로 정의하고 재사용하는 패턴

    • 문법에 등장하는 규칙을 클래스로 표현하고 언어에서의 표현식을 해석하고 평가

    • Expression이라는 추상 클래스를 만드는 경우가 많음

    • Expression 클래스에서 파생된 구체적인 클래스는 언어의 다양한 규칙 또는 요소를 나타냄

  • 반복되는 문제 패턴을 언어 또는 문법으로 정의하고 확장할 수 있음

패턴 구조

interpreter

  • interpret

    • 항상 Context가 있음이 중요
  • TerminalExpression

    • 종료가 가능한 Expression

    • 숫자 연산을 예시로 들면, 숫자 그 자체

    • AST에서 트리의 leaf node로 표현될 수 있는 것

  • NonTerminalExpression

    • 다른 Expression을 참조하는 Expression

    • 숫자 연산을 예시로 들면, 연산을 할 때의 연산 기호

    • 그 자체로 끝날 수 없음

예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
const data = {
  users: {
    u1: "UserName1",
    u2: "UserName2",
    u3: "UserName3",
    u4: "UserName4",
  },
  groups: {
    FP: ["u1", "u2", "u3"],
    OOP: ["u2", "u3"],
  },
  friends: {
    u1: ["u2", "u3"],
    u2: ["u1", "u3", "u4"],
    u3: ["u2"],
  },
};

// 실제 요청 처리 API

function friendsOfN(idList) {
  return Promise.resolve(idList.map((id) => data.friends[id]));
}

function memberOfGroup_(gId, uId) {
  return Promise.resolve(data.groups[gId].indexOf(uId) >= 0);
}

function getNameN(idList) {
  return Promise.resolve(idList.map((id) => data.users[id]));
}

// run/step

function step(stack, value) {
  while (stack.length > 0) {
    const top = stack.pop();
    if (top instanceof Request) {
      const r = { request: top.request };
      return {
        blocked: [r],
        next: () => step(stack, r.value),
      };
    } else if (Array.isArray(top)) {
      const values = top.map((g) => step([g]));
      if (values.every((v) => "done" in v)) {
        value = values.map((v) => v.done);
      } else {
        return {
          blocked: values.reduce((a, b) => a.concat(b.blocked || []), []),
          next: function next() {
            for (const i in values) {
              if ("blocked" in values[i]) {
                values[i] = values[i].next();
              }
            }
            if (values.every((v) => "done" in v)) {
              return step(
                stack,
                values.map((v) => v.done)
              );
            } else {
              return {
                blocked: values.reduce((a, b) => a.concat(b.blocked || []), []),
                next,
              };
            }
          },
        };
      }
    } else if (typeof top.next === "function") {
      const r = top.next(value);
      if (r.done) {
        value = r.value;
      } else {
        stack.push(top); // top not finished
        stack.push(r.value);
      }
    }
  }
  return { done: value };
}

async function run(g, process) {
  let next = () => step([g()]);
  while (true) {
    const r = next();
    if ("done" in r) return r.done;
    await process(r.blocked);
    next = r.next;
  }
}

// generator를 위한 API

function Request(request) {
  this.request = request;
}

function friendsOf(id) {
  return new Request({ type: "friendsOf", id });
}

function memberOfGroup(gId, uId) {
  return new Request({ type: "memberOfGroup", gId, uId });
}

function getName(id) {
  return new Request({ type: "getName", id });
}

//

async function process(requests) {
  console.log(requests);
  const batches = {};
  requests.forEach((r) => {
    batches[r.request.type] = batches[r.request.type] || [];
    batches[r.request.type].push(r);
  });
  const ps = Object.keys(batches).map((type) => {
    if (type === "friendsOf") {
      return friendsOfN(batches[type].map((r) => r.request.id)).then((names) =>
        batches[type].map((r, i) => (r.value = names[i]))
      );
    }
    if (type === "memberOfGroup") {
      return Promise.all(
        batches[type].map((r) =>
          memberOfGroup_(r.request.gId, r.request.uId).then(
            (v) => (r.value = v)
          )
        )
      );
    }
    if (type === "getName") {
      return getNameN(batches[type].map((r) => r.request.id)).then((names) =>
        batches[type].map((r, i) => (r.value = names[i]))
      );
    }
  });
  await Promise.all(ps);
}

// biz logic

function* fpFriends(id) {
  const friends = yield friendsOf(id);
  const fp = yield friends.map((f) => memberOfGroup("FP", f));
  return yield friends.filter((f, i) => fp[i]).map(getName);
}

function* commonFpFriends(id1, id2) {
  const [fs1, fs2] = yield [fpFriends(id1), fpFriends(id2)];
  return intersect(fs1, fs2);
}

function intersect(as, bs) {
  as = new Set(as);
  return bs.filter((b) => as.has(b));
}

run(function* () {
  console.log(yield commonFpFriends("u1", "u2"));
}, process);

function intersect(as, bs) {
  as = new Set(as);
  return bs.filter((b) => as.has(b));
}

참고한 출처 사이트

Refactoring GURU

Inpa Dev Blog (디자인 패턴)

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.