<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Developer.</title>
    <link>https://jypnote.tistory.com/</link>
    <description>블로그 내용이 도움되시길 바랍니다.</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 21:35:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>제이왑</managingEditor>
    <image>
      <title>Developer.</title>
      <url>https://tistory1.daumcdn.net/tistory/1712630/attach/4ca55af1d28a41849ef5e940b896fc59</url>
      <link>https://jypnote.tistory.com</link>
    </image>
    <item>
      <title>podman build 시 too many open files 대응</title>
      <link>https://jypnote.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;podman 을 통한 image build 시, 경우에 따라 `too many open files` 에러가 발생할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;직접 빌드를 수행하는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`podman build` 명령을 통해 직접 빌드를 실행하는 경우, `--ulimit` 설정을 통해 해결할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; style=&quot;background-color: #f6f8fa; color: #1f2328; text-align: start;&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;podman build --ulimit=nofile=1048576:1048576 &amp;lt;...&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;간접적으로 빌드를 수행하는 경우 (ex. docker(podman) compose 를 통한 build)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker-compose 를 통한 빌드를 수행하게 되는 경우에는 --ulimit 설정을 직접적으로 설정하기 어려운 경우가 있다. docker-compose.yml 에서 `service.build.ulimits` 필드의 설정으로 ulimit 을 설정할 수 있지만, podman 4 버전의 경우에는 해당 필드가 무시된다. (1024 로 실행됨)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/containers/podman/issues/22029&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;podman 5 버전에서 해결되었지만&lt;/a&gt;, os 버전에 따라 패키지 관리 시스템에 배포되지 않아 podman 5 지원이 되지 않아 설치가 어렵다. 그런 경우에는 아래 설정을 통해 우회 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1746795623495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# ~/.config/containers/containers.conf

[containers]
default_ulimits = [
 &quot;nofile=65535:65535&quot;,
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;container 실행 시 ulimits 기본값을 변경함으로써 발생하는 too many open files 에러를 회피할 수 있다.&amp;nbsp;&lt;/p&gt;</description>
      <category>TIPS</category>
      <category>Docker</category>
      <category>docker-compose</category>
      <category>PODMAN</category>
      <category>rhel</category>
      <category>too many open files</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/17</guid>
      <comments>https://jypnote.tistory.com/17#entry17comment</comments>
      <pubDate>Fri, 9 May 2025 22:12:14 +0900</pubDate>
    </item>
    <item>
      <title>정보 검색 평가 지표 ( + RAGAS)</title>
      <link>https://jypnote.tistory.com/16</link>
      <description>&lt;script type=&quot;text/javascript&quot; src=&quot;https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML&quot;&gt;MathJax.Hub.Config({ extensions: [&quot;tex2jax.js&quot;,&quot;TeX/AMSmath.js&quot;,&quot;TeX/AMSsymbols.js&quot;], jax: [&quot;input/TeX&quot;, &quot;output/HTML-CSS&quot;], tex2jax: { inlineMath: [ [&quot;$&quot;,&quot;$&quot;], [&quot;\\(&quot;,&quot;\\)&quot;] ], displayMath: [ [&quot;$$&quot;,&quot;$$&quot;], [&quot;\\[&quot;,&quot;\\]&quot;] ], processEscapes: true }, &quot;HTML-CSS&quot;: { availableFonts: [&quot;TeX&quot;] } });&lt;/script&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://amitness.com/posts/information-retrieval-evaluation&quot;&gt;https://amitness.com/posts/information-retrieval-evaluation&lt;/a&gt; 글을 읽고 정리한 문서입니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;지표의 목적&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 N 결과가 얼마나 우수한지 어떻게 평가할 것 인가?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Binary relevance&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgb_eU2nh-vzVdEmyYhh8VWnSw8zd4ox7paZmkzqyUUS_9qWLQCMbEHHt7wiqHrgpHK9U3w6HcElAmXqLsMR4IvYBARwPr50izfvVhu25AKfYGbLrHhiqlDuWyN_dMUdFupRp9KPpLsDl6g2oOnbP-ImA9DknrDaOaCGDkeX7SrLgcfpsuDWSj5fsDnFLct/s1600/information-retrieval_1.excalidraw.png&quot;&gt;&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgb_eU2nh-vzVdEmyYhh8VWnSw8zd4ox7paZmkzqyUUS_9qWLQCMbEHHt7wiqHrgpHK9U3w6HcElAmXqLsMR4IvYBARwPr50izfvVhu25AKfYGbLrHhiqlDuWyN_dMUdFupRp9KPpLsDl6g2oOnbP-ImA9DknrDaOaCGDkeX7SrLgcfpsuDWSj5fsDnFLct/s1600/information-retrieval_1.excalidraw.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문서에 대한 관련성을 &quot;있다 혹은 없다&quot; 로만 판단한다.&lt;/li&gt;
&lt;li&gt;현재 Ranking model 이 query 에 대해서 5개의 각각의 문서 관련도는 `[1, 0, 1, 0, 1]` 로 나타낼 수 있다. (*binary*)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Order-unaware metrics&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Precision@k&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Precision@k = \frac{ true\ positives@k}{(true\ positives@k) + (false\ positives@k)}&lt;br /&gt;$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 메트릭은 상위 K 결과의 관련 항목 수를 정량화합니다.&lt;/li&gt;
&lt;li&gt;추출된 k 랭크 문서 중에서 관련 있는 문서의 갯수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) &lt;i&gt;Precision@2&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL3q0owYN9nTvtbotBGGkYlHSQhKOJkr1rxG7A2gtUDTKbx1oGrqwvp80qOudGl-eDuD8ESKF4UyDgGJ-Gv74gmD5FXSsA0pcI01sC_Kg4IQtnKfKsZKMV-C2auM57FvZgvZPoYQmk6UJKRLI2l8AGRjqYlwPhTup_fzxqlI9IHJKEfU-3kqlbSlhmJWMI/s1600/information-retrieval_2.excalidraw.png&quot;&gt;&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgL3q0owYN9nTvtbotBGGkYlHSQhKOJkr1rxG7A2gtUDTKbx1oGrqwvp80qOudGl-eDuD8ESKF4UyDgGJ-Gv74gmD5FXSsA0pcI01sC_Kg4IQtnKfKsZKMV-C2auM57FvZgvZPoYQmk6UJKRLI2l8AGRjqYlwPhTup_fzxqlI9IHJKEfU-3kqlbSlhmJWMI/s1600/information-retrieval_2.excalidraw.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Recall@k&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;Recall@k = \frac{ true\ positives@k}{(true\ positives@k) + (false\ negatives@k)}&lt;br /&gt;$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 메트릭은 쿼리에 대한 모든 실제 관련 결과 중에서 몇 개의 실제 관련 결과가 표시되었는지 알려줍니다.&lt;/li&gt;
&lt;li&gt;전체 관련 있는 문서 갯수 중에서 k 랭크 내에 추출된 관련 있는 문서의 갯수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시) &lt;i&gt;Recall@2&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVS9k54WtQZsiAdWdlDASxUXYAr75wRI_N7nAB78zdYfe6B0GmSDwCFKrtotR7MMg7gUtr0mS3gdcN8j7II4iOQj6Prjv11omnHTJrWn1pRb_RCY1anci_9koflc9hyphenhyphen5zNW_HJ6t3v8MRMHhGPq4d1kC2gaMA9mkTNkkcUUszyeEMyy3JU7sommsZ3UGsW/s1600/information-retrieval_3.excalidraw.png&quot;&gt;&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVS9k54WtQZsiAdWdlDASxUXYAr75wRI_N7nAB78zdYfe6B0GmSDwCFKrtotR7MMg7gUtr0mS3gdcN8j7II4iOQj6Prjv11omnHTJrWn1pRb_RCY1anci_9koflc9hyphenhyphen5zNW_HJ6t3v8MRMHhGPq4d1kC2gaMA9mkTNkkcUUszyeEMyy3JU7sommsZ3UGsW/s1600/information-retrieval_3.excalidraw.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고: Precision 과 Recall 의 집합관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH9CSv3xoFLezu3tE0lp8AzelGKm5fitkBnRuryoGQA23ILIJWEvyOiipWafOv-V7VpuCP8ef37lzxtsazvjb_6zn4ae-Q3g06aYuTYW3jI1YXKNZLlF9HY1tfJ2kuujorShb7Nmm19pev4NDSIaRFXT1Jyj6baRIZse-wuWPppPOuJ6dugaoUBtdgLx1y/s1600/information-retrieval_4.excalidraw.png&quot;&gt;&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH9CSv3xoFLezu3tE0lp8AzelGKm5fitkBnRuryoGQA23ILIJWEvyOiipWafOv-V7VpuCP8ef37lzxtsazvjb_6zn4ae-Q3g06aYuTYW3jI1YXKNZLlF9HY1tfJ2kuujorShb7Nmm19pev4NDSIaRFXT1Jyj6baRIZse-wuWPppPOuJ6dugaoUBtdgLx1y/s1600/information-retrieval_4.excalidraw.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A = 모델에서 문서가 관련 있다고 예측한 영역 (예측)&lt;/li&gt;
&lt;li&gt;B = 실제 관련 있는 문서가 있는 영역 (정답)&lt;/li&gt;
&lt;li&gt;b 영역 = True Positive 로 모델이 추출한 관련 문서 중 실제 관련 있는 문서가 있었던 영역&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모델이 반환한 결과 중에서 실제 관련도 있는 문서를 추출한 비율이 precision, 실제 관련 있는 문서 목록 중 model 이 올바르게 문서를 추출한 비율이 recall 이라고 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;F1@k&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;F1@k = \frac{2*(Precision@k) * (Recall@k)}{(Precision@k) + (Recall@k)}&lt;br /&gt;$$&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;precision 과 Recall 의 조화평균으로 계산한다.&lt;/li&gt;
&lt;li&gt;조화 평균은 산술 평균보다 비중의 평균을 계산할 때 더 직관적이다. precision 과 recall 모두 중요한 값이기 때문에, 두 값 모두 높을 때 랭킹 모델의 성능이 더 좋다고 말할 수 있다. 따라서 두 값이 조화롭게 좋을 때 가장 높은 조화 평균을 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cs.odu.edu/%7Emukka/cs795sum09dm/Lecturenotes/Day3/F-measure-YS-26Oct07.pdf&quot;&gt;The truth of the F-measure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고. 조화평균&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개별 데이터의 역수의 평균에 대한 역수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조화 평균은 비율의 평균을 계산하는 데 자주 사용되는데, 각 데이터의 가중치가 동일하기 때문에 비율에 대한 가장 적절한 측정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 산술 평균은 큰 데이터 에 높은 가중치를 부여되고 기하 평균은 작은 데이터 요소에 낮은 가중치를 부여되는데 비해 조화평균은 전체적으로 동일한 가중치가 부여된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 표는 산술평균은 모두 같게 평가되지만, 조화평균은 각각 다름을 알 수 있다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;산술평균&lt;/th&gt;
&lt;th&gt;조화평균&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1.8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조화평균은 표에서 확인할 수 있는 것처럼, 두 값이 조화롭게 존재할 때 가장 높게 평가된다 (산술 평균과 같다). 또한 산술평균에 비해 큰 비중이 끼치는 편향이 줄어든다고 볼 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Order-aware metrics&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Mean Reciprocal Rank(MRR, Rank 역수 평균)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;MRR = \frac{1}{|Q|} \sum_{i=1}^{|Q|} \frac{1}{rank_{i}}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;where:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$|Q|$ denotes the total number of queries&lt;/li&gt;
&lt;li&gt;$rank_{i}$ denotes the rank of the first relevant result&lt;/li&gt;
&lt;li&gt;이 메트릭은 시스템이 가장 적절한 항목을 반환하고 해당 항목이 더 높은 위치에 있기를 원할 때 유용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-KmWXF7ZsTk9J911eGHlUhiwks7E1vLGsWfGCHGlvpIjFU24WXDRN8cbAtNr_4JdNf6oZgEdW-Cwrs4ciTi0FDB6BEQDXktLRSqep7wwpAzvGtrqRRmbmJCnseFJfcYfhofykLi8lHIYX1mm-V_b16d41QkKktNV3arPh6LweQJdFVYXtz5dL1og-HjdF/s1600/information-retrieval_5.excalidraw.png&quot;&gt;&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg-KmWXF7ZsTk9J911eGHlUhiwks7E1vLGsWfGCHGlvpIjFU24WXDRN8cbAtNr_4JdNf6oZgEdW-Cwrs4ciTi0FDB6BEQDXktLRSqep7wwpAzvGtrqRRmbmJCnseFJfcYfhofykLi8lHIYX1mm-V_b16d41QkKktNV3arPh6LweQJdFVYXtz5dL1og-HjdF/s1600/information-retrieval_5.excalidraw.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Average Precision(AP, Precision 평균)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;AP = \frac{\sum_{k=1}^{n} (P(k) * rel(k))}{number\ of\ relevant\ items}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;where:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$rel(k)$ is an indicator function which is 1 when the item at rank K is relevant.&lt;/li&gt;
&lt;li&gt;$P(k)$ is the $Precision@k$ metric&lt;/li&gt;
&lt;li&gt;Average Precision은 모델이 선택한 모든 실측 관련 항목의 순위가 더 높은지 여부를 평가하는 지표입니다. MRR과 달리 모든 관련 항목을 고려합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Example 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQAA18N3Xe6K9sbwktTF1Vr-PmnNlQ1Kl_4dk7IQkuS154p2x2f4rCpP71W56wnHiw91TGGrA6Q0CWmiIB_WG4ugnFClIMKiSUFOJTktFruYDiMAbFzD-DH-q72FaufQKSvEC7-AxjpdIhSjhXlHxM8QxIDFJvI8OEyLqYM2Sg7lM24G08HnNflXKnNCpX/s1600/information-retrieval_6.excalidraw.png&quot;&gt;&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQAA18N3Xe6K9sbwktTF1Vr-PmnNlQ1Kl_4dk7IQkuS154p2x2f4rCpP71W56wnHiw91TGGrA6Q0CWmiIB_WG4ugnFClIMKiSUFOJTktFruYDiMAbFzD-DH-q72FaufQKSvEC7-AxjpdIhSjhXlHxM8QxIDFJvI8OEyLqYM2Sg7lM24G08HnNflXKnNCpX/s1600/information-retrieval_6.excalidraw.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Example 2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuxWqxMxAdItFC4RqKKxAeOJPzmUOkj2G9VirvzF28rDkXtsvy7u0it7xotCP9wriVM_ao5p3X5Ji5G5JwwMk-VyMii_gHDQuwiNsrues-lmb3wrIIB159Qg76izoV3TKnu-V4sv5I83XZwSQH5PBYqUc6pG3cgPK98g8vCS9HA_wCU66qWNznbTgkR-bB/s1600/information-retrieval_7.excalidraw.png&quot;&gt;&lt;img src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjuxWqxMxAdItFC4RqKKxAeOJPzmUOkj2G9VirvzF28rDkXtsvy7u0it7xotCP9wriVM_ao5p3X5Ji5G5JwwMk-VyMii_gHDQuwiNsrues-lmb3wrIIB159Qg76izoV3TKnu-V4sv5I83XZwSQH5PBYqUc6pG3cgPK98g8vCS9HA_wCU66qWNznbTgkR-bB/s1600/information-retrieval_7.excalidraw.png&quot; alt=&quot;&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Mean Average Precision&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;MAP = \frac{1}{Q} \sum_{q=1}^{Q} AP(q)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;where&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$Q$ is the total number of queries&lt;/li&gt;
&lt;li&gt;$AP(q)$ is the average precision for query $q$.&lt;/li&gt;
&lt;li&gt;여러 쿼리에 걸쳐 평균 정밀도를 평가하려면 MAP를 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RAGAS 에서 측정하고 있는 지표&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context Utilization (Context Precision)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://docs.ragas.io/en/latest/concepts/metrics/context_precision.html&quot;&gt;https://docs.ragas.io/en/latest/concepts/metrics/context_precision.html&lt;/a&gt;&lt;br /&gt;** context utilization 은 context precision 에서 ground truth 가 아닌 llm 답변을 사용하는 지표이다.&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답변을 정답이라고 가정했을 때, 반환된 context 에서 관련된 문서가 높게 랭킹되었는 지를 수치화한다.&lt;/li&gt;
&lt;li&gt;retriever 의 성능 지표로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{Context Precision@K} = \frac{\sum_{k=1}^{K} \left( \text{Precision@k} \times v_k \right)}{\text{Total number of relevant items in the top } K \text{ results}}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{Precision@k} = {\text{true positives@k} \over (\text{true positives@k} + \text{false positives@k})}&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;where:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;$K$ 는 context 내 Chunk 수&lt;/li&gt;
&lt;li&gt;$v_k \in {0, 1}$ 은 $k$ 랭크에 위치한 문서가 관련되어 있는 지 없는 지를 나타내는 지표이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Answer Relevancy&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://docs.ragas.io/en/latest/concepts/metrics/answer_relevance.html&quot;&gt;https://docs.ragas.io/en/latest/concepts/metrics/answer_relevance.html&lt;/a&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;답변에 기반하여 생성한 질문들의 Embedding vector 와 실제 질문의 embedding vector 를 cosine similarity 로 비교하여 연관도를 측정한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{answer relevancy} = \frac{1}{N} \sum_{i=1}^{N} cos(E_{g_i}, E_o)&lt;br /&gt;$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{answer relevancy} = \frac{1}{N} \sum_{i=1}^{N} \frac{E_{g_i} \cdot E_o}{|E_{g_i}||E_o|}&lt;br /&gt;$$&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Context Relevancy&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;a href=&quot;https://docs.ragas.io/en/latest/concepts/metrics/context_relevancy.html&quot;&gt;https://docs.ragas.io/en/latest/concepts/metrics/context_relevancy.html&lt;/a&gt;&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;context 에 포함된 문장이 답변과 얼마나 연관되어 있는 지를 나타낸다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$&lt;br /&gt;\text{context relevancy} = {|S| \over |\text{Total number of sentences in retrieved context}|}&lt;br /&gt;$$&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 링크&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://sumniya.tistory.com/26&quot;&gt;https://sumniya.tistory.com/26&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://amitness.com/posts/information-retrieval-evaluation#mean-average-precisionmap&quot;&gt;https://amitness.com/posts/information-retrieval-evaluation#mean-average-precisionmap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>TIPS</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/16</guid>
      <comments>https://jypnote.tistory.com/16#entry16comment</comments>
      <pubDate>Sun, 13 Oct 2024 14:59:14 +0900</pubDate>
    </item>
    <item>
      <title>서버에서 Client IP 를 추출하는 여러가지 방법</title>
      <link>https://jypnote.tistory.com/15</link>
      <description>&lt;p&gt;서비스 요구사항에 따라 Client IP 가 필요한 상황이 있다. 보안을 위해서 Client IP 를 확인하여 접근을 허용할 수 있다. 허용되지 않은 IP 의 경우 접근을 막을 수 있다.  로그 요구사항으로 어떤 사용자가 접근하고 있는 지를 기록하기 위해 Client IP 를 남겨야 할 수 있다. &lt;/p&gt;
&lt;p&gt;하지만 사용자나 서비스의 네트워크 구성에 따라서 Client IP 를 추출하는 것이 쉽지 않을 수 있다. 프록시가 있어 직접 연결한 Client 를 실제 사용자로 판단할 수 없는 경우가 그렇다. 프록시 뒤에 있는 사용자를 찾으려고 노력하면 Client IP 를 숨기거나 우회하기 위해서 변조를 시도하는 상황을 마주하게 된다. 그래서 Client IP 를 추출하기 위한 여러 방안들을 아래에 정리하게 되었다. &lt;/p&gt;
&lt;p&gt;결론부터 먼저 말하면, 일반적인 상황에서 나는 &lt;code&gt;X-Forwarded-For&lt;/code&gt; 의 가장 오른쪽의 Public IP 를 Client IP 로 판단하기로 했다. 믿을 수 없는 목록 중에서 신뢰할 수 있으면서 간단하고, 빠른 방법이라 생각하기 때문이다. 하지만 여러 방안들을 조사했을 때, 어떤 상황에서는 사용할 수 있는 지, 또 어떤 지점이 문제가 되는 지 생각해볼 수 있었다. 그래서 고민했었던 여러 방안들을 소개하려고 한다.&lt;/p&gt;
&lt;h3&gt;Remote IP&lt;/h3&gt;
&lt;p&gt;프록시가 없는 간단한 구조의 서비스라면 서버에 연결된 Remote IP 를 Client IP 로 추출할 수 있다. 하지만 Remote IP 가 실제 사용자의 IP 라는 확신이 없다면, Client IP 로 사용하기 어렵다. 사용자 네트워크 구성에서 proxy 가 있다면, 서버에 연결된 Remote IP 는 실제 사용자의 Client IP 가 아닐 수 있다. &lt;/p&gt;
&lt;p&gt;여기에서 서비스 요구사항에 대한 명확한 정의가 필요해질 수 있다. 사용자 네트워크의 사설 IP 를 추출해야 하는 지, 아니면 공인 IP 를 추출해야 하는 지 정의가 필요하다. 사설 IP 는 서비스의 입장에서 큰 의미가 없기 때문에, 보통 공인 IP 를 추출한다. &lt;/p&gt;
&lt;p&gt;요구 사항을 어느 정도까지 정확하게 추출해야 하는 지 명확히 정의하고, 요구 사항에서 필요한 값을 추출해낸다는 생각이 필요하다. &lt;/p&gt;
&lt;h3&gt;&lt;code&gt;X-Forwarded-For&lt;/code&gt; 헤더 가장 왼쪽의 값 (DON&amp;#39;T)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;X-Forwarded-For&lt;/code&gt; 헤더는 요청이 어떤 IP 들을 거쳐 전달되었는 지 알 수 있도록 도와준다. 요청이 전달되면서 거쳐오는 IP 는 우측에 추가되기 때문에, 가장 왼쪽의 IP 를 Client IP 라고  생각할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 이 값을 실제 Client IP 로 판단하는 것은 위험하다. 헤더는 요청을 전달받은 프록시에서 언제든지 수정 가능하기 때문이다. &lt;a href=&quot;https://blog.ircmaxell.com/2012/11/anatomy-of-attack-how-i-hacked.html&quot;&gt;이런 지점을 노린 &lt;code&gt;X-Forwarded-For&lt;/code&gt; 변조는 Stack Overflow 도 당했었던 유명한 공격 기법이다.&lt;/a&gt; 사용자가 언제든지 수정할 수 있는 가장 왼쪽의 IP 를 Client IP 로 판단한다면 예기치 못한 결과가 발생할 수 있다.  &lt;/p&gt;
&lt;h3&gt;&lt;code&gt;X-Forwarded-For&lt;/code&gt; 헤더 내 &lt;code&gt;proxy hop&lt;/code&gt; 수 이전의 값&lt;/h3&gt;
&lt;p&gt;앞에서 헤더는 변조 가능하다고 말했다. 하지만 서비스에서 구성해둔 네트워크로 넘어온 뒤에는  값이 변조되기 쉽지 않다. 이런 맥락에서 &lt;code&gt;X-Forwarded-For&lt;/code&gt; 헤더에서 오른쪽에 위치한 IP 일수록 신뢰할 수 있는 IP 이다. &lt;/p&gt;
&lt;p&gt;서버로 오기 전까지의 서비스 네트워크 hop 수를 &lt;code&gt;proxy hop&lt;/code&gt; 이라고 하자. 이 &lt;code&gt;proxy hop&lt;/code&gt; 을 알 수 있다면, &lt;code&gt;X-Forwarded-for&lt;/code&gt; 헤더의 오른쪽 끝에서 proxy hop 만큼 왼쪽에 있는 IP 를 Client IP 로 판단할 수 있다. 서비스에서 진행한 proxy 구성의 hop 이 고정되어있거나, 알 수 있다면, proxy hop 이전의 값을 client ip 로 사용할 수 있다. &lt;/p&gt;
&lt;p&gt;이 방법의 문제점은 고정된다고 생각했던 &lt;code&gt;proxy hop&lt;/code&gt; 이 언제까지나 고정되지 않기 때문이다. 프록시가 어떻게 구성되는 지에 따라 Client IP 추출에 영향을 줄 수 있다는 점은 불필요한 의존성을 만든다. (네트워크 구성에 따라 서비스가 변경되어야 한다는 점은 권장되지 않는다. )&lt;/p&gt;
&lt;h3&gt;&lt;code&gt;X-Forwarded-For&lt;/code&gt; 헤더 내 가장 우측의 Public IP&lt;/h3&gt;
&lt;p&gt;다시 &lt;code&gt;X-Forwarded-For&lt;/code&gt; 헤더에서 오른쪽에 있는 IP 일 수록 신뢰할 수 있는 IP 라는 점에 집중하자. 그리고 사용자 요청이 인터넷을 통했다는 점을 생각하면, 사용자는 서비스로 요청을 보내기 위해서 공인 IP 를 거쳐야 한다. &lt;/p&gt;
&lt;p&gt;실제 IP 가 x.x.x.x 인 사용자가 값을 &lt;code&gt;X-Forwarded-For&lt;/code&gt; 값을 변조하여 &lt;code&gt;1.1.1.1&lt;/code&gt; 을 넣었다고 하자.&lt;br&gt;이 값으로 요청을 한다면, proxy 는 x.x.x.x 가 요청한 &lt;code&gt;X-Forwarded-For: 1.1.1.1&lt;/code&gt; 값을 받게 된다.&lt;br&gt;서비스는 &lt;code&gt;X-Forwarded-For: 1.1.1.1, x.x.x.x&lt;/code&gt; 값을 받게 될 것이다. 이 때의 최우측 IP 는 &lt;code&gt;x.x.x.x&lt;/code&gt; 가 되므로, 이 값을  Client IP 로 판단할 수 있다. &lt;/p&gt;
&lt;p&gt;이 방법에서도 함정이 있을 수 있다. 다만 서비스 네트워크가 공인 IP 간에 프록시하고 있다면, 단순하게 적용하기 어려울 수 있다. 하지만 공인 IP 프록싱이 고정 대역이라면, 해당 값을 필터링하는 식으로 응용해볼 여지도 충분하다. &lt;/p&gt;
&lt;h3&gt;Proxy 에서 특정 헤더에 할당한 값&lt;/h3&gt;
&lt;p&gt;프록시에서 &lt;code&gt;X-Real-IP&lt;/code&gt;, &lt;code&gt;True-Client-IP&lt;/code&gt; 등의 헤더에 기존 Client IP 를 담아두는 방법이 있다. 제한된 상황에서 많이 쓰이기도 하는 방법이다. &lt;/p&gt;
&lt;p&gt;하지만 헤더에 실제 Client IP 를 담는 방법은 보안에 취약할 수 있다. 해커가 특정 헤더에  Client IP 를 담는다는 것을 알고, 이 값을 변조한다면 이 헤더 값을 신뢰할 수 없다. 프록시에서 Client IP 헤더 값을 항상 덮어 쓰는 방법으로 대응할 수도 있다. 하지만 여러 프록시를 거쳐야 하는 경우에는 항상 덮어 쓰는 것이 어려울 수 있다. 서비스의 구성에 따라서 판단하여 사용할 수 있어야 한다. &lt;/p&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;p&gt;Client IP 를 추출할 때 생각했던 방안들 중에 가장 신뢰할 수 있었던 값은 &lt;code&gt;X-Forwarded-For&lt;/code&gt; 헤더에서 가장 오른쪽에 위치한 Public IP 이다. 하지만 빠르게 처리해야 한다면 proxy hop 을 사용하는 방안도 충분히 고려해볼만 한 아이디어였다. 또 proxy 하는 상황이 없다면, 단순  Remote IP 를 가지고도 충분히 Client IP 를 찾을 수 있다. &lt;/p&gt;
&lt;p&gt;하지만 사용자의 네트워크나 서비스 네트워크 환경에서 여러 프록시가 존재하는 일반적인 구성을 고려한다면 X-Forwarded-For 헤더 값에서 가장 오른쪽에 위치한 Public IP 를 추출하는 것이 효과적인 방법이었다.&lt;/p&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://adam-p.ca/blog/2022/03/x-forwarded-for/&quot;&gt;https://adam-p.ca/blog/2022/03/x-forwarded-for/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>TIPS</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/15</guid>
      <comments>https://jypnote.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 13 Oct 2024 14:55:15 +0900</pubDate>
    </item>
    <item>
      <title>NGINX module 개발 시 참고 하기 좋은 글</title>
      <link>https://jypnote.tistory.com/14</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;  NGINX 모듈은 언제 만드나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 는 많은 웹 서비스들에서 사용하는 웹 서버 프로그램 입니다. Nginx 는 그 자체로 강력하고, 다양한 모듈을 가지고 있기 때문에 많은 서비스들에서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 서비스의 요구사항에 따라서, Nginx 그 자체만으로는 부족한 상황이 발생합니다. 앱 서버에서 처리하기에는 앱의 로직과는 동떨어져있어 책임원칙에 위반하고, nginx 의 설정 로직에서 처리되면 좋을 것 같은 요구사항들이 있습니다. 예를 들면, 프록시를 할 때 특정 값을 가공하여 넘겨주고 싶을 수 있습니다. 또, header 에 있는 값을 가지고 &lt;code&gt;$remote_addr&lt;/code&gt; 를 변조하여 사용하고 싶다거나 하는 상황이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런 상황에서 적절한 처리를 할 수 있는 nginx 모듈을 직접 만들어 nginx 에 적용해줄 수 있습니다. Nginx 는 C 언어로 만들어져 있어 모듈도 보통 C 로 작성됩니다. 하지만 최근에는 &lt;a href=&quot;https://github.com/openresty/lua-nginx-module#readme&quot;&gt;Lua&lt;/a&gt; 로 작성하거나 &lt;a href=&quot;https://nginx.org/en/docs/njs/&quot;&gt;njs (nginx 에서 사용하기 위한 일종의 custom js)&lt;/a&gt; 를 사용하여 모듈이 작성되기도 합니다. 하지만 이 글에서는 C 언어로 Nginx 모듈을 작성할 때 참고하기 좋은 글들을 소개합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx 공식 문서&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot; href=&quot;https://nginx.org/en/docs/dev/development_guide.html&quot;&gt;&amp;gt; https://nginx.org/en/docs/dev/development_guide.html&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서는 어떤 상황에서든지 중요한 것 같습니다. 이미 만들어진 프로그램에 대해서 추가로 개발할 때는 설명서를 자세하게 살펴봐야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에서는 Nginx 에서 사용되는 자료형과 구조체, 변수, 로그 방식부터 Nginx 가 HTTP 관련 요청을 처리하는 방식 등 다양한 내용들이 기록되어 있습니다. 따라서 모듈을 개발하는 경우가 아니더라도, Nginx 를 깊게 이해하고 싶다면 한번 쯤 읽고 공부해보기 좋은 문서입니다. 하지만 이 글을 읽었다고 해서 모듈 개발을 착수하기는 어렵습니다. 개발 시에 참고하기 좋은 튜토리얼이 따로 준비되어 있지는 않습니다. 처음 접하는 사람이 읽기에 다소 이해하기 어려운 용어들도 많습니다. 하지만 개발하는 도중에는 이 문서를 계속 참고하면서 코드를 이해하고 수정해나가야합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NAVER D2 - NGINX 모듈 제작하기&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot; href=&quot;https://d2.naver.com/helloworld/192785&quot;&gt;&amp;gt; https://d2.naver.com/helloworld/192785&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 기술블로그에 작성된 Nginx 모듈 제작 튜토리얼 글입니다. 제가 처음 모듈을 제작해야된다고 했을 때 참고했던 좋은 글이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2012년에 작성되어 조금은 달라진 부분도 있겠지만, 크게 변경된 점이 없고 충분히 컴파일하는데 이슈가 없었습니다. 그리고 Nginx 에서 모듈이 구동되는 개념을 잘 설명하고 있고, 작더라도 구동할 수 있는 커스텀 모듈을 만들 수 있도록 잘 설명하고 있습니다. 모듈을 작성할 때 필요한 인터페이스, 변수와 핸들러, 모듈의 컨텍스트 등 필요한 개념에 대해서 자세한 설명과 함께 작성해볼 수 있는 코드를 제공하여 큰 도움이 되었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Evan Miller 의 Nginx Modules Guide&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot; href=&quot;https://www.evanmiller.org/nginx-modules-guide.html&quot;&gt;&amp;gt; https://www.evanmiller.org/nginx-modules-guide.html&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.evanmiller.org/ab-testing/sample-size.html&quot;&gt;A/B 테스트 샘플 데이터 계산기&lt;/a&gt;로 유명한 Evan Miller 가 작성한 nginx module 가이드입니다. Nginx 모듈의 구성을 좀 더 자세하게 설명합니다. 각 항목에 대한 개념과 형태를 잘 설명하고 있어, 자주 참고하고 여러번 읽으면서 개발해나갔습니다. 특히 개발 문서에서 디테일하게 설명되지 않는 코드 동작 흐름을 잘 설명하고 있습니다. Nginx 모듈의 전체에서 부분으로 설명해 나가는 방식은 모듈을 좀 더 쉽게 이해할 수 있도록 도와주었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx 의 내부 모듈 코드&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; font-size: 16px; letter-spacing: 0px;&quot; href=&quot;https://github.com/nginx/nginx/tree/master/src/http/modules&quot;&gt;&amp;gt; https://github.com/nginx/nginx/tree/master/src/http/modules&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 모듈의 사례를 참고하는 것은 빠르게 개발하는 데 중요합니다. Nginx 코드에는 이미 많은 모듈이 개발되어 함께 컴파일됩니다. 작성된 모듈들을 참고하면 request 에 저장되어 있던 데이터를 좀 더 이해 할 수 있었고, 이해하기 어려웠던 ngx 구조체들을 사용하는 방식을 참고할 수 있었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기타 다른 Nginx 모듈 레포지터리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nginx 모듈 코드 뿐만 아니라, Nginx 써드파티 모듈들도 있어 개발 시에 참고해볼 수 있었습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://github.com/openresty/headers-more-nginx-module&quot;&gt;headersmore (openresty)&lt;/a&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;a href=&quot;https://github.com/alibaba/nginx-http-user-agent&quot;&gt;user-agent (alibaba)&lt;/a&gt;&lt;/h4&gt;</description>
      <category>TIPS</category>
      <category>nginx #nginx모듈 #웹서버</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/14</guid>
      <comments>https://jypnote.tistory.com/14#entry14comment</comments>
      <pubDate>Sun, 13 Oct 2024 14:52:41 +0900</pubDate>
    </item>
    <item>
      <title>Docker Image 를 로컬 파일로 저장하기</title>
      <link>https://jypnote.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 이미지를 활용하여 빠르게 서버 환경을 구축하는 건 요즘 흔한 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 오프라인 환경에서는 외부 환경에서 이미지를 가져올 수 없기 때문에, 컨테이너 이미지를 활용하기 위해서는 파일로 변환하여 폐쇄망으로 자료 전송하는 등의 과정이 필요하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;외부 image 를 로컬 파일로 저장&lt;/h2&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;## (online) - image download
docker pull {image}:{tag} --platform={platform}

## (online) - image -&amp;gt; tar package
docker save {image}:{tag} &amp;gt; {image_filename}.tar&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로컬 파일 image 를 로드&lt;/h2&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;## (offline) load image from package
docker load -i {image_filename}.tar

## (offline) check image loaded
docker image ls &lt;/code&gt;&lt;/pre&gt;</description>
      <category>TIPS</category>
      <category>Docker</category>
      <category>IMAGE</category>
      <category>offline</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/13</guid>
      <comments>https://jypnote.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 19 Mar 2024 12:00:29 +0900</pubDate>
    </item>
    <item>
      <title>여러 줄로 구성된 파일을 line-by-line loop 로 실행하기</title>
      <link>https://jypnote.tistory.com/12</link>
      <description>&lt;p&gt;하나의 파일에 여러 줄로된 정보를 저장해두고, bash로 일괄 처리하고 싶을 때 아래 코드를 복사해서 활용하자. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;list=$(cat list.txt) # multi-line 으로 list 에 할당된다.

while IFS= read -r item; do 

    # TODO: do something
    echo $item

done &amp;lt;&amp;lt;&amp;lt; &amp;quot;$list&amp;quot;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>TIPS</category>
      <category>Bash</category>
      <category>Tips</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/12</guid>
      <comments>https://jypnote.tistory.com/12#entry12comment</comments>
      <pubDate>Mon, 18 Mar 2024 12:00:21 +0900</pubDate>
    </item>
    <item>
      <title>특정 문자열을 파일에서 일괄 변경하기</title>
      <link>https://jypnote.tistory.com/11</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;code&gt;fruit.txt&lt;/code&gt;&amp;nbsp; 파일 내에 있는 모든 &lt;code&gt;apple&lt;/code&gt; 을 &lt;code&gt;banana&lt;/code&gt; 로 변경&lt;/h2&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;# linux
sed -i 's/apple/banana/g' /fruit.txt&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;mac 의 경우 sed 의 -i 에 extension 파라미터가 필요하다. extension은 변경하는 파일을 복사한 뒤 붙일 일종의 postfix 이다. 원본 문서를 저장해두지 않을 경우 빈칸으로 두자.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;# mac 
sed -i '.old' 's/apple/banana/g' /fruit.txt&lt;/code&gt;&lt;/pre&gt;</description>
      <category>TIPS</category>
      <category>tip</category>
      <category>개발</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/11</guid>
      <comments>https://jypnote.tistory.com/11#entry11comment</comments>
      <pubDate>Sun, 17 Mar 2024 12:00:02 +0900</pubDate>
    </item>
    <item>
      <title>Python 에 특정 OpenSSL 버전 연동하여 설치</title>
      <link>https://jypnote.tistory.com/10</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;urllib3 을 사용할 때, 다음과 같은 에러가 발생하면서 python 스크립트가 실행되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;ImportError: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips  26 Jan 2017'.
See: https://github.com/urllib3/urllib3/issues/2168&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인을 파악해보고자 구글링을 하니 다음과 같은 공식 가이드와 짤막한 해결방안이 있었다.&lt;br /&gt;요약하면, urllib3 부터는 OpenSSL 1.1.1+ 버전만을 지원하므로, 서버에 설치되어 있는 openssl 버전을 업그레이드 하여야 한다.&lt;br /&gt;제공된 가이드의 경우 RHEL7 의 경우 os 업그레이드를 하라는 배보다 배꼽이 다소 더 큰 가이드를 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://urllib3.readthedocs.io/en/latest/v2-migration-guide.html#common-upgrading-issues&quot;&gt;https://urllib3.readthedocs.io/en/latest/v2-migration-guide.html#common-upgrading-issues&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710561287556&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;v2.0 Migration Guide&quot; data-og-description=&quot;urllib3 v2.0 is now available! Read below for how to get started and what is contained in the new major release.   Migrating from 1.x to 2.0: We&amp;rsquo;re maintaining functional API compatibility for most...&quot; data-og-host=&quot;urllib3.readthedocs.io&quot; data-og-source-url=&quot;https://urllib3.readthedocs.io/en/latest/v2-migration-guide.html#common-upgrading-issues&quot; data-og-url=&quot;https://urllib3.readthedocs.io/en/latest/v2-migration-guide.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://urllib3.readthedocs.io/en/latest/v2-migration-guide.html#common-upgrading-issues&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://urllib3.readthedocs.io/en/latest/v2-migration-guide.html#common-upgrading-issues&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;v2.0 Migration Guide&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;urllib3 v2.0 is now available! Read below for how to get started and what is contained in the new major release.   Migrating from 1.x to 2.0: We&amp;rsquo;re maintaining functional API compatibility for most...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;urllib3.readthedocs.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결방안&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;python 을 업그레이드한 openssl 을 연동해서 직접 컴파일하여 설치해보자. (아래의 &lt;a href=&quot;https://stackoverflow.com/questions/69371800/how-to-link-python3-to-use-openssl11-or-latest-version-of-openssl-1-1-1-on-c&quot;&gt;stackoverflow 글&lt;/a&gt;을 참고하였습니다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenSSL 설치&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# Install OpenSSL 1.1.1
cd /opt
curl https://ftp.openssl.org/source/old/1.1.1/openssl-1.1.1o.tar.gz --output openssl.tar.gz
tar xzf openssl.tar.gz
rm openssl.tar.gz
cd openssl-1.1.1o/

# --prefix 경로에 openssl 을 설치합니다. 
./config --prefix=/opt/openssl &amp;amp;&amp;amp; make &amp;amp;&amp;amp; make install &lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;설치 경로를 포함한 OpenSSL 버전 확인&lt;/h4&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;openssl version -d&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특정 OpenSSL 을 연동하여 python 설치&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;# Install Python 3.8.18
cd /opt
wget https://www.python.org/ftp/python/3.8.18/Python-3.8.18.tgz
tar xzf Python-3.8.18.tgz
cd Python-3.8.18

# 아까 설치했던 openssl 경로를 입력합니다. 
./configure --with-openssl=/opt/openssl

# build and install
make
make altinstall&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;확인&lt;/h2&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;python3.8
&amp;gt; import ssl
&amp;gt; ssl.OPENSSL_VERSION
'OpenSSL 1.1.1o 14 Dec 2021'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/69371800/how-to-link-python3-to-use-openssl11-or-latest-version-of-openssl-1-1-1-on-c&quot;&gt;https://stackoverflow.com/questions/69371800/how-to-link-python3-to-use-openssl11-or-latest-version-of-openssl-1-1-1-on-c&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1710561317728&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How to link python3 to use openssl11 / or latest version of openssl (1.1.1) on centos 7&quot; data-og-description=&quot;We wanted to upgrade OpenSSL in centos 7 but it didn't happen, the reason may be this. Upgrading CentOS 7 to OpenSSL 1.1.1 by yum install openssl11 I've came to know openssl11 is for &amp;quot;spot&amp;quot;&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/69371800/how-to-link-python3-to-use-openssl11-or-latest-version-of-openssl-1-1-1-on-c&quot; data-og-url=&quot;https://stackoverflow.com/questions/69371800/how-to-link-python3-to-use-openssl11-or-latest-version-of-openssl-1-1-1-on-c&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/F9EZb/hyVALS0x3x/YOltQmkQJ4Y95UaekNSohK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/69371800/how-to-link-python3-to-use-openssl11-or-latest-version-of-openssl-1-1-1-on-c&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/69371800/how-to-link-python3-to-use-openssl11-or-latest-version-of-openssl-1-1-1-on-c&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/F9EZb/hyVALS0x3x/YOltQmkQJ4Y95UaekNSohK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to link python3 to use openssl11 / or latest version of openssl (1.1.1) on centos 7&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We wanted to upgrade OpenSSL in centos 7 but it didn't happen, the reason may be this. Upgrading CentOS 7 to OpenSSL 1.1.1 by yum install openssl11 I've came to know openssl11 is for &quot;spot&quot;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>openssl</category>
      <category>Python</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/10</guid>
      <comments>https://jypnote.tistory.com/10#entry10comment</comments>
      <pubDate>Sat, 16 Mar 2024 12:51:05 +0900</pubDate>
    </item>
    <item>
      <title>다형성을 사용하도록 조건문 다시 작성하기</title>
      <link>https://jypnote.tistory.com/9</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;birds-of-north-america.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2aEfg/btr5TpG3IhE/5TCuCuMvfWlwjV06tAj1tk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2aEfg/btr5TpG3IhE/5TCuCuMvfWlwjV06tAj1tk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2aEfg/btr5TpG3IhE/5TCuCuMvfWlwjV06tAj1tk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2aEfg%2Fbtr5TpG3IhE%2F5TCuCuMvfWlwjV06tAj1tk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;600&quot; data-filename=&quot;birds-of-north-america.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;article id=&quot;0acd69de-78ba-4397-9b17-2d63a60549b4&quot; class=&quot;page sans&quot;&gt;&lt;header&gt;&lt;/header&gt;
&lt;div class=&quot;page-body&quot;&gt;
&lt;p id=&quot;01840bfe-db4d-4442-9048-d021d79d66cb&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;객체 지향 프로그래밍을 하다 보면, 필연적으로 상속 구조를 활용하게 되는데요. 이 때, 서비스 로직에서 실제 인스턴스 타입별로 어떤 코드 실행을 다르게 해주어야 하는 경우가 발생합니다.&lt;/p&gt;
&lt;p class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;3171795e-db2a-4e28-80d8-60fee062c26d&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;고민하지 않고 단순히 인스턴스 별 조건분기문으로 코드를 작성하게 되면, 새로운 요구사항이 추가될 때마다 코드의 변경이 많아질 수 있습니다. 또 그런 변경들이 프로그램의 로직을 복잡하게 만들어낼 수 있는데요. 오늘은 이런 상황에서 조건문을 사용하지 않고, 다형성을 활용할 수 있는 방안에 대해서 살펴보겠습니다.&lt;/p&gt;
&lt;p id=&quot;f4e86c47-f481-4835-a46f-36a4bda31149&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;4923ea05-f0fb-4928-9e86-874c33f27a25&quot; class=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;상황&lt;/h2&gt;
&lt;ul id=&quot;63f69aa8-451f-4ff6-981a-5b8d16e842fd&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;새를 나타내는 &lt;code&gt;Bird&lt;/code&gt; 클래스가 있습니다. 이 &lt;code&gt;Bird&lt;/code&gt; 객체를 API 응답으로 내려주기 위해서는 &lt;code&gt;BirdResult&lt;/code&gt; 객체로 변형되어야 합니다.BirdResult 는 내부 객체와 API 응답 필드들을 분리하기 위하여 사용하고 있는 DTO 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;668a0cb9-2918-4387-949e-a699aa212fc1&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;이 &lt;code&gt;BirdResult&lt;/code&gt; 를 생성하기 위해 &lt;code&gt;Builder&lt;/code&gt; 를 사용하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;09752a04-f587-4914-b5d8-ad49fd996900&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;code&gt;Bird&lt;/code&gt; 로부터 &lt;code&gt;BirdResult&lt;/code&gt; 의 각 응답 필드를 세팅하는 메서드는 아래와 같습니다.
&lt;pre id=&quot;f8ade425-09f4-4cc2-879a-c5d11c44ca5d&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// BirdResult.java

/**
 * Bird 값을 주입한다. (As-is)
 * @param bird 
 * @return Builder
 */
public Builder bird(Bird bird) {
	this.id = bird.getId();
	this.name = bird.getName();
	this.type = bird.getType();
	// ...

	return this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;44e9c5df-b555-49f7-a6af-1942db543fda&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;ffb5d114-d063-4439-b124-c9998a67396c&quot; class=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;새로운 요구사항&lt;/h2&gt;
&lt;ul id=&quot;69698f0f-6442-439b-822a-ab9c9c14781c&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;code&gt;Bird&lt;/code&gt; 는 이제 새로운 하위타입들로 나누어져야 합니다. (&lt;code&gt;Canary&lt;/code&gt; , &lt;code&gt;Duck&lt;/code&gt; , &lt;code&gt;MockingBird&lt;/code&gt; )&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;e314ac77-648a-43d4-bab0-6ca295696885&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;각 하위 타입에 따라서 &lt;code&gt;BirdResult&lt;/code&gt; 에 채워주어야 할 필드가 달라집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;0cfbbceb-808d-4994-9e89-71ab2ae395da&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;69d81e87-6b55-466f-8386-c7cf17e0b287&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 요구사항에 맞춰서, &lt;code&gt;Builder.bird(bird)&lt;/code&gt; 메서드의 구현은 어떻게 달라져야 할까요?&lt;/p&gt;
&lt;p id=&quot;bf8b7c48-0ac6-4325-8ce5-d5501a9e0edc&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;23af25db-c809-4e89-9fa4-0335ef9ec5a7&quot; class=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;타입을 통해 분기한다.&lt;/h2&gt;
&lt;ul id=&quot;ae3f37eb-7f18-4188-9f3b-049d94203e0d&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;type 필드값이나, instanceof 를 통해 실제 객체 타입을 확인하여 분기할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;35a6f7d1-9210-4594-a016-5d453d2e7ecc&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// BirdResult.java

/**
 * Bird 값을 주입한다. (Conditional)
 * @param bird 
 * @return Builder
 */
public Builder bird(Bird bird) {
	this.id = bird.getId();
	this.name = bird.getName();
	this.type = bird.getType();

	switch (bird.getType()) {
		case BirdType.Canary:
			this.feather = (Canary)bird.getColor();
		case BirdType.Duck:
			this.beakSize = (Duck)bird.getBeakSize();
		case BirdType.MockingBird:
			this.message = (MockingBird)bird.getMessage();
			break;
		default:
			// ...
	}

	return this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;e464a0a1-6d89-402a-9e5a-380e9b12cc90&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;3e0619bf-7712-49d0-a31a-30f40d987439&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 위와 같이 구현했을 때, 몇 가지 문제점을 생각해볼 수 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;68ba1b77-1ee4-482f-938b-193f299b40ca&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;타입이 새로 만들어질 때마다, 그에 맞는 케이스를 새로 추가해주어야 합니다.&lt;/h3&gt;
&lt;p id=&quot;ec6f601b-45f4-45aa-b601-1b8317705421&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 코드가 한 곳에서만 사용되었다면, 당장은 문제가 되지 않을 수 있습니다. 하지만 코드는 계속 변경되고 추가되는 것이 문제죠. Bird 객체를 사용하는 모든 메서드를 찾아가면서 조건 분기로 되어 있는 모든 곳을 찾아서 알맞게 수정해주어야 하는 문제점이 있습니다.&lt;/p&gt;
&lt;p id=&quot;481838be-3b01-4daf-a6e4-a9121114dac3&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;170f380f-9689-4721-a607-07588dd56d9a&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;새로운 코드가 추가되거나 변경이 될 때, 해당 코드를 사용하는 다른 모듈에서 모두 변경이 이루어져야 한다면, 이는 &lt;a href=&quot;https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle&quot;&gt;개방-폐쇄 원칙 (Open-closed principle)&lt;/a&gt;을 위반한 코드가 됩니다. 따라서 변경이 되더라도, 사용하는 다른 모듈에서 변경하지 않아도 되도록 수정되어야 합니다.&lt;/p&gt;
&lt;p id=&quot;4dff8d37-106d-479c-9863-4daf85fd7c11&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;b1da7ab1-3efa-4fcb-a85f-2e99723270d3&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;다형성을 사용한 이유가 줄어들었습니다.&lt;/h3&gt;
&lt;p id=&quot;f57b3c80-8be9-4c5a-acd6-d2f668c19460&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;위 코드에서는 단지 다형성을 사용한 목적이 하나의 &amp;ldquo;타입&amp;rdquo; 값으로만 사용되고 있습니다. 그리고 그 타입을 통해 분기하여 다시 타입캐스팅을 하여 필요한 메서드를 일일이 호출하고 있습니다.&lt;/p&gt;
&lt;p id=&quot;fd4a1ac0-fa2f-4a20-9b59-ad0695f35b80&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;f08dc32e-9e8e-48ef-9809-6c07fdd8b3c4&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;다형성의 성격 중 하나는 객체가 스스로 실행할 메서드를 실행 시점에서 결정할 수 있다는 것입니다. (이것을 동적 바인딩이라고 합니다.) 상위 클래스의 메서드를 재정의(overriding) 하면, 해당 메서드의 실행 시점에서 하위 클래스에서 재정의한 메서드를 실행하도록 결정됩니다. 그렇기 때문에 객체를 사용하는 모듈에서는 수정이 되더라도 변경을 할 필요가 없어지도록 구현할 수 있을 것입니다.&lt;/p&gt;
&lt;p id=&quot;c6b8357f-2495-4044-82ad-0598bdf9711d&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;1f25cdef-2b2b-43dd-813a-15830eed3ef2&quot; class=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;다중정의(overloading) 를 사용한다.&lt;/h2&gt;
&lt;ul id=&quot;b67aa48d-b36c-48cb-8f47-e0f97aa684ba&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;code&gt;.bird(bird)&lt;/code&gt; 를 다중정의(overloading)하여 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;cf0cc82d-965a-432f-8080-efb1c6f4f00b&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;c2ba3cdc-5901-4122-b812-134bd1c6f842&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// BirdResult.java

/**
 * Bird 값을 주입한다. (Overloading)
 * @param bird 
 * @return Builder
 */
public Builder bird(Bird serverGroup) {
	this.id = bird.getId();
	this.name = bird.getName();
	this.type = bird.getType();
	return this;
}

public Builder bird(Canary bird) {
	this.bird((Bird)bird)
	this.feather = (Canary)bird.getColor();
	return this;
}

public Builder bird(MockingBird bird) {
	this.bird((Bird)bird)
	this.message = (MockingBird)bird.getMessage();
	return this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;d4a922f3-3b85-41ca-98ca-f8509279fcc9&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;72440233-928e-4eb7-b88c-0afa76aa6f73&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 구현했을 땐, 이제 받는 객체 타입에 따라서 다른 필드들이 세팅될 수 있습니다. 그리고 함께 세팅되어야 하는 공통 필드들에 대해서도 잘 설정될 수 있습니다. 하지만 아쉽게도, 위와 같은 구현 방식은 개방-폐쇄 원칙에 대해서 해결하지 못하고 있습니다.&lt;/p&gt;
&lt;h3 id=&quot;378f821f-e913-417b-8b8f-420fcdaf1069&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;새로운 타입이 만들어질 때마다, 새로운 메서드를 정의해주어야 한다.&lt;/h3&gt;
&lt;p id=&quot;bf9afc37-6659-4b2f-ae6b-17172fa6f559&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;위 &lt;code&gt;bird&lt;/code&gt; 메서드를 호출하는 쪽에서, 어떤 타입인지 먼저 알고 있어야 하고, 타입에 따라 항상 타입 캐스팅을 해주어야 합니다. 그렇지 않으면 잘못된 메서드가 호출될 수 있습니다. 다시 말하면, 위 코드는 &lt;code&gt;Bird&lt;/code&gt; 타입에 대한 분기를 &lt;code&gt;Builder&lt;/code&gt; 를 호출하는 쪽으로 옮겨놓는 수준으로 생각할 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;05143b6a-e753-41e7-ba93-c2ef20a35d55&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;e4afe042-218e-4791-bc07-c192e3a5cf45&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;아래 코드 예시를 통해 알아보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;f0fd7ba2-0d68-49c5-ab7c-2752a44b9498&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// SomethingService.java

// 상위 타입인 Bird 로 받았습니다. 실제 객체가 어떤 타입인지는 알 수 없습니다. 
Bird bird = birdService.getBird(birdId); 

Builder builder = new Bird.Builder();

// 아래 메서드에서는 bird(Bird bird) 가 호출됩니다. 상위 타입으로 전달했기 때문입니다. 
builder.bird(bird);&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;23fb3fd3-d7c9-4270-a466-38401e672286&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;7b010ec5-d67f-4ab8-9666-791d86963fd4&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// SomethingService.java

// 각 타입에 맞게 호출되기 위해서는 빌더 호출 시에 아래와 같은 분기가 필요합니다. 
if (bird instanceof Canary) {
	builder.bird((Canary)bird));
} else if (bird instanceof MockingBird) {
  builder.bird((MockingBird)bird)); 
} else {
	builder.bird(bird);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;977c5d97-d903-4cb7-9aa3-b12a28fa94e7&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;05a45e89-352f-44e0-8f0d-c4318e027ec1&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면, 다형성을 이용하여 위 코드를 구현하려면 어떻게 할 수 있을까요?&lt;/p&gt;
&lt;h2 id=&quot;6d93106b-a0c2-46ff-97c6-419a7c2c3cf2&quot; class=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;Builder 에서 상위 타입의 메서드를 호출하도록 정의하고, 각 하위 타입에서 메서드를 재정의(Overriding) 한다.&lt;/h2&gt;
&lt;p id=&quot;7a3fe8fd-d8e7-4900-8a3b-13c1d66bd41f&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;bbebefba-4d96-4ea6-9225-dcdb150aece1&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;타입에 따라 메서드의 구현이 달라져야 하는 경우에, 여러 하위 타입에서 메서드에 대해 재정의를 하면 일일이 조건문을 작성하지 않아도 다형적으로 호출되게 할 수 있습니다. 아래 처럼 상위 타입의 메서드를 호출하면, 각 하위 타입의 재정의된 메서드를 따라가도록 구현할 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;e53ee425-81d3-44e3-be00-a1b6ec74f698&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;1a642df8-bc9d-4334-8754-76ae5c493b7e&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;아래처럼 Builder 의 구현에서는 상위 타입의 &lt;code&gt;putResult(Builder)&lt;/code&gt; 메서드를 호출합니다. &lt;code&gt;putResult&lt;/code&gt; 에서는 전달한 &lt;code&gt;builder&lt;/code&gt; 의 필드에 필요한 항목들을 주입해주는 역할을 해줍니다.&lt;/p&gt;
&lt;p id=&quot;7c2be3eb-96ca-45ed-a449-6d58c16daa68&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;59908307-5fe3-4966-a9a5-5002ecaf0d9a&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// BirdResult.java

/**
 * Bird 값을 주입한다. (Polymorphism)
 * @param bird 
 * @return Builder
 */
public Builder bird(Bird bird) {
	bird.putResults(this); // bird 에서 builder (this) 에 직접 주입한다.
	return this;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;3793fc09-94c0-4145-a24a-096c38ef251f&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;853c0dec-45fe-423f-96a8-d71cdcea443b&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이 때 Builder 에서 구현되어 있던 필드 설정 부분들을 옮기기 위해 다음과 같이 변경합니다.&lt;/p&gt;
&lt;ul id=&quot;78f4ae7b-fcc4-4d59-ab19-e8951cf7983c&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;상위 타입에서 호출될 메서드를 작성하여, 하위 타입에서 재정의할 수 있도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;f2392981-fb4b-49a2-a24b-24331e45ae7d&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;각 하위 타입 별로 분기문에서 수행되었던 코드를 각 타입의 재정의한 메서드 안으로 옮깁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;fb4db28f-2f43-4978-9d4b-dc16871b22db&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;7b55974f-e643-49b5-98e4-70f357055aaa&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 각각의 &lt;code&gt;Canary&lt;/code&gt;, &lt;code&gt;MockingBird&lt;/code&gt; 타입에서는 추상 클래스 &lt;code&gt;Bird.putResult(Builder)&lt;/code&gt; 에 대한 구현이 이루어져야 합니다.&lt;/p&gt;
&lt;p id=&quot;35dd118d-48fa-49da-b8bb-3e4cde7b86a6&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;c6906944-d554-4732-bdc6-00f134b2a40b&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// Bird.java
public abstract class Bird {
  // ...

	/**
	 * 서버 그룹 필드값들을 BirdResult.Builder 에 할당한다.
	 * @param builder 응답 객체 빌더
	 */
	public void putResult(BirdResult.Builder builder) {
		// 상위 타입에서 호출될 메서드를 작성하여, 하위 타입에서 재정의할 수 있도록 한다.
		builder.id(this.id);
		builder.name(this.name);
		builder.type(this.type);
	}
}

// Canary.java
public class Canary extends Bird {
  // ...

	/**
	 * 서버 그룹 필드값들을 BirdResult.Builder 에 할당한다.
	 * - Bird.putResult 를 오버라이딩하여 동적바인딩 되도록 합니다. 
	 *
	 * @param builder 응답 객체 빌더
	 */
	@Override
	public void putResult(BirdResult.Builder builder) {
		// 공통 필드 세팅
		super.putResult(builder);

		// Canary 필드 세팅
		builder.feather(this.feather);
	}
}

// MockingBird.java
public class MockingBird extends Bird {
  // ...

	/**
	 * 서버 그룹 필드값들을 BirdResult.Builder 에 할당한다.
	 * - Bird.putResult 를 오버라이딩하여 동적바인딩 되도록 합니다. 
	 *
	 * @param builder 응답 객체 빌더
	 */
	@Override
	public void putResult(BirdResult.Builder builder) {
		// 공통 필드 세팅
		super.putResult(builder);

		// MockingBird 필드 세팅
		builder.message(this.message);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;5f9e89a7-685a-40d5-b429-a3a6ca37161c&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;3dfa8f38-830d-4e05-ac15-741516f7063c&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 구현을 하면, 다음과 같은 장점이 있습니다.&lt;/p&gt;
&lt;ul id=&quot;659361f7-0194-4e34-b754-f84833713cf5&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;타입에 따라 기능이 달라지는 여러 객체가 있을 때에도 일일이 조건문을 작성하지 않고, 다형적으로 호출되게 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;ebb7ed80-20a7-4f75-b5b8-7056a69db4bc&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;새로운 타입이 추가되더라도, &lt;code&gt;putResult(Builder)&lt;/code&gt; 만 알맞게 구현해준다면, &lt;code&gt;Builder.bird(Bird)&lt;/code&gt; 를 변경하지 않고, 그대로 사용할 수 있습니다. 따라서 개방-폐쇄 원칙도 지킬 수 있게 되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;fac3307a-91f9-4cf9-8dae-28737637abd1&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;ff5b4351-c155-4fef-a4eb-4349195365be&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이 방법에 대해서 좀 더 자세하게 확인해보시고 싶으신 분들은 &lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001810241&quot;&gt;&lt;b&gt;리팩터링(마틴 파울러)&lt;/b&gt;&lt;/a&gt;에서 &lt;b&gt;&lt;a href=&quot;https://www.refactoring.com/catalog/replaceConditionalWithPolymorphism.html&quot;&gt;조건문을 재정의로 전환 (Replace Conditional with Polymorphism)&lt;/a&gt;&lt;/b&gt; 을 살펴보시면 좋을 것 같습니다. 유명한 리팩터링 항목이기 때문에, 단순 구글링을 하셔도 다양한 글들을 확인하실 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;366403a2-612d-40a2-81b3-6dac64ea48cf&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/article&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;birds-of-north-america.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tBoL4/btr5TqTyYWY/3iWwKxHe6hip1D94VgGuFK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tBoL4/btr5TqTyYWY/3iWwKxHe6hip1D94VgGuFK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tBoL4/btr5TqTyYWY/3iWwKxHe6hip1D94VgGuFK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtBoL4%2Fbtr5TqTyYWY%2F3iWwKxHe6hip1D94VgGuFK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;600&quot; data-filename=&quot;birds-of-north-america.jpeg&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.buymeacoffee.com/leopark95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;img style=&quot;height: 60px !important; width: 217px !important;&quot; src=&quot;https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png&quot; alt=&quot;Buy Me A Coffee&quot; /&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <category>java</category>
      <category>OOP</category>
      <category>다형성</category>
      <category>리팩터링</category>
      <category>상속</category>
      <category>조건문</category>
      <category>추상화</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/9</guid>
      <comments>https://jypnote.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 26 Mar 2023 22:10:11 +0900</pubDate>
    </item>
    <item>
      <title>빌더 패턴을 사용한 객체의 생성</title>
      <link>https://jypnote.tistory.com/8</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_1651217211_Architecture-Photographer-Important-Structural-Photography.jpeg&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cpIvIr/btr4uJnzN9y/b8CaOQrJkzHY8fyk8L2jb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cpIvIr/btr4uJnzN9y/b8CaOQrJkzHY8fyk8L2jb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cpIvIr/btr4uJnzN9y/b8CaOQrJkzHY8fyk8L2jb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcpIvIr%2Fbtr4uJnzN9y%2Fb8CaOQrJkzHY8fyk8L2jb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;931&quot; height=&quot;620&quot; data-filename=&quot;edited_1651217211_Architecture-Photographer-Important-Structural-Photography.jpeg&quot; data-origin-width=&quot;997&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;빌더 패턴은 객체를 생성할 때 쓰이는 유용한 패턴입니다. 하지만 빌더에 대한 추가적인 구현이 필요해서 인지, 생각보다 많은 사람들이 빌더가 필요한 상황에서도 복잡한 생성자를 사용해서 객체를 생성합니다. 그리고 그에 대한 댓가로, 개발자의 파라미터 입력 실수에 의한 오류를 접합니다.&lt;/span&gt;&lt;/p&gt;
&lt;article id=&quot;3b29872a-463b-4378-8460-eb69827e010a&quot; class=&quot;page sans&quot;&gt;
&lt;div class=&quot;page-body&quot;&gt;
&lt;p id=&quot;a236394c-f982-4aae-824e-5654b7b844f1&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;444290fe-7fda-42de-8ce5-16c7af2ba11a&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이 글에서는 Java 환경에서 생성자와 점층적 생성자, 그리고 빌더에 대해서 알아봅니다. 그리고 계층적 클래스 구조에서 빌더를 사용하는 방법에 대해서 설명하고자 합니다.&lt;/p&gt;
&lt;h2 id=&quot;dacdf78e-c419-45df-be54-e2198741921d&quot; class=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;객체의 생성&lt;/h2&gt;
&lt;p id=&quot;a59685e1-5e02-4a58-b2b9-51c97fd6ca53&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;빌더에 대해서 이야기 하기 전에, 먼저 객체를 생성하는 여러가지 방법에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p id=&quot;8b17c44b-073f-400a-b6d8-e12b064ba6de&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;f77ece6c-104f-4892-9cb7-eaeb3c72a767&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;생성자&lt;/h3&gt;
&lt;p id=&quot;435285d7-0e78-40d1-9afd-1ede80aceff2&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;가장 많이 사용하는 방법은 생성자를 통해 객체를 생성하는 것입니다. 생성자는 정의되어 있는 class 를 기반으로 객체를 생성하는 기본적인 방법이다. &lt;code&gt;new Class(&amp;hellip;)&lt;/code&gt; 형태로 직관적이고, class 내에 정의만 하면 되기 때문에 군더더기 없습니다.&lt;/p&gt;
&lt;p id=&quot;d9d2a525-c47f-4140-a2ac-64b385496886&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 객체가 가지고 있는 속성이 많을 경우에는 이야기가 좀 달라집니다. java 의 생성자는 입력 받는 파라미터를 순서와 파라미터의 클래스(Class) 또는 원시 타입(primitive type)으로 구분합니다. 이 때 사용자는 다음과 같이 객체를 생성해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;7df86706-5f64-4b1d-91b1-4149326fe340&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;new Something(id, weight, height, message, pages, rate);&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;4a8346e0-4dd9-43a1-abb3-438274fa8239&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;93169e3d-1f28-4b7a-9981-418e00c8f4c7&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 되었을 때, &lt;code&gt;weight&lt;/code&gt; 와 &lt;code&gt;height&lt;/code&gt; 의 순서가 변경된다면 어떤 일이 발생할까요? 아마 &lt;code&gt;Something&lt;/code&gt; 객체는 개발자가 의도한 것과는 정반대로 생성되어 프로그램의 동작에 문제가 발생할 것입니다. 실제로 개발하면서 이런 문제를 많이 겪습니다. 이런 문제는 변수명과 순서를 매핑할 수 없는 언어적인 한계에서 발생합니다. (python 에서는 명명된 선택적 매개변수를 지원하기 때문에 이런 문제가 없습니다.)&lt;/p&gt;
&lt;p id=&quot;a79aa869-6b46-4880-bc12-b644459ed3ad&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;243d49b1-8e6f-4206-b319-50493d1c2bca&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;점층적 생성자&lt;/h3&gt;
&lt;p id=&quot;edc48f89-e480-4e69-8541-a4d5fc288a40&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;일반적인 생성자를 사용할 때 파라미터가 길어지는 문제점을 방지하기 위해서 점층적 생성자를 사용할 수 있습니다. 생성자에서 필요한 파라미터만 받아서, 기본값을 할당할 수 있는 방법입니다.&lt;/p&gt;
&lt;p id=&quot;70d46303-599e-4c62-b6bb-d107a1ce6c59&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 이럴 경우에도 모든 파라미터를 사용하여 초기화해야하는 경우에는 위 문제가 해결되지 않습니다. 또 생성자가 많아질 수록 코드를 수정하기 어려워집니다.&lt;/p&gt;
&lt;pre id=&quot;9d0cc057-5fdb-4aa3-8093-77126d5054a8&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;class Something {
	// ...
	public Something(int id, int weight, int height) {
			this(id, weight, height, &quot;&quot;, 1, 0.0D);
		}
	}

	public Something(int id, int weight, int height, String message, int pages, double rate) {
		this.id = id;
		this.weight = weight;
		this.height = height;
		this.message = message;
		this.pages = pages;
		this.rate = rate;
	}
}

// 필요한 항목만 사용하여 생성할 수 있도록 점층적 생성자를 선언해줄 수 있다. 
new Something(id, weight, height);

// 하지만 결국에 많이 필요한 경우에는 같은 문제가 유지된다. 
new Something(id, weight, height, message, pages, rate);&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;35beb034-2e75-4cc1-8a1b-b03e83f00309&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;54dbaf65-9269-4140-981e-48489b0cc92b&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;b46ba5b3-0b31-416a-a71b-4d954fbe58eb&quot; class=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;빌더&lt;/h2&gt;
&lt;p id=&quot;2e718fd6-a412-4fac-b826-d436ad4e73d3&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;빌더 패턴은 생성자와 마찬가지로 어떤 객체를 생성해주는 역할을 합니다. 아래 빌더 코드를 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;991c157e-40f2-46ce-a292-1cfa133d7e7a&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;public class Coffee {
	private int shot;
	private String type;

	protected Coffee(Builder builder) { // -- (A)
		this.shot = builder.shot;
		this.type = builder.type;
	}

	public static class Builder { // -- (B)
		private int shot;

		public Builder() {
		}

		public Builder shot(int shot) { // -- (C)
			this.shot = shot;
			return this;
		}

		public Builder type(String type) {
			this.type = type;
			return this;
		}

		public Coffee build() { // -- (D)
			return new Coffee(this);
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul id=&quot;f41c460d-0c8e-4b0f-9329-65ca77190245&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(A) 에서 빌더를 통해 각 필드가 설정되고 객체가 생성될 수 있도록 생성자를 구현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;8ee392c5-cadd-45ee-9f3a-b5f782ff3236&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(B) 는 Builder 클래스의 선언부입니다.
&lt;ul id=&quot;0f305597-98dc-48d4-a2c3-899ecd72fab3&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;외부에서 Builder 를 생성할 수 있어야 하기 때문에 &lt;code&gt;public&lt;/code&gt; 이어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;db896117-e36c-4937-a9c3-efec1cc452d3&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;객체마다 생성될 필요가 없기 때문에 &lt;code&gt;static&lt;/code&gt; 하게 정의하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;9966069d-b9dc-4d5c-bae4-a512f885ce09&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(C) 은 빌더에서 각 속성의 설정자입니다.
&lt;ul id=&quot;b3610b13-8dcb-4cc9-9d3b-82c47d80fd45&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;Builder 를 반환하기 때문에, 메서드 체이닝 형태로 필요한 파라미터를 한번에 설정할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;934815b9-d6ed-4b29-9057-be678e5f7d0c&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(D) 는 최종적으로 객체를 생성하여 반환할 수 있도록 하는 &lt;code&gt;build()&lt;/code&gt; 메서드입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;a0f9261a-55a9-4833-9862-0070ff504d14&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;aead293c-12b5-4d72-83f6-02cc3f82fc3a&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;위 처럼 빌더 클래스를 정의해주었다면, 아래와 같이 빌더를 통해 객체를 생성할 수 있습니다. 생성자를 통한 생성과 비교해보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;71aed249-e0f1-4ca2-a8c4-fb5e9d9a2e3e&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;Coffee coffee = new Coffee.Builder().shot(3).type(&quot;HOT&quot;).build();&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;c4ce9fc8-969e-4488-a2b2-925fd912d964&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;cb7a3968-21fb-4652-87ae-fd96787d8ac0&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;생성자처럼 한번에 객체를 만들어내는 것이 아니라, 필요한 속성들을 하나씩 모아서 개발자가 필요한 시점에 &lt;code&gt;build()&lt;/code&gt; 를 호출하여 객체를 만들어줄 수 있습니다. 이렇게 빌더를 만들어 사용을 하면 다음과 같은 장점이 있습니다.&lt;/p&gt;
&lt;p id=&quot;4d83f7d3-e290-411e-8de3-ce131ad40234&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;53a3d695-4114-49a0-889e-ad0b9865331e&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul id=&quot;dceff101-b8ef-481a-a791-23d6fbe505ea&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Java 에서는 지원되지 않는 명명된 선택적 매개변수(named optional parameter) 를 흉내낼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;027fe8e0-e286-48bd-b9ab-977c998f1d9e&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;객체를 일관성있게 유지할 수 있도록 돕는다. (consistent)&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;e748d582-127b-4602-8857-b6377752a363&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;매개변수의 입력 시점을 다르게 정하면서도, 객체의 불변성을 유지할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;e5cb4c03-e497-4ad9-9326-959f47487aa7&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;계층적인 클래스에 대해서 사용하기 편리하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;f871a658-4cbd-4917-96b0-75cfc3022738&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;ebf0bcd9-19a1-4442-b78b-032b21eca68b&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;Java 에서는 지원되지 않는 명명된 선택적 매개변수를 흉내낼 수 있다.&lt;/h3&gt;
&lt;p id=&quot;52509065-e20f-4f89-a900-4ffb826a4a10&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;Java 는 생성자나 메서드에 파라미터를 전달할 때, 이름을 지정할 수 없습니다. 순서에 의해서만 해당하는 파라미터가 무엇인지 결정됩니다. 이 때문에 앞서 생성자에서 발생했던 문제가 드러납니다.&lt;/p&gt;
&lt;p id=&quot;be15cd3f-670c-4462-9c1e-afc7b4d2aca2&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 빌더를 사용하면 이런 문제를 어느 정도 우회할 수 있습니다. 파라미터를 설정할 때 명확한 설정자를 통하기 때문에, 어떤 파라미터에 무엇을 넣는 지 개발자가 인지할 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;b8742deb-f08d-4ce8-9a6d-77d293503e40&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;ab22a173-0ee8-4f00-a85d-7f471f08c709&quot; class=&quot;language-python&quot;&gt;&lt;code&gt;# python 이라면 다음과 같은 방법을 통해 혼란을 줄일 수 있다.
coffee = Coffee(shot=3, type=&quot;HOT&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;0c5615dc-0527-4a49-a481-f9852ca83dab&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;// 자바는 명명된 선택적 매개변수를 지원하지 않지만,
// 빌더 패턴을 활용하여 명명된 선택적 매개변수의 장점을 취할 수 있다. 
Coffee coffee = new Coffee.Builder().shot(3).type(&quot;HOT&quot;).build();&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;a0ca66f3-eb89-4776-98f8-f6df1b016d6c&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;47db71ac-0fc5-45c0-90de-d84fc0c06050&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;객체를 일관성있게 유지할 수 있도록 돕는다.&lt;/h3&gt;
&lt;p id=&quot;fb9bbdda-860a-49c6-a7ec-9eefb9ec6820&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;어떤 객체가 있는데, 그 객체를 사용할 때 무결한 상태임을 유지하는 것은 중요합니다.&lt;/p&gt;
&lt;p class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;565622b7-d8f6-4e48-8a3b-cbd9d9ce8b75&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;객체를 사용할 때마다 각 필드에 대해서 검증을 해주어야한다면, 이는 유지 보수 비용을 크게 만들 뿐만 아니라 개발할 때에도 성가시고, 어떤 위험이 있을 지 모르기 때문에 개발하기를 두렵게 만듭니다. 하지만 생성되는 순간에 무결성이 검증되고, 객체에 대한 수정이 되지 않음이 보장된다면 마음이 한결 편안해집니다.&lt;/p&gt;
&lt;p class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;ba43555a-1520-4c15-93ae-96c2e56cce78&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;빌더는 각각의 필드에 대해서 검증 로직을 만들어둘 수 있고, 빌드 될 때 한번 더 검증해줄 수 있기 때문에 객체의 일관성을 유지할 수 있도록 돕습니다. 더 나아가서 클래스에서 생성자의 접근자를 private 하게 만들고, 클래스의 설정자(setter)를 제거하면 객체를 불변하게(immutable) 만들 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;86db1179-03a3-4331-8d7e-633ca793d49e&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;// a 는 필드를 설정하는 코드의 사이에서 사용될 수 있기 때문에, 무결한지 확인해주어야 한다.
Coffee a = new Coffee()
a.setShot(...)
// ... setType(..) 가 호출되지 않았을 때, 
// ... 이 사이에 a 가 사용된다면 이는 정상적인 객체인걸까 아닌걸까?
a.setType(...) &lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;6f3ff58e-7675-4b76-990f-0164862256a3&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;e682aa9a-4ba1-4dff-9c36-900fe3716c96&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;// b 는 생성되었을 때, 필요한 필드를 모두 가지고 있을 것이기 때문에,
// 생성된 객체는 무결함이 보장될 수 있다. (무결성에 대한 검증 코드는 build() 나 각 필드 설정자에 포함되어야 한다. )
Coffee b = new Coffee.Builder()
	.shot(...)
	.type(...)
  .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;330677ad-d8ca-44b2-9da3-bce48c4bd1cf&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;aa1a180e-c9bd-4ab2-9768-8786ffe63dca&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;매개변수의 입력 시점을 다르게 정할 수 있다.&lt;/h3&gt;
&lt;p id=&quot;063345cb-478d-4164-9e2e-07b759bcb750&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;필드 마다 필요한 매개변수가 준비되는 시점이 다를 수 있습니다. 이 때 생성자라면, 매개변수가 준비된 시점과 이를 설정하는 코드가 떨어져 가독성을 떨어뜨릴 수 있습니다. 아래 코드를 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;03ae387c-d604-4ce0-93c4-5e59362e6d46&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;// blahblah 
// blahblah 
// (1) blahblah for value `Type`
String type = createType(...);

// blahblah 
// blahblah 
// (2) blahblah for value `shot`
int shot = createShot(...);

// blahblah 
// blahblah 
// blahblah 

// (3) Coffee 의 각 매개변수가 만들어진 시점과 사용하는 시점이 떨어져 있으면
//     가독성이 떨어지고, 유지보수하기 어려워 진다. 
Coffee a = new Coffee(shot, type);&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;f2ac30e8-503e-446a-8ebc-7594d383c0dc&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;(1) 과 (2) 에서 준비된 &lt;code&gt;message&lt;/code&gt; 와 &lt;code&gt;count&lt;/code&gt; 가 (3) 에 와서야 사용됩니다. 명명된 변수가 실제 사용되는 부분과 떨어져있게 되면 코드의 가독성이 떨어지고, 실제 사용되는 시점을 추적해야되기 때문에 유지보수가 어려워집니다.&lt;/p&gt;
&lt;p id=&quot;d0cfb2bb-0f59-47f6-b747-ae009558b28a&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;66f6d946-7b70-4511-9949-03e5311ea85e&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 빌더를 사용하면, 준비가 된 시점에서 빌더의 필드에 저장해둘 수 있어 가독성을 높일 수 있습니다. 아래 코드를 살펴보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;0207e717-9036-425c-8505-bd3ec1baf964&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;Coffee.Builder builder = new Coffee.Builder();

// blahblah 
// blahblah 
// (1) blahblah for value `message`
builder.type(createType(...)); // 매개변수가 만들어진 시점에서 바로 활용된다.

// blahblah 
// blahblah 
// (2) blahblah for value `count`
builder.shot(createShot(...));

// blahblah 
// blahblah 
// blahblah 

// (3) 생성하는 시점에 만들어둔 매개변수는 이미 builder 에 설정되어 있기 때문에 가독성이 좋다.
Coffee b = builder.build(); &lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;1aae62c7-34e0-420d-b1fc-ff377329f0fe&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;40b2e94c-972e-4dcd-b6c2-be4673618c81&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;(1) 과 (2) 를 보면, 변수는 따로 명명할 필요없이 &lt;code&gt;builder&lt;/code&gt; 의 설정자에 바로 전달되어 사용됩니다. 그리고 (3) 에서 생성하는 시점에 만들어졌던 값들은 모두 &lt;code&gt;builder&lt;/code&gt; 에 포함되어있기 때문에, &lt;code&gt;build&lt;/code&gt; 호출만 하면 바로 생성될 수 있습니다.&lt;/p&gt;
&lt;p class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;생성자를 사용했을 때보다 가독성과 유지보수면에서 훨씬 간단해집니다. 실제 개발하는 경우에는 생각보다 사이에 들어있는 코드의 길이가 길어지는 경우가 발생하기 때문에, 빌더를 사용하여 가독성을 높일 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;e7d70898-d62f-47a4-8ef6-60f9044751ce&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;2089ddda-06de-41ef-84b3-8aa8e87d7fc6&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;계층적인 클래스에 대해서 사용하기 편리하다.&lt;/h3&gt;
&lt;p id=&quot;b9d63f80-baec-4033-bfaf-9d170507588d&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;빌더는 뿐만 아니라, 계층적인 구조를 가진 클래스에서도 쉽게 사용할 수 있습니다. 부모 클래스의 빌더를 상속하는 구조로, 자식 클래스에서는 자식 클래스에 추가된 필드에 대해서만 설정하는 빌더를 만들 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;8933bd61-9451-44d6-b492-3b97fce4b497&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;아래 부모 클래스의 빌더 코드를 따라가보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;4581a304-a21b-4168-a5bf-ddf6a4b1df54&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;public class Coffee {
	private Type type;

	protected Coffee(Builder&amp;lt;? extends Builder&amp;lt;?&amp;gt;&amp;gt; builder) { // -- (1)
		this.type = builder.type;
		this.shot = builder.shot;
	}

	// ...

	public static abstract class Builder&amp;lt;T extends Builder&amp;lt;T&amp;gt;&amp;gt; { // -- (2)
		private Type type;

		public Builder() {
		}

		public T type(Type type) { // -- (3)
			this.type = type;
			return self();
		}

		// ...

		protected abstract T self(); // -- (4)

		public abstract Coffee build(); // -- (5)
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;a271f75b-9fdd-488c-9017-09f986c3e5df&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul id=&quot;03d8a714-7388-41ce-8716-ec1d922bcda1&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(1) 은 &lt;code&gt;Builder&lt;/code&gt; 를 통해 &lt;code&gt;Coffee&lt;/code&gt; 를 생성할 수 있도록 만들어주는 생성자입니다.
&lt;ul id=&quot;5eb2264c-79a2-4a2a-bff8-cd408fd19f6d&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;우리는 이 생성자를 코드 외부에서 호출하고 싶지 않지만, 상속 관계에서는 호출하여 부모 클래스의 생성자를 재사용해야 하므로 &lt;code&gt;protected&lt;/code&gt; 여야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;1a4b0201-5409-4510-9a32-6f53358fffc9&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;자식 클래스 빌더도 전달될 수 있도록 하기 위해 &lt;code&gt;Builder&lt;/code&gt; 정의와 같게 정의하였습니다. 이는 (2) 에서 더 자세하게 살펴보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;2132b133-3a7b-4a91-8609-103c903ac01b&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;생성자 내부에서는 &lt;code&gt;builder&lt;/code&gt; 에 설정된 각 필드를 통해 객체 필드를 설정하고 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;99b40c18-b7ae-49c8-8fa5-6b4df7f7cdeb&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(2) 는 빌더의 내부 클래스 선언부 입니다.
&lt;ul id=&quot;dd580649-b6f6-46c9-abd2-ebf4aab20d26&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;하위 클래스에서 명확하게 정의해주어야 하는 추상 메서드가 존재하기 때문에 &lt;code&gt;abstract class&lt;/code&gt; 로 정의하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;0f492631-83c3-4ebb-be31-86dd58ca0c4d&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;받을 타입 파라미터를 &lt;code&gt;T extends Builder&amp;lt;T&amp;gt;&lt;/code&gt; 로 정의하였습니다.
&lt;ul id=&quot;4a7961c7-1927-4353-af2f-c3d8db8f9189&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: square;&quot;&gt;&lt;code&gt;Builder&amp;lt;T&amp;gt;&lt;/code&gt; 를 상속하는 &lt;code&gt;T&lt;/code&gt; 를 타입 파라미터로 받습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;d8b1c9e6-b14a-4670-9acf-bedc5170d5bf&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: square;&quot;&gt;이렇게 정의하면 &lt;code&gt;T&lt;/code&gt; 에 &lt;code&gt;Builder&amp;lt;T&amp;gt;&lt;/code&gt; 속성을 부여하여, 자식 클래스 빌더에서 부모 클래스 빌더에 정의된 값들을 사용할 수 있도록 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;173c51d6-5b42-45c1-858d-a68c20038ffb&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: square;&quot;&gt;&lt;a href=&quot;https://medium.com/@joongwon/java-java%EC%9D%98-generics-604b562530b3&quot;&gt;Java 의 제너릭에 대해 익숙하지 않다면, 다음 글을 참고해주세요.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;648cc9e5-21bd-4156-85be-ac54e5023a1a&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(3) 은 빌더에서 필드의 설정자입니다.
&lt;ul id=&quot;8d8718bd-f17c-48ff-981e-d25a121c3ef1&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;&lt;code&gt;self()&lt;/code&gt; 호출하여 자식 클래스에서 명확하게 정의될 타입 파라미터 &lt;code&gt;T&lt;/code&gt; 를 반환합니다. &lt;code&gt;self()&lt;/code&gt; 에 대한 자세한 설명은 (4) 에서 살펴보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;7b4659e2-693e-40d3-935a-cc9de70c9b6f&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;결국 &lt;code&gt;self()&lt;/code&gt; 를 통해 빌더 자신을 반환하고, 반환된 빌더를 통해 메서드 체이닝으로 나머지 필드를 추가로 설정할 수 있게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;92acf3bb-8c4c-4081-bb24-1505021a808e&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(4) 는 &lt;code&gt;this&lt;/code&gt; 로 부모 클래스 타입을 반환하지 않고, 자식 클래스를 반환할 수 있도록 하기 위한 추상 메서드입니다.
&lt;ul id=&quot;4b790b24-0d8f-4317-8fd1-da5664f1231d&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;&lt;code&gt;this&lt;/code&gt; 가 아닌 &lt;code&gt;self()&lt;/code&gt; 를 사용하는 이유는, &lt;code&gt;this&lt;/code&gt; 를 사용하면 부모 클래스 타입으로 반환되기 때문에, 자식 클래스에서 항상 형변환을 하여 사용해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;ac2e5370-3494-4544-b56d-46d4d59cbe7e&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable&quot;&gt;자바에서는 self 타입이 없기 때문에 이런 우회 방법을 사용하는 것인데, 이런 패턴을 simulate self-type idiom 이라고 합니다. &lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;8415e8f9-ddd6-44e0-8326-a65b9b6aef1c&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(5) 는 최종적으로 &lt;code&gt;Coffee&lt;/code&gt; 객체를 생성하는 빌드 메서드입니다. 자식 클래스에서 이를 구현하여 적절한 객체를 생성하여 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;028a4415-d01a-4d61-9ec6-d33a64ef4ab4&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;7235d082-7094-4ea5-b815-325d4d973d60&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;부모 클래스에서 빌더를 정의하였다면, 이제 실제로 원하는 객체를 만들기 위해서 자식 클래스 빌더에서 부모 클래스 빌더를 상속해주어야 합니. 아래 자식 클래스의 빌더 코드를 따라가보겠습니다.&lt;/p&gt;
&lt;pre id=&quot;a28fdb47-b7d9-4563-acfb-d04992049ba1&quot; class=&quot;language-java&quot;&gt;&lt;code&gt;public class AmericanoCoffee extends Coffee {
	private int water;

	protected AmericanoCoffee(Builder builder) { // -- (6)
		super(builder);
		this.water = builder.water;
	}

	public static class Builder extends Coffee.Builder&amp;lt;Builder&amp;gt; { // -- (7)
		private int water;

		public Builder() {
			super();
		}

		@Override
		protected Builder self() { // -- (8)
			return this;
		}

		@Override
		public Coffee build() { // -- (9)
			return new AmericanoCoffee(this);
		}

		public Builder water(int water) { // -- (10)
			this.water = water;
			return self();
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p id=&quot;3ce8f3bd-85df-454d-b140-9b0e656f250f&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul id=&quot;89e08ed9-05a6-4009-8b62-3a46536dcd5d&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(6) 은 자식 클래스의 생성자입니다.
&lt;ul id=&quot;c5e81a99-9204-4a4f-b19d-bdbc2350bfa8&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;(1) 에서 선언한 부모 생성자를 재사용하여, 자식 클래스에서 추가된 필드만 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;7a2b0bc2-8461-41da-8ddb-351a65f470b6&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;자식 클래스 빌더를 파라미터로 받습니다. (1) 에서 &lt;code&gt;Builder&amp;lt;? extends Builder&amp;lt;?&amp;gt;&amp;gt;&lt;/code&gt; 를 타입으로 정했기 때문에, 자식 클래스 빌더 타입 그대로 전달하더라도 컴파일 에러가 발생하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;43d9010f-54da-4af2-acd4-37107c7fe493&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(7) 에서 &lt;code&gt;Coffee.Builder&amp;lt;Builder&amp;gt;&lt;/code&gt; 를 상속합니다.
&lt;ul id=&quot;02a67277-b2a9-48b8-9539-b2aace7fbbea&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;&lt;code&gt;Coffee.Builder&lt;/code&gt; 에 자식 클래스 빌더인 &lt;code&gt;Builder&lt;/code&gt; 를 타입 파라미터 &lt;code&gt;T&lt;/code&gt; 로 전달해주었기 때문에, 나머지 &lt;code&gt;T&lt;/code&gt;로 반환해주었던 메서드들을 &lt;code&gt;Builder&lt;/code&gt; 로 반환해주어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;4b7551ea-b8c3-462a-82d6-594ded894229&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(8) 에서 (4) 를 구체 메서드로 구현합니다.
&lt;ul id=&quot;ec443b93-eb98-4661-90c6-828863dfefc6&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;여기에서 자기 자신 &lt;code&gt;this&lt;/code&gt; 를 반환해줌으로써, 타입 형변환 없이 코드를 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;b071ecd9-e1ae-43c5-a630-e7f7fa1839ce&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;&lt;code&gt;self()&lt;/code&gt; 가 아니었다면, 부모 클래스의 설정자를 사용할 때마다 타입 형변환이 필요했을 것 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;83d1d465-8a30-4cc4-8660-d10ba04f639d&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(9) 는 실제 자식 클래스의 객체인 &lt;code&gt;AmericanoCoffee&lt;/code&gt; 를 생성하여 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;90fc2672-e72e-44a4-b279-7b853e470902&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;(10) 은 자식 클래스에서 추가된 필드를 설정하는 메서드 입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;975efe5b-308b-445a-bdad-368380c31573&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;89b6be18-7b51-4d16-94cc-2e3df49426b9&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 계층화된 상속 구조에서도 빌더 패턴을 잘 활용할 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;b2fc578c-3d4d-4528-a5b8-4db9848b7d6b&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;96ac6f38-5c3a-4acb-8f33-3e2621b69efb&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;p id=&quot;ebba3811-5530-4bdd-a391-54cc67245eb4&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 빌더 패턴에도 단점이 없는 것은 아닙니다. 아래와 같은 점을 생각할 수 있습니다.&lt;/p&gt;
&lt;ul id=&quot;613debc0-ce59-49c9-8754-4da5aa8307ff&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;객체를 생성하기 위해 빌더가 먼저 만들어져야합니다.
&lt;ul id=&quot;7133818d-2bf9-407e-a09a-a4f6074d3ace&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;빌더가 만들어지기 위한 생성 비용이 큰 것은 아니지만, 성능이 중요한 어플리케이션이라면 충분히 단점으로 작용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;760e138e-7901-4e96-84a5-8c78827777ca&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;빌더를 구현하기 위한 노력이 필요합니다.
&lt;ul id=&quot;5562dd18-466d-49f0-84c7-1c15e341f7f1&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: circle;&quot;&gt;위에서 살펴보았듯이, 생성자를 구현하는 것보다 빌더에 대한 별도 class 정의가 필요합니다. 명확하게 필드가 증가하는 방향으로 변경되지 않는다던지, 생성 시 파라미터의 양이 적은 경우에는 빌더를 선언하는 개발 비용이 더 클 수 있기 때문에 이를 고려해보아야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;03330f8d-9fe7-4133-9475-5a46345d28ac&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;e8bc6dfa-8bd2-4cf0-85cd-67be10f18932&quot; class=&quot;&quot; data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;p id=&quot;d2bf93e8-1792-4d93-b601-b4205cd50e85&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;빌더 패턴은 생성자를 사용할 때 발생할 수 있는 여러가지 문제점을 방지할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;ul id=&quot;f9dde99f-8d4c-4ccc-a13f-74f6f099b145&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;Java 에서 지원되지 않는 명명된 선택적 파라미터의 장점을 가질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;a13553a9-d03a-4146-b220-903f59332c9a&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;객체를 일관성있게 유지할 수 있도록 돕습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;2efee35d-60f3-45ef-9b29-bf87575631fa&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;매개변수의 입력 시점을 다르게 정하면서도, 객체의 불변성을 유지할 수 있도록 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;3a158637-9756-430d-8d5e-36d96b509c1e&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;계층적인 클래스에 대해서 사용하기 편리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p id=&quot;95a1c09c-b596-4bca-84a5-a4bdc6287ec9&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;a7063e4b-dbb6-4841-ba99-5430ba14fb16&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;자바의 생성자를 통해서 생성하면 위에서 설명한 문제들이 있었다면, 빌더 패턴을 사용하여 그런 단점들을 개선해나가보는 것도 좋을 것 같습니다. &lt;a href=&quot;https://github.com/le0park/java-builder-example&quot;&gt;계층적 클래스에서의 빌더 패턴 구현 예제는 업로드해두었으므로 참고해주세요. &lt;/a&gt;&lt;/p&gt;
&lt;p id=&quot;c3137ad0-bf45-460b-a290-9d0f550d6ce0&quot; class=&quot;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;83996568-015d-4296-9b25-00b8c0db9e9d&quot; class=&quot;&quot; data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&gt;
&lt;ul id=&quot;cfa581c2-34f8-4dc0-93cd-cd7880750302&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;a href=&quot;https://github.com/le0park/java-builder-example&quot;&gt;계층적 클래스 빌더 패턴 예제 코드&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;e7cb827d-7781-44eb-982c-5608acda722e&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/65551284&quot;&gt;이펙티브 자바3/E, p14, p105&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul id=&quot;e5294918-b516-46a3-9f49-1ebda3f0d1e3&quot; class=&quot;bulleted-list&quot; style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: disc;&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/7354740/is-there-a-way-to-refer-to-the-current-type-with-a-type-variable&quot;&gt;&lt;b&gt;Is there a way to refer to the current type with a type variable?&lt;/b&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p class=&quot;&quot; style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;028694f8-614f-4e6c-bd49-bbb6513db504&quot; class=&quot;&quot; style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;a style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot; href=&quot;https://www.buymeacoffee.com/leopark95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;img style=&quot;height: 60px !important; width: 217px !important;&quot; src=&quot;https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png&quot; alt=&quot;Buy Me A Coffee&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/article&gt;</description>
      <category>Java</category>
      <category>Design</category>
      <category>java</category>
      <category>OOP</category>
      <category>객체</category>
      <category>빌더</category>
      <category>상속</category>
      <category>설계</category>
      <category>자바</category>
      <category>패턴</category>
      <author>제이왑</author>
      <guid isPermaLink="true">https://jypnote.tistory.com/8</guid>
      <comments>https://jypnote.tistory.com/8#entry8comment</comments>
      <pubDate>Sat, 18 Mar 2023 16:50:52 +0900</pubDate>
    </item>
  </channel>
</rss>