<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>명월 일지</title>
    <link>https://nowonbun.tistory.com/</link>
    <description>개발은 언제나 즐겁다._*)~_</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 16:02:59 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>v명월v</managingEditor>
    <image>
      <title>명월 일지</title>
      <url>https://tistory1.daumcdn.net/tistory/1041549/attach/98c4d66b4aa24934a4439e3645f86024</url>
      <link>https://nowonbun.tistory.com</link>
    </image>
    <item>
      <title>AI Agent의 MD 파일 정리</title>
      <link>https://nowonbun.tistory.com/761</link>
      <description>&lt;h2 data-end=&quot;193&quot; data-start=&quot;157&quot; data-ke-size=&quot;size26&quot;&gt;  1️⃣ System / Rules 계열 (최상위 규칙)&lt;/h2&gt;
&lt;h3 data-end=&quot;236&quot; data-start=&quot;194&quot; data-ke-size=&quot;size23&quot;&gt;  system.md, rules.md, agent.md&lt;/h3&gt;
&lt;p data-end=&quot;252&quot; data-start=&quot;237&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정체성 + 절대 규칙&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;265&quot; data-start=&quot;254&quot; data-ke-size=&quot;size16&quot;&gt;보통 들어가는 내용:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;325&quot; data-start=&quot;266&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;280&quot; data-start=&quot;266&quot;&gt;너는 무엇인가 (역할)&lt;/li&gt;
&lt;li data-end=&quot;299&quot; data-start=&quot;281&quot;&gt;해도 되는 것 / 안 되는 것&lt;/li&gt;
&lt;li data-end=&quot;306&quot; data-start=&quot;300&quot;&gt;우선순위&lt;/li&gt;
&lt;li data-end=&quot;314&quot; data-start=&quot;307&quot;&gt;안전 규칙&lt;/li&gt;
&lt;li data-end=&quot;325&quot; data-start=&quot;315&quot;&gt;출력 포맷 강제&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;329&quot; data-start=&quot;327&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 너는 DB MCP 에이전트다 &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; SQL은 SELECT만 허용 &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; DROP, DELETE 절대 금지 &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;430&quot; data-start=&quot;398&quot; data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;모든 판단의 기준&lt;/b&gt;&lt;br /&gt;  인간으로 치면 &amp;ldquo;헌법&amp;rdquo;&lt;/p&gt;
&lt;hr data-end=&quot;435&quot; data-start=&quot;432&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;463&quot; data-start=&quot;437&quot; data-ke-size=&quot;size26&quot;&gt;⚙️ 2️⃣ Tool / MCP 사용 규칙&lt;/h2&gt;
&lt;h3 data-end=&quot;510&quot; data-start=&quot;464&quot; data-ke-size=&quot;size23&quot;&gt;  tools.md, mcp.md, capabilities.md&lt;/h3&gt;
&lt;p data-end=&quot;521&quot; data-start=&quot;511&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;도구 설명서&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;571&quot; data-start=&quot;523&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;536&quot; data-start=&quot;523&quot;&gt;어떤 MCP가 있는지&lt;/li&gt;
&lt;li data-end=&quot;553&quot; data-start=&quot;537&quot;&gt;언제 어떤 MCP를 쓰는지&lt;/li&gt;
&lt;li data-end=&quot;563&quot; data-start=&quot;554&quot;&gt;파라미터 규칙&lt;/li&gt;
&lt;li data-end=&quot;571&quot; data-start=&quot;564&quot;&gt;호출 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;575&quot; data-start=&quot;573&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;## db-mcp&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 용도: read-only query &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 입력: natural language &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 출력: json &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;683&quot; data-start=&quot;653&quot; data-ke-size=&quot;size16&quot;&gt;  Agent가 &lt;b&gt;도구를 &amp;lsquo;어떻게&amp;rsquo; 쓰는지&lt;/b&gt; 결정&lt;/p&gt;
&lt;hr data-end=&quot;688&quot; data-start=&quot;685&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;716&quot; data-start=&quot;690&quot; data-ke-size=&quot;size26&quot;&gt;  3️⃣ Workflow / 절차 정의&lt;/h2&gt;
&lt;h3 data-end=&quot;766&quot; data-start=&quot;717&quot; data-ke-size=&quot;size23&quot;&gt;  workflow.md, process.md, playbook.md&lt;/h3&gt;
&lt;p data-end=&quot;776&quot; data-start=&quot;767&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;행동 순서&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;780&quot; data-start=&quot;778&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;1.&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 요청 분석 &lt;/span&gt;&lt;span&gt;&lt;span&gt;2.&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 관련 테이블 추론 &lt;/span&gt;&lt;span&gt;&lt;span&gt;3.&lt;/span&gt;&lt;/span&gt;&lt;span&gt; SQL 생성 &lt;/span&gt;&lt;span&gt;&lt;span&gt;4.&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 결과 검증 &lt;/span&gt;&lt;span&gt;&lt;span&gt;5.&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 요약 출력 &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;901&quot; data-start=&quot;842&quot; data-ke-size=&quot;size16&quot;&gt;  Agent가 **생각 순서(Chain)**를 갖게 됨&lt;br /&gt;  MCP랑 결합하면 거의 &amp;ldquo;자동화 로봇&amp;rdquo;&lt;/p&gt;
&lt;hr data-end=&quot;906&quot; data-start=&quot;903&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;939&quot; data-start=&quot;908&quot; data-ke-size=&quot;size26&quot;&gt;  4️⃣ Task / Job 정의 (작업 단위)&lt;/h2&gt;
&lt;h3 data-end=&quot;983&quot; data-start=&quot;940&quot; data-ke-size=&quot;size23&quot;&gt;  tasks.md, jobs.md, missions.md&lt;/h3&gt;
&lt;p data-end=&quot;1001&quot; data-start=&quot;984&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;무슨 일을 하냐&amp;rdquo; 목록&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1005&quot; data-start=&quot;1003&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 월별 리포트 생성 &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 이상 거래 탐지 &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 로그 분석 &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1073&quot; data-start=&quot;1048&quot; data-ke-size=&quot;size16&quot;&gt;  에이전트가 &lt;b&gt;자기 역할 범위를 인식&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;1078&quot; data-start=&quot;1075&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1111&quot; data-start=&quot;1080&quot; data-ke-size=&quot;size26&quot;&gt; ️ 5️⃣ Project / Context 정보&lt;/h2&gt;
&lt;h3 data-end=&quot;1158&quot; data-start=&quot;1112&quot; data-ke-size=&quot;size23&quot;&gt;  context.md, project.md, domain.md&lt;/h3&gt;
&lt;p data-end=&quot;1167&quot; data-start=&quot;1159&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배경지식&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1203&quot; data-start=&quot;1169&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1176&quot; data-start=&quot;1169&quot;&gt;DB 구조&lt;/li&gt;
&lt;li data-end=&quot;1186&quot; data-start=&quot;1177&quot;&gt;비즈니스 용어&lt;/li&gt;
&lt;li data-end=&quot;1195&quot; data-start=&quot;1187&quot;&gt;도메인 규칙&lt;/li&gt;
&lt;li data-end=&quot;1203&quot; data-start=&quot;1196&quot;&gt;약어 설명&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1207&quot; data-start=&quot;1205&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; users = 고객 &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; orders = 거래 &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; status = 0:대기, 1:완료 &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1293&quot; data-start=&quot;1268&quot; data-ke-size=&quot;size16&quot;&gt;  이게 없으면 AI는 &lt;b&gt;추측으로 일함&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;1298&quot; data-start=&quot;1295&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1329&quot; data-start=&quot;1300&quot; data-ke-size=&quot;size26&quot;&gt;  6️⃣ Examples / Few-shot&lt;/h2&gt;
&lt;h3 data-end=&quot;1364&quot; data-start=&quot;1330&quot; data-ke-size=&quot;size23&quot;&gt;  examples.md, samples.md&lt;/h3&gt;
&lt;p data-end=&quot;1374&quot; data-start=&quot;1365&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정답 예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1378&quot; data-start=&quot;1376&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;Q: 이번 달 매출? A: SELECT SUM(amount) ... &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1463&quot; data-start=&quot;1428&quot; data-ke-size=&quot;size16&quot;&gt;  행동 정확도 &lt;b&gt;급상승&lt;/b&gt;&lt;br /&gt;  가장 강력한 md 중 하나&lt;/p&gt;
&lt;hr data-end=&quot;1468&quot; data-start=&quot;1465&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1504&quot; data-start=&quot;1470&quot; data-ke-size=&quot;size26&quot;&gt;  7️⃣ Constraints / Guardrails&lt;/h2&gt;
&lt;h3 data-end=&quot;1541&quot; data-start=&quot;1505&quot; data-ke-size=&quot;size23&quot;&gt;  constraints.md, safety.md&lt;/h3&gt;
&lt;p data-end=&quot;1557&quot; data-start=&quot;1542&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;금지 사항 전용 파일&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1578&quot; data-start=&quot;1559&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1563&quot; data-start=&quot;1559&quot;&gt;보안&lt;/li&gt;
&lt;li data-end=&quot;1570&quot; data-start=&quot;1564&quot;&gt;개인정보&lt;/li&gt;
&lt;li data-end=&quot;1578&quot; data-start=&quot;1571&quot;&gt;위험 행위&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1582&quot; data-start=&quot;1580&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 주민번호 조회 금지 &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; 1000건 초과 조회 금지 &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1647&quot; data-start=&quot;1624&quot; data-ke-size=&quot;size16&quot;&gt;  DB / 사내 시스템에선 &lt;b&gt;필수&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;1652&quot; data-start=&quot;1649&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1682&quot; data-start=&quot;1654&quot; data-ke-size=&quot;size26&quot;&gt;  8️⃣ Output / Format 규칙&lt;/h2&gt;
&lt;h3 data-end=&quot;1716&quot; data-start=&quot;1683&quot; data-ke-size=&quot;size23&quot;&gt;  output.md, response.md&lt;/h3&gt;
&lt;p data-end=&quot;1726&quot; data-start=&quot;1717&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출력 강제&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1730&quot; data-start=&quot;1728&quot; data-ke-size=&quot;size16&quot;&gt;예:&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; JSON으로만 응답 &lt;/span&gt;&lt;span&gt;&lt;span&gt;-&lt;/span&gt;&lt;/span&gt;&lt;span&gt; key는 camelCase &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1794&quot; data-start=&quot;1772&quot; data-ke-size=&quot;size16&quot;&gt;  후처리 / 자동화랑 연결할 때 핵심&lt;/p&gt;
&lt;hr data-end=&quot;1799&quot; data-start=&quot;1796&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1822&quot; data-start=&quot;1801&quot; data-ke-size=&quot;size26&quot;&gt;  구조적으로 보면 이렇게 나뉨&lt;/h2&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;[정체성] system.md [능력] tools.md mcp.md [행동] workflow.md tasks.md [지식] context.md domain.md [정확도] examples.md [안전] constraints.md [출력] output.md&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;</description>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/761</guid>
      <comments>https://nowonbun.tistory.com/761#entry761comment</comments>
      <pubDate>Wed, 28 Jan 2026 01:16:54 +0900</pubDate>
    </item>
    <item>
      <title>다시 시작하는 블로그</title>
      <link>https://nowonbun.tistory.com/760</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요,&amp;nbsp;명월입니다..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;오랜만에 다시 블로그에 찾아왔습니다. 잘들 계셨나요??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 여러 가지 일도 있고 바빠서 글 못 올리다가 이제야 이렇게 올리게 되었네요.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;friends1&quot; data-emoticon-name=&quot;001&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/001.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/friends1/large/001.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 지금까지 프로그래밍 관련 주제로 글을 작성해 왔는데 ChatGPT의 등장으로 &amp;nbsp;이런 글들이 예전보다 많이 무의미해져서 블로그에 글을 올리는 것도 뜸해졌네요.. &lt;br /&gt;&lt;br /&gt;제가 블로그를 시작한 지는 15년정도가 지났는데, 처음 블로그를 한 이유는 대학생때 프로그래밍 공부와 취업 준비를 위해서였습니다. 그리고 시간에 지나면서 여러 가지 논란(?)과 공격을 받으면서 라이브러리 설명이나 언어 문법 위주의 글로 변하게 되었네요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그러다 보니 최근에 작성한 포스트 내용은 개발하면서 자주 검색한 내용이나 실제 개발 과정에서 생겨난 문제들을 정리하여 공유해 왔습니다. 그런데 요즘에는 ChatGPT에 물어보면 그냥 다 나오네요...&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;예전에는 Linux에 레드마인 설치하는 것만 해도 하루 종일 검색하고 문제가 발생하면 찾는데.. 하루는 꼬박 걸렸는데...옆에 ChatGPT 켜놓고 문제가 생길 때마다 물어보면 그냥 바로 해결책을 찾아주네요.. 이런 상황에 제가 이런 문제 해결한 것을 포스팅해 봐야.. 그냥 ChatGPT 사용 후기처럼 보이겠네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;br /&gt;&lt;br /&gt;여러분들도 다들 힘든 시기를 겪고 있겠지만.. 개발 업계도 많이 상당한 힘든 시기를 겪고 있잖아요.... 정리 해고도 많이 있고..또&amp;nbsp;나간&amp;nbsp;사람의&amp;nbsp;일을&amp;nbsp;받고&amp;nbsp;새로운&amp;nbsp;기획자,&amp;nbsp;운영자들과&amp;nbsp;일을&amp;nbsp;하면서&amp;nbsp;저도&amp;nbsp;크고&amp;nbsp;작은&amp;nbsp;충돌이&amp;nbsp;많이&amp;nbsp;있네요..... &lt;br /&gt;&lt;br /&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;br /&gt;&lt;br /&gt;처음에는 블로그 새로 만들까라는 생각도 했었는데.. 그래도 기존 블로그에 하는 것도 나쁘지 않을까 싶어서 여기서 작성을 시작합니다.&lt;br /&gt;어느 정도 자리 잡고 주제가 바뀌게 되면 이전 포스트도 하나하나 정리를 할까라는 생각도 드네요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;지금은 뭐 이것저것 생각하기보단.. 어쨋든 다시 포스팅을 할 수 있게 노력을 해볼 생각입니다..&lt;br /&gt;앞으로도&amp;nbsp;잘&amp;nbsp;부탁드립니다.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;face&quot; data-emoticon-name=&quot;038&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/038.png&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/face/large/038.png&quot; width=&quot;80&quot; /&gt;&lt;/figure&gt;</description>
      <category>Development diary</category>
      <category>새로운 시작</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/760</guid>
      <comments>https://nowonbun.tistory.com/760#entry760comment</comments>
      <pubDate>Wed, 10 Apr 2024 18:56:40 +0900</pubDate>
    </item>
    <item>
      <title>[자료 구조] Hashmap 작성하기</title>
      <link>https://nowonbun.tistory.com/759</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Hashmap 자료구조에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;제가 자료구조와 알고리즘을 해야지해야지 하면서 생각하면서 처음 올리는 글이 되었네요.. 사실 프로그램에서 자료구조와 알고리즘은 굉장히 중요하지만 웬만한 언어에서는 이미 구현이 되어있기 때문에 잘 신경을 쓰지 않는 부분이기도 하네요. 자료구조와 알고리즘은 사실 보통 Stack부터 Queue, Cache 순으로 가는 게 맞습니다만.. 우연히 김포프님 동영상 보다가 Hashtable 알고리즘 못 짜면 개발자도 아니다라고 약간 어그로 식으로 이야기하시길래 처음엔 후훗하면서 그 정도쯤이야 생각하다가 막상 하려니깐 어엇이라고 생각이 되어서 아, 이거 정리해 놔야겠다 싶어서 글을 작성했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저, Hashmap에 대해서 설명을 해야겠네요.&lt;/p&gt;
&lt;p&gt;가장 많이 쓰는 자료구조와 알고리즘에서 배열(Array)과 연결리스트(LinkedList)가 있습니다.&lt;/p&gt;
&lt;p style='text-align:left;'&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;685&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqN6Pa/btrTbuhoISu/SohK1Ab7NOr5y5SI5fW4m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqN6Pa/btrTbuhoISu/SohK1Ab7NOr5y5SI5fW4m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqN6Pa/btrTbuhoISu/SohK1Ab7NOr5y5SI5fW4m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqN6Pa%2FbtrTbuhoISu%2FSohK1Ab7NOr5y5SI5fW4m0%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;685&quot; height=&quot;153&quot; data-origin-width=&quot;685&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 그림에서 위는 배열로 데이터가 들어가 있는 것이고 아래는 연결리스트로 데이터를 넣는 것입니다.&lt;/p&gt;
&lt;p&gt;여기서 우리가 앞에서 3번째의 데이터를 가져올때는 배열[2] 이런 식으로 가져올 것입니다. 그리고 연결리스트는 포인터의 탐색으로 두번 이동한 다음에 취득하겠네요.&lt;/p&gt;
&lt;p&gt;그런데 여기서 우리는 값을 취득할 때 숫자가 아닌 키를 통해 취득하고 싶습니다.&lt;/p&gt;
&lt;p style='text-align:left;'&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;151&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWAhX3/btrTfTth3Ah/keVkOVxfd5NmTFKP0blMJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWAhX3/btrTfTth3Ah/keVkOVxfd5NmTFKP0blMJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWAhX3/btrTfTth3Ah/keVkOVxfd5NmTFKP0blMJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWAhX3%2FbtrTfTth3Ah%2FkeVkOVxfd5NmTFKP0blMJ1%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;679&quot; height=&quot;151&quot; data-origin-width=&quot;679&quot; data-origin-height=&quot;151&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;즉, 배열[A] 이렇게 가져오고 싶은 것입니다. 그런데 배열은 불가능하죠. 배열은 위치를 숫자로 표현할 수 밖에 없으니깐.&lt;/p&gt;
&lt;p&gt;그러나 연결리스트는 가능합니다. 즉, 연결리스트는 포인터로 줄줄히 연결이 되어있고 탐색을 하면서 Key라는 키값을 넣어서 리스트-&amp;gt;get(&quot;A&quot;) 이런 식으로 가져올 수 있겠죠.&lt;/p&gt;
&lt;p&gt;그런데 문제가 무엇일까요? 만약 연결리스트에 값이 1000개 10000개라고 했을 때, 키와 같은 값을 찾으려면 포인터 탐색을 엄청나게 해야겠네요. 즉 가능은 한데.. 엄청나게 느려지겠네요..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 배열의 탐색 속도와 연결리스트의 Key 탐색 기능을 합쳐서 만들 수 있는 방법은 없을까 했을 때, 그것을 합친 기능이 Hashmap, Hashtable입니다.&lt;/p&gt;
&lt;p style='text-align:left;'&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjkpXE/btrTcFQkTVc/hUCXiH2LHtFsZApqNQPfQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjkpXE/btrTcFQkTVc/hUCXiH2LHtFsZApqNQPfQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjkpXE/btrTcFQkTVc/hUCXiH2LHtFsZApqNQPfQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjkpXE%2FbtrTcFQkTVc%2FhUCXiH2LHtFsZApqNQPfQ0%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;375&quot; height=&quot;439&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;즉, 내가 Y라는 Key라는 데이터를 가져오려면 5번째 배열에서 연결리스트로 탐색을 하게 되면. 일반 연결리스트라면 25번의 탐색을 해야하는 것을 4번의 탐색으로 가져오는 것입니다.&lt;/p&gt;
&lt;p&gt;그렇다면 Key로 가져오는건 아는데 배열의 순서는 어떻게 정하는 것일까라고 생각할 때 이게 바로 hash입니다.&lt;/p&gt;
&lt;p&gt;hash라는 것은 프로그램 내부의에서 규칙을 정해서 Object나 String 값을 특정 수로 변환하는 것입니다. 위 그림이라면 대문자 A가 아스키코드로 97를 Key&amp;gt;=97 &amp;&amp; Key&amp;lt;=100의 경우라면 0, Key&amp;gt;=101 &amp;&amp; Key&amp;lt;=104는 1 이런 식으로 설정한 것이겠네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다시 정리하면 Hashmap이란 우리가 키를 통해서 데이터를 가져오게 할 수 있는 방식. 그래서 연결리스트를 응용했지만 연결리스트의 탐색 기능이 너무 느리다 보니 배열(Array)의 탐색을 넣어서 속도를 비약적으로 향상시킨 자료구조라고 할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;c++의 hashmap 자료구조&quot;&gt;#include &amp;lt;iostream&amp;gt;
// 해쉬맵의 배열 개수
#define TABLE_SIZE 10
using namespace std;
// hash 구조체 key가 int형인데.. hashmap의 배열의 최대 개수별로 나누어 나머지로 처리한다.
// 즉, 키가 0이라면 0번째 배열의 연결리스트, 10이라면 0번째 배열의 연결리스트, 11이라면 1번째 배열의 연결리스트
struct KeyHash {
  // 연산자 함수형식의 재정의
  unsigned long operator()(const int key) const
  {
    // key를 10으로 나눈 나머지
    return key % TABLE_SIZE;
  }
};
// 템플릿 설정
template &amp;lt;typename K, typename V, typename F = KeyHash&amp;gt;
class HashMap { // HashMap 클래스
private:
  // 연결리스트를 위한 Node 클래스
  class Node {
  public:
    // Key 값
    K key;
    // Value 값
    V value;
    // 연결리스트의 포인터
    Node* next;
    // 생성자 처리
    Node(const K&amp; key, const V&amp; value) :
      key(key), value(value), next(NULL) {
    }
  };
  // 해쉬맵 배열
  Node** table;
  // hash 함수
  F hashFunc;
public:
  // 생성자
  HashMap() {
    // 배열 선언
    table = new Node * [TABLE_SIZE]();
  }
  // 소멸자
  ~HashMap() {
    // 배열 안의 연결리스트를 반복문으로 취득
    for (int i = 0; i &amp;lt; TABLE_SIZE; ++i) {
      // 연결 리스트 취득
      Node* entry = table[i];
      // null이 아니면
      while (entry != NULL) {
        // 연결리스트 가져오기
        Node* prev = entry;
        // 다음 연결리스트로 연결
        entry = entry-&amp;gt;next;
        // 메모리 해제
        delete prev;
      }
      // 해당 배열을 제거
      table[i] = NULL;
    }
    // 배열 메모리 해제
    delete[] table;
  }
  // key를 통해 데이터 취득
  V get(const K&amp; key) {
    // 해쉬 함수를 통해 해쉬값 취득
    unsigned long hashValue = hashFunc(key);
    // 해쉬값을 통해서 연결리스트 취득
    Node* entry = table[hashValue];
    // 연결리스트 NULL이 발생할때까지
    while (entry != NULL) {
      // key가 같다면
      if (entry-&amp;gt;key == key) {
        // 값을 리턴 
        return entry-&amp;gt;value;
      }
      // key가 다르다면 다음 연결리스트로 연결
      entry = entry-&amp;gt;next;
    }
  }
  // key를 통해 데이터를 입력
  void put(const K&amp; key, const V&amp; value) {
    // 해쉬 함수를 통해 해쉬값 취득
    unsigned long hashValue = hashFunc(key);
    // 포인터 변수 생성
    Node* prev = NULL;
    // 배열에서 연결리스트를 취득
    Node* entry = table[hashValue];
    // 연결리스트로 같은 키를 찾던가 끝까지 가던가
    while (entry != NULL &amp;&amp; entry-&amp;gt;key != key) {
      // 전 포인터 설정
      prev = entry;
      // 포인터 이동
      entry = entry-&amp;gt;next;
    }
    // 연결리스트 끝이라면..
    if (entry == NULL) {
      // Node 클래스 생성
      entry = new Node(key, value);
      // 이전 포인터가 없으면
      if (prev == NULL) {
        // 배열의 가장 앞부분
        table[hashValue] = entry;
      }
      else {
        // 연결리스트 연결하기
        prev-&amp;gt;next = entry;
      }
    }
    // 같은 키라면
    else {
      // 값을 넣기
      entry-&amp;gt;value = value;
    }
  }
  // key를 통해 데이터를 삭제
  void remove(const K&amp; key) {
    // 해쉬 함수를 통해 해쉬값 취득
    unsigned long hashValue = hashFunc(key);
    // 포인터 변수 생성
    Node* prev = NULL;
    // 배열에서 연결리스트를 취득
    Node* entry = table[hashValue];
    // 연결리스트로 같은 키를 찾던가 끝까지 가던가
    while (entry != NULL &amp;&amp; entry-&amp;gt;key != key) {
      // 전 포인터 설정
      prev = entry;
      // 포인터 이동
      entry = entry-&amp;gt;next;
    }
    // 연결리스트 끝이라면..
    if (entry == NULL) {
      // 삭제할 것이 없네...
      return;
    }
    else {
      // 가장 앞의 포인터라면
      if (prev == NULL) {
        // 연결리스트의 맨 앞을 연결
        table[hashValue] = entry-&amp;gt;next;
      }
      else {
        // 연결리스트에서 앞과 다음 포인터를 연결
        prev-&amp;gt;next = entry-&amp;gt;next;
      }
      // 메모리 해제
      delete entry;
    }
  }
};
// String hash코드
struct StringKeyHash {
  // 연산자 함수형식의 재정의
  unsigned long operator()(const string&amp; key) const
  {
    // 변수 초기
    int hashValue = -1;
    int tmp = 0;
    // String를 char로 분해해서 아스키코드를 다 더함
    for (char c : key) {
      tmp += (int)c;
    }
    // 해쉬값 만들기
    hashValue = tmp % TABLE_SIZE;
    // 리턴
    return hashValue;
  }
};
// 실행 함수
int main()
{
  // key가 int형의 hashmap
  HashMap&amp;lt;int, string&amp;gt; hashmap1;
  // key가 string형의 hashmap
  HashMap&amp;lt;string, int, StringKeyHash&amp;gt; hashmap2;

  // 키가 int형식의 값 넣기
  hashmap1.put(1, &quot;abc&quot;);
  hashmap1.put(2, &quot;bcd&quot;);
  hashmap1.put(3, &quot;ttt&quot;);
  hashmap1.put(4, &quot;aaa&quot;);
  // 키가 string형식의 값 넣기
  hashmap2.put(&quot;abc&quot;, 1);
  hashmap2.put(&quot;bcd&quot;, 2);
  hashmap2.put(&quot;ttt&quot;, 3);
  hashmap2.put(&quot;aaa&quot;, 4);

  // 키가 int형식의 값 출력
  cout &amp;lt;&amp;lt; hashmap1.get(1) &amp;lt;&amp;lt; endl;
  cout &amp;lt;&amp;lt; hashmap1.get(2) &amp;lt;&amp;lt; endl;
  cout &amp;lt;&amp;lt; hashmap1.get(3) &amp;lt;&amp;lt; endl;
  cout &amp;lt;&amp;lt; hashmap1.get(4) &amp;lt;&amp;lt; endl;
  // 키가 string형식의 값 출력
  cout &amp;lt;&amp;lt; hashmap2.get(&quot;abc&quot;) &amp;lt;&amp;lt; endl;
  cout &amp;lt;&amp;lt; hashmap2.get(&quot;bcd&quot;) &amp;lt;&amp;lt; endl;
  cout &amp;lt;&amp;lt; hashmap2.get(&quot;ttt&quot;) &amp;lt;&amp;lt; endl;
  cout &amp;lt;&amp;lt; hashmap2.get(&quot;aaa&quot;) &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style='text-align:left;'&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;133&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8lN6W/btrTfUZ3DlC/LBAWeclSGJGUSJdBgYSKr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8lN6W/btrTfUZ3DlC/LBAWeclSGJGUSJdBgYSKr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8lN6W/btrTfUZ3DlC/LBAWeclSGJGUSJdBgYSKr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8lN6W%2FbtrTfUZ3DlC%2FLBAWeclSGJGUSJdBgYSKr1%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;283&quot; height=&quot;133&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;133&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Java의 경우는 클래스 형식의 데이터는 hashCode() 함수를 제공합니다. hashCode함수는 클래스가 Stack - heap 구조의 경우이기 때문에 메모리 주소가 반드시 있고 그것을 int형으로 리턴한 것입니다. (String은 hashCode를 재정의 했기 때문에 문자의 값을 hashCode로 출력합니다.)&lt;/p&gt;
&lt;p style='text-align:left;'&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btI1RT/btrTbUG5RPt/uWUaavsEZGAcr7F2XCwn6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btI1RT/btrTbUG5RPt/uWUaavsEZGAcr7F2XCwn6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btI1RT/btrTbUG5RPt/uWUaavsEZGAcr7F2XCwn6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtI1RT%2FbtrTbUG5RPt%2FuWUaavsEZGAcr7F2XCwn6K%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;612&quot; height=&quot;408&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;java의 hashmap 자료구조&quot;&gt;// 해쉬 맵 클래스
class HashMap&amp;lt;K, V&amp;gt; {
  // 연결리스트의 노드 인라인 클래스
  class Node&amp;lt;K, V&amp;gt; {
    // key 값
    public K key;
    // value 값
    public V value;
    // 연결리스트 다음 포인터
    public Node&amp;lt;K, V&amp;gt; next = null;

    // 생성자
    public Node(K key, V value) {
      this.key = key;
      this.value = value;
    }
  }

  // hashmap의 배열 크기
  private final int TABLE_SIZE = 10;
  // hashmap의 배열
  private final Node&amp;lt;K, V&amp;gt;[] table;

  // 생성자
  public HashMap() {
    // hashmap의 배열 인스턴스 생성
    this.table = new Node[TABLE_SIZE];
  }

  // key로 value값 취득 함수
  public V get(K key) {
    // hashCode를 통해 hash값 변환
    int hash = key.hashCode() % TABLE_SIZE;
    // 연결리스트 취득
    Node&amp;lt;K, V&amp;gt; entry = table[hash];
    // 연결리스트 끝까지
    while (entry != null) {
      // key 값이 같으면
      if (entry.key.equals(key)) {
        // value 리턴
        return entry.value;
      }
      // 다음 연결리스트로
      entry = entry.next;
    }
    // 없으면 null
    return null;
  }

  // key를 통해 데이터를 입력
  public void put(K key, V value) {
    // hashCode를 통해 hash값 변환
    int hash = key.hashCode() % TABLE_SIZE;
    // 포인터 변수 생성
    Node&amp;lt;K, V&amp;gt; prev = null;
    // 배열에서 연결리스트를 취득
    Node&amp;lt;K, V&amp;gt; entry = table[hash];
    // 연결리스트로 같은 키를 찾던가 끝까지 가던가
    while (entry != null &amp;&amp; !entry.key.equals(key)) {
      // 전 포인터 설정
      prev = entry;
      // 포인터 이동
      entry = entry.next;
    }
    // 연결리스트 끝이라면..
    if (entry == null) {
      // 인스턴스 생성
      entry = new Node&amp;lt;K, V&amp;gt;(key, value);
      // 이전이 없다면
      if (prev == null) {
        // hashmap의 배열에 연결
        table[hash] = entry;
      } else {
        // 연결리스트 다음에 연결
        prev.next = entry;
      }
    } else {
      // Node의 값 변경
      entry.value = value;
    }
  }

  // key를 통해 데이터를 삭제
  public void remove(K key) {
    // hashCode를 통해 hash값 변환
    int hash = key.hashCode() % TABLE_SIZE;
    // 포인터 변수 생성
    Node&amp;lt;K, V&amp;gt; prev = null;
    // 배열에서 연결리스트를 취득
    Node&amp;lt;K, V&amp;gt; entry = table[hash];
    // 연결리스트로 같은 키를 찾던가 끝까지 가던가
    while (entry != null &amp;&amp; !entry.key.equals(key)) {
      // 전 포인터 설정
      prev = entry;
      // 포인터 이동
      entry = entry.next;
    }
    // 연결리스트 끝이라면..
    if (entry == null) {
      // 값이 없네?
      return;
    } else {
      // 연결리스트의 가장 맨 앞이라면
      if (prev == null) {
        // 배열의 앞부분을 재연결
        table[hash] = entry.next;
      } else {
        // 앞의 연결리스트와 다음 연결리스트를 연결
        prev.next = entry.next;
      }
    }
  }
}

// 실행 클래스
public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // key가 int형의 hashmap
    HashMap&amp;lt;Integer, String&amp;gt; hashmap1 = new HashMap&amp;lt;&amp;gt;();
    // key가 string형의 hashmap
    HashMap&amp;lt;String, Integer&amp;gt; hashmap2 = new HashMap&amp;lt;&amp;gt;();

    // 키가 int형식의 값 넣기
    hashmap1.put(1, &quot;abc&quot;);
    hashmap1.put(2, &quot;bcd&quot;);
    hashmap1.put(3, &quot;ttt&quot;);
    hashmap1.put(4, &quot;aaa&quot;);
    // 키가 string형식의 값 넣기
    hashmap2.put(&quot;abc&quot;, 1);
    hashmap2.put(&quot;bcd&quot;, 2);
    hashmap2.put(&quot;ttt&quot;, 3);
    hashmap2.put(&quot;aaa&quot;, 4);

    // 키가 int형식의 값 출력
    System.out.println(hashmap1.get(1));
    System.out.println(hashmap1.get(2));
    System.out.println(hashmap1.get(3));
    System.out.println(hashmap1.get(4));

    // 키가 string형식의 값 출력
    System.out.println(hashmap2.get(&quot;abc&quot;));
    System.out.println(hashmap2.get(&quot;bcd&quot;));
    System.out.println(hashmap2.get(&quot;ttt&quot;));
    System.out.println(hashmap2.get(&quot;aaa&quot;));
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style='text-align:left;'&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;333&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/efdFFV/btrTbjG7Yff/Mh6FRkVFCkVrRIVYW7MleK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efdFFV/btrTbjG7Yff/Mh6FRkVFCkVrRIVYW7MleK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efdFFV/btrTbjG7Yff/Mh6FRkVFCkVrRIVYW7MleK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefdFFV%2FbtrTbjG7Yff%2FMh6FRkVFCkVrRIVYW7MleK%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;333&quot; height=&quot;148&quot; data-origin-width=&quot;333&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Java와 C#은 구조가 비슷하기 때문에 비슷한 코드가 나오지 않을까 싶네요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C#의 hashmap 자료구조&quot;&gt;using System;
using System.Globalization;

namespace Blog
{
  // 해쉬 맵 클래스
  class HashMap&amp;lt;K, V&amp;gt;
  {
    // 연결리스트의 노드 인라인 클래스
    class Node
    {
      // key 값
      public K key;
      // value 값
      public V value;
      // 연결리스트 다음 포인터
      public Node next = null;
      // 생성자
      public Node(K key, V value)
      {
        this.key = key;
        this.value = value;
      }
    }
    // hashmap의 배열 크기
    private const int TABLE_SIZE = 10;
    // hashmap의 배열
    private Node[] table;
    // 생성자
    public HashMap()
    {
      // hashmap의 배열 인스턴스 생성
      this.table = new Node[TABLE_SIZE];
    }
    // key로 value값 취득 함수
    public V get(K key)
    {
      // hashCode를 통해 hash값 변환
      int hash = Math.Abs(key.GetHashCode() % TABLE_SIZE);
      // 연결리스트 취득
      Node entry = table[hash];
      // 연결리스트 끝까지
      while (entry != null)
      {
        // key 값이 같으면
        if (entry.key.Equals(key))
        {
          // value 리턴
          return entry.value;
        }
        // 다음 연결리스트로
        entry = entry.next;
      }
      // 없으면 null
      return default(V);
    }
    // key를 통해 데이터를 입력
    public void put(K key, V value)
    {
      // hashCode를 통해 hash값 변환
      int hash = Math.Abs(key.GetHashCode() % TABLE_SIZE);
      // 포인터 변수 생성
      Node prev = null;
      // 배열에서 연결리스트를 취득
      Node entry = table[hash];
      // 연결리스트로 같은 키를 찾던가 끝까지 가던가
      while (entry != null &amp;&amp; !entry.key.Equals(key))
      {
        // 전 포인터 설정
        prev = entry;
        // 포인터 이동
        entry = entry.next;
      }
      // 연결리스트 끝이라면..
      if (entry == null)
      {
        // 인스턴스 생성
        entry = new Node(key, value);
        // 이전이 없다면
        if (prev == null)
        {
          // hashmap의 배열에 연결
          table[hash] = entry;
        }
        else
        {
          // 연결리스트 다음에 연결
          prev.next = entry;
        }
      }
      else
      {
        // Node의 값 변경
        entry.value = value;
      }
    }
    // key를 통해 데이터를 삭제
    public void remove(K key)
    {
      // hashCode를 통해 hash값 변환
      int hash = Math.Abs(key.GetHashCode() % TABLE_SIZE);
      // 포인터 변수 생성
      Node prev = null;
      // 배열에서 연결리스트를 취득
      Node entry = table[hash];
      // 연결리스트로 같은 키를 찾던가 끝까지 가던가
      while (entry != null &amp;&amp; !entry.key.Equals(key))
      {
        // 전 포인터 설정
        prev = entry;
        // 포인터 이동
        entry = entry.next;
      }
      // 연결리스트 끝이라면..
      if (entry == null)
      {
        // 값이 없네?
        return;
      }
      else
      {
        // 연결리스트의 가장 맨 앞이라면
        if (prev == null)
        {
          // 배열의 앞부분을 재연결
          table[hash] = entry.next;
        }
        else
        {
          // 앞의 연결리스트와 다음 연결리스트를 연결
          prev.next = entry.next;
        }
      }
    }
  }
  internal class Program
  {
    static void Main(string[] args)
    {
      // key가 int형의 hashmap
      HashMap&amp;lt;int, String&amp;gt; hashmap1 = new HashMap&amp;lt;int, String&amp;gt;();
      // key가 string형의 hashmap
      HashMap&amp;lt;String, int&amp;gt; hashmap2 = new HashMap&amp;lt;String, int&amp;gt;();

      // 키가 int형식의 값 넣기
      hashmap1.put(1, &quot;abc&quot;);
      hashmap1.put(2, &quot;bcd&quot;);
      hashmap1.put(3, &quot;ttt&quot;);
      hashmap1.put(4, &quot;aaa&quot;);
      // 키가 string형식의 값 넣기
      hashmap2.put(&quot;abc&quot;, 1);
      hashmap2.put(&quot;bcd&quot;, 2);
      hashmap2.put(&quot;ttt&quot;, 3);
      hashmap2.put(&quot;aaa&quot;, 4);

      // 키가 int형식의 값 출력
      Console.WriteLine(hashmap1.get(1));
      Console.WriteLine(hashmap1.get(2));
      Console.WriteLine(hashmap1.get(3));
      Console.WriteLine(hashmap1.get(4));

      // 키가 string형식의 값 출력
      Console.WriteLine(hashmap2.get(&quot;abc&quot;));
      Console.WriteLine(hashmap2.get(&quot;bcd&quot;));
      Console.WriteLine(hashmap2.get(&quot;ttt&quot;));
      Console.WriteLine(hashmap2.get(&quot;aaa&quot;));

      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadKey();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style='text-align:left;'&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;154&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zm6SO/btrTe5VcAqY/JaLCNgMTZHqlMnWEXocKkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zm6SO/btrTe5VcAqY/JaLCNgMTZHqlMnWEXocKkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zm6SO/btrTe5VcAqY/JaLCNgMTZHqlMnWEXocKkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzm6SO%2FbtrTe5VcAqY%2FJaLCNgMTZHqlMnWEXocKkk%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;302&quot; height=&quot;154&quot; data-origin-width=&quot;302&quot; data-origin-height=&quot;154&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;여기까지 Hashmap 자료구조에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Algorithm</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/759</guid>
      <comments>https://nowonbun.tistory.com/759#entry759comment</comments>
      <pubDate>Fri, 9 Dec 2022 14:20:23 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 64. String boot와 React를 연결하는 방법(Build 하는 방법)</title>
      <link>https://nowonbun.tistory.com/275</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이 글은 String boot와 React를 연결하는 방법(Build 하는 방법)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;최근에 프로젝트 트랜드는 프론트엔드와 서버사이드를 구분해서 많이 작업들 합니다. 사실 이렇게 분업해서 작업하는 스타일이 꽤 오래전부터 하였는데 제가 상당히 늦은 감이 있네요. 저는 최근까지 Javascript와 Jquery를 사용해서 화면과 프로그램을 작성했습니다.&lt;/p&gt;
&lt;p&gt;프로젝트를 혼자하는 것은 아니었지만 이런 상황이 솔직히 크게 불편하지는 않았습니다. Jquery로도 충분히 SPA(Single page application)를 구현을 했고 제가 느끼기에 크게 느리다고 생각도 들지도 않았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그런데 Jquery에서 React를 이동해야 겠다라고 생각을 한 것이 하나는 라이브러리들이 탈 Jquery를 외치면서 Jquery를 지원하지 않는 것입니다. 그러면서 굳이 Jquery를 사용할 필요가 있을까? 라는 생각부터 입니다.&lt;/p&gt;
&lt;p&gt;그와 동시에 많은 라이브러리들이 Angular, Vue, React를 지원하고 그 중 React를 선택한 것은 단순히 다운로드가 높고 많은 사람이 사용하는 프레임워크라서 선택했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;예전에는 화면을 만들 때는 그냥 eclipse에서 html 작성하고 css, js 작성하였는데 React의 경우는 eclipse에서 작성하기가 쉽지가 않네요.&lt;/p&gt;
&lt;p&gt;그래서 React는 Node.js를 사용해서 Visual studio code로 개발하고 Spring boot는 Intellij나 eclipse에서 개발합니다.&lt;/p&gt;
&lt;p&gt;각기 다른 IDE툴에서 개발하면 최종적으로 build 하고 나면 결과물이 따로 생성되기 때문에 이것을 하나로 합치는 작업이 필요합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;eclipse에서 Spring boot의 개발 환경을 설정하는 방법은 이전에 설명했습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/313&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Java] 57. Eclipse에서 Spring boot를 설정하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 React를 Visual studio code에 개발 환경을 설정하는 방법도 설명했습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/277&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[Javascript] Node.js를 설치하고 React를 사용하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그러면 일단 두 개의 개발환경을 연결하는 방법은 매우 간단합니다.&lt;/p&gt;
&lt;p&gt;React의 package.json 파일에 Spring boot의 주소를 연결하면 됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbn45i/btrxkfYUxx8/NHiEVuic3ZBKKyL3JeUOT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbn45i/btrxkfYUxx8/NHiEVuic3ZBKKyL3JeUOT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbn45i/btrxkfYUxx8/NHiEVuic3ZBKKyL3JeUOT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdbn45i%2FbtrxkfYUxx8%2FNHiEVuic3ZBKKyL3JeUOT1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjWCWp/btrxh9L2b9F/dxaPwS6kIZohvXUkNYnDqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjWCWp/btrxh9L2b9F/dxaPwS6kIZohvXUkNYnDqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjWCWp/btrxh9L2b9F/dxaPwS6kIZohvXUkNYnDqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjWCWp%2Fbtrxh9L2b9F%2FdxaPwS6kIZohvXUkNYnDqK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;저는 eclipse에서 Spring boot의 포트를 8081로 설정하고 React에서 Proxy를 8081로 접속하는 것으로 맞추었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 eclipse에서 다음과 같은 데이터를 취득하는 함수를 만듭니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;AjaxController.java&quot;&gt;package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

// 컨트럴러 어트리뷰트
@Controller
public class AjaxController {
  // 요청 매핑 어트리뷰트
  @RequestMapping(value = &quot;/data/hello.json&quot;)
  // String의 데이터
  @ResponseBody
  public String helloworld(Model model) {
    // 결과는 hello world
    return &quot;hello world&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;javascript&quot; data-type=&quot;App.tsx&quot;&gt;import React, { useEffect, useState } from 'react';

// ajax 함수
const ajax = (url: string, method = &quot;GET&quot;, data: object | null, success: ((req: XMLHttpRequest) =&amp;gt; void) | undefined | null = null, error: ((req: XMLHttpRequest) =&amp;gt; void) | undefined | null = null, sync = true): void =&amp;gt; {
  // XMLHttpRequest 객체
  let xhr = new XMLHttpRequest();
  // 버퍼 변수
  let json = &quot;&quot;;
  // 비동기 상태 변경 이벤트
  xhr.onreadystatechange = (e) =&amp;gt; {
    // XMLHttpRequest 객체
    let req = e.target as XMLHttpRequest;
    // null이면 처리 안함
    if (req == null) {
      return;
    }
    // 비동기 처리가 완료되면 (Code state - 4)
    if (req.readyState === XMLHttpRequest.DONE) {
      // http status 200 이면(정상 요청이면)
      if (req.status === 200) {
        // success 함수 호출
        if (success !== null &amp;amp;&amp;amp; success !== undefined) {
          success.call(this, req);
        }
      } else {
        // error 함수 호출
        if (error !== null &amp;amp;&amp;amp; error !== undefined) {
          error.call(this, req);
        }
      }
    }
  }
  // data 값이 null이 아니면
  if (data != null) {
    // json 타입으로 변경
    json = JSON.stringify(data);
  }
  // XMLHttpRequest 기본 설정
  xhr.open(method, url, sync);
  // XMLHttpRequest 해더 설정
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.setRequestHeader('Cache-Control', 'no-cache');
  // 비동기 통신 시작
  xhr.send(json);
};
// React 시작 함수
function App() {
  // message 상태 처리
  const [message, setMessage] = useState(&quot;&quot;);
  useEffect(() =&amp;gt; {
    // 비동기 처리
    ajax(&quot;/data/hello.json&quot;, &quot;GET&quot;, null, (msg) =&amp;gt; {
      // 상태 변경
      setMessage(msg.responseText);
    }, null, false);
  }, []);
  // 화면 표시
  return (
    &amp;lt;&amp;gt;
      {message}
    &amp;lt;/&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGltcz/btrxkMvkjoJ/KPhDhL2Mgo7EAtwFUPiHLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGltcz/btrxkMvkjoJ/KPhDhL2Mgo7EAtwFUPiHLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGltcz/btrxkMvkjoJ/KPhDhL2Mgo7EAtwFUPiHLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGltcz%2FbtrxkMvkjoJ%2FKPhDhL2Mgo7EAtwFUPiHLK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKLUyf/btrxlpzSf8o/HR2kwTutt8PSxbR6oaHEw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKLUyf/btrxlpzSf8o/HR2kwTutt8PSxbR6oaHEw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKLUyf/btrxlpzSf8o/HR2kwTutt8PSxbR6oaHEw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKLUyf%2FbtrxlpzSf8o%2FHR2kwTutt8PSxbR6oaHEw0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Spring boot와 react가 연결된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;저의 경우는 프로트엔드와 서버사이드를 같이 개발하니 하나의 로컬에서 작업을 합니다만, 만약 여러 사람과 분업이 되어 있다면 그에 맞는 개발 서버로 proxy를 연결하면 될 듯하네요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지가 개발 환경이고 build 설정을 맞추겠습니다.&lt;/p&gt;
&lt;p&gt;build는 먼저 프론트엔드의 React 소스를 빌드하여 html과 css, js를 생성합니다. 그리고 빌드된 파일을 Spring boot의 static 폴더에 넣어서 Spring boot를 빌드하면 됩니다.&lt;/p&gt;
&lt;p&gt;제가 이야기는 쉬운데 설정하는 게 쉽지가 않더라고요. 그래서 구글링으로 참조를 했습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://github.com/eirslett/frontend-maven-plugin/issues/872&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/eirslett/frontend-maven-plugin/issues/872&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://github.com/eirslett/frontend-maven-plugin&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/eirslett/frontend-maven-plugin&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 Spring boot의 pom.xml의 설정을 확인하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;xml&quot; data-type=&quot;pom.xml&quot;&gt;&amp;lt;!-- 프로퍼티 설정 --&amp;gt;
&amp;lt;properties&amp;gt;
  &amp;lt;java.version&amp;gt;11&amp;lt;/java.version&amp;gt;
  &amp;lt;frontend-src-dir&amp;gt;${project.basedir}/../my-app&amp;lt;/frontend-src-dir&amp;gt;
  &amp;lt;frontend-maven-plugin.version&amp;gt;1.12.1&amp;lt;/frontend-maven-plugin.version&amp;gt;
&amp;lt;/properties&amp;gt;
&amp;lt;build&amp;gt;
  &amp;lt;plugins&amp;gt;
    &amp;lt;!-- Spring boot를 jar로 실행 시키기 위한 빌드 패키지 --&amp;gt;
    &amp;lt;plugin&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
    &amp;lt;/plugin&amp;gt;
    &amp;lt;!-- React를 빌드하기 위한 패키지 --&amp;gt;
    &amp;lt;plugin&amp;gt;
      &amp;lt;groupId&amp;gt;com.github.eirslett&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;frontend-maven-plugin&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;${frontend-maven-plugin.version}&amp;lt;/version&amp;gt;
      &amp;lt;!-- 버전 설정은 build를 하는 환경의 nodejs와 npm 버전 설정 --&amp;gt;
      &amp;lt;configuration&amp;gt;
        &amp;lt;nodeVersion&amp;gt;v16.14.2&amp;lt;/nodeVersion&amp;gt;
        &amp;lt;npmVersion&amp;gt;8.5.0&amp;lt;/npmVersion&amp;gt;
        &amp;lt;workingDirectory&amp;gt;${frontend-src-dir}&amp;lt;/workingDirectory&amp;gt;
        &amp;lt;installDirectory&amp;gt;${project.build.directory}&amp;lt;/installDirectory&amp;gt;
      &amp;lt;/configuration&amp;gt;
      &amp;lt;!-- 실행 명령어 --&amp;gt;
      &amp;lt;executions&amp;gt;
        &amp;lt;execution&amp;gt;
          &amp;lt;id&amp;gt;install-frontend-tools&amp;lt;/id&amp;gt;
          &amp;lt;goals&amp;gt;
            &amp;lt;goal&amp;gt;install-node-and-npm&amp;lt;/goal&amp;gt;
          &amp;lt;/goals&amp;gt;
        &amp;lt;/execution&amp;gt;
        &amp;lt;!-- npm 패키지 다운 및 설치 --&amp;gt;
        &amp;lt;execution&amp;gt;
          &amp;lt;id&amp;gt;npm install&amp;lt;/id&amp;gt;
          &amp;lt;goals&amp;gt;
            &amp;lt;goal&amp;gt;npm&amp;lt;/goal&amp;gt;
          &amp;lt;/goals&amp;gt;
          &amp;lt;configuration&amp;gt;
            &amp;lt;arguments&amp;gt;install&amp;lt;/arguments&amp;gt;
          &amp;lt;/configuration&amp;gt;
        &amp;lt;/execution&amp;gt;
        &amp;lt;!-- npm 빌드 --&amp;gt;
        &amp;lt;execution&amp;gt;
          &amp;lt;id&amp;gt;npm run build&amp;lt;/id&amp;gt;
          &amp;lt;goals&amp;gt;
            &amp;lt;goal&amp;gt;npm&amp;lt;/goal&amp;gt;
          &amp;lt;/goals&amp;gt;
          &amp;lt;configuration&amp;gt;
            &amp;lt;arguments&amp;gt;run build&amp;lt;/arguments&amp;gt;
          &amp;lt;/configuration&amp;gt;
        &amp;lt;/execution&amp;gt;
      &amp;lt;/executions&amp;gt;
    &amp;lt;/plugin&amp;gt;
    &amp;lt;!-- React가 빌드된 폴더에서 Spring boot 프로젝트로 복사하는 패키지 --&amp;gt;
    &amp;lt;plugin&amp;gt;
      &amp;lt;artifactId&amp;gt;maven-resources-plugin&amp;lt;/artifactId&amp;gt;
      &amp;lt;executions&amp;gt;
        &amp;lt;execution&amp;gt;
          &amp;lt;id&amp;gt;position-react-build&amp;lt;/id&amp;gt;
          &amp;lt;goals&amp;gt;
            &amp;lt;goal&amp;gt;copy-resources&amp;lt;/goal&amp;gt;
          &amp;lt;/goals&amp;gt;
          &amp;lt;phase&amp;gt;prepare-package&amp;lt;/phase&amp;gt;
          &amp;lt;configuration&amp;gt;
            &amp;lt;!-- Spring boot의 html 파일 경로 --&amp;gt;
            &amp;lt;outputDirectory&amp;gt;${project.build.outputDirectory}/static&amp;lt;/outputDirectory&amp;gt;
            &amp;lt;resources&amp;gt;
              &amp;lt;resource&amp;gt;
                &amp;lt;directory&amp;gt;${frontend-src-dir}/build&amp;lt;/directory&amp;gt;
                &amp;lt;filtering&amp;gt;false&amp;lt;/filtering&amp;gt;
              &amp;lt;/resource&amp;gt;
            &amp;lt;/resources&amp;gt;
          &amp;lt;/configuration&amp;gt;
        &amp;lt;/execution&amp;gt;
      &amp;lt;/executions&amp;gt;
    &amp;lt;/plugin&amp;gt;
  &amp;lt;/plugins&amp;gt;
&amp;lt;/build&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에서 Node.js와 npm의 버전을 실행하는 환경으로 맞추어야 합니다.&lt;/p&gt;
&lt;p&gt;콘솔에서 npm version 명령어로 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnn8hO/btrxjGvDqSK/2mgskjmZJ45Fp1AlWP6HG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnn8hO/btrxjGvDqSK/2mgskjmZJ45Fp1AlWP6HG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnn8hO/btrxjGvDqSK/2mgskjmZJ45Fp1AlWP6HG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbnn8hO%2FbtrxjGvDqSK%2F2mgskjmZJ45Fp1AlWP6HG1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;설정은 완료되었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이제 빌드해 보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bndS0O/btrxkhh2Jd6/e604O1TsnaivmZn0cBDXk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bndS0O/btrxkhh2Jd6/e604O1TsnaivmZn0cBDXk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bndS0O/btrxkhh2Jd6/e604O1TsnaivmZn0cBDXk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbndS0O%2Fbtrxkhh2Jd6%2Fe604O1TsnaivmZn0cBDXk1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;로그에 React가 빌드되는 것을 확인할 수 있네요.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biy73y/btrxhMwNFxk/n1AuaeckAmKgI7pr1Cq8I0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biy73y/btrxhMwNFxk/n1AuaeckAmKgI7pr1Cq8I0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biy73y/btrxhMwNFxk/n1AuaeckAmKgI7pr1Cq8I0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbiy73y%2FbtrxhMwNFxk%2Fn1AuaeckAmKgI7pr1Cq8I0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;build가 완료되었습니다. 그럼 target 폴더에 jar 파일이 생성된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 윈도우에서 압축 내용을 확인해 보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kHZm9/btrxhMwNGpV/EdC65ANbBkse2evJmyNJek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kHZm9/btrxhMwNGpV/EdC65ANbBkse2evJmyNJek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kHZm9/btrxhMwNGpV/EdC65ANbBkse2evJmyNJek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkHZm9%2FbtrxhMwNGpV%2FEdC65ANbBkse2evJmyNJek%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;jar 파일 안에 React의 html과 js, css가 만들어져서 들어가 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 jar 파일을 실행 시켜 봅니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7S88t/btrxkMWo6XD/qRxGKEMrXiwMUyzx41UCik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7S88t/btrxkMWo6XD/qRxGKEMrXiwMUyzx41UCik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7S88t/btrxkMWo6XD/qRxGKEMrXiwMUyzx41UCik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7S88t%2FbtrxkMWo6XD%2FqRxGKEMrXiwMUyzx41UCik%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실행은 문제없이 되네요.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8VnZY/btrxlq6C8jI/5tplyuA20XZHvItnfD50J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8VnZY/btrxlq6C8jI/5tplyuA20XZHvItnfD50J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8VnZY/btrxlq6C8jI/5tplyuA20XZHvItnfD50J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8VnZY%2Fbtrxlq6C8jI%2F5tplyuA20XZHvItnfD50J0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;화면도 문제없이 실행됩니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이로써 React와 Spring boot를 설정한 개발 환경 설정은 완료가 되었네요.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기까지 String boot와 React를 연결하는 방법(Build 하는 방법)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Java</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/275</guid>
      <comments>https://nowonbun.tistory.com/275#entry275comment</comments>
      <pubDate>Fri, 25 Mar 2022 21:07:58 +0900</pubDate>
    </item>
    <item>
      <title>[Javascript] Node.js를 설치하고 React를 사용하는 방법</title>
      <link>https://nowonbun.tistory.com/277</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Node.js를 설치하고 React를 사용하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;제가 알바나 공부한 시기 빼고, 학교에서 졸업해서 제대로 프로그램을 개발한 지가 벌써 14년~15년에 가깝네요.&lt;/p&gt;
&lt;p&gt;처음부터 웹 개발을 해야지 하고 시작한 건 아니고 CS(Client Server) 프로그램부터 임베디드 프로그램 등 여러 분야로 방황(?)하다가 결국에는 수요가 가장 많은 웹 개발에 정착했습니다.&lt;/p&gt;
&lt;p&gt;웹이라는 것은 초기에는 그렇게 어렵게 생각하지는 않았는데 어느 분야든 마찬가지지만 깊이 갈수록 알아야 하는 규약도 많고 하루가 다르게 나오는 라이브러리와 프레임워크를 배워가야 하는게 엄청 많네요. 사실 그런 것이 웹 개발의 메리트일지도 모르겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;사람마다 개발의 가치관이 다르지만 저의 경우는 새로운 라이브러리와 프레임워크를 빠르게 배우고 전환하는 스타일은 아닙니다. 이유는 여러 가지가 있지만 안전성이 가장 크겠네요.&lt;/p&gt;
&lt;p&gt;Java의 경우는 처음 Java servlet 환경에서부터 시작해서 struts(스트럿츠), Spring web framework, Spring boot까지 했습니다. &lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전에 Java servlet를 꽤 오래동안 사용했지만 프레임워크의 필요성을 느끼고 struts(스트럿츠)로 넘어갈 때가 있었습니다. 초창기에는 servlet보다 빠른 개발과 독특한 개념으로 괜찮다고 생각했습니다만 struts는 생각보다 문제가 많습니다. struct의 문제라고 하면 대표적으로 보안 이슈가 많이 있습니다.&lt;/p&gt;
&lt;p&gt;지금도 이해가 안 되는 것이 servlet에서도 처리가 되는 Injection 문제가 struts에는 왜 뻥뻥 뚫리는지.. 그래서 프로젝트와 관계없는 회피 코드도 많이 만들고 어떨 때는 프레임워크의 라이브러리들을 상속받고 대부분을 재정의한 기억도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런 경험이 있으니 프레임워크나 라이브러리를 사용하는 게 상당히 조심스러워 졌습니다. 프레임워크라는 것이 한번 도입돼서 작성되면 도중에 바꾸기가 무척 어렵습니다. 즉, 프로젝트에서 struts를 사용하다가 spring이 좋다고 프레임워크를 간단하게 바꿀 수 있는 게 아닙니다. 보통은 처음부터 다시 만들어야 하는 경우가 대부분입니다.&lt;/p&gt;
&lt;p&gt;그래서 Spring web framework가 대세일 때도 한동한 Java servlet으로 나만의 프레임워크(?)를 구축을 해서 사용했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;경력의 중반까지도 보통 많은 팀원과 프로젝트를 하는 경우보다 3~4명에서 하는 경우가 많다 보니 그런 방식이 크게 문제 될 것도 없었습니다. 그냥 필요하면 제가 설명하고 만들어 내면 되니깐요.&lt;/p&gt;
&lt;p&gt;팀원이 많아지고 여러 시스템과 같이 사용하는 경우(Microservices Architecture)가 생기면서 많은 사람들과 알고 공유가 가능한 프레임워크를 사용할 필요성을 느끼고 Spring Web framework에서 Spring boot까지 오게 된 것 같네요.&lt;/p&gt;
&lt;p&gt;지금 생각하면 Spring은 참 좋은 개념을 가지고 있고 괜찮은 framework 같습니다. 생각보다 단순한 개념으로 보안 이슈의 영향도 적고 초반부터 빠르게 Spring을 사용했으면 어땠을까 하는 생각도 있네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그와 비슷한 상황에서 Javascript도 제가 상당히 오랫동안 바닐라 버젼을 고집해서 사용하고 Jquery의 Sizzle 엔진이 편해서 Jquery도 상당히 오랫동안 사용했습니다.&lt;/p&gt;
&lt;p&gt;그러다가 ES6(2015) 버젼에 Sizzle의 개념이 Javascript의 바닐라에 생기면서 Jquery을 사용할 이유도 많이 줄었네요.. 그런데 아직도 많은 라이브러리가 Jquery를 의존하고 있고 만들어져서 계속 사용하다가 최근에 많은 라이브러리가 탈 Jquery를 시작하고 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 최근 많은 프론트엔드 개발자 분들이 Jquery를 안좋은 평가를 하는 분도 많이 있습니다. 대표적으로 Jquery가 너무 느리다라는 것인데.... 저는 Jquery가 느리다고 생각된 적이 한번도 없는데...&lt;/p&gt;
&lt;p&gt;결정적으로 저에게는 Bootscript5부터는 Jquery를 빼겠다라는 소식에 충격을 받아서 이제는 Jquery를 벗어나서 새로운 Javascript 프레임워크를 사용해야 겠다라고 생각했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그러던 중에 최근 프론트엔드 개발 스타일은 SPA(Single page application)이기도 하고, 그것을 효과적으로 지원하는 프레임워크를 찾던 중에 React를 찾아서 사용했습니다.. 느낌은 아직까지는 꽤 괜찮다고 생각하는 프레임워크입니다.&lt;/p&gt;
&lt;p&gt;사실 이전에 Angular를 사용해 본 적이 있는데.. Angular는 개념이 너무 어렵더라고요.. 물론 작정하고 익히면 사용할 수 있겠으나 기존의 Javascript(Typescript)의 형태로 작성해도 안되고 Angular만의 규약에 맞추어 작성해야 하는 여러가지 어려운 점이 있었습니다.&lt;/p&gt;
&lt;p&gt;Angular를 좋아하시고 전문으로 사용하시는 분들이 있기에 나쁘게 폄하는 안 하겠습니다. 그냥 저와는 안 맞았습니다. 혹시 모르겠네요.. 결국에는 Angular로 갈지도...&lt;/p&gt;
&lt;p&gt;그래서 지금은 일단 React를 선택하고 시작했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;React를 시작하기 전에 알아야 하는 것이 Node.js와 Typescript입니다.&lt;/p&gt;
&lt;p&gt;사실 Javasciprt라는 것은 웹 전용 스크립트 언어입니다. 그래서 제가 스크립트 언어를 익힐 때, 로컬은 파이썬, 웹 서버는 PHP, 웹 클라이언트는 자바스크립트로 구분하고 사용했습니다. 그래서 그런지 자바스크립트를 아무도 로컬에서 사용한다고 생각하지 않았던 것 같네요.&lt;/p&gt;
&lt;p&gt;그것을 구글에서 로컬에서도 사용하고 싶었는지 서버로 가져다 온 것이 Node.js입니다. 좀 더 복잡한 히스토리가 있습니다만, 중요하지 않으니 간단하게 로컬에서 사용하는 자바스크립트라고 생각하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;React는 웹 브라우저에서 사용하는 프래임워크인데 왜 Node.js가 필요한 것인지?에 대한 궁금증이 생기는데..&lt;/p&gt;
&lt;p&gt;지금은 업무가 프로트엔드, 서버사이드가 완전히 구별이 되어 있습니다. 제가 웹 개발하던 초창기만 해도 프론트엔드 개발자나 웹 디자이너가 CSS와 Html, Javascript를 만들어와도 다시 서버사이드 개발자가 그걸 서버에 환경에 맞추어 다시 파싱 작업을 해야 합니다.&lt;/p&gt;
&lt;p&gt;혹은 프론트엔드 개발자가 자신의 PC에 똑같이 프로그램을 설치하고 그 위에서 작업을 하던가.. 어쨋든 완벽한 분리가 되지 않았습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;지금은 Node.js으로 프론트엔드 개발자들만을 위한 웹서버를 구축하고, proxy 설정으로 서버사이드를 연결해서 사용합니다. 그리고 최종적으로 배포할 때만 Node.js로 빌드된 파일을 서버사이드 개발 코드에 넣고 최종 컴파일하게 되면 프로젝트가 완성이 되겠네요.&lt;/p&gt;
&lt;p&gt;물론 풀스택이라면 서버사이드나 프론트엔드를 동시에 개발하기 때문에 Node.js가 필요 없을 수도 있지만 그냥 IDE에서 개발하는 것보다 Node.js를 이용해서 개발하면 프론트엔드는 정말 편합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Node.js를 설치하는 방법은 그냥 설치 프로그램 다운로드 받고 실행하면 됩니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nodejs.org/ko/&quot; target=&quot;_blank&quot;&gt;https://nodejs.org/ko/ &lt;/a&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RsIXy/btrwZ0n8hne/VctzKHqN3lGTEghyGARn51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RsIXy/btrwZ0n8hne/VctzKHqN3lGTEghyGARn51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RsIXy/btrwZ0n8hne/VctzKHqN3lGTEghyGARn51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRsIXy%2FbtrwZ0n8hne%2FVctzKHqN3lGTEghyGARn51%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;최신 버전이 좋기는 하지만 저는 보통 안정화된 LTS를 선호하기 때문에 LTS를 설치하겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dAvyH3/btrw6gQZRpl/b50l9yp30O7bAqjYj08N9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dAvyH3/btrw6gQZRpl/b50l9yp30O7bAqjYj08N9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dAvyH3/btrw6gQZRpl/b50l9yp30O7bAqjYj08N9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdAvyH3%2Fbtrw6gQZRpl%2Fb50l9yp30O7bAqjYj08N9K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;처음에 로딩 시간이 걸리기는 하는데 1분정도 기다리면 설치 화면이 나옵니다.&lt;/p&gt;
&lt;p&gt;그리고 적당한 경로에 설치하면 됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KVwck/btrwVtklpZy/eNU61OxTKC155KqH33Oft1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KVwck/btrwVtklpZy/eNU61OxTKC155KqH33Oft1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KVwck/btrwVtklpZy/eNU61OxTKC155KqH33Oft1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKVwck%2FbtrwVtklpZy%2FeNU61OxTKC155KqH33Oft1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 설치가 끝났습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3UGyQ/btrw65hol6k/hVkZi0Q4OOGV9l4hYoAOl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3UGyQ/btrw65hol6k/hVkZi0Q4OOGV9l4hYoAOl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3UGyQ/btrw65hol6k/hVkZi0Q4OOGV9l4hYoAOl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3UGyQ%2Fbtrw65hol6k%2FhVkZi0Q4OOGV9l4hYoAOl1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이제 여기서 적당한 경로에서 자바스크립트를 작성하고 웹 서버를 만들어 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;javascript&quot; data-type=&quot;index.js&quot;&gt;const http = require('http');

http.createServer((req, res) =&amp;gt; {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World');
}).listen(3000);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;콘솔에서 node index.js로 서버를 기동합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CYp8p/btrw2O1ZHpD/4nKz4zy4CtEmALekMQ4lU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CYp8p/btrw2O1ZHpD/4nKz4zy4CtEmALekMQ4lU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CYp8p/btrw2O1ZHpD/4nKz4zy4CtEmALekMQ4lU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCYp8p%2Fbtrw2O1ZHpD%2F4nKz4zy4CtEmALekMQ4lU0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2ZPJW/btrw2O1ZIXa/KfQr6VcLAMtsvW40KvisBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2ZPJW/btrw2O1ZIXa/KfQr6VcLAMtsvW40KvisBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2ZPJW/btrw2O1ZIXa/KfQr6VcLAMtsvW40KvisBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2ZPJW%2Fbtrw2O1ZIXa%2FKfQr6VcLAMtsvW40KvisBk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;브라우저에 hello world가 보이네요. 여기까지 프론트엔드 개발 준비는 끝났습니다.&lt;/p&gt;
&lt;p&gt;참고로 프론트엔드의 경우에는 개발 IDE를 Visual studio code로 사용합니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/597&quot; target=&quot;_blank&quot;&gt;[Visual studio] 무료 소스 코드 편집기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이제 React의 프레임워크를 이용해 보겠습니다.&lt;/p&gt;
&lt;p&gt;프론트엔드를 개발한다고 해서 위처럼 서버 소스를 다 작성할 필요는 없습니다. 프론트엔드 프로젝트는 기본적으로 작성되어 있는 형태가 있습니다.&lt;/p&gt;
&lt;p&gt;React의 경우도 같습니다. 콘솔에 아래의 명령으로 작성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell&quot; data-type=&quot;&quot;&gt;npx create-react-app my-app --template typescript
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGZMts/btrw2OgEQWV/EgPhGyuAwWKTNaqFErxTFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGZMts/btrw2OgEQWV/EgPhGyuAwWKTNaqFErxTFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGZMts/btrw2OgEQWV/EgPhGyuAwWKTNaqFErxTFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGZMts%2Fbtrw2OgEQWV%2FEgPhGyuAwWKTNaqFErxTFk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;마지막에 Happy hacking!이 나오면 설치가 완료된 것입니다. 저도 처음에 Hacking!이라고 해서 조금 놀랐는데 한국에서의 즐프와 같은 의미라네요.. 자세한 것은 저도 잘 모릅니다. 그냥 문제없습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위에서 my-app의 옵션을 설정하였기 때문에 my-app에서 생성이 되었습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cm2oDc/btrw2OAWT3n/MVYHxJQne025QzavNjWv2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cm2oDc/btrw2OAWT3n/MVYHxJQne025QzavNjWv2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cm2oDc/btrw2OAWT3n/MVYHxJQne025QzavNjWv2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcm2oDc%2Fbtrw2OAWT3n%2FMVYHxJQne025QzavNjWv2K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;my-app로 가서 서버를 기동해 보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c44jWO/btrw65aBj48/CJYxALQsKEz9kLueddxobk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c44jWO/btrw65aBj48/CJYxALQsKEz9kLueddxobk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c44jWO/btrw65aBj48/CJYxALQsKEz9kLueddxobk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc44jWO%2Fbtrw65aBj48%2FCJYxALQsKEz9kLueddxobk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;콘솔에 위와 같은 로그가 나오면 실행이 된 것입니다. 그러면 브라우저로 http://localhost:3000으로 접속합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxo7qS/btrwXzYDUEK/hnsrshDr8p8HIyFTsRDQXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxo7qS/btrwXzYDUEK/hnsrshDr8p8HIyFTsRDQXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxo7qS/btrwXzYDUEK/hnsrshDr8p8HIyFTsRDQXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdxo7qS%2FbtrwXzYDUEK%2FhnsrshDr8p8HIyFTsRDQXK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위처럼 나오면 설정이 완료된 것입니다.&lt;/p&gt;
&lt;/p&gt;
&lt;p&gt;이제 디렉토리를 확인해 보면 아래의 구조처럼 되어 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBpBYS/btrw64bGXI0/N10Aoj9aMUC51ymiMhtT60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBpBYS/btrw64bGXI0/N10Aoj9aMUC51ymiMhtT60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBpBYS/btrw64bGXI0/N10Aoj9aMUC51ymiMhtT60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBpBYS%2Fbtrw64bGXI0%2FN10Aoj9aMUC51ymiMhtT60%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byRiSI/btrw43xPVtu/JTkXwT9tSGxX0tYmBw7KH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byRiSI/btrw43xPVtu/JTkXwT9tSGxX0tYmBw7KH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byRiSI/btrw43xPVtu/JTkXwT9tSGxX0tYmBw7KH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyRiSI%2Fbtrw43xPVtu%2FJTkXwT9tSGxX0tYmBw7KH0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;분명 자바스크립트로 웹 프론트엔드를 개발한다고 설명했는데 Javascript(.js) 파일은 없네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 모든 것을 설명하기는 힘들고 Typescript에 대해 간략하게 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;자바스크립트는 스크립트 언어이기 때문에 코드에 에러가 있으면 그 지점이 실행되기 전까지는 알 수가 없습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;javascript&quot; data-type=&quot;ex.js&quot;&gt;function func() {
  function test(val) {
    console.log(val.data);
  }
  test();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 소스는 func함수를 호출하기 전까지 에러가 발생하지 않습니다. 즉, 사람이 실수로 작성하더라도 실행은 문제없이 된다는 것입니다.&lt;/p&gt;
&lt;p&gt;그런식 으로 소스가 서비스에 넘어가서 어느 시점에 사용자가 실행하게 되어서 에러가 발생하면 그게 바로 버그인 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 우리는 Javascript도 기계적인 검사로 문법이 문제가 없는지 사전에 알아야 할 필요성이 있습니다.&lt;/p&gt;
&lt;p&gt;그것이 Typescript입니다. &lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Typescript는 Javascript와 문법이 다릅니다. 많은 차이가 있는 것은 아니고 변수에 자료형을 명시한다던가 위처럼 파라미터가 필요한 함수에 파라미터 없이 호출하는 에러를 잡아내는 역할을 합니다.&lt;/p&gt;
&lt;p&gt;즉, Typescript로 조금은 엄격한 문법으로 작성하여 많은 버그를 잡고 Javascript 파일을 Output하는 기능입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;저는 위에서 React 기본 프레임워크를 다운로드 할 때, --template typescript라는 옵션을 넣었습니다. 그래서 typescript 형식으로 만들어 진 것입니다.&lt;/p&gt;
&lt;p&gt;혹시라도 typescript 형식이 필요없으면 --template typescript를 생략하면 js 파일로 React가 만들어 집니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위 프로젝트를 Build 해 봅시다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ylj70/btrw44KhPRm/wCddr2s7bOv6c1JBTHk041/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ylj70/btrw44KhPRm/wCddr2s7bOv6c1JBTHk041/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ylj70/btrw44KhPRm/wCddr2s7bOv6c1JBTHk041/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fylj70%2Fbtrw44KhPRm%2FwCddr2s7bOv6c1JBTHk041%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그러면 최종 결과물은 js 파일로 만들어 집니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RwRo4/btrw6hvBgLW/MbOAar28RKH7AJ4IKxm4V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RwRo4/btrw6hvBgLW/MbOAar28RKH7AJ4IKxm4V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RwRo4/btrw6hvBgLW/MbOAar28RKH7AJ4IKxm4V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRwRo4%2Fbtrw6hvBgLW%2FMbOAar28RKH7AJ4IKxm4V1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;저 파일을 서버사이드에 복사하면 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서는 제가 Node.js를 설치하고 React 프레임워크를 다운받아서 실행하는 방법까지 설명했습니다.&lt;/p&gt;
&lt;p&gt;다음 글에서는 Typescript와 React에 대해 좀 더 자세히 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Node.js를 설치하고 React를 사용하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Javascript, Jquery, CSS</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/277</guid>
      <comments>https://nowonbun.tistory.com/277#entry277comment</comments>
      <pubDate>Wed, 23 Mar 2022 17:59:41 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 63. Spring boot에서 cron 스케줄러와 Component 어노테이션</title>
      <link>https://nowonbun.tistory.com/279</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Java의 Spring boot에서 cron 스케줄러와 Component 어노테이션에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;제가 이전에 Web Framework에서 cron 스케줄러를 사용한 적이 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/671&quot; target=&quot;_blank&quot;&gt;[Java] JSP Spring framework 환경에서 scheduler의 cron 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;우리가 웹 서버를 운영하다 보면 브라우저(Browser)에서 요청에 의한 실행 처리만 할 것이 아니라, 정해진 시간에 프로그램 내부의 캐쉬 관리라던가 데이터베이스의 데이터 관리던가 여러가지 처리를 해야 할 때가 있습니다.&lt;/p&gt;
&lt;p&gt;물론 Window 서버라면 윈도우 스케줄러가 있고 Linux 서버라면 crontab 스케줄러가 있습니다. 웹 서버와 독립적으로 스케줄러를 운영해서 사용해도 됩니다만, 시스템 내부에서 운영을 해야 할 때가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;cron 스케줄러를 프로그램 안에서 구현을 하게 되면 문제점이 하나 있습니다.&lt;/p&gt;
&lt;p&gt;예를 들면, 정해진 시간에 메일을 전송하는 로직이 있다고 가정하면 이것을 cron　스케줄러에 의해 구현했습니다. 그런데 우연히 이 웹서버 로드밸런싱으로 분산 처리가 되어 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 스케줄러가 여러 곳에서 중복 실행이 됩니다.. 그래서 cron 스케줄러를 작성할 때는 항상 이 부분을 생각하고 만들어야 합니다.(제가 이런 바보같은 경험을 가지고 있다는 뜻이 아닙니다.)&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Spring boot에서 cron 스케줄러를 사용하는 건 매우 간단하네요.. 심지어 따로 pom.xml 라이브러리를 추가하지 않아도 됩니다.&lt;/p&gt;
&lt;p&gt;이 부분은 예전 Web framework보다는 매우 편하네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Spring boot를 실행하는 main 함수가 있는 클래스에 @EnableScheduling 어노테이션을 추가하면 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;SpringBootTestApplication.java&quot;&gt;package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

// 패키지 베이스 설정
@SpringBootApplication(scanBasePackages = &quot;com.example.demo.*&quot;) 
// Redis에 세션 공유를 한다.
@EnableRedisHttpSession 
// cron 스케줄러를 사용할 수 있게 설정한다.
@EnableScheduling   
public class SpringBootTestApplication {
  // main 실행 함수
  public static void main(String[] args) {
    SpringApplication.run(SpringBootTestApplication.class, args);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d4vezk/btrv7RkR7Zw/Raq7S5c2JrKOq6J0J2l5Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d4vezk/btrv7RkR7Zw/Raq7S5c2JrKOq6J0J2l5Y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d4vezk/btrv7RkR7Zw/Raq7S5c2JrKOq6J0J2l5Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd4vezk%2Fbtrv7RkR7Zw%2FRaq7S5c2JrKOq6J0J2l5Y1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위처럼 어노테이션만 추가하면 cron 스케줄러를 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 이제 cron 스케줄러를 사용하기 위해 bean을 추가합시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;SchedulerTest.java&quot;&gt;package com.example.demo.scheduler;

import java.util.Date;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.example.demo.dao.UserDao;

// @Component 어노테이션
@Component 
public class SchedulerTest {
  // DI 의존성 주입
  @Autowired 
  // FactoryDao에서 취득
  @Qualifier(&quot;UserDao&quot;) 
  private UserDao userdao;
  
  // cron 스케줄러 설정
  @Scheduled(cron = &quot;0/10 * * * * *&quot;)
  public void test() {
    // 콘솔 출력
    System.out.println(new Date());
    // 콘솔 출력
    System.out.println(userdao.selectById(&quot;nowonbun&quot;).getName());
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 @Component 어노테이션을 추가해서 Bean에 등록합니다.&lt;/p&gt;
&lt;p&gt;그리고 스케줄링 할 함수에 @Scheduled 어노테이션을 추가해서 문법을 이용해서 스케줄을 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서는 제가 10초마다 실행하라고 설정을 했습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsis4y/btrv5nFitaE/sphKuUvHe0Az6lrpnY8bK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsis4y/btrv5nFitaE/sphKuUvHe0Az6lrpnY8bK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsis4y/btrv5nFitaE/sphKuUvHe0Az6lrpnY8bK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdsis4y%2Fbtrv5nFitaE%2FsphKuUvHe0Az6lrpnY8bK1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여기서 Scheduled 어노테이션에 설정하는 cron 문법이 있습니다.&lt;/p&gt;
&lt;p&gt;이 부분은 이전에 설명한 적이 있습니다만, 다시 정리하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/671&quot; target=&quot;_blank&quot;&gt;[Java] JSP Spring framework 환경에서 scheduler의 cron 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 문법은 스페이스를 구분으로 총 7개의 단위가 있고 마지막 하나는 생략이 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot; data-type=&quot;cron&quot;&gt;*  *  *  *  *  *  *
초 분 시 일 월 요일 년도(생략가능 - 생략시에는 매년의 의미)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;먼저 위의 예제는 별 표시(*)를 했는데 이건 매초, 매분을 의미합니다. 즉, 위는 매초마다 실행하라는 의미입니다.&lt;/p&gt;
&lt;p&gt;별 표시 말고는 숫자를 보통 넣는데, 단위가 아니라 지정 시간입니다.&lt;/p&gt;
&lt;p&gt;즉, 「1 1 1 1 1 *」의 의미는 1월 1일 1시 1분 1초에 실행하라는 의미입니다. 6번째는 요일인데 「1 1 1 1 1 1」의 의미 1월 1일 1시 1분 1초 일요일에 실행하라라는 뜻인데.. 1월 1일이 일요일이 아니면 실행 안합니다.&lt;/p&gt;
&lt;p&gt;참고로 요일은 1이 일요일부터 2는 월 3은 화.. 순으로 7은 토요일의 의미를 갖습니다.&lt;/p&gt;
&lt;p&gt;여기서 우리는 복수의 시간을 설정할 수 있는데 콤마(,)의 구분으로 설정합니다. 「1,11 * * * * *」는 매분 1초와 11초에 실행하라는 뜻입니다.&lt;/p&gt;
&lt;p&gt;다시 지정 시간이 아닌 단위 시간으로 설정하고 싶을 때도 있겠네요. 즉 10초 단위, 5분 단위로 처리하는 것입니다. 그럴 경우에는 0/단위 시간을 넣으면 되는데 「0/10 * * * * *」는 10초단위가 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그 밖의 특수 표현식도 있습니다.&lt;/p&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
  &lt;table class=&quot;table table-striped table-bordered table-condensed&quot;&gt;
    &lt;thead&gt;
      &lt;tr style=&quot;background-color:#DAD7D7&quot;&gt;
        &lt;th style=&quot;white-space: nowrap;&quot;&gt;표현식&lt;/th&gt;
        &lt;th&gt;설명&lt;/th&gt;
      &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td style=&quot;white-space: nowrap;&quot;&gt;*&lt;/td&gt;
        &lt;td&gt;ALL의 의미로 매초, 매분, 매시간, 매일, 매월, 매요일, 매년&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;white-space: nowrap;&quot;&gt;?&lt;/td&gt;
        &lt;td&gt;일 요일에서만 사용되는 조건 없음의 의미&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;white-space: nowrap;&quot;&gt;/&lt;/td&gt;
        &lt;td&gt;주기 반복의 의미&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;white-space: nowrap;&quot;&gt;-&lt;/td&gt;
        &lt;td&gt;범위의 의미&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;white-space: nowrap;&quot;&gt;L&lt;/td&gt;
        &lt;td&gt;일, 요일에서만 사용되는데 마지막날의 의미입니다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;white-space: nowrap;&quot;&gt;W&lt;/td&gt;
        &lt;td&gt;일에서만 사용되는데 지정된 가장 가까운 평일을 찾습니다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td style=&quot;white-space: nowrap;&quot;&gt;#&lt;/td&gt;
        &lt;td&gt;요일에서만 사용되는데 주#요일을 나타냅니다.&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;특수 표현식으로 L,W,#이 있는데 L은 마지막 날의 의미를 가지고 있습니다.일과 요일에서만 사용할 수 있는데 일에서 L를 사용하면 해당 달의 마지막 날, 요일에는 토요일을 의미합니다.&lt;/p&gt;
&lt;p&gt;W는 일에서만 사용되는 데 가장 가까운 평일을 뜻합니다. 10W의 경우 10일이 토요일이면 9일에 실행. 10일이 일요일이면 11일에 실행하는 표현식입니다.&lt;/p&gt;
&lt;p&gt;#은 요일에 사용되는 표현식입니다. 2#2라고 하면 두번째 주 월요일에 실행이라는 의미입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;참고로 cron의 문법은 cron 라이브러리 뿐아니라 Linux 스케줄러에서도 같은 문법으로 적용됩니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/14&quot; target=&quot;_blank&quot;&gt;[CentOS] Linux에서 스케줄러(Crontab)를 사용하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;cron 라이브러리를 사용할 때 우리는 Component 어노테이션을 설정해서 Spring Bean을 등록했습니다.&lt;/p&gt;
&lt;p&gt;참고로 Spring에서는 Bean이라는 어노테이션도 있습니다.&lt;/p&gt;
&lt;p&gt;먼저 Component라는 것은 클래스에 붙이는 어노테이션인데, Spring에서 DI 의존성 주입을 위해 사용되는 클래스를 뜻합니다.(scan-auto-detection,dependency injection)&lt;/p&gt;
&lt;p&gt;즉, Singleton 패턴으로 사용되는 데이터 형식이 아닌 Controller 형식으로 사용되는 클래스를 설정하는 것입니다.&lt;/p&gt;
&lt;p&gt;조건으로는 파라미터가 없는 생성자가 있어야하며, 그 생성자의 접근 제한자는 상관없습니다. 즉, 생성자를 작성하지 않거나 작성하더라도 반드시 파라미터가 없는 생성자가 있어야 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x825L/btrv4yl2yT9/KLD5GywD8O5A2VaGu9zOkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x825L/btrv4yl2yT9/KLD5GywD8O5A2VaGu9zOkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x825L/btrv4yl2yT9/KLD5GywD8O5A2VaGu9zOkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx825L%2Fbtrv4yl2yT9%2FKLD5GywD8O5A2VaGu9zOkk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;즉, 이렇게 작성하면 아래와 같이 에러가 발생합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ORUDB/btrwbgEatmz/6yLabxkk2oPmiuiRKby1j0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ORUDB/btrwbgEatmz/6yLabxkk2oPmiuiRKby1j0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ORUDB/btrwbgEatmz/6yLabxkk2oPmiuiRKby1j0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FORUDB%2FbtrwbgEatmz%2F6yLabxkk2oPmiuiRKby1j0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여기서 Bean 어노테이션은 Component와 비슷합니다. 그런데 Bean 어노테이션은 함수에 붙습니다.&lt;/p&gt;
&lt;p&gt;함수는 메모리에 할당하는 의미가 없는데 함수에 붙는 것이 의미가 있을까 라고 생각할 수 있습니다만..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Autowired로 Bean으로 등록된 것을 호출하게 되면 함수가 실행된 결과가 리턴이 됩니다. 그런데 이게 Autowired로 호출될 때마다 Bean이 설정된 함수가 호출되는 것이 아니고 Singleton의 개념과 비슷하게 서버가 기동할 때, 한번 함수를 호출해서 그 결과를 메모리에 할당합니다.&lt;/p&gt;
&lt;p&gt;그리고 Autowired로 호출될 때마다 메모리에 할당된 인스턴스를 가져다 사용하는 구조입니다.&lt;/p&gt;
&lt;p&gt;이게 주의할 점이 반환　값이 같은 인스턴스 타입일 경우 에러가 발생할 수 있습니다. 클래스 명을 같은 이름으로 만들 수가 없습니다만 함수 반환은 그게 가능하니　의존성 주입에서 인스턴스를 받는 입장에서는 어떤 것을 받을 지 에러가 발생하네요&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU02Pm/btrv8cpyhwg/PkCl09jlh0SP3XKCkjvo50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU02Pm/btrv8cpyhwg/PkCl09jlh0SP3XKCkjvo50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU02Pm/btrv8cpyhwg/PkCl09jlh0SP3XKCkjvo50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU02Pm%2Fbtrv8cpyhwg%2FPkCl09jlh0SP3XKCkjvo50%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이렇게 에러가 발생합니다. 즉, 하나의 타입만 Bean 등록을 하거나 Bean에 name을 넣어서 DI쪽에서는 @Qualifier 어노테이션으로 받을 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QBHnn/btrwao3w68d/NYyZ4DcQ4lZgsT6XxPkkhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QBHnn/btrwao3w68d/NYyZ4DcQ4lZgsT6XxPkkhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QBHnn/btrwao3w68d/NYyZ4DcQ4lZgsT6XxPkkhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQBHnn%2Fbtrwao3w68d%2FNYyZ4DcQ4lZgsT6XxPkkhk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Cly4Z/btrv7Q7laxT/BDqjwzuxDcymlExgnJPAzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Cly4Z/btrv7Q7laxT/BDqjwzuxDcymlExgnJPAzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Cly4Z/btrv7Q7laxT/BDqjwzuxDcymlExgnJPAzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCly4Z%2Fbtrv7Q7laxT%2FBDqjwzuxDcymlExgnJPAzk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;저는 그래서 애초에 Bean 어노테이션은 name을 설정해 놓습니다. 물론 name은 String 값이기 때문에 여기가 다른 값과 겹치게 되면 에러가 발생합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 또 다른 부분이 의문이 생기는 데 Repository 어노테이션, Controller 어노테이션, Service 어노테이션입니다.&lt;/p&gt;
&lt;p&gt;Repository 어노테이션, Controller 어노테이션, Service 어노테이션도 Component 어노테이션과 비슷하게 scan-auto-detection와 dependency injection를 사용할 수 있는데, Component 어노테이션과 무슨 차이가 있는 것일까?&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;쉽게 생각하면 Repository 어노테이션, Controller 어노테이션, Service 어노테이션이 Component 어노테이션을 상속 받은 어노테이션이라고 생각하면 됩니다. 실제는 상속을 받은 것은 아닙니다만 개념적으로 그렇습니다.&lt;/p&gt;
&lt;p&gt;즉, 위 스케줄러에 제가 Component 어노테이션을 사용해서  @Scheduled 어노테이션으로 cron 스케줄러를 사용했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;저기에 Component 어노테이션 대신에 Repository 어노테이션, Controller 어노테이션, Service 어노테이션를 사용하면? 작동됩니다. 아무런 에러없이 똑같이 작동이 됩니다.&lt;/p&gt;
&lt;p&gt;Controller는 Component 기능 + 요청 매핑이 가능한 기능이 있습니다. 즉 RequestMapping 어노테이션을 사용할 수 있는 어노테이션인 것입니다. &lt;/p&gt;
&lt;p&gt;그래서 Component 어노테이션이 들어가는 클래스에 Repository 어노테이션, Controller 어노테이션, Service 어노테이션를　대신 사용해도 문제없지만, Repository 어노테이션, Controller 어노테이션, Service 어노테이션를 사용하는 어노테이션에 Component 어노테이션이나 다른 어노테이션을 사용하면 에러가 발생할 것입니다.&lt;/p&gt;
&lt;p&gt;Controller 어노테이션은 RequestMapping 어노테이션을 사용할 수 있는 어노테이션이라고 생각하면 Repository 어노테이션과는 무슨 차이가 있을까?&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이게 저도 정확한 의미를 잘 모르겠는데 JPA를 사용할 때에 특정 에러, 즉 디버깅 중에 에러를 잡지 않는 요소(RuntimException)가 있는데, 이런 에러를 추가해 주는 기능을 하고 있다라고 합니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://www.baeldung.com/spring-component-repository-service&quot; target=&quot;_blank&quot;&gt;https://www.baeldung.com/spring-component-repository-service&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;사실 이게 저도 무슨 소리인지 몰라 여러가 테스트를 해 봤는데 명확한 차이를 모르겠더군요...&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Service 어노테이션은 현재로서는 Component 어노테이션과 크게 차이가 있는 어노테이션이 아니라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;즉, 우리가 Component 어노테이션을 사용해도 되는 데 업무적 특성상, Controller 어노테이션, Repository 어노테이션, Service 어노테이션으로 구분해서 사용하게 됩니다. Component 어노테이션으로 하면 프로젝트가 커질수록 살짝 헤갈리는 요소가 있긴 하더라고요..&lt;/p&gt;
&lt;p&gt;Controller는 RequestMapping용으로 사용하고, 우리가 기능적인 함수등을 만들 때는 Service 어노테이션, 데이터와 관계된 것은 Repository 어노테이션을 사용하는 것이 좋을 듯 싶습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Java의 Spring boot에서 cron 스케줄러와 Component 어노테이션에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Development note/Java</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/279</guid>
      <comments>https://nowonbun.tistory.com/279#entry279comment</comments>
      <pubDate>Wed, 16 Mar 2022 18:55:22 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 62. Spring boot에서 Web-Filter를 설정하는 방법(Spring Security)</title>
      <link>https://nowonbun.tistory.com/280</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Java의 Spring boot에서 Web-Filter를 설정하는 방법(Spring Security)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;저도 사실 Spring Security에 대해서는 자세히 모릅니다. 관련 사양서와 도큐멘트를 봤는데도 양도 엄청나고 전부 활용할 수 있을까 하는 의문도 있습니다. 단지 「브라우저로부터 요청(Request)가 있을 때, Controller에서 처리가 되기 전에 Filter로써 호출이 되는 것」 정도로 알고 있습니다. Web Framework 시절에도 그냥 세션 확인해서 User 인스턴스가 있으면 인증이고 아니면 인증 실패 정도로 설정한 게 전부였던 것으로 기억합니다.&lt;/p&gt;
&lt;p&gt;그래서 이번에 Spring boot를 전체적으로 정리하는 중에 있어서 Web-Filter에 대해서 어떻게 정리하는 게 좋을까 고민을 정말 많이 했던 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이번에 제가 Web framework에서 Spring boot로 옮기는 작업을 하면서 또 하나의 목표를 설정한 것이 프론트엔드(front-end)와 백엔드(back-end)의 완전한 분리를 목표로 작업하고 있습니다.&lt;/p&gt;
&lt;p&gt;그러면서 적용을 해야 하는 부분이 세션을 이용한 로그인 인증이 아닌 JWT(Json web token)을 이용한 로그인 관리가 되어야 합니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/281&quot; target=&quot;_blank&quot;&gt;[Java] JWT(Json Web Token)을 발행, 확인하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이전부터 제가 프론트엔드의 프레임워크(Angular, Vue, React)를 사용하고 싶었으나, 언제나 세션을 완전히 분리하지 않는 이상 한계가 존재하게 되네요.. &lt;/p&gt;
&lt;p&gt;SPA(Single page application)의 환경에서는 메인 페이지의 Page request가 최초 한번만 발생하고, Javascript를 이용해서 동적으로 DOM과 해더를 설정하는 환경이 됩니다만, 그럴 경우 페이지에서와 쿠키와 세션, 변수 데이터 관리, 그리고 비동기로 요청되는 ajax의 쿠키와 세션, 변수 데이터 관리가 꽤나 복잡하게 되더군요.&lt;/p&gt;
&lt;p&gt;저만 그런지도 모르겠습니다. 이 부분은 각자의 경험에 따라 느끼는 부분이 차이가 있을 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;일단 이번 글에서의 목표는 Spring boot에서 Web-Filter를 사용해 JWT(Json web token)을 이용해서 인증하는 프로그램을 만들 생각입니다.&lt;/p&gt;
&lt;p&gt;JWT 인증에 관해서는 「그랩의 블로그」의 블로그를 많이 참조했습니다. 감사합니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://tansfil.tistory.com/59&quot; target=&quot;_blank&quot;&gt;https://tansfil.tistory.com/59&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 제가 JWT 인증을 사용하기 위해서는 Access Token과 Refresh Token을 구현해야 했습니다.&lt;/p&gt;
&lt;p&gt;먼저 이전의 프로젝트에서 pom.xml에 JWT 라이브러리를 추가하겠습니다.&lt;/p&gt;
&lt;p&gt;레포지토리 - &lt;a href=&quot;https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt/0.9.1&quot; target=&quot;_blank&quot;&gt;https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt/0.9.1&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p7O6a/btrvYIbarS4/4uts1HjWV8YqNzKHaD8kKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p7O6a/btrvYIbarS4/4uts1HjWV8YqNzKHaD8kKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p7O6a/btrvYIbarS4/4uts1HjWV8YqNzKHaD8kKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp7O6a%2FbtrvYIbarS4%2F4uts1HjWV8YqNzKHaD8kKK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;xml&quot; data-type=&quot;pom.xml&quot;&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;io.jsonwebtoken&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;jjwt&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;0.9.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 filter 패키지를 생성하고 JWT(Json web token)을 다루는 Provider 클래스를 생성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;JwtProvider.java&quot;&gt;package com.example.demo.filter;

import java.util.Base64;
import java.util.Date;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

// Autowired의 의존성 주입이 가능하게 Component 어트리뷰트를 설정
@Component
public class JwtProvider {
  // Refresh-Token의 보안 키를 application.properties에서 설정할 수 있게 설정
  @Value(&quot;spring.jwt.secret&quot;)
  private String SECRET_KEY = &quot;secret&quot;;
  // Access-Token의 보안 키를 application.properties에서 설정할 수 있게 설정
  @Value(&quot;spring.jwt.access&quot;)
  private String ACCESS_KEY = &quot;access&quot;;
  // tick 기준 - 24시간
  private final int TICK_24HOUR = 1000 * 60 * 60 * 24;
  // tick 기준 - 10분
  private final int TICK_10MIN = 1000 * 60 * 10;
  // Cookie의 Key 명
  private final String X_AUTH_TOKEN_REFRESH = &quot;X-AUTH-TOKEN-REFRESH&quot;;
  private final String X_AUTH_TOKEN_ACCESS = &quot;X-AUTH-TOKEN-ACCESS&quot;;
  // 생성자
  public JwtProvider() {
    // 키를 Base64 암호화하여 복잡도를 높힌다.
    SECRET_KEY = Base64.getEncoder().encodeToString(SECRET_KEY.getBytes());
    ACCESS_KEY = Base64.getEncoder().encodeToString(ACCESS_KEY.getBytes());
  }
  // Refresh-Token 생성 함수
  public String createRefreshToken(String id, HttpServletRequest req, HttpServletResponse res) {
    // token을 생성한다. 유효기간 2주
    var token = createToken(id, TICK_24HOUR * 14, SECRET_KEY, req);
    // 쿠키를 생성한다.
    var cookie = createCookie(token, X_AUTH_TOKEN_REFRESH, TICK_24HOUR * 14 / 1000);
    // 해더에 쿠키를 추가한다. Set-Cookie
    res.addCookie(cookie);
    // 토큰 String 값을 리턴
    return token;
  }
  // Access-Token 생성 함수
  public String createAccessToken(String id, HttpServletRequest req, HttpServletResponse res) {
    // token을 생성한다. 유효기간 10분
    var token = createToken(id, TICK_10MIN, ACCESS_KEY, req);
    // 쿠키를 생성한다.
    var cookie = createCookie(token, X_AUTH_TOKEN_ACCESS, TICK_10MIN / 1000);
    // 해더에 쿠키를 추가한다. Set-Cookie
    res.addCookie(cookie);
    // 토큰 String 값을 리턴
    return token;
  }
  // 해더로 쿠키를 만료시킨다.
  public void clearToken(HttpServletRequest req, HttpServletResponse res) {
    // 쿠키 데이터가 없으면 종료
    if (req.getCookies() == null) {
      return;
    }
    // Refresh-Token 쿠키 만료
    res.addCookie(this.createCookie(null, X_AUTH_TOKEN_REFRESH, 0));
    // Access-Token 쿠키 만료
    res.addCookie(this.createCookie(null, X_AUTH_TOKEN_ACCESS, 0));
  }
  // 토큰 생성 함수
  public String createToken(String id, long milisecond, String signature, HttpServletRequest req) {
    // Claims을 생성
    var claims = Jwts.claims().setId(id);
    // 현재 시간
    var now = new Date();
    // JWT 토큰을 만드는데, Payload 정보와 생성시간, 만료시간, 알고리즘 종류와 암호화 키를 넣어 암호화 함.
    return Jwts.builder()
               .setClaims(claims)
               .setIssuedAt(now)
               .setExpiration(new Date(now.getTime() + milisecond))
               .signWith(SignatureAlgorithm.HS256, signature).compact();
  }
  // String으로 된 토큰을 Jws&amp;lt;Claims&amp;gt; 타입으로 변환
  public Jws&amp;lt;Claims&amp;gt; parseToken(String jwt, String signature) {
    try {
      // 암호화 키로 복호화한다.
      // 암호화 키나 만료되었으면 에러가 발생
      return Jwts.parser()
                 .setSigningKey(signature)
                 .parseClaimsJws(jwt);
    } catch (SignatureException | ExpiredJwtException e) {
      // 에러가 발생하면 null을 리턴
      return null;
    }
  }
  // 토큰에서 id 값을 리턴
  public String getId(Jws&amp;lt;Claims&amp;gt; token) {
    return token.getBody().getId();
  }
  // 쿠키에서 Refresh-Token을 취득하는 함수
  public Jws&amp;lt;Claims&amp;gt; getRefreshToken(HttpServletRequest req) {
    // 쿠키에서 Refresh-Token을 취득한다.
    var cookie = this.getCookie(req, X_AUTH_TOKEN_REFRESH);
    // 쿠키에 데이터가 없으면
    if (cookie != null) {
      // Jws&amp;lt;Claims&amp;gt; 타입으로 변환
      var token = parseToken(cookie, SECRET_KEY);
      // 복호화가 되면
      if (token != null) {
        // 토큰 반환
        return token;
      }
    }
    // 그 외는 모두 null
    return null;
  }
  // 쿠키에서 Access-Token을 취득하는 함수
  public Jws&amp;lt;Claims&amp;gt; getAccessToken(HttpServletRequest req) {
    // 쿠키에서 Access-Token을 취득한다.
    var cookie = this.getCookie(req, X_AUTH_TOKEN_ACCESS);
    // 쿠키에 데이터가 없으면
    if (cookie != null) {
      // Jws&amp;lt;Claims&amp;gt; 타입으로 변환
      var token = parseToken(cookie, ACCESS_KEY);
      // 복호화가 되면
      if (token != null) {
        // 토큰 반환
        return token;
      }
    }
    // 그 외는 모두 null
    return null;
  }
  // key 명으로 쿠키 값을 취득
  private String getCookie(HttpServletRequest req, String key) {
    // 쿠키 값이 없으면 null
    if (req.getCookies() == null) {
      return null;
    }
    // 반복문으로
    for (var c : req.getCookies()) {
      // 키를 찾는다.
      if (key.equals(c.getName())) {
        // 쿠키 값 반환
        return c.getValue();
      }
    }
    // 없으면 null
    return null;
  }
  // 쿠키 생성합니다.
  private Cookie createCookie(String token, String key, int expire) {
    // 쿠키 생성 key-value
    Cookie cookie = new Cookie(key, token);
    // 쿠키 경로
    cookie.setPath(&quot;/&quot;);
    // Javascript에서는 읽을 수 없다.
    cookie.setHttpOnly(true);
    cookie.setSecure(true);
    // 만료 시간 설정
    cookie.setMaxAge(expire);
    // 쿠키 리턴
    return cookie;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기까지 JWT(Json web token)을 발행하고 값을 취득합니다. 그리고 쿠키에 등록, 삭제하는 클래스를 만들었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이걸로 Spring security 라이브러리에서 필터(filter) 설정 하겠습니다.&lt;/p&gt;
&lt;p&gt;이 부분도 아래의 블로그님들이 글을 많이 참조했습니다. 감사합니다.&lt;/p&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://kimchanjung.github.io/programming/2020/07/02/spring-security-02/&quot; target=&quot;_blank&quot;&gt;https://kimchanjung.github.io/programming/2020/07/02/spring-security-02/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://qiita.com/nyasba/items/f9b1b6be5540743f8bac&quot; target=&quot;_blank&quot;&gt;https://qiita.com/nyasba/items/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://www.baeldung.com/spring-security-login&quot; target=&quot;_blank&quot;&gt;https://www.baeldung.com/spring-security-login&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://catsbi.oopy.io/c0a4f395-24b2-44e5-8eeb-275d19e2a536&quot; target=&quot;_blank&quot;&gt;https://catsbi.oopy.io/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://tmdrl5779.tistory.com/78&quot; target=&quot;_blank&quot;&gt;https://tmdrl5779.tistory.com/78&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://fenderist.tistory.com/342&quot; target=&quot;_blank&quot;&gt;https://fenderist.tistory.com/342&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 pom.xml에 라이브러리를 추가합니다.&lt;/p&gt;
&lt;p&gt;레포지토리 - &lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security/2.6.4&quot; target=&quot;_blank&quot;&gt;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security/2.6.4&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkdzcP/btrv6kT6osH/MqvgwRk3VTgpa6Xvz4oJk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkdzcP/btrv6kT6osH/MqvgwRk3VTgpa6Xvz4oJk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkdzcP/btrv6kT6osH/MqvgwRk3VTgpa6Xvz4oJk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkdzcP%2Fbtrv6kT6osH%2FMqvgwRk3VTgpa6Xvz4oJk0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;xml&quot; data-type=&quot;pom.xml&quot;&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-security&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;2.6.4&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;SecurityConfig.java&quot;&gt;package com.example.demo.filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

// 어트리뷰트 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  // JWT 프로바이더를 의존성 주입으로 받는다.
  @Autowired
  private JwtProvider jwtProvider;
  // WebFilter 예외 패턴
  private String[] passUrl = new String[] { &quot;/login.html(.*)&quot;, &quot;/logout.html(.*)&quot;, &quot;/refresh.html(.*)&quot; };

  // 설정
  @Override
  protected void configure(HttpSecurity http) throws Exception {
  　　// 라이브러리의 기본 /login 페이지와 csrf 기능 끄기
    http.httpBasic().disable()
        .csrf().disable()
        
        // 세션 설정하고
        // Token 형식에서는 SessionCreationPolicy.STATELESS를 설정해 세션을 생성하지 않는다.
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        // 인증 처리
        // 예외 패턴 이외는 모두 인증 요구
        .authorizeRequests()
        .antMatchers(passUrl).permitAll()
        .anyRequest().authenticated()
        .and()
        // 필터 적용
        .addFilterAfter(new WebFilter(jwtProvider, passUrl), UsernamePasswordAuthenticationFilter.class);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에 소스를 보면 제가 passUrl를 사용했습니다.&lt;/p&gt;
&lt;p&gt;이게 제가 예전부터 사용하던 방법인데... 다른 블로그를 보니 HttpSecurity 인스턴스에서 깔끔하게 예외처리를 하던데.. 저는 어떻게 설정해도 필터가 깔끔하게 되지를 않네요..&lt;/p&gt;
&lt;p&gt;아무리 짱구를 굴려봐도 어떤 형식으로 필터가 걸리는 지 이해를 못하는 것 같네요.. 그래서 저는 위 방식으로 사용했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;WebFilter.java&quot;&gt;package com.example.demo.filter;

import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;
// WebFilter 클래스
public class WebFilter extends GenericFilterBean {

  // SecurityConfig 클래스에서 JWT 프로바이더와 passUrl 정보를 받는다.
  private JwtProvider jwtProvider;
  private String[] passUrl;
  // 생성자
  public WebFilter(JwtProvider jwtProvider, String[] passUrl) {
    this.jwtProvider = jwtProvider;
    this.passUrl = passUrl;
  }
  // 필터링
  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {

    var req = (HttpServletRequest) request;
    var res = (HttpServletResponse) response;
    // passUrl 정보로 인증이 필요없는 페이지는 넘긴다.
    String url = req.getRequestURI();
    for (String buf : passUrl) {
        if (url.matches(buf)) {
            chain.doFilter(req, res);
            return;
        }
    }
    // JWT 프로바이더에서 Access-Token을 취득한다.
    var access = jwtProvider.getAccessToken(req);
    // 만료가 되었거나 인증 정보가 맞지 않으면 null이 발생하기 때문에 인증이 되지 않는다.
    if (access != null) {
      // 인증이 되었으면 필터를 넘어갈 수 있게 인증 처리한다.
      SecurityContextHolder.getContext().setAuthentication(
          new UsernamePasswordAuthenticationToken(jwtProvider.getId(access), null, new ArrayList&amp;lt;&amp;gt;()));
    } else {
      // 인증 실패 코드 - 403 권한 없음
      res.setStatus(403);  
    }
    chain.doFilter(request, response);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기까지가 JWT 인증 처리입니다. 저도 Spring security 사양을 정확하게 잘 몰라서 많은 블로그와 글을 참조해서 작성했습니다. 근데 저는 꽤 심플하게 나왔네요...&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;실제 Controller에서 어떻게 움직이는지 확인해 봅시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;HomeController.java&quot;&gt;package com.example.demo.controller;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.example.demo.filter.JwtProvider;

// Session처럼 유저 정보 취득할 때 디비 접속을 줄이기 위해 유저 정보 클래스를 생성
class User implements Serializable {
  // 직렬화
  private static final long serialVersionUID = 1L;
  // 맴버 변수
  private String id;
  // getter
  public String getId() {
    return id;
  }
  // setter
  public void setId(String id) {
    this.id = id;
  }
  // User 클래스를 직렬화한다.
  public static String convertSerializable(User user) {
    try (var baos = new ByteArrayOutputStream()) {
      try (var oos = new ObjectOutputStream(baos)) {
        oos.writeObject(user);
        var data = baos.toByteArray();
        // byte 형식을 base64로 압축
        return Base64.getEncoder().encodeToString(data);
      }
    } catch (Throwable e) {
      e.printStackTrace();
      return null;
    }
  }
  // User 클래스를 역직렬화한다.
  public static User convertData(String code) {
    // base64 형식을 byte 형식으로 변환
    var data = Base64.getDecoder().decode(code);
    try (var bais = new ByteArrayInputStream(data)) {
      try (var ois = new ObjectInputStream(bais)) {
        Object objectMember = ois.readObject();
        return (User) objectMember;
      }
    } catch (Throwable e) {
      e.printStackTrace();
      return null;
    }
  }
}

// 컨트럴러 어트리뷰트
@Controller
// Controller 클래스
public class HomeController {
  // JWT 토큰 프로바이더
  @Autowired
  private JwtProvider jwtProvider;

  // 매핑 주소
  @RequestMapping(value = { &quot;/&quot;, &quot;/index.html&quot; })
  @ResponseBody // 예제를 위해 template를 사용하지 않는다.
  public String index(Model model, HttpServletRequest req, HttpServletResponse res) {
    // Access 토큰 취득
    var access = jwtProvider.getAccessToken(req);
    // user 직렬화 코드 취득
    var code = jwtProvider.getId(access);
    // 역직렬화
    var user = User.convertData(code);
    // id 값 출력
    return user.getId();
  }
  // 매핑 주소
  @RequestMapping(value = &quot;/login.html&quot;)
  @ResponseBody // 예제를 위해 template를 사용하지 않는다.
  public String login(Model model, HttpServletRequest req, HttpServletResponse res) {
    // Refresh 토큰을 생성
    jwtProvider.createRefreshToken(&quot;nowonbun&quot;, req, res);
    // User 인스턴스를 생성
    var user = new User();
    // id 설정
    user.setId(&quot;nowonbun&quot;);
    // 인스턴스를 직렬화 하여 Access 토큰을 생성
    jwtProvider.createAccessToken(User.convertSerializable(user), req, res);
    // 응답 코드 - 정상 200
    res.setStatus(200);
    return &quot;login&quot;;
  }
  // 매핑 주소
  @RequestMapping(value = &quot;/refresh.html&quot;)
  public void refresh(Model model, HttpServletRequest req, HttpServletResponse res) throws IOException {
    // Refresh 토큰을 취득
    var refresh = jwtProvider.getRefreshToken(req);
    if (refresh != null) {
      // User 인스턴스를 생성
      var user = new User();
      // Refresh 토큰에서 유저 id 취득
      user.setId(jwtProvider.getId(refresh));
      // Access 토큰 생성
      jwtProvider.createAccessToken(User.convertSerializable(user), req, res);
      // 응답 코드 - 정상 200
      res.setStatus(200);
      return;
    }
    // refresh 토큰이 없으면 에러! - 403 권한 없음
    res.setStatus(403);
  }
  // 매핑 주소
  @RequestMapping(value = &quot;logout.html&quot;)
  public void logout(Model model, HttpServletRequest req, HttpServletResponse res) throws IOException {
    // 쿠키 모두 삭제
    jwtProvider.clearToken(req, res);
    // 응답 코드 - 정상 200
    res.setStatus(200);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저는 Access 토큰에 User 클래스를 직렬화해서 넣었습니다. 이유는 사실 예전에 Session을 쓸 때도 클래스를 직렬화하여 넣은 다음, 유저 정보를 디비 접속 없이 세션에서 가져다 쓰곤 했습니다.&lt;/p&gt;
&lt;p&gt;그런데 JWT를 사용하면 id로 디비를 검색해야 하는 번거로움이 발생하기 떄문에 그냥 Session처럼 직렬화해서 넣었습니다. 저는 예제를 위해 직렬화만 했지만 실제로 사용하면 직렬화 코드를 한번 변조하는 것이 보안상 좋겠네요..&lt;/p&gt;
&lt;p&gt;이제 실행해 보도록 하겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4KvBo/btrvVLlTz3W/RKXvdk8ckkh3NtJT38ZbvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4KvBo/btrvVLlTz3W/RKXvdk8ckkh3NtJT38ZbvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4KvBo/btrvVLlTz3W/RKXvdk8ckkh3NtJT38ZbvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4KvBo%2FbtrvVLlTz3W%2FRKXvdk8ckkh3NtJT38ZbvK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;맨 처음 루트 페이지나 index.html를 접속하면 에러가 발생합니다.. 인증이 되지 않은 것입니다.&lt;/p&gt;
&lt;p&gt;로그인을 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A5y2W/btrv3geiipU/z4XbyTdfp67J8eVkeguaa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A5y2W/btrv3geiipU/z4XbyTdfp67J8eVkeguaa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A5y2W/btrv3geiipU/z4XbyTdfp67J8eVkeguaa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA5y2W%2Fbtrv3geiipU%2Fz4XbyTdfp67J8eVkeguaa0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/201WE/btrv45JFhkm/zdKqFcVN30gIIWParzsk6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/201WE/btrv45JFhkm/zdKqFcVN30gIIWParzsk6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/201WE/btrv45JFhkm/zdKqFcVN30gIIWParzsk6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F201WE%2Fbtrv45JFhkm%2FzdKqFcVN30gIIWParzsk6k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;정상 코드 200이 나왔고 Response에는 Set-Cookie 해더로 쿠기가 브라우저의 데이터에 입력되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다시 루트 페이지나 index.html로 접속을 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DImD6/btrv2Fk2FXK/S9QON1KL7TkG0LHEqeSec1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DImD6/btrv2Fk2FXK/S9QON1KL7TkG0LHEqeSec1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DImD6/btrv2Fk2FXK/S9QON1KL7TkG0LHEqeSec1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDImD6%2Fbtrv2Fk2FXK%2FS9QON1KL7TkG0LHEqeSec1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이번에는 정상 인증이 되어서 페이지가 나왔습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다시 Access-Token를 삭제해 봅시다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rNoVO/btrvX6QRlb2/B4cfC7Rnh8ESJcEJFRgHGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rNoVO/btrvX6QRlb2/B4cfC7Rnh8ESJcEJFRgHGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rNoVO/btrvX6QRlb2/B4cfC7Rnh8ESJcEJFRgHGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrNoVO%2FbtrvX6QRlb2%2FB4cfC7Rnh8ESJcEJFRgHGK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QQp99/btrvZHpxV2C/Son8hq1kzKEKLzVkLDPLq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QQp99/btrvZHpxV2C/Son8hq1kzKEKLzVkLDPLq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QQp99/btrvZHpxV2C/Son8hq1kzKEKLzVkLDPLq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQQp99%2FbtrvZHpxV2C%2FSon8hq1kzKEKLzVkLDPLq1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Refresh-Token이 있으나 루트 페이지는 인증 실패가 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다시 Refresh 페이지로 가서 Access-Token을 갱신합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lBgIc/btrvYIhSIQA/jx9SKs3T33IyPeQ8qGvKfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lBgIc/btrvYIhSIQA/jx9SKs3T33IyPeQ8qGvKfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lBgIc/btrvYIhSIQA/jx9SKs3T33IyPeQ8qGvKfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlBgIc%2FbtrvYIhSIQA%2Fjx9SKs3T33IyPeQ8qGvKfK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Access-Token이 갱신되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS2aPO/btrv0P9sQJu/yPHLAHaTOCJOmrfvGvCpx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS2aPO/btrv0P9sQJu/yPHLAHaTOCJOmrfvGvCpx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS2aPO/btrv0P9sQJu/yPHLAHaTOCJOmrfvGvCpx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS2aPO%2Fbtrv0P9sQJu%2FyPHLAHaTOCJOmrfvGvCpx0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;다시 루트 페이지가 열리는 것을 확인 할 수 있네요..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이번에는 로그아웃입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vRzGf/btrvVL7fy3w/cIQLsIawxAdWpJudzhvmlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vRzGf/btrvVL7fy3w/cIQLsIawxAdWpJudzhvmlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vRzGf/btrvVL7fy3w/cIQLsIawxAdWpJudzhvmlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvRzGf%2FbtrvVL7fy3w%2FcIQLsIawxAdWpJudzhvmlK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;쿠키 삭제되었습니다. Refresh페이지를 열어봅니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0MoOx/btrv4mSPbxh/Vtx362AtodEdDC8AkMbdwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0MoOx/btrv4mSPbxh/Vtx362AtodEdDC8AkMbdwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0MoOx/btrv4mSPbxh/Vtx362AtodEdDC8AkMbdwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0MoOx%2Fbtrv4mSPbxh%2FVtx362AtodEdDC8AkMbdwK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Refresh-Token이 없으니 에러가 발생합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 제가 구상한 대로 작성이 된 것 같네요.&lt;/p&gt;
&lt;p&gt;좀 이해하기 쉽게 모두 쿠키에서 작업을 했습니다만, 좀 더 보안을 엄격하게 하기 위해서 Access-Token의 경우는 해더로 데이터를 받고 Request는 form-data로 주고 받게 해서 브라우저 메모리에 남지 않게 하는 방법도 좋을 듯 싶네요.&lt;/p&gt;
&lt;p&gt;아니면 토큰에 좀 더 암호화 된 데이터를 넣어서 validate(검증)을 더 복잡하게 하는 방법도 있겠네요..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Java의 Spring boot에서 Web-Filter를 설정하는 방법(Spring Security)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Java</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/280</guid>
      <comments>https://nowonbun.tistory.com/280#entry280comment</comments>
      <pubDate>Tue, 15 Mar 2022 22:14:02 +0900</pubDate>
    </item>
    <item>
      <title>[Java] JWT(Json Web Token)을 발행, 확인하는 방법</title>
      <link>https://nowonbun.tistory.com/281</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Java에서 JWT(Json Web Token)을 발행, 확인하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;저는 지금까지 웹 환경에서 로그인, 로그아웃 기능을 만들 때 보통 세션을 이용한 방법으로 로그인, 로그아웃을 사용했습니다. 사실 최근까지도 그렇게 사용했습니다.&lt;/p&gt;
&lt;p&gt;세션에 정보를 넣는다 해도 쿠키의 세션 ID를 탈취하면 보안에 문제가 생기는 건 마찬가지지만 제가 알기로는 그나마 가장 보편적으로 사용하는 인증 방식이지 않을까 생각합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;최근에 프로젝트 형식이 마이크로 서비스 아키틱처에서 모듈별로 서버를 분할하거나 대용량 트래픽에 맞추어서 웹 서버 로드 밸런싱으로 여러 서버에 트래픽 분산 형식으로 많이 작성합니다.&lt;/p&gt;
&lt;p&gt;그럴 경우 문제가 로그인 세션을 어떻게 구성하는 것입니다. 가장 많이 사용하는 방법으로는 하나의 세션 서버를 만들어서 Redis 데이터베이스를 설치하고 각 서버에서 Redis 서버에 세션 체크를 하는 것으로 대응이 가능합니다.&lt;/p&gt;
&lt;p&gt;그런데 이것도 만능이 아니라서 마이크로 서비스로 웹 서버를 극단적으로 분할을 하게 된다면 세션 서버의 부하가 걸리고 여러가지 이유가 있겠네요..&lt;/p&gt;
&lt;p&gt;사실 저의 경우는 그런 상황에서 JWT를 사용한 건 아니고 프로트엔드와 백엔드의 작업을 분리하는 과정에서 보안을 생각했을 때 좀 더 효과적으로 로그인 관리 기능을 사용하는 방법이 없을까 고민하던 차에 JWT라는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://jwt.io/introduction&quot; target=&quot;_blank&quot;&gt;https://jwt.io/introduction&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;JWT의 기능을 이해하는데 저도 좀 시간이 걸렸습니다. 사실 생각해 보면 굉장히 단순한 논리였는데.. 로그인 정보를 서버에 두어야 한다는 고정관념 때문인가 이게 과연 보안이 유용할까를 계속 고민했던 것 같습니다.&lt;/p&gt;
&lt;p&gt;사실 인증만 된다고 하면 그 정보를 굳이 서버의 세션에 둘 필요는 없었는데.. 생각해 보면 지금까지 그렇게 비효율적으로 로그인 정보를 두었을까 생각이 되네요...&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5dJXJ/btrvS7brBLn/CMZgmkDlGM777PuksKVcDk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5dJXJ/btrvS7brBLn/CMZgmkDlGM777PuksKVcDk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5dJXJ/btrvS7brBLn/CMZgmkDlGM777PuksKVcDk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5dJXJ%2FbtrvS7brBLn%2FCMZgmkDlGM777PuksKVcDk%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://ansibytecode.com/jwt-peek-into-the-jargon-java-web-token/&quot; target=&quot;_blank&quot;&gt;https://ansibytecode.com/jwt-peek-into-the-jargon-java-web-token/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;JWT는 위 이미지처럼 XXXXX.XXXXX.XXXXX의 구조로 되어있습니다.&lt;/p&gt;
&lt;p&gt;먼저 Header는 토큰 타입이나 알고리즘 정보에 대해서 설정되어 있습니다. &lt;/p&gt;
&lt;p&gt;그리고 Payload는 세션처럼 사용할 정보가 담겨져 있습니다. 물론 여기에는 유저 정보가 담겨져 있으면 안됩니다. 이름이나 아이디 정도는 괜찮치만 패스워드나 개인정보가 있으면 탈취당할 수 있겠네요.&lt;/p&gt;
&lt;p&gt;마지막 Signature는 토큰 정보가 맞는 정보인지 확인하는 코드가 담겨져 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;저는 이것을 주민등록증으로 생각하고 이해했습니다. 사실 그렇게 생각하니 모든게 쉽게 이해가 되더라고요.&lt;/p&gt;
&lt;p&gt;우리 주민등록증의 경우는 앞에 생년월일이 있고, 다음에는 구분자, 즉 남성 여성, 태어난 지역과 등록된 동사무소 번호, 등록한 순번까지의 정보가 있습니다. 그러고 가장 마지막 번호가 Signature인 셈입니다. 즉, 우리가 주민 등록 번호를 조회하지 않더라도 번호만으로 일단 맞는 번호인지 아닌지를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;JWT도 Signature 정보로 맞는 토큰인지 아닌지를 확인할 수 있습니다. 즉, 임의로 변조해서는 사용할 수 없게 해 놓은 것입니다.&lt;/p&gt;
&lt;p&gt;이런 기능을 이용하게 된다면 쿠키에 SESSION-ID를 넣어서 서버의 세션를 이용해서 로그인이 되었는지 확인 할 필요가 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;즉, 단순하게 JWT로 이 토큰은 우리가 사용하는 서비스에서 유용한 토큰인지 확인할 수 있고, 좀 더 보안을 강화하고 싶으면 발행했을 때의 Signature를 Redis의 데이터베이스에 넣어서 확인하는 것으로 정확한 로그인 여부를 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;JWT를 Java에서 어떻게 Token를 생성하는지 그리고 그 값이 맞는 값인지 확인하기 위한 방법에 대해 소개하겠습니다.&lt;/p&gt;
&lt;p&gt;사실 이 JWT는 Web 환경에서 사용하는 것인데 웹 환경의 설정과 복합적으로 설명하게 되면 복잡하게 되지 단순하게 콘솔에서 발행 비교하는 것으로 설명해서 그것을 로그인과 web-filter에 대신 사용하게 되면 좋지 않을까 싶네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 Java에서 JWT를 사용하기 위해서는 maven으로 라이브러리를 하나 연결해야 합니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt/0.9.1&quot; target=&quot;_blank&quot;&gt;https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt/0.9.1&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FspsS/btrvW4EOEsO/ast3Hu2rEATsIajcnlf8k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FspsS/btrvW4EOEsO/ast3Hu2rEATsIajcnlf8k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FspsS/btrvW4EOEsO/ast3Hu2rEATsIajcnlf8k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFspsS%2FbtrvW4EOEsO%2Fast3Hu2rEATsIajcnlf8k0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;xml&quot; data-type=&quot;pom.xml&quot;&gt;&amp;lt;dependencies&amp;gt;
  &amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;io.jsonwebtoken&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jjwt&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;0.9.1&amp;lt;/version&amp;gt;
  &amp;lt;/dependency&amp;gt;
  &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;javax.xml.bind&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;jaxb-api&amp;lt;/artifactId&amp;gt;
      &amp;lt;version&amp;gt;2.1&amp;lt;/version&amp;gt;
  &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;제가 위 소스에 javax.xml.bind도 추가했습니다만 이게 xml를 분석, 생성하는 라이브러리입니다. Web환경에서는 굳이 선언하지 않아도 web 라이브러리에 포함되어 있는 라이브러리입니다만, 콘솔에서는 없으니깐 따로 xml를 분석하는 라이브러리가 없이니 의존성으로 선언을 했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Program.java&quot;&gt;package jwtTest;

import java.util.Date;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

// main 함수가 있는 클래스
public class Program {
  // 암호화하기 위한 키
  private static String SECRET_KEY = &quot;secret&quot;;
  // JWT 만료 시간 1시간
  private static long tokenValidMilisecond = 1000L * 60 * 60;
  // 실행 함수
  public static void main(String[] args) {
    // Program 인스턴스 생성
    var p = new Program();
    // JWT 토큰 생성 - id는 nowonbun
    var token = p.createToken(&quot;nowonbun&quot;);
    // 콘솔 출력
    System.out.println(token);
    // JWT 토큰 복호화
    var claims = p.getClaims(token);
    // JWT 토큰 검증
    if (claims != null &amp;&amp; p.validateToken(claims)) {
      // id를 취득한다.
      var id = p.getKey(claims);
      // Payload 값의 test 키의 값을 취득
      var test = p.getClaims(claims, &quot;test&quot;);
      // 콘솔 출력
      System.out.println(id);
      System.out.println(test);
    } else {
      // 토큰이 정합성이 맞지 않으면
      System.out.println(&quot;error&quot;);
    }
  }
  // 토큰 생성 함수
  public String createToken(String key) {
    // Claims을 생성
    var claims = Jwts.claims().setId(key);
    // Payload 데이터 추가
    claims.put(&quot;test&quot;, &quot;Hello world&quot;);
    // 현재 시간
    Date now = new Date();
    // JWT 토큰을 만드는데, Payload 정보와 생성시간, 만료시간, 알고리즘 종류와 암호화 키를 넣어 암호화 함.
    return Jwts.builder()
               .setClaims(claims)
               .setIssuedAt(now)
               .setExpiration(new Date(now.getTime() + tokenValidMilisecond))
               .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
               .compact();
  }
  // String으로 된 코드를 복호화한다.
  public Jws&amp;lt;Claims&amp;gt; getClaims(String jwt) {
    try {
      // 암호화 키로 복호화한다.
      // 즉 암호화 키가 다르면 에러가 발생한다.
      return Jwts.parser()
                 .setSigningKey(SECRET_KEY)
                 .parseClaimsJws(jwt);
    }catch(SignatureException e) {
      return null;
    }
  }
  // 토큰 검증 함수
  public boolean validateToken(Jws&amp;lt;Claims&amp;gt; claims) {
    // 토큰 만료 시간이 현재 시간을 지났는지 검증
    return !claims.getBody()
                  .getExpiration()
                  .before(new Date());
  }
  // 토큰을 통해 Payload의 ID를 취득
  public String getKey(Jws&amp;lt;Claims&amp;gt; claims) {
    // Id 취득
    return claims.getBody()
                 .getId();
  }
  // 토큰을 통해 Payload의 데이터를 취득
  public Object getClaims(Jws&amp;lt;Claims&amp;gt; claims, String key) {
    // 데이터 취득
    return claims.getBody()
                 .get(key);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bN61SR/btrvYwniF9a/JrICK2JI3e1ERCs1Ray0Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bN61SR/btrvYwniF9a/JrICK2JI3e1ERCs1Ray0Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bN61SR/btrvYwniF9a/JrICK2JI3e1ERCs1Ray0Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbN61SR%2FbtrvYwniF9a%2FJrICK2JI3e1ERCs1Ray0Qk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;먼저 JWT 암호화 과정에서 SECRET_KEY라는 것이 필요합니다. 이 SECRET_KEY란 고유의 암호화 키로 SECRET_KEY가 다른 PC에서 암호화된 토큰 데이터를 가지고 있으면 검증 과정에서 에러가 발생하게 됩니다.&lt;/p&gt;
&lt;p&gt;즉, SECRET_KEY만 탈취되지 않으면 주민등록증처럼 TOKEN 키를 사용할 수 있는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 토큰을 가지고 복호화를 해보곘습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://jwt.io&quot; target=&quot;_blank&quot;&gt;https://jwt.io&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dV20CT/btrvW4Y4AEl/xMvu239FXBx9ZFnPi3rR5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dV20CT/btrvW4Y4AEl/xMvu239FXBx9ZFnPi3rR5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dV20CT/btrvW4Y4AEl/xMvu239FXBx9ZFnPi3rR5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdV20CT%2FbtrvW4Y4AEl%2FxMvu239FXBx9ZFnPi3rR5k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;jwt 사이트에서 복호화를 해보니 우리가 넣었던 데이터가 표시가 됩니다. 마지막 Signature 검증은 SECRET_KEY를 우리만 알고 있기 때문에 올바른 키인지 검사는 프로그램 상에서만 확인이 됩니다.&lt;/p&gt;
&lt;p&gt;여기까지가 일단 JWT 토큰을 만들고 검증하는 과정입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;사실 이것을 그대로 사용하기에는 조금 위험성이 있습니다. 왜냐하면 Payload 값이 그대로 보이기 때문입니다.&lt;/p&gt;
&lt;p&gt;우리가 로그인 정보를 세션에 넣을때 단순하게 id만 넣는 것이 아니고 유저 정보도 넣는 경우가 많이 있습니다. 즉, 너무 민감한 정보를 넣는 것은 위험성이 있지만 간단한 정보 정도는 넣고 싶네요.&lt;/p&gt;
&lt;p&gt;그러나 Payload의 값은 위처럼 사이트에서 복호화가 가능하기 때문에 좀 더 암호화가 필요합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Program.java&quot;&gt;package jwtTest;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Base64;
import java.util.Date;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

// User 클래스 (직렬화 인터페이스 상속)
class User implements Serializable {
  private static final long serialVersionUID = 1L;
  // 맴버 변수
  private String id;
  private String userName;
  // setter
  public void setId(String id) {
    this.id = id;
  }
  public void setUserName(String userName) {
    this.userName = userName;
  }
  // 맴버 변수 출력 함수
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;id = &quot; + this.id + &quot; userName = &quot; + this.userName);
  }
}

// main 함수가 있는 클래스
public class Program {
  // 암호화하기 위한 키
  private static String SECRET_KEY = &quot;secret&quot;;
  // JWT 만료 시간 1시간
  private static long tokenValidMilisecond = 1000L * 60 * 60;

  // 실행 함수
  public static void main(String[] args) {
    // 설정한 암호화키를 Base64로 암호화한다.
    SECRET_KEY = Base64.getEncoder().encodeToString(SECRET_KEY.getBytes());
    // User 인스턴스 생성
    var user = new User();
    // Id 설정
    user.setId(&quot;nowonbun&quot;);
    // UserName 설정
    user.setUserName(&quot;hello world&quot;);
    // Program 인스턴스 생성
    var p = new Program();
    // User 인스턴스를 직렬화한다.
    var code = p.convertSerializable(user);
    // 직렬화된 코드에 1 코드를 추가한다.
    code = &quot;1&quot; + code;
    // JWT 토큰 생성
    var token = p.createToken(code);
    // 콘솔 출력
    System.out.println(token);
    // JWT 토큰 복호화
    var claims = p.getClaims(token);
    // JWT 토큰 검증
    if (claims != null &amp;&amp; p.validateToken(claims)) {
      // id를 취득한다.
      var id = p.getKey(claims);
      // 콘솔 출력
      System.out.println(id);
      // 1코드를 제거
      id = id.substring(1);
      // 역직렬화
      user = p.convertData(id);
      // 콘솔 출력
      user.print();
    } else {
      // 토큰이 정합성이 맞지 않으면
      System.out.println(&quot;error&quot;);
    }
  }
  // 직렬화 함수
  public String convertSerializable(User user) {
    try (var baos = new ByteArrayOutputStream()) {
      try (var oos = new ObjectOutputStream(baos)) {
        oos.writeObject(user);
        // 직렬화 코드
        var data = baos.toByteArray();
        // 직렬화된 것은 Base64로 암호화
        return Base64.getEncoder().encodeToString(data);
      }
    } catch (Throwable e) {
      e.printStackTrace();
      return null;
    }
  }
  // 역직렬화 함수
  public User convertData(String code) {
    // Base64 복호화
    var data = Base64.getDecoder().decode(code);
    // 역직렬화
    try (var bais = new ByteArrayInputStream(data)) {
      try (var ois = new ObjectInputStream(bais)) {
        Object objectMember = ois.readObject();
        // User 인스턴스로 캐스팅
        return (User) objectMember;
      }
    } catch (Throwable e) {
      e.printStackTrace();
      return null;
    }
  }

  // 토큰 생성 함수
  public String createToken(String key) {
    // Claims을 생성
    var claims = Jwts.claims().setId(key);
    // 현재 시간
    Date now = new Date();
    // JWT 토큰을 만드는데, Payload 정보와 생성시간, 만료시간, 알고리즘 종료와 암호화 키를 넣어 암호화 함.
    return Jwts.builder()
               .setClaims(claims)
               .setIssuedAt(now)
               .setExpiration(new Date(now.getTime() + tokenValidMilisecond))
               .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
               .compact();
  }

  // String으로 된 코드를 복호화한다.
  public Jws&amp;lt;Claims&amp;gt; getClaims(String jwt) {
    try {
      // 암호화 키로 복호화한다.
      // 즉 암호화 키가 다르면 에러가 발생한다.
      return Jwts.parser()
                 .setSigningKey(SECRET_KEY)
                 .parseClaimsJws(jwt);
    } catch (SignatureException e) {
      return null;
    }
  }

  // 토큰 검증 함수
  public boolean validateToken(Jws&amp;lt;Claims&amp;gt; claims) {
    // 토큰 만료 시간이 현재 시간을 지났는지 검증
    return !claims.getBody()
                  .getExpiration()
                  .before(new Date());
  }

  // 토큰을 통해 Payload의 ID를 취득
  public String getKey(Jws&amp;lt;Claims&amp;gt; claims) {
    // Id 취득
    return claims.getBody().getId();
  }

  // 토큰을 통해 Payload의 데이터를 취득
  public Object getClaims(Jws&amp;lt;Claims&amp;gt; claims, String key) {
    // 데이터 취득
    return claims.getBody().get(key);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQ8KQG/btrvXVt5Fq9/qSbatgp8rJK8tawzRUl6i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQ8KQG/btrvXVt5Fq9/qSbatgp8rJK8tawzRUl6i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQ8KQG/btrvXVt5Fq9/qSbatgp8rJK8tawzRUl6i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQ8KQG%2FbtrvXVt5Fq9%2FqSbatgp8rJK8tawzRUl6i0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;User 클래스를 만들어서 직렬화하여 JWT의 id에 넣습니다. 그럼 직접적으로 정보를 해독할 수 는 없습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sbAEo/btrvZ4DPcF2/N8v73hFWf5vh4VK3pTksVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sbAEo/btrvZ4DPcF2/N8v73hFWf5vh4VK3pTksVK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sbAEo/btrvZ4DPcF2/N8v73hFWf5vh4VK3pTksVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsbAEo%2FbtrvZ4DPcF2%2FN8v73hFWf5vh4VK3pTksVK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그런데 눈썰미가 있는 사람은 뒤에 ==가 있는 것으로 이건 Base64코드라는 것을 알 수 있습니다. 즉, Base64를 복호화하면 알 수 있지 않을까하는 것입니다. 그러서 제가 임의의 데이터 1를 추가했습니다. 즉, 단순하게는 Base64 복호화가 안됩니다.&lt;/p&gt;
&lt;p&gt;사실 저는 그냥 1를 넣었기 때문에 눈치 빠른 사람은 알 수 있겠지만.. 위 코드를 Ascii코드로 1번째부터 5번째가 반전 데이터를 넣으면 분석하기 어렵습니다. 특히 위 데이터는 Java　클래스를 직렬화로 되어 있기 때문에, 이 정도의 암호화 작업이면 사양을 알지 않는 이상 복호화가 힘들겠네요..&lt;/p&gt;
&lt;p&gt;이렇게 한다면 로그인 정보를 세션에 넣지 않아도 여러 서버에서 SECRET_KEY와 암복호화 과정만 일치시킨다면 세션 클러스터링을 하지 않아도 정보를 공유하지 않을까 싶네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Java에서 JWT(Json Web Token)을 발행, 확인하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Development note/Java</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/281</guid>
      <comments>https://nowonbun.tistory.com/281#entry281comment</comments>
      <pubDate>Mon, 14 Mar 2022 19:08:46 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 61. Spring boot에서 Redis 데이터베이스를 이용한 세션 클러스터링 설정하는 방법</title>
      <link>https://nowonbun.tistory.com/309</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Spring boot에서 Redis 데이터베이스를 이용한 세션 클러스터링 설정하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전 글에서 Apache와 Spring boot의 Tomcat을 연결하여 로드벨런싱하는 방법에 대해서 설명했습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/355&quot; target=&quot;_blank&quot;&gt;[Java] 60. Spring boot에서 Apache 연결과 로드벨런싱을 설정하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;로드벨런싱은 일단 웹 환경에서 많은 트래픽(접속)이 발생하면 웹 서버도 브라우져(클라이언트)와 소켓 통신을 하는 커넥션이 있는 리소스이기 때문에 동시에 발생하면 그 접속과 응답이 느려질 수 밖에 없습니다. 또 웹 서버 환경에 요청하는 처리가 복잡하면 복잡해 질수록 더더욱 느려집니다.&lt;/p&gt;
&lt;p&gt;그래서 접속 캐싱은 Apache에서 관리하고, 동적 웹 처리(Html 파싱 처리)는 어플리케이션 서버(WAS:Tomcat)에서 관리하는 것으로 나누고, 또 그 부하가 많아 질 경우 Tomcat을 여러 개로 나누어서 로드벨런싱으로 관리하는 것으로 대용량 트래픽을 처리합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;일단 이전 글에서 간단하게 설명했습니다만 Apache에서 같은 Session이라면 같은 Tomcat을 요청하게 끔 설정을 합니다만, 상황에 따라서 1번 Tomcat에서 요청 응답하던 커넥션이 2번 Tomcat으로 넘어가는 경우도 발생할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그 상황이란 것이 갑자기 1번 Tomcat이 서버 다운이 발생하던가 갑자기 한쪽으로 대용량 트래픽이 발생하면 Apache로 이동할 수 있습니다. 그 외에도 여러 가지 상황이 있습니다만, 대표적인 것은 이 두 가지이지 않을까 싶습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 문제가 발생하는 것이 세션입니다.&lt;/p&gt;
&lt;p&gt;세션이라는 것은 간단하게 설명하겠습니다.
&lt;p&gt;브라우저에서 각 사이트에 접속할 때마다 로컬에서 보관하고 있는 데이터 쿠키라는 것이 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oEioh/btruNWHmElX/St3z0pxnd6UQUYMMUoQqY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oEioh/btruNWHmElX/St3z0pxnd6UQUYMMUoQqY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oEioh/btruNWHmElX/St3z0pxnd6UQUYMMUoQqY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoEioh%2FbtruNWHmElX%2FSt3z0pxnd6UQUYMMUoQqY1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnwVny/btruNV9xY7A/kNzrKyzWk6wnkJuM4pGaFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnwVny/btruNV9xY7A/kNzrKyzWk6wnkJuM4pGaFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnwVny/btruNV9xY7A/kNzrKyzWk6wnkJuM4pGaFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnwVny%2FbtruNV9xY7A%2FkNzrKyzWk6wnkJuM4pGaFK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 헤더 정보는 제가 브라우저에서 제 블로그를 접속했을 때의 접속 정보입니다. 여기서는 쿠키 값이 알고리즘으로 암호화되어 있습니다.&lt;/p&gt;
&lt;p&gt;암호화가 되어 있어도 이 쿠키라는 값을 브라우저에서 보여지게 됩니다. 이것을 왜 브라우저에서 보관하게 되냐고 하면 브라우저의 역사 내용까지 나오기 때문에 설명하기 어렵습니다만, 간단하게 설명하면 웹 프로토콜 사양은 소켓 비동기 형태, 즉 소켓으로 요청할 때 접속하고 응답 받으면 접속을 끊는 형태입니다.&lt;/p&gt;
&lt;p&gt;즉, 웹 서핑을 할 때, 웹 서버와 계속 연결하는 것이 아니고 페이지 이동할 때만 서버에 요청해서 데이터를 가져오는 형태입니다. 그런데 웹 서버에 접속하는 사람이 나 혼자가 아니고 여러 명이 동시에 접속 한다고 하면 누구의 요청인지 알 수가 없습니다.&lt;/p&gt;
&lt;p&gt;그래서 요청할 때마다 데이터를 보내 구분을 하기 위해서 쿠키라는 게 있는 것인데.. 이 쿠키가 문제가 보안이 매우 좋지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위 이미지에서도 브라우저 개발 모드로 가면 값이 다 보입니다. 그래서 그 값들을 클라이언트(브라우저 유저)에게 보이지 않게 사용하려고 무작위의 유니크 문자열를 생성해서 쿠키 키로 설정하고 서버의 메모리 혹은 파일로 데이터를 보관하는 형태를 세션이라고 합니다.&lt;/p&gt;
&lt;p&gt;세션이라는 것은 Tomcat 서버는 메모리에 데이터를 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 로드밸런싱의 상황에서 유저가 1번 Tomcat으로 접속해서 세션 정보, 로그인 정보를 가지고 있는 상황에서 2번 Tomcat으로 넘어가는 상황이 발생하면 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;그렇습니다. 2번 Tomcat에서는 세션을 가지고 있지 않기 때문에 로그인이 풀려 버립니다..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 이 세션 정보를 1번 Tomcat과 2번 Tomcat, 여러 대의 경우는 모든 Tomcat의 세션을 공유해야 합니다. 그것을 세션 클러스터링이라고 합니다.&lt;/p&gt;
&lt;p&gt;세션 클러스터링의 방법은 여러가지가 있습닌다. 공유 파일 서버를 만들어서 파일로 세션을 관리할 수도 있고 데이터베이스도 있고 여러가지 방법이 있습니다만, 로드밸런싱의 단계까지 왔으면 이미 대용량 트래픽이라는 조건까지 도달한 상황일 것입니다.&lt;/p&gt;
&lt;p&gt;즉, 공유 세션에 값을 추가, 수정, 취득의 요청, 응답이 빨라야 하고 시간대별로 그 처리 기준이 정확한 동기화가 되어야 합니다.(즉, 데이터베이스의 자체 처리 속도로 약 0.1초 전에 요청한 처리(수정)가 이루어지지 않고 다른 서버의 취득 요청이 0.2초전의 데이터를 취득해 버리는 형태)&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이러한 많은 조건을 충족해주는 데이터베이스는 Redis입니다.&lt;/p&gt;
&lt;p&gt;Redis 데이터베이스가 만능은 아닙니다만, 개인적으로 세션 클러스터링에서는 가장 빠르고 정확하다고 생각됩니다. 개인적인 생각이니 다른 의견이 물론 있을 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Redis 데이터베이스를 설치하는 방법과 사용 방법에 대해서는 다른 글에서 소개한 적이 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/384&quot; target=&quot;_blank&quot;&gt;[CentOS] Redis 데이터베이스 설치와 명령어 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이제 Spring boot에서 Redis를 사용하기 위해서는 먼저 Spring boot 위자드에서 Redis 라이브러리를 추가하던가 pom.xml를 추가하여야 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n07yt/btruMoRExbg/1sadgn7yOFYdhTZ1zj5QK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n07yt/btruMoRExbg/1sadgn7yOFYdhTZ1zj5QK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n07yt/btruMoRExbg/1sadgn7yOFYdhTZ1zj5QK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn07yt%2FbtruMoRExbg%2F1sadgn7yOFYdhTZ1zj5QK1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;참고로 이게 이클립스 버그인지는 잘 모르겠으나, 위자드로 하면 기존에 선택한 것들을 전부 다시 선택해야 합니다. 이전에 선택하던　것을 선택안하고 Finish를 누르면 기존 라이브러리가 전부 빠지는 현상이 발생합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj7o3l/btruwIKhE0v/sEbmKlSsJgdthv3KY7dA90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj7o3l/btruwIKhE0v/sEbmKlSsJgdthv3KY7dA90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj7o3l/btruwIKhE0v/sEbmKlSsJgdthv3KY7dA90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj7o3l%2FbtruwIKhE0v%2FsEbmKlSsJgdthv3KY7dA90%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/du8lU6/btruE8O6kxV/iUvhgLpXDAEkeRyBmve6C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/du8lU6/btruE8O6kxV/iUvhgLpXDAEkeRyBmve6C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/du8lU6/btruE8O6kxV/iUvhgLpXDAEkeRyBmve6C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdu8lU6%2FbtruE8O6kxV%2FiUvhgLpXDAEkeRyBmve6C1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;pom.xml에 라이브러리가 추가되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;또는 위자드가 아닌 직접 pom.xml에 추가하는 방법도 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis&quot; target=&quot;_blank&quot;&gt;https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lbvSY/btruQq8XLHO/rSSVVvAoKcWgO9XgC0VnYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lbvSY/btruQq8XLHO/rSSVVvAoKcWgO9XgC0VnYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lbvSY/btruQq8XLHO/rSSVVvAoKcWgO9XgC0VnYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlbvSY%2FbtruQq8XLHO%2FrSSVVvAoKcWgO9XgC0VnYk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;xml&quot; data-type=&quot;pom.xml&quot;&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-boot-starter-data-redis&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 우리는 Spring boot에서 단지 Redis를 사용하는 것이 아니고 세션 클러스터링으로 사용할 것입니다. 그래서 세션 클러스러링 라이브러리도 추가합니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis&quot; target=&quot;_blank&quot;&gt;https://mvnrepository.com/artifact/org.springframework.session/spring-session-data-redis&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjOTX0/btruQlNqbF4/C585tk08vkBfu4ErroFDO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjOTX0/btruQlNqbF4/C585tk08vkBfu4ErroFDO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjOTX0/btruQlNqbF4/C585tk08vkBfu4ErroFDO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjOTX0%2FbtruQlNqbF4%2FC585tk08vkBfu4ErroFDO0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;xml&quot; data-type=&quot;pom.xml&quot;&gt;&amp;lt;dependency&amp;gt;
  &amp;lt;groupId&amp;gt;org.springframework.session&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;spring-session-data-redis&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;maven 라이브러리 추가는 끝났습니다.&lt;/p&gt;
&lt;p&gt;이제 실제 프로젝트에서 session을 Redis 데이터베이스로 사용할 수 있겠끔 설정을 하겠습니다.&lt;/p&gt;
&lt;p&gt;Spring boot의 main 함수가 있는 클래스에 @EnableRedisHttpSession 어노테이션을 추가하는 것으로 세션을 Redis 데이터베이스에서 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;SpringBootTestApplication.java&quot;&gt;package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

// Spring boot의 base 패키지
@SpringBootApplication(scanBasePackages = &quot;com.example.demo.*&quot;)
// Redis 세션 사용
@EnableRedisHttpSession
// 클래스
public class SpringBootTestApplication {
  // 메인 함수
  public static void main(String[] args) {
    // 실행
    SpringApplication.run(SpringBootTestApplication.class, args);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;소스 설정이 완료되었으면 application.properties에 Redis 데이터베이스 정보를 설정합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot; data-type=&quot;application.properties&quot;&gt;# redis 데이터베이스가 있는 서버 ip
spring.redis.host=localhost
# redis 데이터베이스가 있는 서버 port
spring.redis.port=6379
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;저는 localhost에 설치되어 있는 것이 아니고 다른 서버에 있는 관계로 ip 주소를 넣었습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KbvY5/btruIUX4Y29/1oQGjoGt3BUk4CytYbcdK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KbvY5/btruIUX4Y29/1oQGjoGt3BUk4CytYbcdK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KbvY5/btruIUX4Y29/1oQGjoGt3BUk4CytYbcdK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKbvY5%2FbtruIUX4Y29%2F1oQGjoGt3BUk4CytYbcdK1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 기존 프로젝트에는 그냥 화면에 hello world 표시 밖에 없기 때문에 세션을 넣는 코드를 작성해 보도록 하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;HomeController.java&quot;&gt;package com.example.demo.Controller;

import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

// 컨트럴러 어트리뷰트
@Controller
// Controller 클래스
public class HomeController {
  // application.properties에서 설정 값을 받아온다.
  @Value(&quot;${tomcat.ajp.port}&quot;)
  private int port;

  // 매핑 주소
  @RequestMapping(value = { &quot;/&quot;, &quot;/index.html&quot; })
  public String index(Model model, HttpSession session) {
    // 데이터를 템플릿에 전달한다.
    model.addAttribute(&quot;data&quot;, session.getAttribute(&quot;session&quot;));
    // 템플릿 파일명
    return &quot;Home/index&quot;;
  }
  // 매핑 주소
  @RequestMapping(value = { &quot;/setSessionData.json&quot; })
  // String 데이터를 리턴한다.
  @ResponseBody
  public String setSessionData(@RequestParam(&quot;data&quot;) String data, Model model, HttpSession session) {
    // 세션에 값을 등록
    session.setAttribute(&quot;session&quot;, &quot;(&quot; + port + &quot;)&quot; + data);
    // 결과 json 값
    return &quot;{\&quot;result\&quot;:\&quot;OK\&quot;}&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;메인 화면에서는 세션 값이 있으면 session의 키로 값을 취득해서 화면에 출력합니다.&lt;/p&gt;
&lt;p&gt;그리고 setSessionData.json의 주소로 비동기 형태의 data값을 받습니다. 그리고 session의 키로 데이터를 세션에 저장합니다. &lt;/p&gt;
&lt;p&gt;위 사양에 맞추어서 html 파일도 수정합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;html&quot; data-type=&quot;index.html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Insert title here&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  Session result :
  &amp;lt;span th:text=&quot;${data}&quot;&amp;gt;message&amp;lt;/span&amp;gt;
  &amp;lt;br /&amp;gt;
  &amp;lt;br /&amp;gt;
  &amp;lt;input type=&quot;text&quot; id=&quot;sessionText&quot;&amp;gt;
  &amp;lt;button id=&quot;updateBtn&quot;&amp;gt;Update Session Data&amp;lt;/button&amp;gt;
  &amp;lt;script&amp;gt;
  document.addEventListener('DOMContentLoaded',() =&amp;gt; {
    document.getElementById(&quot;updateBtn&quot;).addEventListener(&quot;click&quot;, ()=&amp;gt;
    {
      // ajax를 하기 위한 XmlHttpRequest 객체
      let xhttp = new XMLHttpRequest();
      // XmlHttpRequest의 요청
      xhttp.onreadystatechange = (e)=&amp;gt;{
        // XMLHttpRequest를 이벤트 파라미터에서 취득
        let req = e.target;
        // 통신 상태가 완료가 되면.
        if(req.readyState === XMLHttpRequest.DONE) {
          // Http response 응답코드가 200(정상)이면
          if(req.status === 200) {
            // json 타입이므로 object 형식으로 변환
            console.log(JSON.parse(req.responseText));
          }
        }
      }
      // http 요청 타입과 주소, 동기식 여부
      xhttp.open(&quot;POST&quot;, &quot;setSessionData.json&quot;, false);
      // form 형식
      xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
      // http 요청
      xhttp.send(&quot;data=&quot; + document.getElementById(&quot;sessionText&quot;).value);
    });
  });
  &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;최초 span에는 session에서 취득해 온 값을 출력하고 그 다음 줄에는 TextBox와 Button로 setSessionData.json 비동기 주소로 세션을 등록합니다.&lt;/p&gt;
&lt;p&gt;그런 후에 페이지 재요청하면 화면에 세션 값이 출력되는 형태로 만들어 집니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxNqVp/btruNjhZtfZ/mkcwWBZD5HhMvXs9V8JGGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxNqVp/btruNjhZtfZ/mkcwWBZD5HhMvXs9V8JGGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxNqVp/btruNjhZtfZ/mkcwWBZD5HhMvXs9V8JGGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxNqVp%2FbtruNjhZtfZ%2FmkcwWBZD5HhMvXs9V8JGGK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여기에서 텍스트에 값을 넣고 버튼을 누릅니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0Ts7x/btruI6pUgcG/BtbQLUC6xUh1H61U7gubfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0Ts7x/btruI6pUgcG/BtbQLUC6xUh1H61U7gubfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0Ts7x/btruI6pUgcG/BtbQLUC6xUh1H61U7gubfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0Ts7x%2FbtruI6pUgcG%2FBtbQLUC6xUh1H61U7gubfk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;콘솔에 ok가 된 값이 나오는 것을 확인할 수 있습니다. 다시 페이지를 재요청합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kQZR2/btruE9tKBIS/cwGLCsHM22IgsqY9rn2tb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kQZR2/btruE9tKBIS/cwGLCsHM22IgsqY9rn2tb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kQZR2/btruE9tKBIS/cwGLCsHM22IgsqY9rn2tb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkQZR2%2FbtruE9tKBIS%2FcwGLCsHM22IgsqY9rn2tb1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그럼 화면에 (9091)hello world의 결과가 나온 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;9091인 이유는 AJP 예제에서 설정한 값입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw8LWM/btrurxbq4M9/kidDe64oV8dQpm9AkMxr7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw8LWM/btrurxbq4M9/kidDe64oV8dQpm9AkMxr7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw8LWM/btrurxbq4M9/kidDe64oV8dQpm9AkMxr7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw8LWM%2Fbtrurxbq4M9%2FkidDe64oV8dQpm9AkMxr7K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실제 Redis 데이터베이스를 확인해 보니 Session이 등록되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;여기까지 Spring boot에서 Redis 데이터베이스를 이용한 세션 클러스터링 설정된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 로드벨런싱 환경에서도 제대로 사용되는 지 확인해 보겠습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/355&quot; target=&quot;_blank&quot;&gt;[Java] 60. Spring boot에서 Apache 연결과 로드벨런싱을 설정하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위 예제에서 설정한 것과 같이 Apache를 실행하고 Spring boot 프로젝트를 두 곳으로 복사하여 실행합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAz1IO/btruNj3lva7/6HnPBZAQJ5tK8Z6Gj2Q0Hk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAz1IO/btruNj3lva7/6HnPBZAQJ5tK8Z6Gj2Q0Hk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAz1IO/btruNj3lva7/6HnPBZAQJ5tK8Z6Gj2Q0Hk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAz1IO%2FbtruNj3lva7%2F6HnPBZAQJ5tK8Z6Gj2Q0Hk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실행하여 텍스트에 데이터를 넣고 버튼을 누른 다음에 세션을 확인해 보니 위 예제는 9092로 두번째 Tomcat에 연결되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 여기서 제가 두번째 Tomcat을 실행 중지 해보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6Xvrq/btruPbK3TY0/OnF4Q6mDVjbjDjtImWbgD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6Xvrq/btruPbK3TY0/OnF4Q6mDVjbjDjtImWbgD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6Xvrq/btruPbK3TY0/OnF4Q6mDVjbjDjtImWbgD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6Xvrq%2FbtruPbK3TY0%2FOnF4Q6mDVjbjDjtImWbgD1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;종료되었습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D5jaM/btruAixJzBi/KU3YsZn2kyHEwNMEfVRwsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D5jaM/btruAixJzBi/KU3YsZn2kyHEwNMEfVRwsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D5jaM/btruAixJzBi/KU3YsZn2kyHEwNMEfVRwsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD5jaM%2FbtruAixJzBi%2FKU3YsZn2kyHEwNMEfVRwsk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;2번 서버가 확실히 서버 다운된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 다시 웹 페이지를 재요청해 보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EZm38/btruGZFAFc0/JQfJ4jPAvBIE6WytOKuMV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EZm38/btruGZFAFc0/JQfJ4jPAvBIE6WytOKuMV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EZm38/btruGZFAFc0/JQfJ4jPAvBIE6WytOKuMV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEZm38%2FbtruGZFAFc0%2FJQfJ4jPAvBIE6WytOKuMV1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;AJP 포트가 9092 서버는 확실이 다운 되었는데 AJP 포트가 9091인 1번 서버는 그대로 세션을 유지한 채로 값을 가져오는 것을 확인 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;서버가 하나 셧 다운되더라도 웹 서버가 그대로 운영되는 것도 확인이 되네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Spring boot에서 Redis 데이터베이스를 이용한 세션 클러스터링 설정하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Java</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/309</guid>
      <comments>https://nowonbun.tistory.com/309#entry309comment</comments>
      <pubDate>Tue, 1 Mar 2022 18:19:11 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 60. Spring boot에서 Apache 연결과 로드벨런싱을 설정하는 방법</title>
      <link>https://nowonbun.tistory.com/355</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Spring boot에서 Apache 연결과 로드벨런싱을 설정하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Spring boot에서는 Tomcat이 내장되어 있기 때문에 바로 빌드하고 배포해도 웹 서버가 생성이 됩니다. 즉, 복잡한 Tomcat 설정이 필요가 없습니다.&lt;/p&gt;
&lt;p&gt;이 웹이라는 것은 브라우저에서 요청과 응답 처리로 접속부터 처리 파싱까지 여러가지 처리가 있습니다. 그런데 트래픽(접속자)가 많아지면 Tomcat 하나로 서버가 버틸 수가 없기 때문에 Apache와 Tomcat으로 분할하여 역할을 나눕니다.&lt;/p&gt;
&lt;p&gt;역할이라는 것은 브라우저의 요청과 응답 처리와 여러가지 프로토콜 처리를 Apache에 맡가고 Tomcat은 Html 파싱과 세션 관리의 역할을 맡습니다.&lt;/p&gt;
&lt;p&gt;그래서 Apache 서버와 Tomcat을 연결하는 방법이 있는데 이전 글에서 Apache와 Tomcat을 연결하는 방법에 대해 설명한 적이 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/396&quot; target=&quot;_blank&quot;&gt;[CentOS] apache-tomcat 연동하기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런데 Spring boot에서는 Tomcat에 프레임워크 안에 포함되어 있는 형태이네요.. 그래서 Tomcat 설정(이전 server.xml)를 프로젝트 안에서 설정을 해야 합니다.&lt;/p&gt;
&lt;p&gt;먼저 이전 프로젝트에서 @Configuration 어노테이션을 선언한 AJPConfig 클래스 파일을 생성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;AJPConfig.java&quot;&gt;package com.example.demo.Controller;

import org.apache.catalina.connector.Connector;
import org.apache.coyote.ajp.AbstractAjpProtocol;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 설정 클래스
@Configuration
public class AJPConfig {
  // application.properties에서 설정 값을 받아온다.
  @Value(&quot;${tomcat.ajp.port}&quot;)
  private int port;
  // bean 설정
  @Bean
  public ServletWebServerFactory servletContainer() {
    // AJP/1.3 프로토콜의 Connector를 생성
    var ajpConnector = new Connector(&quot;AJP/1.3&quot;);
    // 포트 설정을 application.properties에서 설정
    ajpConnector.setPort(port);
    // ajp 로그 설정 이전 server.xml의 allowTrace 어트리뷰트
    ajpConnector.setAllowTrace(false);
    // http와 https 처리.. 현재는 필요없기 때문에 주석 처리
    // ajpConnector.setScheme(&quot;http&quot;);
    // SSL연결 시에 사용
    // ajpConnector.setSecure(false);
    // server.xml에서 secretRequired 설정
    ((AbstractAjpProtocol&amp;lt;?&amp;gt;)ajpConnector.getProtocolHandler()).setSecretRequired(false);
    // tomcat 설정
    var tomcat = new TomcatServletWebServerFactory();
    // 추가
    tomcat.addAdditionalTomcatConnectors(ajpConnector);
    
    return tomcat;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;AJPConfig 클래스를 위처럼 작성하고 application.properties에 tomcat.ajp.port를 추가합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot; data-type=&quot;application.properties&quot;&gt;# 서버 포트 설정
server.port=8081
# ajp 포트 설정
tomcat.ajp.port=9091
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apache 설정의 경우는 mod_jk.so 파일을 설치하고 httpd.config 파일를 수정하고 workers.properties 파일을 추가합니다. (window의 경우는 Apache Lounge에서 다운로드 합니다. 링크 - &lt;a href=&quot;https://www.apachelounge.com/download/&quot; target=&quot;_blank&quot;&gt;https://www.apachelounge.com/download/&lt;/a&gt;)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot; data-type=&quot;httpd.conf&quot;&gt;# 모드 로드
LoadModule jk_module modules/mod_jk.so
# 모드 설정
&amp;lt;IfModule mod_jk.c&amp;gt;
  JkWorkersFile conf/workers.properties
  JkShmFile run/mod_jk.shm
  JkLogFile logs/mod_jk.log
  JkLogLevel info
  JkLogStampFormat &quot;[%y %m %d %H:%M:%S] &quot;
&amp;lt;/IfModule&amp;gt;	
# 80 포트일 경우
&amp;lt;VirtualHost *:80&amp;gt;
  JkMount /* springboot
  JkMount /jkmanager/* jkstatus
  ServerName localhost
&amp;lt;/VirtualHost&amp;gt;
# 가상 디렉토리 jkmanager로 접속하는 경우
&amp;lt;Location /jkmanager/&amp;gt;
  JkMount statusmanager
  # 로컬만 접속
  Require ip 127.0.0.1
&amp;lt;/Location&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot; data-type=&quot;worker.properties&quot;&gt;# 리스트 키
worker.list=springboot,jkstatus
# 로드밸런싱 상태
worker.jkstatus.type=status 
# ajp 포트
worker.springboot.port=9091
# 호스트명
worker.springboot.host=localhost
# 타입
worker.springboot.type=ajp13
# 부하 분산 비율
worker.springboot.lbfactor=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 아파치를 기동하고 이클립스에서 Spring boot를 기동합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/57Bd8/btruK5cK7z2/n3ljZEa3sZdILWsVpvOMX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/57Bd8/btruK5cK7z2/n3ljZEa3sZdILWsVpvOMX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/57Bd8/btruK5cK7z2/n3ljZEa3sZdILWsVpvOMX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F57Bd8%2FbtruK5cK7z2%2Fn3ljZEa3sZdILWsVpvOMX1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;ajp 프로토콜이 기동되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;아파치로 80을 접속하니 톰켓에 연결된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7CDev/btruITjznjt/bWLv7Ki4VbkKomMDp9NotK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7CDev/btruITjznjt/bWLv7Ki4VbkKomMDp9NotK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7CDev/btruITjznjt/bWLv7Ki4VbkKomMDp9NotK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7CDev%2FbtruITjznjt%2FbWLv7Ki4VbkKomMDp9NotK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이제부터 Apache와 Tomcat을 연결해서 운영을 합니다.&lt;/p&gt;
&lt;p&gt;그런데 트래픽(접속자)가 더 늘어나서 Apache와 Tomcat만으로 버티기가 힘들어 지는 시기가 옵니다. 그러면 보통 웹의 요청과 응답의 처리에서 가장 시간이 많이 걸리는 부분이 Tomcat의 Html 파싱 부분이 오래 걸립니다.&lt;/p&gt;
&lt;p&gt;사양에 따른 그 결과가 항상 다르기 때문에 요청에 따른 Html를 작성해기 때문입니다. 그럼 Apache와 Tomcat 환경에서 Tomcat을 여러 개로 늘려서 하나의 Apache에 연결하는 데, 그것을 로드밸런싱이라고 합니다.&lt;/p&gt;
&lt;p&gt;이제 이　프로젝트를 두 개의 톰켓 서버로 나누고 아파치에서 로드밸런싱을 하겠습니다.&lt;/p&gt;
&lt;p&gt;먼저 두개의 서버가 작동되는 것을 확인하기 위헤 index 화면을 수정하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;HomeController.java&quot;&gt;package com.example.demo.Controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

// 컨트럴러 어트리뷰트
@Controller
// Controller 클래스
public class HomeController {
  // application.properties에서 설정 값을 받아온다.
  @Value(&quot;${tomcat.ajp.port}&quot;)
  private int port;

  // 매핑 주소
  @RequestMapping(value = { &quot;/&quot;, &quot;/index.html&quot; })
  public String index(Model model) {
    // 데이터를 템플릿에 전달한다.
    model.addAttribute(&quot;data&quot;, port);
    // 템플릿 파일명
    return &quot;Home/index&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;화면에 application.properties에서 설정하는 ajp 포트 번호를 화면에 표시하는 것으로 수정했습니다.&lt;/p&gt;
&lt;p&gt;그리고 이클립스에서는 두 개의 서버를 실행할 수 없기 때문에 테스트할 수 있는 디렉토리로 소스를 복사하겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N8TgO/btruDCJTtAj/HnjCWEWsGdogrdocHtKEI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N8TgO/btruDCJTtAj/HnjCWEWsGdogrdocHtKEI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N8TgO/btruDCJTtAj/HnjCWEWsGdogrdocHtKEI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN8TgO%2FbtruDCJTtAj%2FHnjCWEWsGdogrdocHtKEI1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;저는 d드라이브의 work 폴더에 복사했습니다.&lt;/p&gt;
&lt;p&gt;그리고 각 폴더의 application.properties에서 http를 8081와 8082로 설정하고 ajp를 9091과 9092로 서로 겹치지 않게 설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;설정을 하고 윈도우의 경우에는 프로젝트에 mvnw.cmd 파일이 있는데 그것을 이용하여 빌드하고 실행하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;cmd&quot;&gt;mvnw clean install spring-boot:run
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/esFXWv/btruKkgSHK9/xgfZCPtlKFIePw6biwkx90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/esFXWv/btruKkgSHK9/xgfZCPtlKFIePw6biwkx90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/esFXWv/btruKkgSHK9/xgfZCPtlKFIePw6biwkx90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FesFXWv%2FbtruKkgSHK9%2FxgfZCPtlKFIePw6biwkx90%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;테스트가 실행되고 로그 메시지가 표시되는데 특별히 에러가 발생하지 않으면 정상적으로 실행이 된것 입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;일단은 톰켓은 실행이 된 것 같고 그럼 다시 Apache 설정을 수저하고 실행하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-apache&quot; data-type=&quot;worker.properties&quot;&gt;# work 리스트
worker.list=springboot,jkstatus
# apache에 연결되어 있는 mod_jk에 대한 상태(로드밸런싱 표시)
worker.jkstatus.type=status
# type lb는 로드밸런싱이라는 뜻이다.
worker.springboot.type=lb
# 동일한 sessionID일 경우, 한쪽 tomcat에만 지속적으로 관리하여야 하는 경우.
worker.springboot.sticky_session=true
# 로드밸런싱 리스트
worker.springboot.balance_workers=worker1,worker2	
# 1번째 로드벨런싱 톰켓
worker.worker1.type=ajp13
# 호스트명
worker.worker1.host=localhost
# ajp 포트
worker.worker1.port=9091
# 처리 할당 비율, 즉, 2번째에 1이라고 설정을 했기 때문에 1:1 비율로 로드 벨런싱이 이루어진다.
worker.worker1.lbfactor=1	
# 2번째 로드 벨런싱 톰켓
worker.worker2.type=ajp13
# 호스트명
worker.worker2.host=localhost
# ajp 포트
worker.worker2.port=9092
# 부하 분산 비율
worker.worker2.lbfactor=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위처럼 works.properties 파일에 로드벨런싱 설정을 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이제 apache를 실행해서 브라우져에서 접속해 봅시다.&lt;/p&gt;
&lt;p&gt;그럼 localhost에 접속할 때마다 결과가 9091이 나올 때가 있고 9092가 나올 때도 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC3fNt/btruITjznCF/INfcKkT4H8nEkiVE1KyqQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC3fNt/btruITjznCF/INfcKkT4H8nEkiVE1KyqQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC3fNt/btruITjznCF/INfcKkT4H8nEkiVE1KyqQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC3fNt%2FbtruITjznCF%2FINfcKkT4H8nEkiVE1KyqQk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddilEP/btruGZxM9UY/ovk1IA95BZJuwHfov0H9Ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddilEP/btruGZxM9UY/ovk1IA95BZJuwHfov0H9Ok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddilEP/btruGZxM9UY/ovk1IA95BZJuwHfov0H9Ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddilEP%2FbtruGZxM9UY%2Fovk1IA95BZJuwHfov0H9Ok%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그럼 접속 수에 따라 Aphache에서 두개의 tomcat으로 분산 처리를 하는 것을 확인할 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위의 httpd.conf 설청에서 jkmanager를 설정했습니다.&lt;/p&gt;
&lt;p&gt;그럼 localhost/jkmanager/로 접속을 해 봅시다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chB2Bg/btrulSUtakf/6vgfCqnkHZ0sGLQS987AU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chB2Bg/btrulSUtakf/6vgfCqnkHZ0sGLQS987AU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chB2Bg/btrulSUtakf/6vgfCqnkHZ0sGLQS987AU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchB2Bg%2FbtrulSUtakf%2F6vgfCqnkHZ0sGLQS987AU1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Apache에서 Tomcat 서버의 로드벨런싱하는 상황이 표시되네요..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;설명이 복잡했습니다만 실제로 해보면 예전보다 설정이 훨씬 간단해 졌다는 것을 느낄 수 있네요. Tomcat 자체도 프레임워크 안에 있다 보니 빌드 공정이나 배포 전략　설정이 훨씬 쉬울 듯 싶네요..&lt;/p&gt;
&lt;p&gt;다음　글에서는 로드밸런싱 된 서버에서 세션 공유를 할 수 있는 세션 클러스터링에 대해 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Spring boot에서 Apache 연결과 로드벨런싱을 설정하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Java</category>
      <category>Apache</category>
      <category>SpringBoot</category>
      <category>tomcat</category>
      <category>로드밸런싱</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/355</guid>
      <comments>https://nowonbun.tistory.com/355#entry355comment</comments>
      <pubDate>Mon, 28 Feb 2022 18:43:06 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 59. Spring boot의 JPA에서 EntityManager를 사용하는 방법</title>
      <link>https://nowonbun.tistory.com/357</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Spring boot의 JPA에서 EntityManager를 사용하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전 글에서 Spring boot framework 환경에서 JPA를 설정하여 사용하는 방법에 대해 설명했습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/362&quot; target=&quot;_blank&quot;&gt;[Java] 58. Eclipse에서 Spring boot의 JPA를 설정하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런데 문제가 JpaRepository 인터페이스를 상속받아서 쓴다는 것입니다. JpaRepository 인터페이스 자체가 문제가 있는 것은 아닙니다. 데이터베이스 커넥션을 보다 쉽게 접근하고 트랜잭션을 자동으로 처리해 준다는 점은 매우 편한 부분입니다. 그러나 이 자동으로 처리하는 게 문제가 있습니다.&lt;/p&gt;
&lt;p&gt;자동이라는 점은 초기 접근에 매우 좋지만 결국에는 트랜젹선을 컨트럴하는데 한계가 존재한다는 점입니다. 예를 들면, 여러 개의 테이블을 동시에 입력하는데 처리 도중에 에러가 발생했습니다. 그럴 경우 하나의 테이블이 아닌 여러 개의 테이블을 전부 롤백을 해야 하는데, 트랜젝션을 제어하기가 까다로워서 프로그램이 복잡해 질 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉, JpaRepository도 트랜잭션을 따로 취득해서 제어가 가능한데 통합적(?)인 관리가 되지 않기 때문에 역으로 소스가 복잡해지고 리소스 관리등의 명확한 흐름이 제어가 되지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저, AbstractDao와 FactoryDao를 만들겠습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/551&quot; target=&quot;_blank&quot;&gt;[Java] 50. JPA 프로젝트에서 DAO 클래스 작성하기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/553&quot; target=&quot;_blank&quot;&gt;[Java] 52. Spring 프레임워크에서 DAO를 Factory Pattern으로 의존성 주입하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 AbstractDao의 추상클래스를 만들어봅시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;AbstractDao.java&quot;&gt;package com.example.demo.dao;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
 
// Dao 추상클래스
public abstract class AbstractDao&amp;lt;T&amp;gt; {
  // Spring boot에서는 persistence.xml 파일이 없어서 아래의 소스가 에러가 발생한다.
  private static EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;JpaExample&quot;);
  private Class&amp;lt;T&amp;gt; clazz;
  // 람다식을 위한 interface
  protected interface EntityManagerRunable {
    void run(EntityManager em);
  }
  // 람다식을 위한 interface
  protected interface EntityManagerCallable&amp;lt;V&amp;gt; {
    V run(EntityManager em);
  }
  // 생성자를 protected로 설정
  protected AbstractDao(Class&amp;lt;T&amp;gt; clazz) {
    this.clazz = clazz;
  }
  // 클래스 타입을 취득하는 함수
  protected final Class&amp;lt;T&amp;gt; getClazz() {
    return clazz;
  }
  // 테이블에서 key의 조건으로 데이터를 취득한다.
  public T findOne(Object id) {
    return transaction((em) -&amp;gt; {
      return em.find(clazz, id);
    });
  }
  // Entity를 데이터 베이스에 Insert한다.
  public T create(T entity) {
    return transaction((em) -&amp;gt; {
      em.persist(entity);
      return entity;
    });
  }
  // Entity를 데이터 베이스에 Update한다.
  public T update(T entity) {
    return transaction((em) -&amp;gt; {
      // 클래스를 데이터베이스 데이터와 매핑시킨다.
      em.detach(entity);
      // update
      return em.merge(entity);
    });
  }
  // Entity를 데이터 베이스에 Delete한다.
  public void delete(T entity) {
    transaction((em) -&amp;gt; {
      // 클래스를 데이터베이스와 매핑시킨다.
      em.detach(entity);
      // 그리고 update를 하고 삭제한다.
      em.remove(em.merge(entity));
    });
  }
  // 반환 값이 있는 트랜젝션 (일반 트랜젹션으로 데이터가 변화한다.)
  public &amp;lt;V&amp;gt; V transaction(EntityManagerCallable&amp;lt;V&amp;gt; callable) {
    return transaction(callable, false);
  }
  // 반환 값이 있는 트랜젝션 (readonly를 true 설정하면 함수를 호출하는 가운데서는 commit이 발생하지 않는다.)
  public &amp;lt;V&amp;gt; V transaction(EntityManagerCallable&amp;lt;V&amp;gt; callable, boolean readonly) {
    // Manager를 생성한다. EntityManagerFactory를 persistence.xml에서 취득을 못했기 때문에 EntityManager를 취득하지 못한다.
    EntityManager em = emf.createEntityManager();
    // transaction을 가져온다.
    EntityTransaction transaction = em.getTransaction();
    // 트랜젝션을 시작한다.
    transaction.begin();
    try {
      // 람다식을 실행한다.
      V ret = callable.run(em);
      // readonly가 true면 rollback한다.
      if (readonly) {
        transaction.rollback();
      } else {
        // 트랜젝션을 데이터베이스에 입력한다.
        transaction.commit();
      }
      // 결과를 리턴한다.
      return ret;
      // 에러가 발생할 경우
    } catch (Throwable e) {
      // transaction이 활성 중이라면
      if (transaction.isActive()) {
        // rollback
        transaction.rollback();
      }
      // RuntimeException로 변환
      throw new RuntimeException(e);
    } finally {
      // Manager를 닫는다.
      em.close();
    }
  }
  // 반환 값이 없는 transaction (일반 트랜젹션으로 데이터가 변화한다.)
  public void transaction(EntityManagerRunable runnable) {
    transaction(runnable, false);
  }
  // 반환 값이 없는 트랜젝션 (readonly를 true 설정하면 함수를 호출하는 가운데서는 commit이 발생하지 않는다.)
  public void transaction(EntityManagerRunable runnable, boolean readonly) {
    // Manager를 생성한다. EntityManagerFactory를 persistence.xml에서 취득을 못했기 때문에 EntityManager를 취득하지 못한다.
    EntityManager em = emf.createEntityManager();
    // transaction을 가져온다.
    EntityTransaction transaction = em.getTransaction();
    // 트랜젝션을 시작한다.
    transaction.begin();
    try {
      // 람다식을 실행한다.
      runnable.run(em);
      // readonly가 true면 rollback한다.
      if (readonly) {
        transaction.rollback();
      } else {
        // 트랜젝션을 데이터베이스에 입력한다.
        transaction.commit();
      }
      // 에러가 발생할 경우
    } catch (Throwable e) {
      // transaction이 활성 중이라면
      if (transaction.isActive()) {
        // rollback
        transaction.rollback();
      }
      // RuntimeException로 변환
      throw new RuntimeException(e);
    } finally {
      // Manager를 닫는다.
      em.close();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cnTfeE/btruh6MaUvi/zuKTe1uzRatvSU1DEQt1Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cnTfeE/btruh6MaUvi/zuKTe1uzRatvSU1DEQt1Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cnTfeE/btruh6MaUvi/zuKTe1uzRatvSU1DEQt1Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcnTfeE%2Fbtruh6MaUvi%2FzuKTe1uzRatvSU1DEQt1Rk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 소스를 그대로 상속해서 Dao를 만들고 실행하면 에러가 발생합니다.&lt;/p&gt;
&lt;p&gt;즉, AbstractDao에서 11번째 라인 내용을 보면 Spring boot에서는 persistence.xml 파일이 없기 때문에 EntityManagerFactory를 취득하지 못합니다.&lt;/p&gt;
&lt;p&gt;76번째 라인과 116번째 라인을 보면 EntityManagerFactory에서 EntityManager를 취득하고 transaction를 받아와서 실행하는데 역시 EntityManagerFactory를 취득을 못해 에러가 발생했기 때문에 전부 에러가 발생하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그러면 EntityManagerFactory를 취득하면 해결이 되는데.. EntityManagerFactory를 어디서 취득할까 하면 ApplicationConfig 클래스, 즉 @Configuration 어노테이션이 설정되어 있는 클래스에서 의존성 주입으로 취득할 수 있습니다.&lt;/p&gt;
&lt;p&gt;근데 순서가 FactoryDao가 ApplicationConfig보다 먼저 생성되는 부분이기 때문에 Singleton 패턴으로 해결하면 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;ApplicationConfig.java&quot;&gt;package com.example.demo.Controller;

import javax.persistence.EntityManagerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.example.demo.dao.FactoryDao;
import com.example.demo.dao.UserDao;

// 설정 어트리뷰트
@Configuration
public class ApplicationConfig {
  // EntityManagerFactory 인스턴스를 의존성 주입으로 받는다.
  @Autowired
  private EntityManagerFactory emf;
  // 싱글톤 설정
  private static ApplicationConfig instance;
  // 생성자
  public ApplicationConfig() {
    // 싱글톤 인스턴스 설정
    ApplicationConfig.instance = this;
  }
  // 싱글톤 인스턴스 취득 함수
  public static ApplicationConfig getInstance() {
    // 싱글톤 인스턴스 리턴
    return ApplicationConfig.instance;
  }
  // EntityManagerFactory 인스턴스 취득 함수
  public EntityManagerFactory getEntityManagerFactory() {
    // EntityManagerFactory 인스턴스 리턴
    return this.emf;
  }
  // Bean 설정, 이름은 UserDao
  @Bean(name = &quot;UserDao&quot;)
  public UserDao getUserDao() {
    // FactoryDao에서 UserDao의 인스턴스를 취득한다.
    return FactoryDao.getDao(UserDao.class);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다시 AbstractDao를 수정하고 AbstractDao를 상속받은 UserDao 클래스와 Dao 클래스를 관리하는 FactoryDao 클래스를 만듭니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;AbstractDao.java&quot;&gt;package com.example.demo.dao;

import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import com.example.demo.Controller.ApplicationConfig;

// Dao 추상클래스
public abstract class AbstractDao&amp;lt;T&amp;gt; {
  private Class&amp;lt;T&amp;gt; clazz;
  // 람다식을 위한 interface
  protected interface EntityManagerRunable {
    void run(EntityManager em);
  }
  // 람다식을 위한 interface
  protected interface EntityManagerCallable&amp;lt;V&amp;gt; {
    V run(EntityManager em);
  }
  // 생성자를 protected로 설정
  protected AbstractDao(Class&amp;lt;T&amp;gt; clazz) {
    this.clazz = clazz;
  }
  // 클래스 타입을 취득하는 함수
  protected final Class&amp;lt;T&amp;gt; getClazz() {
    return clazz;
  }
  // 테이블에서 key의 조건으로 데이터를 취득한다.
  public T findOne(Object id) {
    return transaction((em) -&amp;gt; {
      return em.find(clazz, id);
    });
  }
  // Entity를 데이터 베이스에 Insert한다.
  public T create(T entity) {
    return transaction((em) -&amp;gt; {
      em.persist(entity);
      return entity;
    });
  }
  // Entity를 데이터 베이스에 Update한다.
  public T update(T entity) {
    return transaction((em) -&amp;gt; {
      // 클래스를 데이터베이스 데이터와 매핑시킨다.
      em.detach(entity);
      // update
      return em.merge(entity);
    });
  }
  // Entity를 데이터 베이스에 Delete한다.
  public void delete(T entity) {
    transaction((em) -&amp;gt; {
      // 클래스를 데이터베이스와 매핑시킨다.
      em.detach(entity);
      // 그리고 update를 하고 삭제한다.
      em.remove(em.merge(entity));
    });
  }
  // 반환 값이 있는 트랜젝션 (일반 트랜젹션으로 데이터가 변화한다.)
  public &amp;lt;V&amp;gt; V transaction(EntityManagerCallable&amp;lt;V&amp;gt; callable) {
    return transaction(callable, false);
  }
  // 반환 값이 있는 트랜젝션 (readonly를 true 설정하면 함수를 호출하는 가운데서는 commit이 발생하지 않는다.)
  public &amp;lt;V&amp;gt; V transaction(EntityManagerCallable&amp;lt;V&amp;gt; callable, boolean readonly) {
    // ApplicationConfig 인스턴스에서 EntityManagerFactory를 취득하여 Manager를 생성한다.
    EntityManager em = ApplicationConfig.getInstance().getEntityManagerFactory().createEntityManager();
    // transaction을 가져온다.
    EntityTransaction transaction = em.getTransaction();
    // 트랜젝션을 시작한다.
    transaction.begin();
    try {
      // 람다식을 실행한다.
      V ret = callable.run(em);
      // readonly가 true면 rollback한다.
      if (readonly) {
        transaction.rollback();
      } else {
        // 트랜젝션을 데이터베이스에 입력한다.
        transaction.commit();
      }
      // 결과를 리턴한다.
      return ret;
      // 에러가 발생할 경우
    } catch (Throwable e) {
      // transaction이 활성 중이라면
      if (transaction.isActive()) {
        // rollback
        transaction.rollback();
      }
      // RuntimeException로 변환
      throw new RuntimeException(e);
    } finally {
      // Manager를 닫는다.
      em.close();
    }
  }
  // 반환 값이 없는 transaction (일반 트랜젹션으로 데이터가 변화한다.)
  public void transaction(EntityManagerRunable runnable) {
    transaction(runnable, false);
  }
  // 반환 값이 없는 트랜젝션 (readonly를 true 설정하면 함수를 호출하는 가운데서는 commit이 발생하지 않는다.)
  public void transaction(EntityManagerRunable runnable, boolean readonly) {
    // ApplicationConfig 인스턴스에서 EntityManagerFactory를 취득하여 Manager를 생성한다.
    EntityManager em = ApplicationConfig.getInstance().getEntityManagerFactory().createEntityManager();
    // transaction을 가져온다.
    EntityTransaction transaction = em.getTransaction();
    // 트랜젝션을 시작한다.
    transaction.begin();
    try {
      // 람다식을 실행한다.
      runnable.run(em);
      // readonly가 true면 rollback한다.
      if (readonly) {
        transaction.rollback();
      } else {
        // 트랜젝션을 데이터베이스에 입력한다.
        transaction.commit();
      }
      // 에러가 발생할 경우
    } catch (Throwable e) {
      // transaction이 활성 중이라면
      if (transaction.isActive()) {
        // rollback
        transaction.rollback();
      }
      // RuntimeException로 변환
      throw new RuntimeException(e);
    } finally {
      // Manager를 닫는다.
      em.close();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;transaction 함수에서 ApplicationConfig의 싱글톤 인스턴스를 취득해서 EntityManagerFactory를 취득해옵니다. 그리고 EntityManager 인스턴스를 취득합니다. &lt;/p&gt;
&lt;p&gt;그외는 transaction의 내용은 같습니다. 람다식의 인터페이스를 파라미터로 받아서 함수를 실행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;FactoryDao.java&quot;&gt;package com.example.demo.dao;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class FactoryDao {
  // FactoryDao 클래스의 singleton 패턴의 인스턴스 변수
  private static FactoryDao instance = null;
  // Dao 클래스의 인스턴스를 가지고 있을 flyweight 패턴의 맵
  private final Map&amp;lt;Class&amp;lt;?&amp;gt;, AbstractDao&amp;lt;?&amp;gt;&amp;gt; flyweight;

  // Singleton 패턴을 유지하기 위해 생성자를 private 타입으로 선언한다.
  private FactoryDao() {
    // flyweight 패턴의 맵의 인스턴스 생성
    flyweight = new HashMap&amp;lt;Class&amp;lt;?&amp;gt;, AbstractDao&amp;lt;?&amp;gt;&amp;gt;();
  }
  @SuppressWarnings(&quot;unchecked&quot;)
  // DAO 인스턴스를 취득하기 위한 Singleton 패턴의 함수
  public static &amp;lt;T&amp;gt; T getDao(Class&amp;lt;T&amp;gt; clz) {
    try {
      // FactoryDao의 인스턴스가 없으면 생성한다.
      if (instance == null) {
        // 인스턴스 생성
        instance = new FactoryDao();
      }
      // FactoryDao의 flyweight 맵에서 파라미터의 클래스 타입의 DAO가 존재하지 않을 경우
      if (!instance.flyweight.containsKey(clz)) {
        // Reflection 기능으로 생성자를 찾는다.
        Constructor&amp;lt;T&amp;gt; constructor = clz.getDeclaredConstructor();
        // 접근 제한자와 관계없이 접근 가능하게 설정
        constructor.setAccessible(true);
        // flyweight에 클래스 타입을 키로 설정하고 인스턴스를 저장한다.
        instance.flyweight.put(clz, (AbstractDao&amp;lt;?&amp;gt;) constructor.newInstance());
      }
      // flyweight에 저장된 DAO 인스턴스를 리턴한다.
      return (T) instance.flyweight.get(clz);
    } catch (Throwable e) {
      // 에러가 발생
      throw new RuntimeException(e);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FactoryDao는 Reflection 기능을 사용해서 클래스의 타입을 받아서 flyweight 패턴으로 인스턴스를 저장하고 가져오는 형식의 클래스입니다.&lt;/p&gt;
&lt;p&gt;UserDao는 데이터베이스에 접속해서 데이터를 취득하는 클래스입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;UserDao.java&quot;&gt;package com.example.demo.dao;

import java.util.List;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import com.example.demo.model.User;

// User 데이터 Dao 클래스, AbstractDao를 상속받고 제네릭은 User 클래스를 설정한다.
public class UserDao extends AbstractDao&amp;lt;User&amp;gt; {
  // 생성자 재정의 protected에서 private으로 바꾸고 파라미터를 재설정한다.
  private UserDao() {
    // protected 생성자 호출
    super(User.class);
  }
  // 테이블의 전체 레코드 취득 함수
  @SuppressWarnings(&quot;unchecked&quot;)
  public List&amp;lt;User&amp;gt; findAll() {
    // AbstractDao 추상 클래스의 transaction 함수를 사용한다.
    return transaction((em) -&amp;gt; {
      try {
        // User 클래스의 @NamedQuery 쿼리로 취득
        Query query = em.createNamedQuery(&quot;User.findAll&quot;, User.class);
        // 결과 리턴
        return (List&amp;lt;User&amp;gt;) query.getResultList();
      } catch (NoResultException e) {
        return null;
      }
    });
  }
  // Id에 의한 데이터를 취득한다.
  public User selectById(String id) {
    // AbstractDao 추상 클래스의 transaction 함수를 사용한다.
    return super.transaction((em) -&amp;gt; {
      // 쿼리를 만든다. (실무에서는 createQuery가 아닌 createNamedQuery를 사용해서 Entity에서 쿼리를 일괄 관리한다.)
      Query query = em.createQuery(&quot;select u from User u where u.id = :id&quot;);
      // 파라미터 설정
      query.setParameter(&quot;id&quot;, id);
      try {
        // 결과 리턴
        return (User) query.getSingleResult();
      } catch (NoResultException e) {
        // 데이터가 없어서 에러가 발생하면 null를 리턴
        return null;
      }
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 HomeController에서 UserDao를 의존성 주입으로 인스턴스를 취득하고 데이터베이스로부터 데이터를 취득하여 화면에 출력합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;HomeController.java&quot;&gt;package com.example.demo.Controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.dao.UserDao;

// 컨트럴러 어트리뷰트
@Controller
// Controller 클래스
public class HomeController {
  // 의존성 주입
  @Autowired
  // ApplicationConfig 클래스에서 설정한 bean-id
  @Qualifier(&quot;UserDao&quot;)
  private UserDao userdao;
  // 매핑 주소
  @RequestMapping(value = { &quot;/&quot;, &quot;/index.html&quot; })
  public String index(Model model) {
    // UserDao를 이용해서 nowonbun로 데이터를 취득한다.
    // 데이터를 템플릿에 전달한다.
    model.addAttribute(&quot;data&quot;, userdao.selectById(&quot;nowonbun&quot;));
    // 템플릿 파일명
    return &quot;Home/index&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/du7M3E/btrurS5SzYq/Cq9u3mINiZMZ2CIODpXNmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/du7M3E/btrurS5SzYq/Cq9u3mINiZMZ2CIODpXNmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/du7M3E/btrurS5SzYq/Cq9u3mINiZMZ2CIODpXNmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdu7M3E%2FbtrurS5SzYq%2FCq9u3mINiZMZ2CIODpXNmk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Repository 인터페이스가 아닌 Dao 클래스로 데이터를 취득했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;전체적인 프로젝트 구조는 다음과 같습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JqxY4/btruoLTLEI9/PBuklLkwT8QyqMXM0pP240/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JqxY4/btruoLTLEI9/PBuklLkwT8QyqMXM0pP240/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JqxY4/btruoLTLEI9/PBuklLkwT8QyqMXM0pP240/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJqxY4%2FbtruoLTLEI9%2FPBuklLkwT8QyqMXM0pP240%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이전 Spring Web Framework에서 구성하던 방법으로 구성되었습니다. 물론 이전의 Repository 방식이 틀리다는 것은 아닙니다. 각자의 개발 스타일이 있으니 편한 방법으로 사용하면 됩니다.&lt;/p&gt;
&lt;p&gt;저의 경우는 transaction을 관리할 수 있고 각 쿼리로 직접 데이터베이스의 데이터를 관리하는 방법이 좀 더 직관적이고 관리하기 편해서 저는 이런 방법을 선호합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Spring boot의 JPA에서 EntityManager를 사용하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Java</category>
      <category>명월일지</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/357</guid>
      <comments>https://nowonbun.tistory.com/357#entry357comment</comments>
      <pubDate>Fri, 25 Feb 2022 19:15:20 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 58. Eclipse에서 Spring boot의 JPA를 설정하는 방법</title>
      <link>https://nowonbun.tistory.com/362</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Eclipse에서 Spring boot의 JPA를 설정하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Spring Web Framework 환경에서 JPA를 설정하는 방법에 대해서 설명한 적이 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/545&quot; target=&quot;_blank&quot;&gt;[Java] 45. JPA 설정하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;기본적인 설정은 Spring Web Framework 환경에서 설정하는 것과 같습니다. 다른 점이 있다면 Spring Web Framework는 persistence.xml에서 접속 환경을 설정하고 Spring boot는 application.properties에서 설정을 하게 됩니다.&lt;/p&gt;
&lt;p&gt;Spring boot는 환경 설정 파일이 하나로 통합이 되니 관리하기 편하네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 JPA를 연결하기 위해서 로컬 데이터베이스에 테스트 테이블을 작성합시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-type=&quot;DDL.sql&quot;&gt;-- test 데이터 베이스에 접속
use test;
-- 이전 테이블이 있다면 일단 삭제
-- drop table info;
-- drop table user;
-- user 테이블 생성
create table user(
  id varchar(255) not null,
  name nvarchar(255) not null,
  
  primary key(id) -- 키 설정
);
-- info 테이블 생성
create table info(
  idx int not null auto_increment, -- 자동 증가
  id varchar(255) not null,
  age int not null,
  
  primary key(idx), -- 키 설정
  foreign key(id) references user(id) -- id는 user의 id로 연결
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 테이블에 테스트 데이터를 넣겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-type=&quot;test.sql&quot;&gt;-- user 테이블에 데이터를 삽입
insert into user (id, name) values('nowonbun', 'Hello');
-- info 테이블에 데이터를 삽입
insert into info(id, age) values('nowonbun', 20);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 위 데이터를 Spring boot를 이용해서 화면에 표시해 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;우선, 이전 글에서 작성한 프로젝트에서 JPA 라이브러리를 추가하겠습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/313&quot; target=&quot;_blank&quot;&gt;[Java] 57. Eclipse에서 Spring boot를 설정하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 위자드를 실행해서 JPA를 추가합니다.&lt;/p&gt;
&lt;p&gt;프로젝트에서 마우스 오른쪽을 눌러 Spring의 Add Starter를 실행합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kZx6x/btrt4JXXjNJ/dw5fCAAjIUKC2NkBXFVJd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kZx6x/btrt4JXXjNJ/dw5fCAAjIUKC2NkBXFVJd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kZx6x/btrt4JXXjNJ/dw5fCAAjIUKC2NkBXFVJd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkZx6x%2Fbtrt4JXXjNJ%2Fdw5fCAAjIUKC2NkBXFVJd0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 이게 이클립스 버그인지는 모르겠는데.. 기존에 선택한 것이 선택이 되지 않았습니다. 그래서 선택하지 않고 Finish를 누르고 설정하게 되면 기존에 설정한 라이브러리가 빠지는 현상이 발생하네요.&lt;/p&gt;
&lt;p&gt;그래서 이전에 설정한 것도 체크해야 합니다. (버그인듯...)&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwpCce/btrt4sag6hS/pKJXCfkqrkffLuKK2eiDzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwpCce/btrt4sag6hS/pKJXCfkqrkffLuKK2eiDzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwpCce/btrt4sag6hS/pKJXCfkqrkffLuKK2eiDzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwpCce%2Fbtrt4sag6hS%2FpKJXCfkqrkffLuKK2eiDzk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;JPA를 사용하기 위해서 JDBC API, Spring Data JDBC, Spring Data JPA, MariaDB Driver를 추가해야 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJGghQ/btrt9TrtjfB/uNKbuQxFYAKfO5kOf2FNt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJGghQ/btrt9TrtjfB/uNKbuQxFYAKfO5kOf2FNt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJGghQ/btrt9TrtjfB/uNKbuQxFYAKfO5kOf2FNt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJGghQ%2Fbtrt9TrtjfB%2FuNKbuQxFYAKfO5kOf2FNt1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Next 버튼을 누르면 소스 비교 화면이 나오는데, 여기서 pom.xml만 변경하는 것으로 체크하고 Finish 버튼을 누르면 maven 업데이트가 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-type=&quot;pom.xml&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
  xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&amp;gt;
  &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;
  &amp;lt;parent&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-parent&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;2.6.3&amp;lt;/version&amp;gt;
    &amp;lt;relativePath/&amp;gt; &amp;lt;!-- lookup parent from repository --&amp;gt;
  &amp;lt;/parent&amp;gt;
  &amp;lt;groupId&amp;gt;com.example&amp;lt;/groupId&amp;gt;
  &amp;lt;artifactId&amp;gt;SpringBootTest&amp;lt;/artifactId&amp;gt;
  &amp;lt;version&amp;gt;0.0.1-SNAPSHOT&amp;lt;/version&amp;gt;
  &amp;lt;name&amp;gt;SpringBootTest&amp;lt;/name&amp;gt;
  &amp;lt;description&amp;gt;Demo project for Spring Boot&amp;lt;/description&amp;gt;
  &amp;lt;properties&amp;gt;
    &amp;lt;java.version&amp;gt;11&amp;lt;/java.version&amp;gt;
  &amp;lt;/properties&amp;gt;
  &amp;lt;dependencies&amp;gt;
    &amp;lt;!-- thymeleaf 라이브러리 --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-starter-thymeleaf&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- Spring boot 라이브러리 --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-starter-web&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- JUnit 라이브러리 --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-starter-test&amp;lt;/artifactId&amp;gt;
      &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- devtools 라이브러리 --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-devtools&amp;lt;/artifactId&amp;gt;
      &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
      &amp;lt;optional&amp;gt;true&amp;lt;/optional&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- data-jdbc 라이브러리 --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jdbc&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- data-jpa 라이브러리 --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-starter-data-jpa&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- jdbc 라이브러리 --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;spring-boot-starter-jdbc&amp;lt;/artifactId&amp;gt;
    &amp;lt;/dependency&amp;gt;
    &amp;lt;!-- mariadb-java-client 라이브러리 --&amp;gt;
    &amp;lt;dependency&amp;gt;
      &amp;lt;groupId&amp;gt;org.mariadb.jdbc&amp;lt;/groupId&amp;gt;
      &amp;lt;artifactId&amp;gt;mariadb-java-client&amp;lt;/artifactId&amp;gt;
      &amp;lt;scope&amp;gt;runtime&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
  &amp;lt;/dependencies&amp;gt;

  &amp;lt;build&amp;gt;
    &amp;lt;plugins&amp;gt;
      &amp;lt;plugin&amp;gt;
        &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;spring-boot-maven-plugin&amp;lt;/artifactId&amp;gt;
      &amp;lt;/plugin&amp;gt;
    &amp;lt;/plugins&amp;gt;
  &amp;lt;/build&amp;gt;

&amp;lt;/project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위자드를 사용하지 않고 pom.xml를 직접 수정하는 방법도 있습니다. 사실 위자드를 사용하는 것보다 직접 환경 설정을 하는 것이 정확하기 때문에.. 직접 pom.xml를 설정하는 것을 추천합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 이클립스에서 JPA를 사용해야 하기 때문에 JPA 모드로 변경해야 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8rylP/btrt7tGJ3X3/SkH0XMFbBOen5eTKyngPO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8rylP/btrt7tGJ3X3/SkH0XMFbBOen5eTKyngPO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8rylP/btrt7tGJ3X3/SkH0XMFbBOen5eTKyngPO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8rylP%2Fbtrt7tGJ3X3%2FSkH0XMFbBOen5eTKyngPO0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oyjiS/btrt6u61ehE/L6DPNhYtp5WawSli45sYz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oyjiS/btrt6u61ehE/L6DPNhYtp5WawSli45sYz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oyjiS/btrt6u61ehE/L6DPNhYtp5WawSli45sYz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoyjiS%2Fbtrt6u61ehE%2FL6DPNhYtp5WawSli45sYz1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이클립스는 Spring boot 기준이 아니고 Web framework 기준으로 설정되어 있습니다. 그래서 이클립스 환경 설정을 약간 수정해야 합니다.&lt;/p&gt;
&lt;p&gt;먼저 소스 안에 META-INF/persistence.xml 파일을 지웁니다..&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EQFV7/btrt4IxYWoP/YSt8e0R6zDj8vhgIXcTsCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EQFV7/btrt4IxYWoP/YSt8e0R6zDj8vhgIXcTsCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EQFV7/btrt4IxYWoP/YSt8e0R6zDj8vhgIXcTsCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEQFV7%2Fbtrt4IxYWoP%2FYSt8e0R6zDj8vhgIXcTsCk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그러면 이클립스에서 META-INF/persistence.xml 파일이 없다고 에러를 표시합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c940Ln/btrt6eCCfH2/iEVls9LwpUk9AtypCYcqiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c940Ln/btrt6eCCfH2/iEVls9LwpUk9AtypCYcqiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c940Ln/btrt6eCCfH2/iEVls9LwpUk9AtypCYcqiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc940Ln%2Fbtrt6eCCfH2%2FiEVls9LwpUk9AtypCYcqiK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Window -&amp;gt; Preferences 탭으로 가서 Java Persistence -&amp;gt; JPA -&amp;gt; Errors/Warnings의 Project -&amp;gt; No persistence.xml file found in project를 ignore로 바꿉니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLPjBm/btrt82PEu1K/KCrkHPWwq8uezetZ5NlyKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLPjBm/btrt82PEu1K/KCrkHPWwq8uezetZ5NlyKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLPjBm/btrt82PEu1K/KCrkHPWwq8uezetZ5NlyKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLPjBm%2Fbtrt82PEu1K%2FKCrkHPWwq8uezetZ5NlyKk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;에러 메시지가 없어졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 이제 데이터베이스에서 Entity를 만들어 봅시다. 프로젝트에서 마우스 오른쪽 클릭을 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmp9nx/btrt7usaT5Y/GsoW5rCWfIUlyzmsnMkhO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmp9nx/btrt7usaT5Y/GsoW5rCWfIUlyzmsnMkhO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmp9nx/btrt7usaT5Y/GsoW5rCWfIUlyzmsnMkhO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcmp9nx%2Fbtrt7usaT5Y%2FGsoW5rCWfIUlyzmsnMkhO1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0XEBf/btrubh6pe0h/U67n1YL6r7qjqQ5Tam9ywK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0XEBf/btrubh6pe0h/U67n1YL6r7qjqQ5Tam9ywK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0XEBf/btrubh6pe0h/U67n1YL6r7qjqQ5Tam9ywK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0XEBf%2Fbtrubh6pe0h%2FU67n1YL6r7qjqQ5Tam9ywK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;클래스를 생성을 하면 model.entity 클래스가 만들어 지는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zTqNO/btrt6uToPb2/qkEV7IeqcVyU1A9C51KbM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zTqNO/btrt6uToPb2/qkEV7IeqcVyU1A9C51KbM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zTqNO/btrt6uToPb2/qkEV7IeqcVyU1A9C51KbM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzTqNO%2Fbtrt6uToPb2%2FqkEV7IeqcVyU1A9C51KbM0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이것을 SpringBootTestApplication 클래스의 하위 패키지로 이동합니다. 이전에 Controller처럼 scanBasePackages를 맞추지 않으면 에러가 발생합니다. 역시 scanBasePackages를 설정하면 다른 패키지로 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이제 application.properties로 가서 접속 정보를 설정합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot; data-type=&quot;application.properties&quot;&gt;# 서버 포트 설정
server.port=8081
# thymeleaf 파일 설정
spring.thymeleaf.prefix=classpath:templates/
spring.thymeleaf.check-template-location=true
# thymeleaf 파일 확장자
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
# thymeleaf 캐쉬 모드
spring.thymeleaf.cache=false
# 데이터베이스 접속 정보
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&amp;useUnicode=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Seoul
# 아이디
spring.datasource.username=root
# 패스워드
spring.datasource.password=****
# 드라이브 클래스
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
# 쿼리 로그 표시하기
spring.jpa.show-sql=true
# 콘솔 표시
spring.h2.console.enabled=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;데이터베이스 접속 설정이 되었으면 이제 레포지토리 인터페이스를 생성해서 화면에 출력해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;UserRepository.java&quot;&gt;package com.example.demo.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.demo.model.User;
// 레포지토리 인터페이스
public interface UserRepository extends JpaRepository&amp;lt;User, String&amp;gt; {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spring boot에서는 transaction을 설정하지 않아도 Repository를 통해서 데이터를 취득할 수 있습니다.&lt;/p&gt;
&lt;p&gt;레포지토리 인터페이스를 생성했으면, 기존 Controller의 HomeController에 의존성 추가(@Autowired)를 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;HomeController.java&quot;&gt;package com.example.demo.Controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.repository.UserRepository;

// 컨트럴러 어노테이션
@Controller
public class HomeController {
  // 레포지토리 의존성 주입
  @Autowired
  private UserRepository userRepository;
  // 매핑 주소
  @RequestMapping(value = { &quot;/&quot;, &quot;/index.html&quot; })
  public String index(Model model) {
    // 레포지토리에서 id를 통해 User 데이터를 취득한다.
    var user = userRepository.findById(&quot;nowonbun&quot;).get();
    // 템플릿에 전달할 데이터
    model.addAttribute(&quot;data&quot;, user.getName());
    // 템플릿 파일명
    return &quot;Home/index&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;root 페이지가 호출되면 userRepository에서 nowonbun의 키로 데이터를 취득해 옵니다. 그리고 Thymeleaf 템플릿에 name 데이터를 표시합니다.&lt;/p&gt;
&lt;p&gt;대략 아래와 같은 구조의 프로젝트가 생성됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciW8Sh/btrt7tzZ0HT/S7ZKxA6JvLuoOvvpDKodTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciW8Sh/btrt7tzZ0HT/S7ZKxA6JvLuoOvvpDKodTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciW8Sh/btrt7tzZ0HT/S7ZKxA6JvLuoOvvpDKodTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciW8Sh%2Fbtrt7tzZ0HT%2FS7ZKxA6JvLuoOvvpDKodTk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그럼 프로젝트를 실행하니 아래와 같은 결과가 나왔습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YyNNc/btrubMZowcq/EjGOaL9UdDlfcVmsRgaXIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YyNNc/btrubMZowcq/EjGOaL9UdDlfcVmsRgaXIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YyNNc/btrubMZowcq/EjGOaL9UdDlfcVmsRgaXIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYyNNc%2FbtrubMZowcq%2FEjGOaL9UdDlfcVmsRgaXIK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;user테이블의 nowonbun의 레코드의 name 값이 Hello이니 정상적으로 데이터베이스에서 데이터를 취득해서 출력한 결과입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;설정 방법이 다를 뿐 Entity를 다루는 것은 이전 Web framework때와 같습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/546&quot; target=&quot;_blank&quot;&gt;[Java] 46. JPA의 Entity 클래스의 기본 설정(@GeneratedValue, @ManyToMany)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/548&quot; target=&quot;_blank&quot;&gt;[Java] 47. JPA의 Entity 클래스의 레퍼런스 설정(cascade, fetch)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;기존 Web Framework에서는 제가 Dao를 생성하고 FactoryDao를 이용하여 트랜젝션을 관리했습니다.&lt;/p&gt;
&lt;p&gt;그런데 Spring boot에서는 Repository 인터페이스를 만들고 의존성 주입된 인스턴스에서 자동으로 트랜잭션을 관리하네요... 사실 자동이라고 표현이지만 이게 commit rollback을 어떻게 관리를 해야 하며 에러가 발생할 경우 어떻게 rollback을 해야할 지에 대해 불명확합니다.&lt;/p&gt;
&lt;p&gt;그래서 Spring boot에서도 EntityManager를 취득해서 트랜젝션을 직접 관리하는 방법이 있습니다. 다음 글에서는 Spring boot에서 Dao와 Factory, 실질적인 트랜젝션 관리하는 방법에 대해서 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Eclipse에서 Spring boot의 JPA를 설정하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Java</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/362</guid>
      <comments>https://nowonbun.tistory.com/362#entry362comment</comments>
      <pubDate>Wed, 23 Feb 2022 18:09:20 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 57. Eclipse에서 Spring boot를 설정하는 방법</title>
      <link>https://nowonbun.tistory.com/313</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Eclipse에서 Spring boot를 설정하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;최근에 제가 여러 가지 이유 때문에 프레임워크를 Spring Web framework에서 Spring boot로 갈아탔습니다. Spring boot가 편하다는 건 이전부터 알고는 있었는데.&lt;/p&gt;
&lt;p&gt;개인적으로 무언가 자동으로 설정되는 것을 좋아하지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이유는 여러 가지 있지만, 그 중에서 문제가 발생했을 때.. 어디서 에러가 발생하는지 명확하게 알기가 힘들다는 게 가장 큰 이유이겠네요. 그래서　저는 일부터 열까지 제가 한 땀 한 땀 설정하고 작성하는 것을 좋아합니다.(「프로그램은 자동으로 처리해 주는 것이 절대 없다! 분명 어딘가에서 처리를 했기 때문이다.」라는 주의입니다. 즉, 버그도 어딘가에서 잘못된 처리를 했기 때문에 발생한다라는 생각입니다.)&lt;/p&gt;
&lt;p&gt;그런데.. 하나하나 설정해서 나가는 게 좋은 것만은 아닙니다. 생산성이 매우 떨어지고 설정해야 할 것이 너무 많아서 관리가 어렵다는 큰 문제가 있네요.. 그래서 안전성이 100%로 확보되지 않는 라이브러리는 사용도 안 합니다. 특히 오픈 라이브러리...(옛날 사람.. 옛날 사람...ㅎㅎ;;)&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;저도 생산성도 매우 중요하다고 생각합니다. 그러나 버그가 발생하고.. 그 버그를 파악하기 힘들고 대처하기 힘들다면.. 차라리 안 쓰는 게 낫다는 생각이라 여태 Spring boot를 사용하지 않았네요.&lt;/p&gt;
&lt;p&gt;근데 최근 이슈로.. Spring boot framework를 주로 사용하시는 분이 많고 반대로 Web framework로 시작하시는 분이 많이 없네요.. 프로젝트를 혼자하는 것도 아니고.. &lt;/p&gt;
&lt;p&gt;혼자 Spring boot를 사용 안 할 수도 없고 해서 최근에는 Spring boot를 사용하기 시작했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런데.. 아 너무 좋네요... Spring boot.. 너무 편합니다.. 최근에는 진작에 왜 사용하지 않았을까 하는 의구심이 들 정도로 안정성도 좋고 편하네요.&lt;/p&gt;
&lt;p&gt;특히, 배포 전략이나 빌드 공정을 설정하는데 너무 편하네요... 다만 한 서버에 여러 웹 어플리케이션을 설치할 경우 톰캣이 많아진다는 문제가 있네요. 이것도 설정할 수 있을지도 모르겠으나.. 아직은 잘 모르겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 지금까지 Web framework에서 설정한 것을 Spring boot에서는 어떻게 설정이 되는지 개발 환경을 만드는지 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;IntelliJ는 Ultimate 버전을 설치하면 Spring boot 라이브러리도 자동으로 설정되더라고요.. IntelliJ가 너무 편하고 좋은데 유료라서.... 집에서는 Eclipse를 사용해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 Eclipse를 설치해야 하는데... 이건 기존 방법과 같습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/259&quot; target=&quot;_blank&quot;&gt;[Java] 01. Java 설치와 Eclipse 설치&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Eclipse 다운로드 링크 - &lt;a href=&quot;https://www.eclipse.org/downloads/packages/&quot; target=&quot;_blank&quot;&gt;https://www.eclipse.org/downloads/packages/&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr8CxJ/btrt3fOU5jz/8wWRDLIIEvpqmSBDn9yFek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr8CxJ/btrt3fOU5jz/8wWRDLIIEvpqmSBDn9yFek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr8CxJ/btrt3fOU5jz/8wWRDLIIEvpqmSBDn9yFek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr8CxJ%2Fbtrt3fOU5jz%2F8wWRDLIIEvpqmSBDn9yFek%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;적당한 곳에 압축을 풉니다. 저는 d:\eclipse 폴더에 설치했습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJccsk/btrt4Ipupw0/Qp6H9ZZE9ZtUKcWGram7g1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJccsk/btrt4Ipupw0/Qp6H9ZZE9ZtUKcWGram7g1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJccsk/btrt4Ipupw0/Qp6H9ZZE9ZtUKcWGram7g1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJccsk%2Fbtrt4Ipupw0%2FQp6H9ZZE9ZtUKcWGram7g1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이클립스를 실행하여 프로젝트 생성 위자드를 실행하게 되면 아직 Spring boot를 만들 수 있는 영역이 없습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nrjux/btrt4IXnvHM/8XbpASt2ff8aRh4yjVdSyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nrjux/btrt4IXnvHM/8XbpASt2ff8aRh4yjVdSyk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nrjux/btrt4IXnvHM/8XbpASt2ff8aRh4yjVdSyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnrjux%2Fbtrt4IXnvHM%2F8XbpASt2ff8aRh4yjVdSyk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Help -&amp;gt; Eclipse MarketPlace를 선택합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnbQBM/btrtZ6Sslmt/YfrySnuA1qX8ypEPUyPJt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnbQBM/btrtZ6Sslmt/YfrySnuA1qX8ypEPUyPJt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnbQBM/btrtZ6Sslmt/YfrySnuA1qX8ypEPUyPJt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnbQBM%2FbtrtZ6Sslmt%2FYfrySnuA1qX8ypEPUyPJt1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 검색창에 sts로 검색을 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그러면 Spring tools 4가 보이는데 설치합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uzFYw/btrt3gG3RiC/jjTKxIVS5y1FRtObfZqF90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uzFYw/btrt3gG3RiC/jjTKxIVS5y1FRtObfZqF90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uzFYw/btrt3gG3RiC/jjTKxIVS5y1FRtObfZqF90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuzFYw%2Fbtrt3gG3RiC%2FjjTKxIVS5y1FRtObfZqF90%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9NxHm/btrtZ6EYuWA/whGcrUI6DLJcyIiSeDNkZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9NxHm/btrtZ6EYuWA/whGcrUI6DLJcyIiSeDNkZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9NxHm/btrtZ6EYuWA/whGcrUI6DLJcyIiSeDNkZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9NxHm%2FbtrtZ6EYuWA%2FwhGcrUI6DLJcyIiSeDNkZ0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;오른쪽 아래의 툴바를 보면 설치 상황이 보이는데 설치가 완료될 때까지 기다립니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lSA4E/btrt6uYz718/tPRs4OQQgVkCXKt1JBAUF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lSA4E/btrt6uYz718/tPRs4OQQgVkCXKt1JBAUF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lSA4E/btrt6uYz718/tPRs4OQQgVkCXKt1JBAUF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlSA4E%2Fbtrt6uYz718%2FtPRs4OQQgVkCXKt1JBAUF0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;설치가 완료되면 Eclipse가 재시작이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고　다시 프로젝트 생성 위자드를 열면 Spring boot 카테고리가 생성된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DJ661/btrt1JWYlnN/azIpKiYTSrlrFtqLm5WiQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DJ661/btrt1JWYlnN/azIpKiYTSrlrFtqLm5WiQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DJ661/btrt1JWYlnN/azIpKiYTSrlrFtqLm5WiQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDJ661%2Fbtrt1JWYlnN%2FazIpKiYTSrlrFtqLm5WiQ0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Spring Starter Project를 선택하고 프로젝트를 생성합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brye5T/btrtXxDikLn/RNqF87obD40fE9oILUmAsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brye5T/btrtXxDikLn/RNqF87obD40fE9oILUmAsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brye5T/btrtXxDikLn/RNqF87obD40fE9oILUmAsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrye5T%2FbtrtXxDikLn%2FRNqF87obD40fE9oILUmAsK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;프로젝트 이름과 기본 패키지, Maven에서 사용될 Group과 버전 설정을 하고 Next를 누릅니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Spring boot는 처음 생성할 때 기본 Dependencies를 설정할 수 있는데.. 웹 환경을 사용하려면 Spring Boot DevTools, Thymeleaf, Spring Web을 설정합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QzaPf/btrtYdYUGGd/OkhiVI9B29YpT30aFeAOo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QzaPf/btrtYdYUGGd/OkhiVI9B29YpT30aFeAOo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QzaPf/btrtYdYUGGd/OkhiVI9B29YpT30aFeAOo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQzaPf%2FbtrtYdYUGGd%2FOkhiVI9B29YpT30aFeAOo1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Spring Boot DevTools는 개발할 때 도움을 주는 라이브러리이고, Spring Web은 Web Framework 라이브러리입니다.&lt;/p&gt;
&lt;p&gt;DevTools를 체크하지 않아도 개발하는 데는 문제없으나, 없으면 디비깅 중 소스 수정할 때 템플릿 캐싱 설정이나 톰캣 재기동등 불편한 점이 있기 때문에 있는 편이 좋습니다. 나중에 릴리스(Production deploy)할 때는 pom.xml에서 제거하면 됩니다.&lt;/p&gt;
&lt;p&gt;Spring Web은 기존의 Web Framework입니다. 다른 점은 예전에는 xml로 Web framework를 설정해야 할 것이 많이 있습니다. 그런데 Spring boot에서는 application.properties에서 모든 설정이 통합이 되어서 설정하기가 더 편해졌습니다.&lt;/p&gt;
&lt;p&gt;Thymeleaf는 템플릿 프레임워크입니다. 사실 저는 Spring boot를 사용하기 전까지 JSTL(JSP)를 자주 사용했는데.. Spring boot에서는 Thymeleaf 템플릿 프레임워크를 추천하네요..&lt;/p&gt;
&lt;p&gt;성능에 대해서 개인적으로 여러 테스트를 해봤는데 크게 차이를 못 느끼겠고.. 가독성 면에서는 JSP보다 Thymeleaf가 더 깔끔한 느낌이더라구요.. 근데 Thymeleaf는 좀 구조가 독특해서.. Thymeleaf의 문법에 대해서는 따로 설명하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;마지막으로 Finish를 눌러서 프로젝트를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;프로젝트를 생성 후에 몇 분이 지나면 프로젝트가 생성이 되는데 아래와 같은 구조가 생성이 됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkECet/btrt1IRj7Pv/jJkVJg5RT5dvItTjbhBMX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkECet/btrt1IRj7Pv/jJkVJg5RT5dvItTjbhBMX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkECet/btrt1IRj7Pv/jJkVJg5RT5dvItTjbhBMX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkECet%2Fbtrt1IRj7Pv%2FjJkVJg5RT5dvItTjbhBMX1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;먼저 설정할 부분이 application.properties입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot; data-type=&quot;application.properties&quot;&gt;# 서버 포트 설정
server.port=8081
# thymeleaf 파일 설정
spring.thymeleaf.prefix=classpath:templates/
spring.thymeleaf.check-template-location=true
# thymeleaf 파일 확장자
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
# thymeleaf 캐쉬 모드
spring.thymeleaf.cache=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 기본적으로 Controller 클래스를 생성할 때는 @SpringBootApplication 어노테이션과 main 함수가 있는 클래스와 같은 패키지 혹은 하위 패키지에 Controller를 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;저는 하위 패키지를 생성해서 HomeController를 생성했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;HomeController.java&quot;&gt;package com.example.demo.Controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

// 컨트럴러 어노테이션
@Controller
public class HomeController {
  // 매핑 주소
  @RequestMapping(value = {&quot;/&quot;, &quot;/index.html&quot;})
  public String index(Model model) {
    // 템플릿에 전달할 데이터
    model.addAttribute(&quot;data&quot;, &quot;hello world&quot;);
    // 템플릿 파일명
    return &quot;Home/index&quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg1l9w/btrt4gfHcKq/0pZ36lm0y10jTmgle4h1lK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg1l9w/btrt4gfHcKq/0pZ36lm0y10jTmgle4h1lK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg1l9w/btrt4gfHcKq/0pZ36lm0y10jTmgle4h1lK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg1l9w%2Fbtrt4gfHcKq%2F0pZ36lm0y10jTmgle4h1lK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;같은 패키지가 아닌 곳에 설정하려면 @SpringBootApplication 어노테이션에 scanBasePackages 값을 설정하면 됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcn2yS/btrt3e3xVQu/J56V8XxKB5QEVJcY2kiu21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcn2yS/btrt3e3xVQu/J56V8XxKB5QEVJcY2kiu21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcn2yS/btrt3e3xVQu/J56V8XxKB5QEVJcY2kiu21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbcn2yS%2Fbtrt3e3xVQu%2FJ56V8XxKB5QEVJcY2kiu21%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이번에는 컨트럴러에서 호출한 Thymeleaf 템플릿을 설정합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2dsV/btrtZ7DQeFn/yTrKywHNN2yQuUPwKzSrU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2dsV/btrtZ7DQeFn/yTrKywHNN2yQuUPwKzSrU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2dsV/btrtZ7DQeFn/yTrKywHNN2yQuUPwKzSrU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2dsV%2FbtrtZ7DQeFn%2FyTrKywHNN2yQuUPwKzSrU1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;index 함수에서 Home/index파일을 호출하였기 때문에 Home 폴더 밑의 index.html 파일을 생성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;html&quot; data-type=&quot;index.html&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
&amp;lt;title&amp;gt;Insert title here&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;!-- 컨트럴러에서 받은 데이터를 출력 --&amp;gt;
  &amp;lt;span th:text=&quot;${data}&quot;&amp;gt;message&amp;lt;/span&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;템플릿 파일까지 생성이 되었으면 이제 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;실행하는 방법은 Project Explorer의 프로젝트에서 마우스 오른쪽 클릭을 하거나 main 함수가 있는 곳에서 마우스 오른쪽 클릭을 하면 Run as 또는 Debug as 에서 Spring boot App 메뉴가 있는데 클릭을 하면 실행을 할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LLKTl/btrt5xnJf4x/eZr2jpcLMVMqDwIrEWJbN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LLKTl/btrt5xnJf4x/eZr2jpcLMVMqDwIrEWJbN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LLKTl/btrt5xnJf4x/eZr2jpcLMVMqDwIrEWJbN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLLKTl%2Fbtrt5xnJf4x%2FeZr2jpcLMVMqDwIrEWJbN0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b216x3/btrtYV4GW4U/OqDB0W8xWYpk79GlRyrWY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b216x3/btrtYV4GW4U/OqDB0W8xWYpk79GlRyrWY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b216x3/btrtYV4GW4U/OqDB0W8xWYpk79GlRyrWY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb216x3%2FbtrtYV4GW4U%2FOqDB0W8xWYpk79GlRyrWY0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실행을 하면 위와 같은 로그가 발생하는데 application.properties에서 설정한 포트로 접속을 하면 정상적으로 실행하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OKxW0/btrt6uK2yXO/I0siAFC9dI5TFfshxdAY8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OKxW0/btrt6uK2yXO/I0siAFC9dI5TFfshxdAY8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OKxW0/btrt6uK2yXO/I0siAFC9dI5TFfshxdAY8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOKxW0%2Fbtrt6uK2yXO%2FI0siAFC9dI5TFfshxdAY8K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여기까지가 Spring boot의 기본적인 설정입니다.&lt;/p&gt;
&lt;p&gt;앞으로 JPA도 Web Framework와 설정이 다르기 때문에 다시 설정을 해야 할 것 같고, Apache 연결 등도 다시 확인해야 하겠네요.&lt;/p&gt;
&lt;p&gt;Thymeleaf 문법도 정리가 필요할 것 같고, 처음 템플릿 위자드을 보니 기본적으로 지원하는 라이브러리가 많이 있는데 하나하나 확인해 봐야겠네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Eclipse에서 Spring boot를 설정하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Java</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/313</guid>
      <comments>https://nowonbun.tistory.com/313#entry313comment</comments>
      <pubDate>Tue, 22 Feb 2022 18:51:04 +0900</pubDate>
    </item>
    <item>
      <title>[Python] Redis 데이터베이스를 접속해서 사용하는 방법</title>
      <link>https://nowonbun.tistory.com/365</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Python에서 Redis 데이터베이스를 접속해서 사용하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Redis 데이터베이스는 RDB 종류가 아닌 NoSQL 종류의 Key-Value타입의 데이터베이스입니다. 간단하게 공유 메모리 데이터베이스입니다.&lt;/p&gt;
&lt;p&gt;이전 글에서 Linux 환경에서 설치 및 사용하는 방법에 대해 설명한 적 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/384&quot; target=&quot;_blank&quot;&gt;[CentOS] Redis 데이터베이스 설치와 명령어 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 Redis 데이터베이스를 Python에서 사용해 보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;먼저 Python에서 Redis 데이터베이스를 사용하기 위해서는 Redis 라이브러리를 설치해야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;pip install&quot;&gt;pip3 install redis
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/77SrL/btrtOzf0iMx/kwyhZTB7MELMrk9AT9R4u1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/77SrL/btrtOzf0iMx/kwyhZTB7MELMrk9AT9R4u1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/77SrL/btrtOzf0iMx/kwyhZTB7MELMrk9AT9R4u1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F77SrL%2FbtrtOzf0iMx%2FkwyhZTB7MELMrk9AT9R4u1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;저는 이미 설치가 되어 있어서 설치되어있다고 표시가 되네요.. 설치가 되지 않으신 분들을 위 명령어로 설치하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 다시 Python 소스에서 Redis를 사용해 보도록 하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-type=&quot;Program.py&quot;&gt;# redis 라이브러리 선언
import redis

# 호스트와 포트 설정, 데이터베이스 설정하여 connection 취득
with redis.StrictRedis(host='192.168.0.201', port=6379, db=0) as conn:
  # test를 키로 hello world 값을 입력
  conn.set('test', 'hello world')
  # test를 키로 데이터를 출력
  data = conn.get('test')
  # 콘솔 출력
  print(data)

# 호스트와 포트 설정, 데이터베이스 설정하여 pool를 설정
redis_pool = redis.ConnectionPool(host='192.168.0.201', port=6379, db=0, max_connections=4)
# pool로 connection을 취득
with redis.StrictRedis(connection_pool=redis_pool) as conn:
  # test1를 키로 hello world1 값을 입력
  conn.set('test1', 'hello world1')
  # test를 키로 데이터를 출력
  data = conn.get('test1')
  # 콘솔 출력
  print(data)
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaIrO8/btrtRWCyCBE/fCKmky8ynRBd0DLyyXF6Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaIrO8/btrtRWCyCBE/fCKmky8ynRBd0DLyyXF6Gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaIrO8/btrtRWCyCBE/fCKmky8ynRBd0DLyyXF6Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeaIrO8%2FbtrtRWCyCBE%2FfCKmky8ynRBd0DLyyXF6Gk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdZbDP/btrtIOSQjGV/vMSiQM6lSoKfaQcZPchk10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdZbDP/btrtIOSQjGV/vMSiQM6lSoKfaQcZPchk10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdZbDP/btrtIOSQjGV/vMSiQM6lSoKfaQcZPchk10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdZbDP%2FbtrtIOSQjGV%2FvMSiQM6lSoKfaQcZPchk10%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;기본적인 사용법은 key를 이용해 데이터를 입력하고 취득하는 함수합니다.&lt;/p&gt;
&lt;p&gt;먼저 접속 방법에는 StrictRedis를 이용해서 직접 connection을 취득하는 방법이 있고 pool를 먼저 생성해서 pool로 connection을 취득하는 방법입니다.&lt;/p&gt;
&lt;p&gt;어느 쪽이 더 좋다라고 하기는 어렵지만 pool이 커넥션을 관리하기에는 더 용이해 보이네요... 위는 싱글 프로세스로 움직이니 pool를 사용하지 않아도 관리가 되지만 만약 멀티 thread 환경이라면 connection 관리가 필요하니 pool를 사용하는 편이 더 좋을 듯 싶네요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-type=&quot;Program.py&quot;&gt;# redis 라이브러리 선언
import redis

# 호스트와 포트 설정, 데이터베이스 설정하여 connection 취득
with redis.StrictRedis(host='192.168.0.201', port=6379, db=0) as conn:
  # test를 키로 hello world 값을 입력, 500초의 만료시간
  conn.set('test', 'hello world', 500)
  # test 키의 만료시간을 출력
  print(conn.ttl('test'))
  # 만료시간 설정 100초
  conn.expire('test', 100)
  # test 키의 만료시간을 출력
  print(conn.ttl('test'))
  # test를 키로 데이터를 출력
  data = conn.get('test')
  # 콘솔 출력
  print(data)
  # 모든 키 지우기
  #conn.flushall()
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ETrUK/btrtTLOgl4m/HUVpVJoLNqxNkUwyb60YB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ETrUK/btrtTLOgl4m/HUVpVJoLNqxNkUwyb60YB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ETrUK/btrtTLOgl4m/HUVpVJoLNqxNkUwyb60YB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FETrUK%2FbtrtTLOgl4m%2FHUVpVJoLNqxNkUwyb60YB0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d6QIgs/btrtIO6kTQa/mN7Vymn9KEkfr0ZS7RJMhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d6QIgs/btrtIO6kTQa/mN7Vymn9KEkfr0ZS7RJMhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d6QIgs/btrtIO6kTQa/mN7Vymn9KEkfr0ZS7RJMhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd6QIgs%2FbtrtIO6kTQa%2FmN7Vymn9KEkfr0ZS7RJMhK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 소스는 만료시간 설정입니다. 단위는 초 단위로 이루어져 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다음은 Redis에서 사용될 List와 Map, Set, SortedSet 형식의 자료형입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot; data-type=&quot;Program.py&quot;&gt;# redis 라이브러리 선언
import redis

# 호스트와 포트 설정, 데이터베이스 설정하여 connection 취득
with redis.StrictRedis(host='192.168.0.201', port=6379, db=0) as conn:
  # 리스트에서 오른쪽 입력
  conn.rpush(&quot;list&quot;, &quot;1&quot;);
  # 리스트에서 왼쪽 입력
  conn.lpush(&quot;list&quot;, &quot;2&quot;);
  # 리스트 데이터 출력 - 2, 1
  for i in conn.lrange('list',0 ,10):
    # 콘솔 출력
    print(i)
  # 콘솔 출력
  print('lpop list')
  # 리스트 왼쪽 pop - 2
  print(conn.lpop('list'))
  # 콘솔 출력
  print('list')
  # 리스트 데이터 출력 - 1
  for i in conn.lrange('list',0 ,10):
    # 콘솔 출력
    print(i)
  # 개행
  print()
  # Hash 형식의 key-value 값 입력
  conn.hset(&quot;map&quot;, &quot;a&quot;, &quot;1&quot;)
  conn.hset(&quot;map&quot;, &quot;b&quot;, &quot;2&quot;)
  conn.hset(&quot;map&quot;, &quot;c&quot;, &quot;3&quot;)
  # Hash 형식의 모든 데이터 출력
  print(conn.hgetall('map'))
  # Hash 형식의 a key의 데이터 출력
  print(conn.hget('map', 'a'))
  # 개행
  print()
  # Set 형식의 값 입력
  conn.sadd('set', 'C')
  conn.sadd('set', 'B')
  conn.sadd('set', 'A')
  # 중복은 입력 안된다.
  conn.sadd('set', 'A')
  conn.sadd('set', 'A')
  # Set 형식 출력
  print(conn.smembers('set'))
  # Set 형식의 값 입력 - 변수 명 set1
  conn.sadd('set1', 'A')
  conn.sadd('set1', 'D')
  # set과 set1의 교집합
  print(conn.sinter('set', 'set1'))
  # set과 set1의 합집합
  print(conn.sunion('set', 'set1'))
  # 개행
  print()
  # SortedSet 형식의 값 입력
  conn.zadd('sortedset', {'B': 1})
  conn.zadd('sortedset', {'A': 3})
  conn.zadd('sortedset', {'C': 0})
  # 10개의 데이터 출력
  print(conn.zrange('sortedset',0 , 9))
  # 10개의 데이터 출력 - 내림차순
  print(conn.zrange('sortedset',0 , 9, desc=True))
  # 2개의 데이터 출력 - 내림차순
  print(conn.zrange('sortedset',0 , 1, desc=True))
  # 모든 키 지우기 
  #conn.flushall()
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmzLlW/btrtVnTU2hh/7MJfAK83ON8mglWeteD0Q0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmzLlW/btrtVnTU2hh/7MJfAK83ON8mglWeteD0Q0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmzLlW/btrtVnTU2hh/7MJfAK83ON8mglWeteD0Q0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmzLlW%2FbtrtVnTU2hh%2F7MJfAK83ON8mglWeteD0Q0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b231PI/btrtRWvLift/zRcQVrN4Lz8XA3VUBPVeQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b231PI/btrtRWvLift/zRcQVrN4Lz8XA3VUBPVeQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b231PI/btrtRWvLift/zRcQVrN4Lz8XA3VUBPVeQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb231PI%2FbtrtRWvLift%2FzRcQVrN4Lz8XA3VUBPVeQ0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Python은 사용하는 사람에 따라 다르기는 하지만, 저는 무슨 프로그램을 만들기보다는 프로젝트의 보조 수단으로 배치 스크립트등으로 활용을 자주하는 편입니다.&lt;/p&gt;
&lt;p&gt;그럼에 있어서 여러 로컬에서 데이터를 공유하거나 Java나 C#에서 Redis를 사용하게 되면 그 값을 확인하거나 테스트하는 목적으로 사용되곤 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Python에서 Redis 데이터베이스를 접속해서 사용하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Development note/Python</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/365</guid>
      <comments>https://nowonbun.tistory.com/365#entry365comment</comments>
      <pubDate>Mon, 21 Feb 2022 18:22:50 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Redis 데이터베이스를 접속해서 사용하는 방법(Jedis 라이브러리)</title>
      <link>https://nowonbun.tistory.com/382</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 Java에서 Redis 데이터베이스를 접속해서 사용하는 방법(Jedis 라이브러리)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Redis 데이터베이스는 RDB 종류가 아닌 NoSQL 종류의 Key-Value타입의 데이터베이스입니다. 간단하게 공유 메모리 데이터베이스입니다.&lt;/p&gt;
&lt;p&gt;이전 글에서 Linux 환경에서 설치 및 사용하는 방법에 대해 설명한 적 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/384&quot; target=&quot;_blank&quot;&gt;[CentOS] Redis 데이터베이스 설치와 명령어 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 Redis 데이터베이스를 Java에서 사용해 보겠습니다.&lt;/p&gt;
&lt;p&gt;Java에서 Redis 데이터베이스를 사용하기 위해서는 Maven을 통해서 Jedis 라이브러리를 설치해야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot; data-type=&quot;pom.xml&quot;&gt;&amp;lt;!-- https://mvnrepository.com/artifact/redis.clients/jedis --&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;redis.clients&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;jedis&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;4.1.1&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QDuD7/btrtqCZkMp3/AEtzRqf5DH4mzjac4vEr41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QDuD7/btrtqCZkMp3/AEtzRqf5DH4mzjac4vEr41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QDuD7/btrtqCZkMp3/AEtzRqf5DH4mzjac4vEr41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQDuD7%2FbtrtqCZkMp3%2FAEtzRqf5DH4mzjac4vEr41%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;리포지토리 - &lt;a href=&quot;https://mvnrepository.com/artifact/redis.clients/jedis/4.1.1&quot; target=&quot;_blank&quot;&gt;https://mvnrepository.com/artifact/redis.clients/jedis/4.1.1&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bXkk1U/btrtrahcQNx/KZ69Q7bqEpMkwj1iRBfBX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bXkk1U/btrtrahcQNx/KZ69Q7bqEpMkwj1iRBfBX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bXkk1U/btrtrahcQNx/KZ69Q7bqEpMkwj1iRBfBX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbXkk1U%2FbtrtrahcQNx%2FKZ69Q7bqEpMkwj1iRBfBX1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;먼저 기본적으로 Redis 데이터베이스에 값을 저장하고 취득하는 코드를 작성하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-type=&quot;Program.java&quot;&gt;import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

public class Program {
  // 실행 함수
  public static void main(String... args) {
    // redis 접속한다.
    try (var pool = new JedisPool(&quot;192.168.1.200&quot;, Protocol.DEFAULT_PORT)) {
      // Resource 취득한다. (사실 이게 Pool에서 접속을 하는지 Resource 취득할 때 접속을 하는지 명확한 설명이 없음)
      try (var jedis = pool.getResource()) {
        // test 키로 hello world 값을 넣는다.
        jedis.set(&quot;test&quot;, &quot;hello world&quot;);
        // test1 키로 hello world expire 값을 넣는다. (만료시간 60초)
        jedis.setex(&quot;test1&quot;, 60, &quot;hello world expire&quot;);

        // test 키로 값을 취득한다.
        var data = jedis.get(&quot;test&quot;);
        // 콘솔 출력
        System.out.println(data);
        // test1 키로 값을 취득
        data = jedis.get(&quot;test1&quot;);
        // 콘솔 출력
        System.out.println(data);
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/L64tH/btrtt48pDOi/waKpoWjbrar8a0DqySIQXK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/L64tH/btrtt48pDOi/waKpoWjbrar8a0DqySIQXK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/L64tH/btrtt48pDOi/waKpoWjbrar8a0DqySIQXK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FL64tH%2Fbtrtt48pDOi%2FwaKpoWjbrar8a0DqySIQXK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVfKaF/btrtwrINA15/iwBc0nZAAb6RlqFKOtQfUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVfKaF/btrtwrINA15/iwBc0nZAAb6RlqFKOtQfUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVfKaF/btrtwrINA15/iwBc0nZAAb6RlqFKOtQfUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVfKaF%2FbtrtwrINA15%2FiwBc0nZAAb6RlqFKOtQfUk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 소스에서는 test 키로 hello world 값을 넣었습니다. 그리고 test1로 만료시간 60초인 hello world expire 값을 넣었습니다.&lt;/p&gt;
&lt;p&gt;그리고 test와 test1의 키로 데이터를 취득하니 위에서 넣은 hello world 값이 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;관련 함수에 관해서는 API 도큐멘트를 참조하세요.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://javadoc.io/doc/redis.clients/jedis/latest/index.html&quot; target=&quot;_blank&quot;&gt;https://javadoc.io/doc/redis.clients/jedis/latest/index.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위 소스는 단순하게 key 값을 이용해서 데이터를 입력하고 취득하는 처리입니다.&lt;/p&gt;
&lt;p&gt;실전에서 사용할 만한 코드를 작성해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-type=&quot;Program.java&quot;&gt;import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

// Node 클래스
class Node implements Serializable {
  // Serializable 인터페이스를 상속받으면 serialVersionUID를 설정해야 한다.
  private static final long serialVersionUID = 1L;
  // 맴버 변수
  private String data;
  // 맴버 변수 setter 
  public void setData(String data) {
    // 맴버 변수 설정
    this.data = data;
  }
  // 출력 함수
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;data = &quot; + data);
  }
}

public class Program {
  // Node 클래스를 직렬화하여 byte타입으로 변환
  static byte[] convertToStringFromClass(Node node) {
    // 직렬화
    try (var baos = new ByteArrayOutputStream()) {
      try (var oos = new ObjectOutputStream(baos)) {
        // 변환
        oos.writeObject(node);
        // Node클래스를 byte 배열로 변환
        return baos.toByteArray();
      }
    } catch (Throwable e) {
      throw new RuntimeException(e);
    }
  }

  // 실행 함수
  public static void main(String... args) {
    // redis 접속한다.
    try (var pool = new JedisPool(&quot;192.168.1.200&quot;, Protocol.DEFAULT_PORT)) {
      // Resource 취득한다. (사실 이게 Pool에서 접속을 하는지 Resource 취득할 때 접속을 하는지 명확한 설명이 없음)
      try (var jedis = pool.getResource()) {
        // 인스턴스 생성
        var node = new Node();
        // 데이터 설정
        node.setData(&quot;Hello world&quot;);
        
        // Redis 데이터베이스에 node 키로 설정
        jedis.set(&quot;node&quot;.getBytes(), convertToStringFromClass(node));
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NiAgz/btrtvsupBaz/sRatI3o4WB4ikkTpcCeN8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NiAgz/btrtvsupBaz/sRatI3o4WB4ikkTpcCeN8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NiAgz/btrtvsupBaz/sRatI3o4WB4ikkTpcCeN8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNiAgz%2FbtrtvsupBaz%2FsRatI3o4WB4ikkTpcCeN8k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Node 클래스를 인스턴스 생성해서 직렬화로 byte 타입으로 변환했습니다. 그리고 byte 타입으로 변환한 값을 Redis 데이터베이스에 입력했습니다.&lt;/p&gt;
&lt;p&gt;다시 이걸 취득해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-type=&quot;Program.java&quot;&gt;import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

// Node 클래스
class Node implements Serializable {
  // Serializable 인터페이스를 상속받으면 serialVersionUID를 설정해야 한다.
  private static final long serialVersionUID = 1L;
  // 맴버 변수
  private String data;
  // 맴버 변수 setter
  public void setData(String data) {
    // 맴버 변수 설정
    this.data = data;
  }
  // 출력 함수
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;data = &quot; + data);
  }
}

public class Program {
  // 데이터를 역직렬화 해서 Node 클래스로 변환
  static Node convertToClassFromString(byte[] binary) {
    // 역직렬화
    try (var bais = new ByteArrayInputStream(binary)) {
      try (var ois = new ObjectInputStream(bais)) {
        // byte 배열의 데이터를 Node의 클래스로 변환한다.
        return (Node) ois.readObject();
      }
    } catch (Throwable e) {
      throw new RuntimeException(e);
    }
  }

  // 실행 함수
  public static void main(String... args) {
    // redis 접속한다.
    try (var pool = new JedisPool(&quot;192.168.1.200&quot;, Protocol.DEFAULT_PORT)) {
      // Resource 취득한다. (사실 이게 Pool에서 접속을 하는지 Resource 취득할 때 접속을 하는지 명확한 설명이 없음)
      try (var jedis = pool.getResource()) {
        // Redis 데이터베이스에 node 키로 취득
        var binary = jedis.get(&quot;node&quot;.getBytes());
        // byte 타입을 node 클래스로 변환
        var node = convertToClassFromString(binary);
        // print 함수 실행
        node.print();
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ri1rE/btrtwqXoIj4/5wkldl16lcga4kzTl5vPY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ri1rE/btrtwqXoIj4/5wkldl16lcga4kzTl5vPY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ri1rE/btrtwqXoIj4/5wkldl16lcga4kzTl5vPY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fri1rE%2FbtrtwqXoIj4%2F5wkldl16lcga4kzTl5vPY1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Redis 데이터베이스에서 node 키로 되어 있는 byte 값을 취득하고 역직렬화를 통해 Node 클래스로 변환합니다.&lt;/p&gt;
&lt;p&gt;print 함수를 호출하면 위에서 입력한 Hello world 값이 출력되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;직렬화를 사용하면 같은 Java 프로그램에서는 문제 없겠지만 다른 언어의 프로그램에서는 사용할 수 없습니다. 그래서 Json 타입으로 변환해서 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-type=&quot;Program.java&quot;&gt;import java.io.Serializable;
import com.google.gson.Gson;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

// Node 클래스
class Node implements Serializable {
  // Serializable 인터페이스를 상속받으면 serialVersionUID를 설정해야 한다.
  private static final long serialVersionUID = 1L;
  // 맴버 변수
  private String data;
  // 맴버 변수 setter
  public void setData(String data) {
    // 맴버 변수 설정
    this.data = data;
  }
  // 출력 함수
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;data = &quot; + data);
  }
}

public class Program {
  // 실행 함수
  public static void main(String... args) {
    Gson gson = new Gson();
    // redis 접속한다.
    try (var pool = new JedisPool(&quot;192.168.1.200&quot;, Protocol.DEFAULT_PORT)) {
      // Resource 취득한다. (사실 이게 Pool에서 접속을 하는지 Resource 취득할 때 접속을 하는지 명확한 설명이 없음)
      try (var jedis = pool.getResource()) {
        // 인스턴스 생성
        var node = new Node();
        // 데이터 설정
        node.setData(&quot;Hello world&quot;);
        // node 인스턴스를 json 타입의 string으로 변환
        var json = gson.toJson(node);
        // Redis 데이터베이스에 node 키로 설정
        jedis.set(&quot;node&quot;, json);
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UTqD4/btrtsUZdTqN/k8mOcEzks5gKrVzduGNgHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UTqD4/btrtsUZdTqN/k8mOcEzks5gKrVzduGNgHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UTqD4/btrtsUZdTqN/k8mOcEzks5gKrVzduGNgHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUTqD4%2FbtrtsUZdTqN%2Fk8mOcEzks5gKrVzduGNgHK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Node 인스턴스를 Gson클래스로 사용해서 Json 타입의 String 데이터로 변환했습니다. 그대로 String 데이터를 Redis로 입력했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-type=&quot;Program.cs&quot;&gt;import java.io.Serializable;
import com.google.gson.Gson;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

// Node 클래스
class Node implements Serializable {
  // Serializable 인터페이스를 상속받으면 serialVersionUID를 설정해야 한다.
  private static final long serialVersionUID = 1L;
  // 맴버 변수
  private String data;

  // 맴버 변수 setter
  public void setData(String data) {
    // 맴버 변수 설정
    this.data = data;
  }

  // 출력 함수
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;data = &quot; + data);
  }
}

public class Program {
  // 실행 함수
  public static void main(String... args) {
    Gson gson = new Gson();
    // redis 접속한다.
    try (var pool = new JedisPool(&quot;192.168.1.200&quot;, Protocol.DEFAULT_PORT)) {
      // Resource 취득한다. (사실 이게 Pool에서 접속을 하는지 Resource 취득할 때 접속을 하는지 명확한 설명이 없음)
      try (var jedis = pool.getResource()) {
        // Redis 데이터베이스에서 node 키로 데이터 취득
        var data = jedis.get(&quot;node&quot;);
        // json 타입의 데이터를 node로 변환
        var node = gson.fromJson(data, Node.class);
        // print 함수 실행
        node.print();
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcG3qq/btrtxk3ujgh/YjjdzsBLCC3fncB2ae4Ng0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcG3qq/btrtxk3ujgh/YjjdzsBLCC3fncB2ae4Ng0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcG3qq/btrtxk3ujgh/YjjdzsBLCC3fncB2ae4Ng0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcG3qq%2Fbtrtxk3ujgh%2FYjjdzsBLCC3fncB2ae4Ng0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Redis 데이터베이스에 node키로 json 타입의 string 데이터를 취득합니다.&lt;/p&gt;
&lt;p&gt;그리고 데이터를 Gson 클래스를 이용해서 Node 클래스로 변환합니다. 그리고 print 함수를 호출하니 위에 입력한 Hello world 값이 출력되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다음은 Redis에서 사용될 List와 Map, SortedSet 형식의 자료형입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot; data-type=&quot;Program.cs&quot;&gt;import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

public class Program {
  // 실행 함수
  public static void main(String... args) {
    // redis 접속한다.
    try (var pool = new JedisPool(&quot;192.168.1.200&quot;, Protocol.DEFAULT_PORT)) {
      // Resource 취득한다. (사실 이게 Pool에서 접속을 하는지 Resource 취득할 때 접속을 하는지 명확한 설명이 없음)
      try (var jedis = pool.getResource()) {
        // List 형식 오른쪽 Push
        jedis.rpush(&quot;list&quot;, &quot;1&quot;);
        // List 형식 왼쪽 Push
        jedis.lpush(&quot;list&quot;, &quot;2&quot;);
        // 2, 1
        // 콘솔 출력
        System.out.println(jedis.lpop(&quot;list&quot;));
        System.out.println(jedis.lpop(&quot;list&quot;));
        // 개행
        System.out.println();

        // Hash 형식의 key-value 값 입력
        jedis.hset(&quot;map&quot;, &quot;a&quot;, &quot;1&quot;);
        jedis.hset(&quot;map&quot;, &quot;b&quot;, &quot;2&quot;);
        jedis.hset(&quot;map&quot;, &quot;c&quot;, &quot;3&quot;);

        // 개행
        System.out.println(jedis.hget(&quot;map&quot;, &quot;b&quot;));
        // 개행
        System.out.println();
        // SortedSet 형식의 값 입력
        jedis.zadd(&quot;SortedSet&quot;, 1, &quot;aaa&quot;);
        jedis.zadd(&quot;SortedSet&quot;, 0, &quot;bbb&quot;);
        // sort 되어 출력
        for (var sort : jedis.zscan(&quot;SortedSet&quot;, &quot;&quot;).getResult()) {
          // 콘솔 출력
          System.out.println(sort.getElement());
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfnIqk/btrtwrve1vS/pGiqmZampiefmyL5cJpTE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfnIqk/btrtwrve1vS/pGiqmZampiefmyL5cJpTE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfnIqk/btrtwrve1vS/pGiqmZampiefmyL5cJpTE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfnIqk%2Fbtrtwrve1vS%2FpGiqmZampiefmyL5cJpTE0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위는 Redis에서 list와 map, SortedSet 형식으로 사용되는 타입입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;사실 저는 잘 사용하지 않는 타입이기도 합니다.&lt;/p&gt;
&lt;p&gt;Redis 프로그램의 알고리즘이 나쁘다고는 생각하지는 않지만 Redis 데이터베이스의 성능에 대한 병목 현상이나 알고리즘의 성능이 C#의 기본 List나 Dictionary보다 더 좋다고 생각되지는 않아서 가능하면 데이터를 그대로 넣고 취득해서 프로그램 내부에서 처리합니다.&lt;/p&gt;
&lt;p&gt;제가 아직 Redis의 경험이 많지 않아서 정확하게는 잘 모르겠네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Java에서 Redis 데이터베이스를 접속해서 사용하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Development note/Java</category>
      <category>구글</category>
      <category>템플릿</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/382</guid>
      <comments>https://nowonbun.tistory.com/382#entry382comment</comments>
      <pubDate>Wed, 16 Feb 2022 18:12:03 +0900</pubDate>
    </item>
    <item>
      <title>[C#] Redis 데이터베이스를 접속해서 사용하는 방법 (StackExchange.Redis 라이브러리 사용법)</title>
      <link>https://nowonbun.tistory.com/383</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 Redis 데이터베이스를 접속해서 사용하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Redis 데이터베이스는 RDB 종류가 아닌 NoSQL 종류의 Key-Value타입의 데이터베이스입니다. 간단하게 공유 메모리 데이터베이스입니다.&lt;/p&gt;
&lt;p&gt;이전 글에서 Linux 환경에서 설치 및 사용하는 방법에 대해 설명한 적 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/384&quot; target=&quot;_blank&quot;&gt;[CentOS] Redis 데이터베이스 설치와 명령어 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그 Redis 데이터베이스를 C#에서 사용해 보겠습니다.&lt;/p&gt;
&lt;p&gt;먼저 C#에서 사용하기 위해서는 Nuget을 이용해서 StackExchange.Redis 라이브러리를 설치합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOUDbH/btrtlT7pehV/iukoXE6NDDCUpulesmvVMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOUDbH/btrtlT7pehV/iukoXE6NDDCUpulesmvVMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOUDbH/btrtlT7pehV/iukoXE6NDDCUpulesmvVMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOUDbH%2FbtrtlT7pehV%2FiukoXE6NDDCUpulesmvVMK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uGgyc/btrtrW2FZtg/rfJd8Iqj9xLVsdo6y79n6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uGgyc/btrtrW2FZtg/rfJd8Iqj9xLVsdo6y79n6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uGgyc/btrtrW2FZtg/rfJd8Iqj9xLVsdo6y79n6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuGgyc%2FbtrtrW2FZtg%2FrfJd8Iqj9xLVsdo6y79n6K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그럼 라이브러리가 설치되었으면 Redis 데이터베이스에 값을 저장하고 취득하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using StackExchange.Redis;

namespace ConsoleApp1
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // redis 접속한다.
      ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
      {
        EndPoints = { &quot;192.168.1.200:6379&quot; }
      });
      // 기본 데이터베이스 취득
      var db = redis.GetDatabase();
      // ping pong 확인
      var pong = db.Ping();
      // 콘솔 출력
      Console.WriteLine(pong);

      // test 키로 hello world 값을 넣는다. 만료시간은 10분입니다.
      db.StringSet(&quot;test&quot;, &quot;hello world&quot;, TimeSpan.FromMinutes(10));
      // test 키로 데이터를 취득한다.
      string data = db.StringGet(&quot;test&quot;);
      // 콘솔 출력
      Console.WriteLine(data);

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZnQAf/btrtramyYUD/r73YhJsNibCWfwFccos04K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZnQAf/btrtramyYUD/r73YhJsNibCWfwFccos04K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZnQAf/btrtramyYUD/r73YhJsNibCWfwFccos04K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZnQAf%2FbtrtramyYUD%2Fr73YhJsNibCWfwFccos04K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beNPKL/btrtk0MJo0C/dq7xe086mZaGwoBsNEkgfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beNPKL/btrtk0MJo0C/dq7xe086mZaGwoBsNEkgfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beNPKL/btrtk0MJo0C/dq7xe086mZaGwoBsNEkgfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeNPKL%2Fbtrtk0MJo0C%2Fdq7xe086mZaGwoBsNEkgfK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 소스에서 test의 키로 hello world를 넣었습니다. 그리고 10분 뒤에는 자동으로 key-value가 사라집니다.&lt;/p&gt;
&lt;p&gt;그리고 test키로 취득하니 입력한 값이 콘솔에 출력이 되네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;관련 함수에 관해서는 API 도큐멘트를 참조하세요.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://stackexchange.github.io/StackExchange.Redis/&quot; target=&quot;_blank&quot;&gt;https://stackexchange.github.io/StackExchange.Redis/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위에는 단순히 key 값으로 데이터를 넣어서 취득하는 기본적인 처리입니다.&lt;/p&gt;
&lt;p&gt;그럼 실제 프로그램 작성할 때 자주 사용할 만한 처리식을 만들어 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.IO;
using StackExchange.Redis;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace ConsoleApp1
{
  class Program
  {
    // 직렬화 타입
    [Serializable]
    // 예제 클래스
    class Node
    {
      // 맴버 변수
      private string data;
      // 생성자
      public Node(string data)
      {
        // 맴버 변수 설정
        this.data = data;
      }
      // 출력 함수
      public void Print()
      {
        // 콘솔 출력
        Console.WriteLine(this.data);
      }
    }
    // Node 클래스를 직렬화하여 binary로 바꾼뒤 String 타입으로 변환
    static String ConvertToStringFromClass(Node node)
    {
      // 직렬화 클래스
      var formatter = new BinaryFormatter();
      // 클래스를 직렬화하여 보관할 데이터
      byte[] data;
      // 메모리 스트림
      using (MemoryStream stream = new MemoryStream())
      {
        // 클래스를 직렬화하여 stream으로 변환
        formatter.Serialize(stream, node);
        // 스트림을 byte[] 데이터로 변환한다.
        data = stream.GetBuffer();
      }
      // 값의 변형이 없게 ASCII 코드로 String 타입으로 변환
      return Encoding.ASCII.GetString(data);
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // redis 접속한다.
      ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
      {
        EndPoints = { &quot;192.168.1.200:6379&quot; }
      });
      // 기본 데이터베이스 취득
      var db = redis.GetDatabase();

      // 인스턴스 생성
      var node = new Node(&quot;Test&quot;);
      // Node 인스턴스를 string 타입으로 변환
      var data = ConvertToStringFromClass(node);
      // Redis 데이터베이스에 test 키로 저장 (만료시간 10분)
      db.StringSet(&quot;test&quot;, data, TimeSpan.FromMinutes(10));

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOjVmf/btrtlUrJMIv/QV0dPOqYaZIihQEp1hyVm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOjVmf/btrtlUrJMIv/QV0dPOqYaZIihQEp1hyVm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOjVmf/btrtlUrJMIv/QV0dPOqYaZIihQEp1hyVm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOjVmf%2FbtrtlUrJMIv%2FQV0dPOqYaZIihQEp1hyVm1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pAVFT/btrtmVYhbY8/ITi5jIvuMRz8Uei8XSMJAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pAVFT/btrtmVYhbY8/ITi5jIvuMRz8Uei8XSMJAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pAVFT/btrtmVYhbY8/ITi5jIvuMRz8Uei8XSMJAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpAVFT%2FbtrtmVYhbY8%2FITi5jIvuMRz8Uei8XSMJAk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 소스에서 Node 클래스의 인스턴스를 직렬화로 이용하여 byte로 바꾸고 다시 String 타입으로 변환하여 Redis 데이터베이스에 입력했습니다.&lt;/p&gt;
&lt;p&gt;그럼 이걸 다른 형식의 프로그램에서 취득할 수 있을까?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.IO;
using StackExchange.Redis;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace ConsoleApp1
{
  class Program
  {
    // 직렬화 타입
    [Serializable]
    // 예제 클래스
    class Node
    {
      // 맴버 변수
      private string data;
      // 생성자
      public Node(string data)
      {
        // 맴버 변수 설정
        this.data = data;
      }
      // 출력 함수
      public void Print()
      {
        // 콘솔 출력
        Console.WriteLine(this.data);
      }
    }
    // String 타입으로 byte로 변환하여 직렬화를 통해 인스턴스로 변환
    static Node ConvertToClassFromString(String val)
    {
      // String을 통해서 byte로 변환
      var binary = Encoding.ASCII.GetBytes(val);
      // 직렬화 클래스
      var formatter = new BinaryFormatter();
      // 메모리 스트림
      using (MemoryStream stream = new MemoryStream(binary))
      {
        // stream을 직렬화하여 인스턴스로 변환
        return (Node)formatter.Deserialize(stream);
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // redis 접속한다.
      ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
      {
        EndPoints = { &quot;192.168.1.200:6379&quot; }
      });
      // 기본 데이터베이스 취득
      var db = redis.GetDatabase();

      // test 키로 데이터를 취득한다.
      var data = db.StringGet(&quot;test&quot;);
      // string 타입을 Node 인스턴스로 변환
      var node = ConvertToClassFromString(data);
      // Node 클래스의 Print 함수 호출
      node.Print();

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYOLeU/btrtsVJcMfb/6CKRpSLyAmddEoRkA8s3p0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYOLeU/btrtsVJcMfb/6CKRpSLyAmddEoRkA8s3p0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYOLeU/btrtsVJcMfb/6CKRpSLyAmddEoRkA8s3p0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYOLeU%2FbtrtsVJcMfb%2F6CKRpSLyAmddEoRkA8s3p0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;다른 프로그램에서 생성된 Node 인스턴스를 redis 데이터베이스에 넣고, 다시 다른 프로그램에서 취득하여 인스턴스로 변환하여 사용할 수 있는 것을 확인했습니다.&lt;/p&gt;
&lt;p&gt;이 뜻은 직렬화를 통해서 클래스 형식으로 여러 프로그램에서 값을 공유할 수 있다는 뜻입니다. C#의 직렬화이니 다른 언어 프로그램에서는 사용할 수 없고 같은 언어인 C#에서만 사용이 가능하겠네요..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다른 언어간에 사용하려면 Json 타입으로 변환해서 사용하게 되면 언어가 다른 경우에도 값을 공유할 수 있겠네요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using StackExchange.Redis;
using Newtonsoft.Json;

namespace ConsoleApp1
{
  class Program
  {
    // 예제 클래스
    class Node
    {
      // 맴버 변수
      public string Data { get; set; }
      // 생성자
      public Node(string data)
      {
        // 맴버 변수 설정
        this.Data = data;
      }
      // 출력 함수
      public void Print()
      {
        // 콘솔 출력
        Console.WriteLine(this.Data);
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // redis 접속한다.
      ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
      {
        EndPoints = { &quot;192.168.1.200:6379&quot; }
      });
      // 기본 데이터베이스 취득
      var db = redis.GetDatabase();

      // 인스턴스 생성
      var node = new Node(&quot;Test&quot;);
      // 인스턴스를 Json 타입으로 변환
      var data = JsonConvert.SerializeObject(node);
      // Redis 데이터베이스에 test1 키로 저장 (만료시간 10분)
      db.StringSet(&quot;test1&quot;, data, TimeSpan.FromMinutes(10));

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GamtM/btrtrX8lnMa/ZwFPiLqwQgVt4yqPbcYuvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GamtM/btrtrX8lnMa/ZwFPiLqwQgVt4yqPbcYuvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GamtM/btrtrX8lnMa/ZwFPiLqwQgVt4yqPbcYuvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGamtM%2FbtrtrX8lnMa%2FZwFPiLqwQgVt4yqPbcYuvk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgEY7h/btrtmVjE96v/GgR9qy7XV2oU4fsxDg5fh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgEY7h/btrtmVjE96v/GgR9qy7XV2oU4fsxDg5fh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgEY7h/btrtmVjE96v/GgR9qy7XV2oU4fsxDg5fh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgEY7h%2FbtrtmVjE96v%2FGgR9qy7XV2oU4fsxDg5fh0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 직렬화를 통해서 String으로 변환하여 Redis 데이터베이스에 저장한 것과 비슷합니다만 이번에는 직렬화가 아닌 Json 구조 타입으로 변환해서 입력했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using StackExchange.Redis;
using Newtonsoft.Json;

namespace ConsoleApp1
{
  class Program
  {
    // 예제 클래스
    class Node
    {
      // 맴버 변수
      public string Data { get; set; }
      // 생성자
      public Node(string data)
      {
        // 맴버 변수 설정
        this.Data = data;
      }
      // 출력 함수
      public void Print()
      {
        // 콘솔 출력
        Console.WriteLine(this.Data);
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // redis 접속한다.
      ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
      {
        EndPoints = { &quot;192.168.1.200:6379&quot; }
      });
      // 기본 데이터베이스 취득
      var db = redis.GetDatabase();

      // test1 키로 데이터를 취득한다.
      var data = db.StringGet(&quot;test1&quot;);
      // Json 타입의 String타입을 인스턴스로 변환
      var node = JsonConvert.DeserializeObject&amp;lt;Node&amp;gt;(data);
      // Node 클래스의 Print 함수 호출
      node.Print();

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxZFhA/btrtk1Sub1h/IkuMyQI3Q3imnKWHjlTDf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxZFhA/btrtk1Sub1h/IkuMyQI3Q3imnKWHjlTDf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxZFhA/btrtk1Sub1h/IkuMyQI3Q3imnKWHjlTDf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxZFhA%2Fbtrtk1Sub1h%2FIkuMyQI3Q3imnKWHjlTDf0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Redis 데이터베이스에서 test1의 key값으로 Json 타입의 String값을 취득한 후 Node 인스턴스로 변환했습니다.&lt;/p&gt;
&lt;p&gt;역시 Print 함수를 호출하니 위에서 입력한 Test 값이 콘솔에 출력이 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다음은 Redis에서 사용될 List와 Map, SortedSet 형식의 자료형입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using StackExchange.Redis;

namespace ConsoleApp1
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // redis 접속한다.
      ConnectionMultiplexer redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
      {
        EndPoints = { &quot;192.168.1.200:6379&quot; }
      });
      // 기본 데이터베이스 취득
      var db = redis.GetDatabase();

      // List 형식 오른쪽 Push
      db.ListRightPush(&quot;list&quot;, &quot;1&quot;);
      // List 형식 왼쪽 Push
      db.ListLeftPush(&quot;list&quot;, &quot;2&quot;);
      // 2,1
      db.ListSetByIndex(&quot;list&quot;, 1, &quot;3&quot;);
      // 2,3
      // List 왼쪽부터 출력
      Console.WriteLine(db.ListLeftPop(&quot;list&quot;));
      Console.WriteLine(db.ListLeftPop(&quot;list&quot;));

      // 개행
      Console.WriteLine();
      // Hash 형식의 key-value 값 입력
      db.HashSet(&quot;map&quot;, &quot;a&quot;, &quot;1&quot;);
      db.HashSet(&quot;map&quot;, &quot;b&quot;, &quot;2&quot;);
      db.HashSet(&quot;map&quot;, &quot;c&quot;, &quot;3&quot;);
      // {{&quot;a&quot;,&quot;1&quot;},{&quot;b&quot;,&quot;2&quot;},{&quot;c&quot;,&quot;3&quot;}}

      // map의 b 키로 출력
      Console.WriteLine(db.HashGet(&quot;map&quot;, &quot;b&quot;));

      // 개행
      Console.WriteLine();
      // SortedSet 형식의 값 입력
      db.SortedSetAdd(&quot;SortedSet&quot;, &quot;aaa&quot;, 1);
      db.SortedSetAdd(&quot;SortedSet&quot;, &quot;bbb&quot;, 0);

      // 입력 순서대로 sort
      foreach(var sort in db.SortedSetScan(&quot;SortedSet&quot;))
      {
        // 콘솔 출력
        Console.WriteLine(sort);
      }
      // 개행
      Console.WriteLine();
      // Score 순위로 sort
      foreach (var sort in db.SortedSetRangeByRank(&quot;SortedSet&quot;))
      {
        // 콘솔 출력
        Console.WriteLine(sort);
      }
      // 개행
      Console.WriteLine();
      // aaa의 Socre
      Console.WriteLine(db.SortedSetScore(&quot;SortedSet&quot;, &quot;aaa&quot;));

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbCqeK/btrtt3Vyr0u/lvOAkGSmOHXmL0K29RAbR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbCqeK/btrtt3Vyr0u/lvOAkGSmOHXmL0K29RAbR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbCqeK/btrtt3Vyr0u/lvOAkGSmOHXmL0K29RAbR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbCqeK%2Fbtrtt3Vyr0u%2FlvOAkGSmOHXmL0K29RAbR0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위는 Redis에서 list와 map, SortedSet 형식으로 사용되는 타입입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;사실 저는 잘 사용하지 않는 타입이기도 합니다.&lt;/p&gt;
&lt;p&gt;Redis 프로그램의 알고리즘이 나쁘다고는 생각하지는 않지만 Redis 데이터베이스의 성능에 대한 병목 현상이나 알고리즘의 성능이 C#의 기본 List나 Dictionary보다 더 좋다고 생각되지는 않아서 가능하면 데이터를 그대로 넣고 취득해서 프로그램 내부에서 처리합니다.&lt;/p&gt;
&lt;p&gt;제가 아직 Redis의 경험이 많지 않아서 정확하게는 잘 모르겠네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 Redis 데이터베이스를 접속해서 사용하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Development note/C＃</category>
      <category>Blogger</category>
      <category>블로그</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/383</guid>
      <comments>https://nowonbun.tistory.com/383#entry383comment</comments>
      <pubDate>Tue, 15 Feb 2022 18:43:49 +0900</pubDate>
    </item>
    <item>
      <title>[CentOS] Redis 데이터베이스 설치와 명령어 사용법</title>
      <link>https://nowonbun.tistory.com/384</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 CentOS에서 Redis 데이터베이스 설치와 명령어 사용법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Redis란 RDB 타입과는 다른 NoSQL 타입의 데이터베이스입니다. 즉, SQL로 데이터를 입력, 취급하는 데이터베이스가 아닌 특정 API를 통해 다루는 데이터베이스입니다.&lt;/p&gt;
&lt;p&gt;구조는 Key - Value 구조이고 InMemory 구조로 되어 있어 빠른 삽입, 검색이 가능한 데이터베이스입니다...&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;사실 Redis에 대해서 구글등을 통해 조사하면 여러가지 정확한 정의와 설명이 있습니다. &lt;/p&gt;
&lt;p&gt;저는 조금 더 이해하기 쉽게 설명하면 그냥 Share Memory, 즉 메모리를 공유하는 시스템이라고 생각됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;보통 프로그램을 기동하면 안의 변수등을 통해서 메모리에 데이터를 할당합니다. 그 메모리에 할당된 데이터들은 OS의 프로세스 메모리 보호 기능으로 다른 프로그램에서 메모리 접근을 할 수 없습니다.&lt;/p&gt;
&lt;p&gt;즉, A프로그램과 B프로그램을 실행하고, B프로그램에서 할당된 클래스의 데이터를 A프로그램에서 사용하고 싶다고 하면 소켓 통신을 통해서 데이터를 공유하게 됩니다. 일반적인 방법입니다.&lt;/p&gt;
&lt;p&gt;여기서 프로그램이 하나 더 추가된다고 하면 메모리 공유 흐름이 복잡하게 됩니다. B프로그램에서 생성된 메모리 데이터를 A프로그램에서 수정합니다. 그럼 B프로그램은 C프로그램에 값이 변경된 것을 통보합니다.&lt;/p&gt;
&lt;p&gt;다시 여기서 프로그램 하나 더 추가합니다. 즉, 프로그램이 늘어날수록 흐름이 훨씬 더 복잡하게 되겠네요. &lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;물론 File IO로 공유할 수도 있습니다만, 그럼 다시 IO의 Connection에 흐름을 생각해야 합니다. 즉, 파일에 데이터를 쓰는 입장에서 Connection를 잡고 있다고 하면 다른 프로그램에서는 파일 수정이 불가능합니다.&lt;/p&gt;
&lt;p&gt;여러가지 불편한 흐름과 규약이 생기겠네요..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇다면 가장 쉽게 해결할 수 있는 것이 Map 구조로 된 메모리와 소켓 통신으로 Set, Get으로 Map 메모리에 접근이 가능한 프로그램이 있으면 됩니다. 그것이 Redis입니다.&lt;/p&gt;
&lt;p&gt;Redis 프로그램은 Java에서 Map, C#에서는 Dictionary 구조의 변수 하나와 소켓을 통해 외부에서 데이터를 저장, 취득이 가능한 프로그램입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;CentOS에서는 간단한게 yum으로 설치가 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;&quot;&gt;# Redis 설치
yum install redis
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7JKQm/btrtajyOcya/68OZiULU9jzEFkYJAOYBU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7JKQm/btrtajyOcya/68OZiULU9jzEFkYJAOYBU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7JKQm/btrtajyOcya/68OZiULU9jzEFkYJAOYBU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7JKQm%2FbtrtajyOcya%2F68OZiULU9jzEFkYJAOYBU0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;&quot;&gt;# Redis 실행
systemctl start redis
# Redis 부팅 등록
systemctl enable redis
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKZ0FO/btrtk9VDi6Z/XGYKJP8C0hrllQyg4yErv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKZ0FO/btrtk9VDi6Z/XGYKJP8C0hrllQyg4yErv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKZ0FO/btrtk9VDi6Z/XGYKJP8C0hrllQyg4yErv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKZ0FO%2Fbtrtk9VDi6Z%2FXGYKJP8C0hrllQyg4yErv1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;&quot;&gt;# 버젼 확인
redis-server --version
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp9TmJ/btrtejrtRVr/lxOknSGt4P7TWca7g0ABCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp9TmJ/btrtejrtRVr/lxOknSGt4P7TWca7g0ABCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp9TmJ/btrtejrtRVr/lxOknSGt4P7TWca7g0ABCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp9TmJ%2FbtrtejrtRVr%2FlxOknSGt4P7TWca7g0ABCK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 환경 설정은 /etc/redis.conf의 경로에 있습니다. 환경 설정에 대해서는 아래에서 자세히 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;일단은 yum으로 간단하게 설치가 가능합니다만, 버젼이 매우 낮네요.. redis 홈페이지에는 6.2 버전이 있는데 yum으로는 3.x 버전이네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 홈페이지에서 다운 받아서 설치해 보겠습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://redis.io/download&quot; target=&quot;_blank&quot;&gt;https://redis.io/download&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 링크 주소를 복사합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O6l0m/btrs9i7Xxbl/LPf7SUatK9Hqctx7aGXYkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O6l0m/btrs9i7Xxbl/LPf7SUatK9Hqctx7aGXYkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O6l0m/btrs9i7Xxbl/LPf7SUatK9Hqctx7aGXYkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO6l0m%2Fbtrs9i7Xxbl%2FLPf7SUatK9Hqctx7aGXYkK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 wget으로 다운로드합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AiFnk/btrs8PRGpXM/8CvRSi6KHHCjOJDKR6TA5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AiFnk/btrs8PRGpXM/8CvRSi6KHHCjOJDKR6TA5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AiFnk/btrs8PRGpXM/8CvRSi6KHHCjOJDKR6TA5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAiFnk%2Fbtrs8PRGpXM%2F8CvRSi6KHHCjOJDKR6TA5K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 압축 풀고 빌드합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;&quot;&gt;tar xzf redis-6.2.6.tar.gz
cd redis-6.2.6
make install
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RMRiW/btrtgsIt3OG/iCQVRMOHngDIOVq5FJqQg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RMRiW/btrtgsIt3OG/iCQVRMOHngDIOVq5FJqQg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RMRiW/btrtgsIt3OG/iCQVRMOHngDIOVq5FJqQg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRMRiW%2FbtrtgsIt3OG%2FiCQVRMOHngDIOVq5FJqQg1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;설치가 완료되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 서비스 등록 파일을 작성합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;&quot;&gt;vim /etc/systemd/system/redis.service
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;redis.service&quot;&gt;[Unit]
Description=Redis

[Service]
User=root
Group=root
ExecStart=/home/redis/redis-6.2.6/src/redis-server /home/redis/redis-6.2.6/redis.conf
ExecStop=/home/redis/redis-6.2.6/src/redis-cli shutdown
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCotnU/btrtakYMwiy/XuhKmuLW2rSK2x6Ln7y7y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCotnU/btrtakYMwiy/XuhKmuLW2rSK2x6Ln7y7y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCotnU/btrtakYMwiy/XuhKmuLW2rSK2x6Ln7y7y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCotnU%2FbtrtakYMwiy%2FXuhKmuLW2rSK2x6Ln7y7y1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이제 Redis를 실행하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-shell&quot; data-type=&quot;&quot;&gt;#실행
systemctl start redis.service
#정지
systemctl stop redis.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;실행하고 src폴더의 redis-cli를 실행하겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PCF9t/btrtk2I1KNH/uPrZI99vHmNOCBeLgpMhV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PCF9t/btrtk2I1KNH/uPrZI99vHmNOCBeLgpMhV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PCF9t/btrtk2I1KNH/uPrZI99vHmNOCBeLgpMhV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPCF9t%2Fbtrtk2I1KNH%2FuPrZI99vHmNOCBeLgpMhV1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여기까지의 화면이 나오면 설치가 완료되고 실행이 된 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이제는 Redis 명령어로 데이터를 저장, 취득해 보겠습니다.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;Redis 명령어&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Redis에서 사용되는 명령어는 굉장히 많습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://redis.io/commands&quot; target=&quot;_blank&quot;&gt;https://redis.io/commands&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;그런데 자주 사용되는 명령어는 얼마 되지 않습니다. 자주 사용하는 명령어만 정리합니다.&lt;/p&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
  &lt;table class=&quot;table table-striped table-bordered table-condensed&quot;&gt;
    &lt;thead&gt;
      &lt;tr style=&quot;background-color:#DAD7D7&quot;&gt;
        &lt;th style=&quot;white-space: nowrap;&quot;&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;set [key] [value]&lt;/td&gt;
        &lt;td&gt;key의 값을 키로 데이터를 저장한다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;get [key]&lt;/td&gt;
        &lt;td&gt;key의 값을 키로 데이터를 취득한다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;rename [key] [newkey]&lt;/td&gt;
        &lt;td&gt;key의 값을 변경한다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;keys [pattern]&lt;/td&gt;
        &lt;td&gt;key를 검색한다. 「*를 넣으면 모든 키를 취득한다.」&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;exists [key]&lt;/td&gt;
        &lt;td&gt;key가 존재하는 지 확인한다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;del [key]&lt;/td&gt;
        &lt;td&gt;key의 값을 키로 데이터를 삭제한다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;flushall&lt;/td&gt;
        &lt;td&gt;모든 키를 삭제한다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;append [key] [value]&lt;/td&gt;
        &lt;td&gt;key의 값을 키로 데이터의 값을 추가한다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;setex [key] [seconds] [value]&lt;/td&gt;
        &lt;td&gt;key의 값을 키로 데이터의 만료 시간 설정한다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;ttl [key]&lt;/td&gt;
        &lt;td&gt;key의 값을 키로 데이터의 만료 시간 확인한다. -1의 경우, 만료 시간이 설정되지 않았다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;save&lt;/td&gt;
        &lt;td&gt;현재 Redis의 데이터 상태를 백업한다. 파일명은 config에서 설정한다.&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bddVY7/btrs8PqGbUb/VpLKKLkamo9UKWG7KBdYI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bddVY7/btrs8PqGbUb/VpLKKLkamo9UKWG7KBdYI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bddVY7/btrs8PqGbUb/VpLKKLkamo9UKWG7KBdYI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbddVY7%2Fbtrs8PqGbUb%2FVpLKKLkamo9UKWG7KBdYI0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h2&gt;&lt;b&gt;&lt;span&gt;Redis 환경 설정&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;Redis의 환경 설정은 버전마다 차이가 큽니다. 확인하고 설정을 해야 합니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://redis.io/topics/config&quot; target=&quot;_blank&quot;&gt;https://redis.io/topics/config&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;http://redisgate.kr/redis/server/redis_conf_han.php&quot; target=&quot;_blank&quot;&gt;http://redisgate.kr/redis/server/redis_conf_han.php&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;세부적인 설정이 다를 뿐 자주 사용되는 포트 설정과 bind 설정은 비슷하니 확인하면 됩니다.&lt;/p&gt;
&lt;div class=&quot;table-responsive&quot;&gt;
  &lt;table class=&quot;table table-striped table-bordered table-condensed&quot;&gt;
    &lt;thead&gt;
      &lt;tr style=&quot;background-color:#DAD7D7&quot;&gt;
        &lt;th style=&quot;white-space: nowrap;&quot;&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;bind [ip] ...&lt;/td&gt;
        &lt;td&gt;허용되는 ip 설정 &lt;br /&gt; 예) bind 192.168.0.100 192.168.0.100 (해당 ip만 허가) &lt;br /&gt; 예) bind 127.0.0.1 -::1 (로컬만 허가) &lt;br /&gt; 예) bind * -::* (모든 ip 허가)&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;protected-mode [boolean]&lt;/td&gt;
        &lt;td&gt;보호 모드가 활성화되어 있으면 로컬만 접속 허용합니다.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;port [number]&lt;/td&gt;
        &lt;td&gt;포트 설정&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;dir [direcotry path]&lt;/td&gt;
        &lt;td&gt;작업 디렉토리 설정&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;dbfilename [filename]&lt;/td&gt;
        &lt;td&gt;백업 저장 파일명 설정&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td&gt;save [seconds][changes]&lt;/td&gt;
        &lt;td&gt;지정된 시간 안에 키가 지정된 개수만큼 변경되면 백업 &lt;br /&gt; # save 900 1 (900초(15분) 동안 적어도 1개 이상의 키가 변경되면 저장합니다.)&lt;/td&gt;
      &lt;/tr&gt;   
    &lt;/tbody&gt;
  &lt;/table&gt;
&lt;/div&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/psMR9/btrs8aV4LCd/ungx05x1v9uotBeEAtPJk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/psMR9/btrs8aV4LCd/ungx05x1v9uotBeEAtPJk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/psMR9/btrs8aV4LCd/ungx05x1v9uotBeEAtPJk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpsMR9%2Fbtrs8aV4LCd%2Fungx05x1v9uotBeEAtPJk0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Redis에 대해서 4~5년 전부터 알고는 있었는데, 그 활용 여부에 대해서는 크게 느끼지를 못하다가 1~2년 전에 웹 서버 세션 클러스터링으로 성능이 괜찮다고 생각 된 후로 계속 사용하게 된 데이터베이스입니다.&lt;/p&gt;
&lt;p&gt;그 후로 여러 시스템에서 쉐어 메모리로써 사용하기에 생각보다 사용하기 편하고 설치도 간편해서 최근에는 자주 사용하게 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;물론 프로그램 간에 데이터를 주고 받을 때는 직접적인 소켓 통신이 정확하지만, 같은 서버에서 여러 프로그램을 움직이거나 마이크로 서비스, 즉 하나의 도메인에서 여러 서버로 분할하여 프로그램을 작성할 때는 꽤 유용하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 CentOS에서 Redis 데이터베이스 설치와 명령어 사용법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Development note/Linux</category>
      <category>구글 블로거</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/384</guid>
      <comments>https://nowonbun.tistory.com/384#entry384comment</comments>
      <pubDate>Mon, 14 Feb 2022 18:30:12 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 3-6. 상태 패턴 (State pattern)</title>
      <link>https://nowonbun.tistory.com/465</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 상태 패턴(State pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 상태 패턴이라는 것은 클래스의 상태에 따라 처리되는 결과 값이 다른 형태로 만드는 것을 말합니다.&lt;/p&gt;
&lt;p&gt;어떻게 보면 전략 패턴과 약간 비슷한 구조가 될 수 있는데... &lt;/p&gt;
&lt;p&gt;차이를 두자면 전략 패턴은 전략 인스턴스에 의해 외부의 값이 변환되어 실행되는 것이고 상태 패턴이라는 것은 상태 인스턴스에 의해 내부의 값의 처리가 바뀌는 것을 뜻합니다.&lt;/p&gt;
&lt;p&gt;사실 디자인 패턴이라는 것은 실무에서 딱 이건 무슨 패턴이다 정의된 것은 없고.. 그 내용만 이해하여 사용하면 되는 것입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OwhX5/btrliMBOJHz/tamku8qQebkA7E6FXJGdF1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OwhX5/btrliMBOJHz/tamku8qQebkA7E6FXJGdF1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OwhX5/btrliMBOJHz/tamku8qQebkA7E6FXJGdF1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOwhX5%2FbtrliMBOJHz%2Ftamku8qQebkA7E6FXJGdF1%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/State_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/State_pattern&lt;/a&gt;&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 상태 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// 상태 패턴 인터페이스
class IState {
public:
  // 함수 추상화
  virtual void print(const char* str) = 0;
  virtual ~IState() {};
};
// AState 상태 패턴 클래스, IState 인터페이스를 상속
class AState : public IState {
public:
  // 함수 재정의
  void print(const char* str) {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;AState - &quot; &amp;lt;&amp;lt; str &amp;lt;&amp;lt; endl;
  }
};
// BState 상태 패턴 클래스, IState 인터페이스를 상속
class BState : public IState {
public:
  // 함수 재정의
  void print(const char* str) {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;BState - &quot; &amp;lt;&amp;lt; str &amp;lt;&amp;lt; endl;
  }
};
// 상태 패턴을 사용할 클래스
class Context {
private:
  // 상태 패턴 맴버 변수
  IState* state;
public:
  // 생성자, 초기 상태를 설정한다.
  Context(IState* state) {
    // 상태 설정 함수 호출
    this-&amp;gt;setState(state);
  }
  // 상태 설정 함수
  void setState(IState* state) {
    // 맴버 변수 설정
    this-&amp;gt;state = state;
  }
  // 실행
  void run() {
    // 상태 패턴의 print 함수를 호출한다.
    this-&amp;gt;state-&amp;gt;print(&quot;Hello world&quot;);
  }
};
// 실행 함수
int main()
{
  // 상태 패턴 인스턴스 생성
  AState astate;
  BState bstate;
  // Context 인스턴스 생성, 최초 AState 인스턴스의 상태를 설정한다.
  Context context(&amp;astate);
  // Context 인스턴스의 run함수 실행
  context.run();
  // 상태를 BState로 설정한다.
  context.setState(&amp;bstate);
  // Context 인스턴스의 run함수 실행
  context.run();

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccPXCs/btrlqCxq131/0eT6ce1IvIQ95AWdFh9XZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccPXCs/btrlqCxq131/0eT6ce1IvIQ95AWdFh9XZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccPXCs/btrlqCxq131/0eT6ce1IvIQ95AWdFh9XZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccPXCs%2FbtrlqCxq131%2F0eT6ce1IvIQ95AWdFh9XZk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;전략 패턴에서는 아마 run 함수에서 무엇가의 값을 받았을 것입니다. 즉, run 함수에서 값을 받고 그 전략 패턴에 의해 다른 값을 리턴하는 것이고, 상태 패턴은 run의 함수에서는 받은 것은 없지만 상태 인스턴스에 의해 내부의 처리가 바뀐 것을 뜻합니다.&lt;/p&gt;
&lt;p&gt;제가 알고 있는 전략 패턴과 상태 패턴의 차이는 이것입니다. 사실 저도, 실무에서 적용할 때 전략 패턴, 상태 패턴 구분해서 사용하지는 않습니다. 그냥 사양에 맞게 맞는 패턴 쓰는 거지 뭐...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 상태 패턴 예제&quot;&gt;import java.util.HashMap;
import java.util.Map;
// 상태 패턴의 인터페이스
interface IState {
  // 함수 추상화
  void print(String str);
}
// AState의 상태 패턴 클래스, IState 인터페이스를 상속
class AState implements IState {
  // 함수 재정의
  public void print(String str) {
    // 콘솔 출력
    System.out.println(&quot;AState - &quot; + str);
  }
}
// BState의 상태 패턴 클래스, IState 인터페이스를 상속
class BState implements IState {
  // 함수 재정의
  public void print(String str) {
    // 콘솔 출력
    System.out.println(&quot;BState - &quot; + str);
  }
}
// Process 클래스
class Process {
  // flyweight 패턴의 맵
  private Map&amp;lt;Class&amp;lt;? extends IState&amp;gt;, IState&amp;gt; flyweight = new HashMap&amp;lt;&amp;gt;();
  // 상태 패턴의 맴버 변수
  private IState state = null;
  // 생성자
  public Process(Class&amp;lt;? extends IState&amp;gt; clz) {
    // 상태 설정
    setState(clz);
  }
  // flyweight 패턴의 상태 패턴 인스턴스 취득 함수
  private IState getState(Class&amp;lt;? extends IState&amp;gt; clz) {
    // flyweight 맵에 클래스 타입이 있는지 확인
    if (!flyweight.containsKey(clz)) {
      try {
        // 없으면 인스턴스를 생성해서 입력한다.
        flyweight.put(clz, clz.getDeclaredConstructor().newInstance());
      } catch (Exception e) {
        // Exception 처리를 RuntimeException 처리로 변환
        throw new RuntimeException(e);
      }
    }
    // 상태 패턴 인스턴스 취득
    return flyweight.get(clz);
  }
  // 상태 패턴 타입 입력
  public void setState(Class&amp;lt;? extends IState&amp;gt; clz) {
    // flyweight 맵에서 인스턴스 취득
    this.state = getState(clz);
  }
  // 출력 함수
  public void run() {
    // 상태 패턴의 print 함수를 호출한다.
    this.state.print(&quot;Hello world&quot;);
  }
}
// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Process 인스턴스 생성, 상태 패턴 타입 설정
    Process process = new Process(AState.class);
    // run 함수 호출
    process.run();
    // 상태 패턴 타입 설정
    process.setState(BState.class);
    // run 함수 호출
    process.run();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccK201/btrlgFQr5Cr/ftnZzhaDzxPrKIbwfyApqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccK201/btrlgFQr5Cr/ftnZzhaDzxPrKIbwfyApqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccK201/btrlgFQr5Cr/ftnZzhaDzxPrKIbwfyApqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccK201%2FbtrlgFQr5Cr%2FftnZzhaDzxPrKIbwfyApqk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;상태 패턴도 전략 패턴과 같은 구조로 flyweight 패턴을 적용해서 상태 패턴 인스턴스 생성을 최소화 하였습니다. 그리고 인스턴스 재사용률을 높여서 시스템의 성능을 올릴 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 상태 패턴 예제&quot;&gt;using System;
using System.Collections.Generic;
// 상태 패턴의 인터페이스
interface IState
{
  // 함수 추상화
  void Print(String str);
}
// AState의 상태 패턴 클래스, IState 인터페이스를 상속
class AState : IState
{
  // 함수 재정의
  public void Print(String str)
  {
    // 콘솔 출력
    Console.WriteLine(&quot;AState - &quot; + str);
  }
}
// BState의 전략 패턴 클래스, IState 인터페이스를 상속
class BState : IState
{
  // 함수 재정의
  public void Print(String str)
  {
    // 콘솔 출력
    Console.WriteLine(&quot;BState - &quot; + str);
  }
}
// Process 클래스
class Process
{
  // flyweight 패턴의 딕셔너리
  private Dictionary&amp;lt;Type, IState&amp;gt; flyweight = new Dictionary&amp;lt;Type, IState&amp;gt;();
  // 상태 패턴의 맴버 변수
  private IState state = null;
  // 생성자
  public Process(Type clz)
  {
    // 상태 설정
    SetState(clz);
  }
  // flyweight 패턴의 상태 패턴 인스턴스 취득 함수
  private IState GetState(Type clz)
  {
    // flyweight 딕셔너리에 클래스 타입이 있는지 확인
    if (!flyweight.ContainsKey(clz))
    {
      // 없으면 인스턴스를 생성해서 입력한다.
      flyweight.Add(clz, Activator.CreateInstance(clz) as IState);
    }
    // 상태 패턴 인스턴스 취득
    return flyweight[clz];
  }
  // 상태 패턴 타입 입력
  public void SetState(Type clz)
  {
    // flyweight 딕셔너리에서 인스턴스 취득
    this.state = GetState(clz);
  }
  // 출력 함수
  public void Run()
  {
    // 상태 패턴의 Print 함수를 호출한다.
    this.state.Print(&quot;Hello world&quot;);
  }
}
// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // Process 인스턴스 생성, 상태 패턴 설정
    Process process = new Process(typeof(AState));
    // Run 함수 호출
    process.Run();
    // 상태 패턴 설정
    process.SetState(typeof(BState));
    // Run 함수 호출
    process.Run();
    // 아무 키나 누르면 종료
    Console.WriteLine(&quot;Press Any key...&quot;);
    Console.ReadLine();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qwpIk/btrlpKiaeN3/hO56nJM3pi8ql2UbCVnN5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qwpIk/btrlpKiaeN3/hO56nJM3pi8ql2UbCVnN5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qwpIk/btrlpKiaeN3/hO56nJM3pi8ql2UbCVnN5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqwpIk%2FbtrlpKiaeN3%2FhO56nJM3pi8ql2UbCVnN5k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;상태 패턴도 전략 패턴과 비슷한 목적으로 사용됩니다. 클래스의 결합도를 낮추고 재사용성을 높이는 호과를 위해서 사용합니다.&lt;/p&gt;
&lt;p&gt;단지 그 차이점이 전략 패턴은 외부의 값이 패턴에 의해 다른 결과를 내는 것이고, 상태 패턴은 패턴에 의해 내부의 값이 다른 결과를 내는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 상태 패턴(State pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/465</guid>
      <comments>https://nowonbun.tistory.com/465#entry465comment</comments>
      <pubDate>Wed, 17 Nov 2021 20:02:04 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 3-5. 메멘토 패턴 (Memento pattern)</title>
      <link>https://nowonbun.tistory.com/464</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 메멘토 패턴(Memento pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;메멘토 패턴은 클래스의 현재 상태를 다른 클래스로 저장하는 패턴입니다. &lt;/p&gt;
&lt;p&gt;클래스의 데이터를 저장하는 형태로는 꼭 메멘토 패턴이 아닌 클래스 복사(인스턴스 복사)로 현재의 상태를 보관할 수 있습니다. 그러나 그렇게 되면 현재의 인스턴스가 아닌 새로운 인스턴스로 객체를 생성하는 것이고, 만약에 객체 안에 리소스(IO나 Socket)를 사용하고 있다면 새로운 커넥션을 생성해야 하는 문제점도 발생하는 것입니다.&lt;/p&gt;
&lt;p&gt;즉, 메멘토 패턴은 인스턴스의 객체는 변하지 않으면서 안의 값만 저장하여, 상태를 복구하는 역할을 하는 패턴이 메멘토 패턴입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddKMMa/btrk9swLnzy/1rEkcLAk3Vx3mcUBFoZu2K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddKMMa/btrk9swLnzy/1rEkcLAk3Vx3mcUBFoZu2K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddKMMa/btrk9swLnzy/1rEkcLAk3Vx3mcUBFoZu2K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddKMMa%2Fbtrk9swLnzy%2F1rEkcLAk3Vx3mcUBFoZu2K%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Memento_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Memento_pattern&lt;/a&gt;&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 메멘토 패턴 예제&quot;&gt;#pragma once
#define _CRT_SECURE_NO_DEPRECATE
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// 메멘토 클래스
class Memento {
private:
  // Node 클래스에서 사용되는 변수
  int data;
  int state;
public:
  // 생성자
  Memento(int data, int state) {
    this-&amp;gt;data = data;
    this-&amp;gt;state = state;
  }
  // data 값 취득
  int getData() {
    return this-&amp;gt;data;
  }
  // state 값 취득
  int getState() {
    return this-&amp;gt;state;
  }
};
// Node 클래스
class Node {
private:
  // 맴버 변수
  int data;
  int state;
public:
  // data 맴버 변수 설정 함수
  void setData(int data) {
    this-&amp;gt;data = data;
  }
  // state 맴버 변수 설정 함수
  void setState(int state) {
    this-&amp;gt;state = state;
  }
  // 메멘토 클래스로 맴버 변수 설정
  void setMemento(Memento* memento) {
    this-&amp;gt;data = memento-&amp;gt;getData();
    this-&amp;gt;state = memento-&amp;gt;getState();
  }
  // 현재 상태를 메멘토 인스턴스로 리턴
  Memento* getMemento() {
    return new Memento(this-&amp;gt;data, this-&amp;gt;state);
  }
  // 출력 함수
  void print() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;data - &quot; &amp;lt;&amp;lt; this-&amp;gt;data &amp;lt;&amp;lt; &quot;  state - &quot; &amp;lt;&amp;lt; this-&amp;gt;state &amp;lt;&amp;lt; endl;
  }
};
// memento 데이터를 파일로 쓰기 함수
void writeMemento(Memento* memento) {
  // 파일 리소스 포인터를 취득, 파라미터 w는 작성이다. (리소스 취득)
  FILE* fp = fopen(&quot;d:\\work\\memento.dat&quot;, &quot;w&quot;);
  // 포인터가 null이면 프로그램을 종료한다.
  if (fp == NULL) {
    cout &amp;lt;&amp;lt; &quot;File open failed&quot; &amp;lt;&amp;lt; endl;
    return;
  }
  // 데이터를 파일에 쓰기
  fwrite(memento, 1, sizeof(Memento), fp);
  // 파일을 닫는다. (리소스 반환)
  fclose(fp);
}
// memento 데이터를 파일로부터 읽기 함수
Memento* readMemento() {
  // 인스턴스 생성
  Memento* memento = (Memento*)malloc(sizeof(Memento));
  // 파일 리소스 포인터를 취득, 파라미터 r는 읽기이다. (리소스 취득)
  FILE* fp = fopen(&quot;d:\\work\\memento.dat&quot;, &quot;r&quot;);
  // 포인터가 null이면 프로그램을 종료한다.
  if (fp == NULL) {
    cout &amp;lt;&amp;lt; &quot;File open failed&quot; &amp;lt;&amp;lt; endl;
    return nullptr;
  }
  // 데이터 읽기
  fread(memento, 1, sizeof(Memento), fp);
  // 파일을 닫는다. (리소스 반환)
  fclose(fp);
  // 리턴
  return memento;
}
// 실행 함수
int main()
{
  // Node 인스턴스 생성
  Node node;
  // 데이터 설정
  node.setData(10);
  node.setState(1);
  // 콘솔 출력
  node.print();
  // 메멘토 인스턴스 취득
  Memento* memento = node.getMemento();
  // 파일 저장
  writeMemento(memento);
  // 메모리 해제
  delete memento;
  // 데이터 재설정
  node.setData(11);
  node.setState(2);
  // 콘솔 출력
  node.print();
  // 파일로부터 메멘토 데이터 취득
  memento = readMemento();
  // Node 인스턴스에 데이터 재설정
  node.setMemento(memento);
  // 콘솔 출력
  node.print();
  // 메모리 해제
  delete memento;

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MyJtV/btrlhBS0Ddv/h6jj3JsluiAZKue0I9k7Rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MyJtV/btrlhBS0Ddv/h6jj3JsluiAZKue0I9k7Rk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MyJtV/btrlhBS0Ddv/h6jj3JsluiAZKue0I9k7Rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMyJtV%2FbtrlhBS0Ddv%2Fh6jj3JsluiAZKue0I9k7Rk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제에서 Node 클래스의 상태를 저장하는 Memento 인스턴스를 취득했습니다.&lt;/p&gt;
&lt;p&gt;이 Memento 클래스에서 해당 클래스를 파일로 저장하고, 또 파일로 읽어와서 다시 Node 인스턴스에 설정하면 데이터가 이전 데이터로 복원되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;예를 들면, 게임에서 중간 세이브하고 다시 파일로 읽어 들여서 현재 상태를 복원하는 것과 같은 형태의 패턴입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 메멘토 패턴 예제&quot;&gt;import java.io.*;

// Node 클래스
class Node implements Serializable {
  // 직렬화 ID
  private static final long serialVersionUID = 1L;

  // Memento 클래스
  class Memento implements Serializable {
    // 직렬화 ID
    private static final long serialVersionUID = 1L;
    // Node 클래스로부터 보관할 맴버 변수
    private int data;
    private int state;
  }

  // 맴버 변수
  private int data;
  private int state;

  // data의 set 함수
  public void setData(int data) {
    this.data = data;
  }

  // state의 set 함수
  public void setState(int state) {
    this.state = state;
  }

  // 메멘토 인스턴스 취득 함수
  public Serializable getMenent() {
    // 인스턴스 생성
    Memento memento = new Memento();
    // 보관할 데이터 설정
    memento.data = data;
    memento.state = state;
    // 리턴
    return memento;
  }

  // 메멘토 인스턴스로 상태 복원
  public void setMement(Serializable memento) {
    // 클래스가 Memento 클래스 타입이 아니면 처리 중단
    if (memento.getClass() != Memento.class) {
      // 콘솔 출력
      System.out.println(&quot;The class type does not match.&quot;);
      return;
    }
    // 형 변환
    Memento memento1 = (Memento) memento;
    // Node 클래스에 데이터 재설정
    this.data = memento1.data;
    this.state = memento1.state;
  }

  // 출력 함수
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;data - &quot; + this.data + &quot; state - &quot; + this.state);
  }
}

public class Program {
  // 메멘토 인스턴스를 파일로 저장할 함수
  private static void writeFile(Serializable serialize) {
    // 파일 설정
    File file = new File(&quot;d:/work/memento.dat&quot;);
    // 파일이 존재하면 삭제
    if (file.exists()) {
      // 삭제
      file.delete();
    }
    // 직렬화
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
      try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
        oos.writeObject(serialize);
        // 메멘토 인스턴스를 byte 배열로 변환했다.
        byte[] data = baos.toByteArray();
        // 파일 스트림을 이용해 변환한 data를 파일로 저장한다.
        try (FileOutputStream stream = new FileOutputStream(file)) {
          // 파일 쓰기
          stream.write(data, 0, data.length);
        }
      }
    } catch (Throwable e) {
      // 예외 처리
      throw new RuntimeException(e);
    }
  }
  // 메멘토 인스턴스를 파일로부터 읽어오는 함수
  private static Serializable readFile() {
    // 파일 설정
    File file = new File(&quot;d:/work/memento.dat&quot;);
    // 파일이 존재하지 않으면
    if (!file.exists()) {
      // 에러 처리
      throw new RuntimeException(new FileNotFoundException());
    }
    // 역직렬화
    try (FileInputStream stream = new FileInputStream(file)) {
      byte[] data = new byte[(int) file.length()];
      // 파일로부터 바이너리를 읽어 드린다.
      stream.read(data, 0, data.length);
      try (ByteArrayInputStream bais = new ByteArrayInputStream(data)) {
        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
          // byte 배열의 데이터를 메멘토 인스턴스로 변환한다.
          return (Serializable) ois.readObject();
        }
      }
    } catch (Throwable e) {
      // 예외 처리
      throw new RuntimeException(e);
    }
  }
  // 실행 함수
  public static void main(String[] args) {
    // Node 인스턴스 생성
    Node node = new Node();
    // 맴버 변수 설정
    node.setData(10);
    node.setState(1);
    // 콘솔 출력
    node.print();
    // 메멘토 인스턴스을 생성
    var memento = node.getMenent();
    // 파일 저장
    writeFile(memento);
    // 맴버 변수 재설정
    node.setData(11);
    node.setState(2);
    // 콘솔 출력
    node.print();
    // 파일로부터 메멘토 인스턴스 생성
    memento = readFile();
    // 인스턴스 값 재설정
    node.setMement(memento);
    // 콘솔 출력
    node.print();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clpPMa/btrlfYuoAgz/olwwNLNcTf5Tdb75uRhvT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clpPMa/btrlfYuoAgz/olwwNLNcTf5Tdb75uRhvT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clpPMa/btrlfYuoAgz/olwwNLNcTf5Tdb75uRhvT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclpPMa%2FbtrlfYuoAgz%2FolwwNLNcTf5Tdb75uRhvT0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;C/C++로 짠 소스의 구조와 거의 비슷합니다. 단지 Memeno 클래스를 Node 클래스의 인라인으로 만들었습니다.&lt;/p&gt;
&lt;p&gt;즉, Memento 클래스는 사양으로는 상태를 저장하는 역할을 가지고 있기 때문에 Node 클래스의 이외에서는 데이터 설정을 할 수 없도록 하는 것이 기본 원칙입니다.&lt;/p&gt;
&lt;p&gt;상태를 마음대로 설정 가능하게 되다면, 그건 메멘토 패턴이 아니고 단순 파라미터를 주고 받는 클래스의 역할이 되는 것입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 메멘토 패턴 예제&quot;&gt;using System;
using System.Reflection;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

// Node 클래스
class Node
{
  // Reflection을 위한 타입 설정
  private Type reflectionMemento = typeof(Memento);
  // 맴버 변수
  public int Data { get; set; }
  public int State { get; set; }
  // 메멘토 클래스 (파일로 직렬화하기 위한 어트리뷰트 설정)
  [Serializable]
  class Memento
  {
    // 맴버 변수
    private int data;
    private int state;
  }
  // 출력 함수
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Data - &quot; + Data + &quot; State - &quot; + State);
  }
  // 메멘토 인스턴스 취득 함수
  public object GetMemento()
  {
    // 인스턴스 생성
    Memento memento = new Memento();
    // 맴버 변수가 private로 설정되어 있기 때문에 접근하기 위해서는 Reflection을 이용한다.　변수 설정
    reflectionMemento.GetField(&quot;data&quot;, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(memento, this.Data);
    reflectionMemento.GetField(&quot;state&quot;, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(memento, this.State);
    // 인스턴스 리턴
    return memento;
  }
  // 메멘토 인스턴스로 멤버 변수 설정 함수
  public void SetMemento(Object obj)
  {
    // 파라미터가 Memento 클래스가 아니면 예외 처리
    if (obj.GetType() != reflectionMemento)
    {
      // 예외 처리
      throw new Exception(&quot;The class type does not match.&quot;);
    }
    // 맴버 변수가 private로 설정되어 있기 때문에 접근하기 위해서는 Reflection을 이용한다. 값을 취득
    this.Data = (int)reflectionMemento.GetField(&quot;data&quot;, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
    this.State = (int)reflectionMemento.GetField(&quot;state&quot;, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
  }
}
class Program
{
  // 메멘토 인스턴스를 파일로 저장하는 함수
  static void writeMemento(object memento)
  {
    // 직렬화 클래스
    var formatter = new BinaryFormatter();
    // 직렬화 데이터를 파일로 저장한다.
    using (FileStream stream = new FileStream(&quot;d:\\work\\memento.dat&quot;, FileMode.Create, FileAccess.Write))
    {
      // 직렬화
      formatter.Serialize(stream, memento);
    }
  }
  // 메멘토 인스턴스를 파일로 읽어오는 함수
  static object readMemento()
  {
    // 직렬화 클래스
    var formatter = new BinaryFormatter();
    // 파일로부터 직렬화 데이터를 읽어온다.
    using (FileStream stream = new FileStream(&quot;d:\\work\\memento.dat&quot;, FileMode.Open, FileAccess.Read))
    {
      // 역직렬화
      return formatter.Deserialize(stream);
    }
  }
  // 실행 함수
  static void Main(string[] args)
  {
    // 인스턴스 생성
    Node node = new Node();
    // 맴버 변수 설정
    node.Data = 10;
    node.State = 1;
    // 출력 함수
    node.Print();
    // 메멘토 인스턴스 취득
    object memento = node.GetMemento();
    // 메멘토 인스턴스를 파일로 출력
    writeMemento(memento);
    // Node 인스턴스의 맴버 변수 설정
    node.Data = 11;
    node.State = 2;
    // 출력 함수
    node.Print();
    // 메멘토 인스턴스를 파일로 읽어 온다.
    memento = readMemento();
    // Node 인스턴스에 메멘토 재설정
    node.SetMemento(memento);
    // 출력 함수
    node.Print();
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daV4rf/btrlhDcd7p5/9XaLBxC1r4N8PrXKri3s30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daV4rf/btrlhDcd7p5/9XaLBxC1r4N8PrXKri3s30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daV4rf/btrlhDcd7p5/9XaLBxC1r4N8PrXKri3s30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaV4rf%2FbtrlhDcd7p5%2F9XaLBxC1r4N8PrXKri3s30%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;C#도 Java와 비슷한 소스입니다. Memento 클래스가 Node 클래스의 인라인으로 설정하여 Node 클래스 외부에서는 설정하지 못하게 작성했습니다.&lt;/p&gt;
&lt;p&gt;그러나 C#은 인라인 클래스라도 public이 아니면 접근을 하지 못하기 때문에 Reflection을 이용하여 직접 private 변수를 설정할 수 있게 작성했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 메멘토 패턴(Memento pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/464</guid>
      <comments>https://nowonbun.tistory.com/464#entry464comment</comments>
      <pubDate>Tue, 16 Nov 2021 20:00:21 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern]  3-4. 반복자 패턴 (Iterator pattern)</title>
      <link>https://nowonbun.tistory.com/463</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 반복자 패턴(Iterator pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;반복자 패턴은 우리가 디자인 패턴을 모르는 상태에서도 아주 자주 사용하는 패턴입니다. C#에서는 list를 foreach로 사용하는 구문이고 Java에서는 for(var x : list)의 형태로 자주 사용되는 패턴입니다.&lt;/p&gt;
&lt;p&gt;일반적인 배열(Array)에서는 어차피 index로 배열을 참조하기 때문에 반복자 패턴이 의미가 없습니다만, 연결 리스트(LinkedList)에서는 이야기가 다릅니다.&lt;/p&gt;
&lt;p&gt;get(100)을 취득하게 되면 index를 0부터 100까지 이동하게 되기 때문에 실제 for에서 사용하게 되면 엄청나게 느려지겠네요.&lt;/p&gt;
&lt;p&gt;예를 들면 0번째는 리스트의 가장 앞쪽이니 문제가 없습니다만 for의 1로 가게 되면 0를 참조해서 1번을 취득합니다. 다시 2로 가게되면 0을 참조해서 1로 이동하고 2를 취득합니다. 다시 3이면 0을 참조해서 1로 이동하고 2로 이동하고 3을 취득합니다.&lt;/p&gt;
&lt;p&gt;그래서 반복자 패턴을 이용해서 매번 참조 할 때마다 포인터를 이동하여 찾을 필요가 없고 현재의 위치를 저장해서 현재의 값을 리턴, 그리고 다음 포인터로 이동하는 형식의 패턴이 필요합니다.&lt;/p&gt;
&lt;p&gt;실무에서는 이걸 구현할 필요는 없고 List 타입은 모두 반복자 패턴을 상속 받고 있기 때문에 foreach를 하게 되면 자동으로 반복자 패턴 (Iterator pattern)으로 전환이 되기 때문에 패턴의 내용만 인지하고 응용하는 데에 사용할 수 있어야 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FT6rS/btrk8OSmr4S/ONQbCVHgxUN806aT1KbWy0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FT6rS/btrk8OSmr4S/ONQbCVHgxUN806aT1KbWy0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FT6rS/btrk8OSmr4S/ONQbCVHgxUN806aT1KbWy0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFT6rS%2Fbtrk8OSmr4S%2FONQbCVHgxUN806aT1KbWy0%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Iterator_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Iterator_pattern&lt;/a&gt;&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 반복자 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include&amp;lt;vector&amp;gt;
using namespace std;

// 실행 함수
int main()
{
  // 백터 인스턴스 생성
  vector&amp;lt;int&amp;gt; node;
  // 백터에 값을 넣는다.
  for (int i = 0; i &amp;lt; 10; i++) {
    node.push_back(i);
  }
  // 반복자 패턴으로 포인터를 취득
  for (vector&amp;lt;int&amp;gt;::iterator ptr = node.begin(); ptr &amp;lt; node.end(); ptr++) {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; *ptr &amp;lt;&amp;lt; endl;
  }
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXYed1/btrk7FOT0dv/Kjpek3XTRwhariZigM4CUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXYed1/btrk7FOT0dv/Kjpek3XTRwhariZigM4CUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXYed1/btrk7FOT0dv/Kjpek3XTRwhariZigM4CUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXYed1%2Fbtrk7FOT0dv%2FKjpek3XTRwhariZigM4CUk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;우리가 실제 프로그램에서 사용하는 반복자 패턴은 for를 이용해서 값의 포인터를 가져와서 일렬의 순서대로 출력을 하는 패턴입니다.&lt;/p&gt;
&lt;p&gt;사실 제가 C/C++로 반복자 패턴을 구현해보려 했는데... 어렵네요... C/C++를 사용 안 한지도 오래되었고 막상 구현하려니 엄청 어렵네요.. 그래서 여기에서는 그냥 반복자 패턴을 사용하는 방법에 대해서만 소개하겠습니다.&lt;/p&gt;
&lt;p&gt;혹시, 반복자 패턴을 구현하시는 분이 있으면 알려주세요...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 반복자 패턴 예제&quot;&gt;import java.util.Iterator;
// iterator 패턴을 사용하기 위해서 Iterable 인터페이스를 상속
class LinkedList&amp;lt;T&amp;gt; implements Iterable&amp;lt;T&amp;gt; {
  // 연결 리스트 알고리즘을 위한 인스턴스 클래스
  private class Node {
    // 리스트에 담는 값
    T data;
    // 연결 리스트의 다음 인스턴스 포인터
    Node next;
  }
  // 연결 리스트의 앞쪽 인스턴스 포인터
  private Node first = null;
  // 연결 리스트의 뒤쪽 인스턴스 포인터
  private Node end = null;
  // 값 추가 함수
  public void add(T data) {
    // 인스턴스 생성
    Node ptr = new Node();
    // 데이터 설정
    ptr.data = data;
    // 리스트 안에 없을 경우
    if (first == null) {
      // 연결 리스트의 앞과 뒤에 설정
      first = ptr;
      end = ptr;
      return;
    }
    // 리스트가 있으면 가장 뒤에 연결
    end.next = ptr;
    // 뒤쪽 인스턴스 변경
    end = ptr;
  }
  // Iterator 패턴을 위한 클래스, Iterator 인터페이스 상속
  private class Itr implements Iterator&amp;lt;T&amp;gt; {
    // for에서 현재 출력될 인스턴스 포인터 변수
    Node point;
    // 다음 인스턴스가 있는 여부
    public boolean hasNext() {
      // 포인터가 없으면 false
      return point != null;
    }
    // 현재의 값을 리턴하고 포인터를 이동
    public T next() {
      // 리턴할 값을 취득
      T ret = point.data;
      // 포인터 이동
      point = point.next;
      // 값 리턴
      return ret;
    }
  }
  // Iterable 인터페이스의 재정의 함수
  @Override
  public Iterator&amp;lt;T&amp;gt; iterator() {
    // Iterator 패턴을 위한 클래스의 인스턴스 생성
    Itr itr = new Itr();
    // 가장 첫번째 인스턴스 설정
    itr.point = first;
    // Iterator 패턴 클래스의 인스턴스 리턴
    return itr;
  }
}
public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Iterable 인터페이스를 상속 받은 클래스의 인스턴스를 생성
    LinkedList&amp;lt;Integer&amp;gt; list = new LinkedList&amp;lt;&amp;gt;();
    // 값 데이터 입력
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);
    // iterator 함수를 호출하여 hasnext의 값으로 true, false를 확인하고 값을 출력한다.
    for (var data : list) {
      // 콘솔 출력
      System.out.println(data);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJdQl/btrk6jZ1kQx/ViIEEqXRGL7XtY2UhktHQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJdQl/btrk6jZ1kQx/ViIEEqXRGL7XtY2UhktHQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJdQl/btrk6jZ1kQx/ViIEEqXRGL7XtY2UhktHQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJdQl%2Fbtrk6jZ1kQx%2FViIEEqXRGL7XtY2UhktHQ0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 간단한 연결 리스트의 알고리즘입니다.&lt;/p&gt;
&lt;p&gt;그리고 임의로 연결 리스트의 LinkedList 클래스를 for의 반복문에 반복자 패턴(Iterator pattern)으로 데이터가 순서대로 출력되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉, for의 반복문에 반복자 패턴을 사용하려면 Iterable의 인터페이스를 상속 받으면 됩니다.&lt;/p&gt;
&lt;p&gt;Iterable의 인터페이스에서는 iterator 함수를 재정의해야 하는 데 iterator 함수의 반환 값은 Iterator의 인터페이스 형식입니다.&lt;/p&gt;
&lt;p&gt;그럼 다시 Iterator의 인터페이스를 상속 받은 Itr 클래스를 생성하고 Itr 클래스는 hasNext 함수와 next 함수를 재정의 해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;즉, for에서의 반복자 패턴을 사용하게 되면 hasNext를 호출하여 값이 있는지 true, false의 값을 받아, true가 되면 next 함수를 호출하게 됩니다.&lt;/p&gt;
&lt;p&gt;next함수에서는 값을 출력받지만, 다음 포인터의 위치로 움직여야 하는 처리를 해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇게 마지막에 hasNext에서 false가 나오게 되면 for문을 빠져나오게 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 반복자 패턴 예제&quot;&gt;using System;
using System.Collections;
using System.Collections.Generic;
// iterator 패턴을 사용하기 위해서 IEnumerable 인터페이스를 상속
public class LinkedList&amp;lt;T&amp;gt; : IEnumerable&amp;lt;T&amp;gt;
{
  // 연결 리스트 알고리즘을 위한 인스턴스 클래스
  class Node
  {
    // 리스트에 담는 값
    public T Data { get; set; }
    // 연결 리스트의 다음 인스턴스 포인터
    public Node Next { get; set; }
  }
  // 연결 리스트의 앞쪽 인스턴스 포인터
  private Node first = null;
  // 연결 리스트의 뒤쪽 인스턴스 포인터
  private Node end = null;
  // 값 추가 함수
  public void Add(T data)
  {
    // 인스턴스 생성
    Node node = new Node() { Data = data };
    // 리스트 안에 없을 경우
    if(first == null)
    {
      // 연결 리스트의 앞과 뒤에 설정
      first = node;
      end = node;
      return;
    }
    // 리스트가 있으면 가장 뒤에 연결
    end.Next = node;
    // 뒤쪽 인스턴스 변경
    end = node;
  }
  // Iterator 패턴을 위한 클래스, IEnumerator 인터페이스 상속
  class Enumerator : IEnumerator&amp;lt;T&amp;gt;
  {
    // Reset 함수를 위한 리스트의 앞쪽 변수
    private Node first;
    // 현재 위치 변수
    private Node current;
    // 생성자
    public Enumerator(Node first)
    {
      // 맴버 변수 설정
      this.first = first;
      this.current = first;
    }
    // 현재 위치 값 반환
    public T Current
    {
      get
      {
        // 리턴 값을 취득
        T ret = current.Data;
        // 포인터 이동
        current = current.Next;
        // 값 리턴
        return ret;
      }
    }
    // Current의 값과 동일
    object IEnumerator.Current =&amp;gt; Current;
    // foreach가 끝났을 경우 호출
    public void Dispose()
    {
      // 만약 리소스를 사용할 경우, 즉 IO나 Socket이 사용된 경우에는 커넥션 종료를 한다.
    }
    // 다음 인스턴스가 있는 여부
    public bool MoveNext()
    {
      // 없으면 false, 있으면 true
      return current != null;
    }
    // Iterator 패턴 초기화
    public void Reset()
    {
      current = first;
    }
  }
  // Iterator 패턴 클래스의 인스턴스 리턴
  public IEnumerator&amp;lt;T&amp;gt; GetEnumerator()
  {
    // 인스턴스 생성
    return new Enumerator(first);
  }
  // GetEnumerator 함수와 동일
  IEnumerator IEnumerable.GetEnumerator()
  {
    return GetEnumerator();
  }
}

class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // 연결 리스트 인스턴스 생성
    LinkedList&amp;lt;int&amp;gt; iter = new LinkedList&amp;lt;int&amp;gt;();
    // 값 추가
    iter.Add(1);
    iter.Add(2);
    iter.Add(3);
    iter.Add(4);
    iter.Add(5);
    // MoveNext()를 호출하고 true면 Current를 호출한다.
    // 결과는 1, 2, 3, 4, 5
    foreach (var data in iter)
    {
      // 콘솔 출력
      Console.WriteLine(data);
    }
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PFLIu/btrk5Zm9qQS/0p0TrgyAEJOKrGZ2BENTc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PFLIu/btrk5Zm9qQS/0p0TrgyAEJOKrGZ2BENTc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PFLIu/btrk5Zm9qQS/0p0TrgyAEJOKrGZ2BENTc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPFLIu%2Fbtrk5Zm9qQS%2F0p0TrgyAEJOKrGZ2BENTc1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;내용은 Java의 예제와 동일합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;반복자 패턴(Iterator pattern)은 우리가 패턴을 알아도 몰라도 자주 사용하는 패턴입니다. 특히, 대용량의 값을 Queue와 LinkedList로 사용할 경우 자주 사용하게 되는 패턴 중에 하나입니다.&lt;/p&gt;
&lt;p&gt;알고리즘 특성상 for(int i=0;i&amp;lt;size;i++)식으로 구현하게 되면 이중 탐색이 되어 버리기 때문에 성능 상에 많은 문제가 발생할 수 있겠네요.&lt;/p&gt;
&lt;p&gt;그리고 사양에 따라서 프로그램 상에서 반복자 패턴(Iterator pattern)를 구현을 해야 할 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;그럴 경우에는 꼭 단순한 LinkedList의 FIFO식의 알고리즘이 아닌 캐쉬 알고리즘 LRU(Least Recently Used)나 LFU(Least Frequency Used)로 응용해서 사용하는 경우도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 반복자 패턴(Iterator pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/463</guid>
      <comments>https://nowonbun.tistory.com/463#entry463comment</comments>
      <pubDate>Mon, 15 Nov 2021 19:23:26 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 3-3. 커맨드 패턴 (Command pattern)</title>
      <link>https://nowonbun.tistory.com/458</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 커맨드 패턴(Command pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;커맨드 패턴(Command pattern)은 살짝 복잡한 패턴입니다만, 간단하게 이야기하면 발동자(invoker)가 수신자(receiver)를 실행하기 위해서 명령자(command)를 가운데 두는 패턴입니다.&lt;/p&gt;
&lt;p&gt;보통 커맨드 패턴은 전등의 예로 설명을 많이 합니다만, 스위치(invoker)가 있고 전등(receiver)이 있습니다. 그것을 전원 ON과 OFF의 명령자(command)가 있는 형태입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c73qDF/btrj2Aae2xT/Hv7MW2PaslGrWk54DBGca0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c73qDF/btrj2Aae2xT/Hv7MW2PaslGrWk54DBGca0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c73qDF/btrj2Aae2xT/Hv7MW2PaslGrWk54DBGca0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc73qDF%2Fbtrj2Aae2xT%2FHv7MW2PaslGrWk54DBGca0%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Command_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Command_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 커맨드 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// 전등 클래스(reciever)
class Light {
public:
  // 전등 켜질 때 함수
  void on() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;Power on!&quot; &amp;lt;&amp;lt; endl;
  }
  // 전등 꺼질 때 함수
  void off() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;Power off!&quot; &amp;lt;&amp;lt; endl;
  }
};
// 커맨드 인터페이스
class ICommand {
public:
  // 함수 추상화
  virtual void execute() = 0;
};
// 전등 키는 클래스, 커맨드 인터페이스 상속
class TurnOnCommand : public ICommand {
private:
  // 전등 인스턴스 맴버 변수
  Light* light;
public:
  // 생성자
  TurnOnCommand(Light* light) {
    // 맴버 변수 설정
    this-&amp;gt;light = light;
  }
  // 실행 함수 재정의
  void execute() {
    // 전등 켜는 함수 실행
    this-&amp;gt;light-&amp;gt;on();
  }
};
// 전등 끄는 클래스, 커맨드 인터페이스 상속
class TurnOffCommand : public ICommand {
private:
  // 전등 인스턴스 맴버 변수
  Light* light;
public:
  // 생성자
  TurnOffCommand(Light* light) {
    // 맴버 변수 설정
    this-&amp;gt;light = light;
  }
  // 실행 함수 재정의
  void execute() {
    // 전등 끄는 함수 실행
    this-&amp;gt;light-&amp;gt;off();
  }
};
// 스위치(invoker)
class Switch {
private:
  // 커맨드 맴버 변수
  ICommand* turnOnCmd;
  ICommand* turnOffCmd;
public:
  // 생성자
  Switch(ICommand* turnOnCmd, ICommand* turnOffCmd) {
    // 맴버 변수 설정
    this-&amp;gt;turnOnCmd = turnOnCmd;
    this-&amp;gt;turnOffCmd = turnOffCmd;
  }
  // 실행 함수
  void run() {
    // 스위치 켜는 커맨드 실행
    this-&amp;gt;turnOnCmd-&amp;gt;execute();
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;It turns off after 5 minutes.&quot; &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; &quot;It turns off after 4 minutes.&quot; &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; &quot;It turns off after 3 minutes.&quot; &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; &quot;It turns off after 2 minutes.&quot; &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; &quot;It turns off after 1 minutes.&quot; &amp;lt;&amp;lt; endl;
    // 스위치 끄는 커맨드 실행
    this-&amp;gt;turnOffCmd-&amp;gt;execute();
  }
};
// 실행 함수
int main() {
  // 전등 인스턴스 생성
  Light light;
  // 커맨드 인스턴스 생성
  TurnOnCommand turnOnCmd(&amp;light);
  TurnOffCommand turnOffCmd(&amp;light);
  // 스위치 인스턴스 생성(switch는 키워드라 변수 선언이 안되네요...;;;)
  Switch button(&amp;turnOnCmd, &amp;turnOffCmd);
  // 실행
  button.run();

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJzCba/btrj2gwh6ZL/Ob6oiXGN5pruLbhuzbCsfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJzCba/btrj2gwh6ZL/Ob6oiXGN5pruLbhuzbCsfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJzCba/btrj2gwh6ZL/Ob6oiXGN5pruLbhuzbCsfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJzCba%2Fbtrj2gwh6ZL%2FOb6oiXGN5pruLbhuzbCsfK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예를 보면 쉽게 이해가 됩니다. 커맨드 패턴은 발동자(invoker)의 함수를 클래스 별로 나눈 것입니다.&lt;/p&gt;
&lt;p&gt;함수는 인스턴스를 구현할 수 없기 때문에 함수 별로 인스턴스를 만든 형태가 커맨드 패턴(Command pattern)입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 커맨드 패턴 예제&quot;&gt;import java.util.ArrayList;
import java.util.List;
// Node 클래스
class Node {
  // processA 함수
  void processA() {
    // 콘솔 출력
    System.out.println(&quot;execute ProcessA&quot;);
  }
  // processB 함수
  void processB() {
    // 콘솔 출력
    System.out.println(&quot;execute ProcessB&quot;);
  }
}
// 커맨드 추상 클래스
abstract class ACommand {
  // Node 클래스 맴버 변수
  private Node node;
  // 생성자
  public ACommand(Node node) {
    // 맴버 변수 설정
    this.node = node;
  }
  // 맴버 변수 취득 함수
  protected Node getNode() {
    return this.node;
  }
  // 실행 함수 추상화
  public abstract void execute();
}
// ProcessACommand 클래스, 추상 클래스 ACommand 상속
class ProcessACommand extends ACommand {
  // 생성자
  public ProcessACommand(Node node) {
    super(node);
  }
  // 실행
  public void execute() {
    // Node 인스턴스를 취득해서 processA함수 실행
    getNode().processA();
  }
}
// ProcessBCommand 클래스, 추상 클래스 ACommand 상속
class ProcessBCommand extends ACommand {
  // 생성자
  public ProcessBCommand(Node node) {
    super(node);
  }
  // 실행
  public void execute() {
    // Node 인스턴스를 취득해서 processB함수 실행
    getNode().processB();
  }
}
// Procedure 클래스
class Procedure {
  // 리스트 맴버 변수
  private List&amp;lt;ACommand&amp;gt; commands = new ArrayList&amp;lt;&amp;gt;();
  // 커맨드 추가
  public void addCommand(ACommand command) {
    // 리스트에 커맨드 추가
    commands.add(command);
  }
  // 실행
  public void run() {
    // 리스트의 순서대로
    for (var cmd : commands) {
      // 실행
      cmd.execute();
    }
  }
}

public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Node 인스턴스 생성
    var node = new Node();
    // Procedure 인스턴스 생성
    var procedure = new Procedure();
    // ProcessACommand 인스턴스 추가
    procedure.addCommand(new ProcessACommand(node));
    // ProcessBCommand 인스턴스 추가
    procedure.addCommand(new ProcessBCommand(node));
    // ProcessACommand 인스턴스 추가
    procedure.addCommand(new ProcessACommand(node));
    // 실행
    procedure.run();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkUhXS/btrj2VFdAXB/jTwgKVZ7A8HCSg7gokbDQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkUhXS/btrj2VFdAXB/jTwgKVZ7A8HCSg7gokbDQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkUhXS/btrj2VFdAXB/jTwgKVZ7A8HCSg7gokbDQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkUhXS%2Fbtrj2VFdAXB%2FjTwgKVZ7A8HCSg7gokbDQk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;함수를 클래스의 인스턴스로 만들 수 있으면 위처럼 list 등으로 명령 순서를 설정할 수도 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 커맨드 패턴 예제&quot;&gt;using System;
using System.Collections.Generic;
// 자동차 클래스
class Car
{
  // 좌회전 함수
  public void LeftTurn()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;LeftTurn!&quot;);
  }
  // 우회전 함수
  public void RightTurn()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;RightTurn!&quot;);
  }
  // 악셀 함수
  public void Accelerator()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Accelerator!&quot;);
  }
  // 브레이크 함수
  public void Break()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Break!&quot;);
  }
}
// 커맨드 추상 클래스
abstract class ACommand
{
  // 맴버 변수
  private Car car = new Car();
  // 생성자
  public ACommand(Car car)
  {
    // 맴버 변수 설정
    this.car = car;
  }
  // 실행
  public void Execute()
  {
    // 추상 함수 실행
    Run(car);
  }
  // 추상 함수
  protected abstract void Run(Car car);
}
// 좌회전 커맨드 클래스
class LeftTurnCommand : ACommand
{
  // 생성자
  public LeftTurnCommand(Car car) : base(car) { }
  // 추상 함수 재정의
  protected override void Run(Car car)
  {
    // 좌회전 함수 실행
    car.LeftTurn();
  }
}
// 우회전 커맨드 클래스
class RightTurnCommand : ACommand
{
  // 생성자
  public RightTurnCommand(Car car) : base(car) { }
  // 추상 함수 재정의
  protected override void Run(Car car)
  {
    // 우회전 함수 실행
    car.RightTurn();
  }
}
// 악셀 커맨드 클래스
class AcceleratorCommand : ACommand
{
  // 생성자
  public AcceleratorCommand(Car car) : base(car) { }
  // 추상 함수 재정의
  protected override void Run(Car car)
  {
    // 악셀 함수 실행
    car.Accelerator();
  }
}
// 브레이크 커맨드 클래스
class BreakCommand : ACommand
{
  // 생성자
  public BreakCommand(Car car) : base(car) { }
  // 추상 함수 재정의
  protected override void Run(Car car)
  {
    // 브레이크 함수 실행
    car.Break();
  }
}
// 운전 클래스
class Driving
{
  // 커맨드 리스트
  private List&amp;lt;ACommand&amp;gt; cmds = new List&amp;lt;ACommand&amp;gt;();
  // 브레이크 커맨드 맴버 변수
  private ACommand breakCmd;
  // 생성자
  public Driving(ACommand breakCmd)
  {
    // 브레이크 커맨드 맴버 변수 설정
    this.breakCmd = breakCmd;
  }
  // 커맨드 추가 함수
  public void AddCommand(ACommand cmd)
  {
    // 커맨드 추가
    cmds.Add(cmd);
  }
  // 출발
  public void Start()
  {
    // 커맨드 리스트에서 커맨드 취득
    foreach (var cmd in this.cmds)
    {
      // 커맨드 실행
      cmd.Execute();
      // 브레이크 커맨드 실행
      this.breakCmd.Execute();
    }
  }
}
// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // 자동차 인스턴스 생성
    Car car = new Car();
    // 운전 인스턴스 생성
    Driving driving = new Driving(new BreakCommand(car));
    // 명령 추가(코맨드 인스턴스 추가)
    driving.AddCommand(new AcceleratorCommand(car));
    driving.AddCommand(new LeftTurnCommand(car));
    driving.AddCommand(new RightTurnCommand(car));
    // 출발
    driving.Start();
    // 아무 키나 누르면 종료
    Console.WriteLine(&quot;Press Any key...&quot;);
    Console.ReadLine();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GXorR/btrj2U7ow6P/OUe69LaeDZVXBBi0k58QB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GXorR/btrj2U7ow6P/OUe69LaeDZVXBBi0k58QB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GXorR/btrj2U7ow6P/OUe69LaeDZVXBBi0k58QB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGXorR%2Fbtrj2U7ow6P%2FOUe69LaeDZVXBBi0k58QB0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;커맨드 패턴은 중요한 점이 발동자(invoker)의 함수를 클래스 별로 나눈 것입니다. 즉, 여러가지 함수를 복합적으로 커맨드 패턴으로 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;여기서의 예제는 발동자(invoker)의 클래스를 하나만 생성해서 커맨드　패턴을 만들었지만, 여러 개의 발동자 클래스를 커맨드 패턴 별로 나누고 전략 패턴과 같이 묶어서 사용할 수 도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 커맨드 패턴(Command pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/458</guid>
      <comments>https://nowonbun.tistory.com/458#entry458comment</comments>
      <pubDate>Fri, 5 Nov 2021 16:50:05 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 60. 윈도우 폼(Window form)에서 스레드(Thread)를 사용하는 방법, 크로스 스레드 문제 해결</title>
      <link>https://nowonbun.tistory.com/265</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#의 윈도우 폼(Window form)에서 스레드(Thread)를 사용하는 방법, 크로스 스레드 문제 해결에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전 글에서 C#에서의 스레드를 설명한 적이 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/133&quot; target=&quot;_blank&quot;&gt;[C#] 36. 스레드(Thread)를 사용하는 방법, Thread.Sleep 함수 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;스레드는 프로그램 내에서 병렬 처리를 뜻합니다. 먼저 윈도우는 프로그램에서 싱글 스레드의 무한 루프로 움직이고 있습니다.&lt;/p&gt;
&lt;p&gt;그런데 우리가 버튼의 클릭 이벤트에서 루프를 실행하는 로직을 만들어 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp
{
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수 버튼
    private Button button1 = null;
    // 컨트럴 클래스의 초기 설정 함수
    private T setInitControl&amp;lt;T&amp;gt;(T ctl, string name, int point) where T : Control
    {
      // 위치 설정
      ctl.Location = new System.Drawing.Point(27, point);
      // 컨트럴 이름 설정
      ctl.Name = name;
      // 컨트럴 크기 설정
      ctl.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      ctl.TabIndex = 0;
      // 리턴
      return ctl;
    }
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 인스턴스 생성 후 초기 설정
      this.button1 = setInitControl(new Button(), &quot;Button1&quot;, 40);
      // 버튼의 Text 설정
      this.button1.Text = &quot;Button&quot;;
      // 폼에 Control 추가
      this.Controls.Add(button1);
      // 이벤트 추가(람다식으로 추가)
      this.button1.Click += (sender, e) =&amp;gt;
      {
        // 0부터 9999까지 루프
        for (int i = 0; i &amp;lt; 10000; i++)
        {
          // 콘솔 출력
          Console.WriteLine(i);
          // 스레드 대기 1초
          Thread.Sleep(1000);
        }
      };
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caCtjE/btrjQKeNbCm/sqRgFYpgmYMkUIO5jv0b6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caCtjE/btrjQKeNbCm/sqRgFYpgmYMkUIO5jv0b6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caCtjE/btrjQKeNbCm/sqRgFYpgmYMkUIO5jv0b6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaCtjE%2FbtrjQKeNbCm%2FsqRgFYpgmYMkUIO5jv0b6k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;프로그램을 실행하고 버튼을 누르면 루프가 끝날 때까지 윈도우 폼은 움직이지 않습니다. 시간이 지나면 응답 없음으로 변할 때도 있습니다.&lt;/p&gt;
&lt;p&gt;즉, 윈도우는 싱글 스레드 상태라서 저 함수 스택에 걸리면 처리가 끝날 때까지 움직이지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇다면 버튼을 누를 때, 복잡한 처리를 한다고 하면 어떻게 처리를 해야 할까요? 스레드를 사용하면 됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp
{
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수 버튼
    private Button button1 = null;
    // 컨트럴 클래스의 초기 설정 함수
    private T setInitControl&amp;lt;T&amp;gt;(T ctl, string name, int point) where T : Control
    {
      // 위치 설정
      ctl.Location = new System.Drawing.Point(27, point);
      // 컨트럴 이름 설정
      ctl.Name = name;
      // 컨트럴 크기 설정
      ctl.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      ctl.TabIndex = 0;
      // 리턴
      return ctl;
    }
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 인스턴스 생성 후 초기 설정
      this.button1 = setInitControl(new Button(), &quot;Button1&quot;, 40);
      // 버튼의 Text 설정
      this.button1.Text = &quot;Button&quot;;
      // 폼에 Control 추가
      this.Controls.Add(button1);
      // 이벤트 추가(람다식으로 추가)
      this.button1.Click += (sender, e) =&amp;gt;
      {
        // 스레드 풀 생성
        ThreadPool.QueueUserWorkItem((_) =&amp;gt;
        {
          // 0부터 9999까지 루프
          for (int i = 0; i &amp;lt; 10000; i++)
          {
            // 콘솔 출력
            Console.WriteLine(i);
            // 스레드 대기 1초
            Thread.Sleep(1000);
          }
        });
      };
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MybMD/btrjWVk8Cgj/xRZhIIDRRbREovfEkQvmu1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MybMD/btrjWVk8Cgj/xRZhIIDRRbREovfEkQvmu1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MybMD/btrjWVk8Cgj/xRZhIIDRRbREovfEkQvmu1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/MybMD/btrjWVk8Cgj/xRZhIIDRRbREovfEkQvmu1/img.gif&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;버튼을 클릭을 해서 콘솔에 1씩 출력이 되도 윈도우가 멈추지 않는 것을 확인 할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 이번에는 콘솔에 값을 출력하는 것이 아니고 윈도우 컨트럴에서 값이 출력이 되게 만들어 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp
{
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수 버튼
    private Button button1 = null;
    private Label label1 = null;
    // 컨트럴 클래스의 초기 설정 함수
    private T setInitControl&amp;lt;T&amp;gt;(T ctl, string name, int point) where T : Control
    {
      // 위치 설정
      ctl.Location = new System.Drawing.Point(27, point);
      // 컨트럴 이름 설정
      ctl.Name = name;
      // 컨트럴 크기 설정
      ctl.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      ctl.TabIndex = 0;
      // 리턴
      return ctl;
    }
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 인스턴스 생성 후 초기 설정
      this.button1 = setInitControl(new Button(), &quot;Button1&quot;, 80);
      // 라벨 인스턴스 생성 후 초기 설정
      this.label1 = setInitControl(new Label(), &quot;Label1&quot;, 40);
      // 버튼의 Text 설정
      this.button1.Text = &quot;Button&quot;;
      // 폼에 Control 추가
      this.Controls.Add(button1);
      this.Controls.Add(label1);
      // 이벤트 추가(람다식으로 추가)
      this.button1.Click += (sender, e) =&amp;gt;
      {
        // 스레드 풀 생성
        ThreadPool.QueueUserWorkItem((_) =&amp;gt;
        {
          // 0부터 9999까지 루프
          for (int i = 0; i &amp;lt; 10000; i++)
          {
            // Label의 텍스트 설정
            this.label1.Text = $&quot;{i}&quot;;
            // 스레드 대기 1초
            Thread.Sleep(1000);
          }
        });
      };
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zbFA2/btrjT0H0lzZ/lHXhgJAA7JOVJ4Wvtcrev1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zbFA2/btrjT0H0lzZ/lHXhgJAA7JOVJ4Wvtcrev1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zbFA2/btrjT0H0lzZ/lHXhgJAA7JOVJ4Wvtcrev1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzbFA2%2FbtrjT0H0lzZ%2FlHXhgJAA7JOVJ4Wvtcrev1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;버튼을 클릭하면 바로 에러가 발생합니다.&lt;/p&gt;
&lt;p&gt;이유는 Window에서 도는 스레드와 스레드 풀에서 도는 스레드가 동기화가 되지 않아서 그렇습니다.&lt;/p&gt;
&lt;p&gt;스레드끼리 동기화라고 하면 서로 간에 lock을 설정해서 동기화를 시키면 되는 데, 윈도우 메시지를 돌리고 있는 스레드에 lock 걸 방법이 없습니다.&lt;/p&gt;
&lt;p&gt;이것을 C# 윈도우 개발에서는 크로스 스레드 문제라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이걸 할 수 있는 방법이 각 컨트럴에 있는 Invoke 함수를 사용해서 visitor 패턴, 즉 일명 콜백 함수로 이걸 처리할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp
{
  // 확장 함수를 위한 클래스
  public static class Util
  {
    // 확장 함수
    public static void InvokeControl(this Control ctl, Action func)
    {
      // Thread ID 비교
      if (ctl.InvokeRequired)
      {
        // 대리자로 실행
        ctl.Invoke(func);
      }
      else
      {
        // Thread ID가 같으면 그냥 실행(같은 스레드라는 의미)
        func();
      }
    }
  }
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수 버튼
    private Button button1 = null;
    private Label label1 = null;
    // 컨트럴 클래스의 초기 설정 함수
    private T setInitControl&amp;lt;T&amp;gt;(T ctl, string name, int point) where T : Control
    {
      // 위치 설정
      ctl.Location = new System.Drawing.Point(27, point);
      // 컨트럴 이름 설정
      ctl.Name = name;
      // 컨트럴 크기 설정
      ctl.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      ctl.TabIndex = 0;
      // 리턴
      return ctl;
    }
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 인스턴스 생성 후 초기 설정
      this.button1 = setInitControl(new Button(), &quot;Button1&quot;, 80);
      this.label1 = setInitControl(new Label(), &quot;Label1&quot;, 40);
      // 버튼의 Text 설정
      this.button1.Text = &quot;Button&quot;;
      // 폼에 Control 추가
      this.Controls.Add(button1);
      this.Controls.Add(label1);
      // 이벤트 추가(람다식으로 추가)
      this.button1.Click += (sender, e) =&amp;gt;
      {
        // 스레드 풀 생성
        ThreadPool.QueueUserWorkItem((_) =&amp;gt;
        {
          // 0부터 9999까지 루프
          for (int i = 0; i &amp;lt; 10000; i++)
          {
            // 대리자를 통해 윈도우 스레드에서 아래의 함수를 호출하게 만든다.
            this.label1.InvokeControl(() =&amp;gt;
            {
              // Label의 텍스트 설정
              this.label1.Text = $&quot;{i}&quot;;
            });
            // 스레드 대기 1초
            Thread.Sleep(1000);
          }
        });
      };
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4dqsm/btrjVBusYdL/lYX2eOGPlSlTO30kRKVLB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4dqsm/btrjVBusYdL/lYX2eOGPlSlTO30kRKVLB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4dqsm/btrjVBusYdL/lYX2eOGPlSlTO30kRKVLB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4dqsm%2FbtrjVBusYdL%2FlYX2eOGPlSlTO30kRKVLB1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 static Util 클래스를 만들고 Control 클래스의 확장 함수를 만들었습니다. 그리고 스레드 풀 안에서 label1 인스턴스에 InvokeControl 함수를 호출하여 람다식으로 콜백 함수를 만들었습니다.&lt;/p&gt;
&lt;p&gt;버튼을 클릭하면 Label에 숫자가 1초 단위로 바뀌는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#의 윈도우 폼(Window form)에서 스레드(Thread)를 사용하는 방법, 크로스 스레드 문제 해결에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/265</guid>
      <comments>https://nowonbun.tistory.com/265#entry265comment</comments>
      <pubDate>Thu, 4 Nov 2021 19:12:14 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 3-2. 책임 연쇄 패턴(Chain of responsibility pattern)</title>
      <link>https://nowonbun.tistory.com/456</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 책임 연쇄 패턴(Chain of responsibility pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;책임 연쇄 패턴이란 클래스 안에 연결 리스트 알고리즘을 걸고, 특정 함수를 실행하면 연속적으로 실행하는 패턴을 이야기합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ogS1a/btrjQK0cV8Z/l09KiBUzJfQ81CWp1XMljk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ogS1a/btrjQK0cV8Z/l09KiBUzJfQ81CWp1XMljk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ogS1a/btrjQK0cV8Z/l09KiBUzJfQ81CWp1XMljk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FogS1a%2FbtrjQK0cV8Z%2Fl09KiBUzJfQ81CWp1XMljk%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;이게 굉장히 많이 사용되는 패턴은 아닙니다만, 로그 처리나 하나의 처리로 여러가지 결과를 동시에 만들어야 할 때 사용하는 패턴입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 책임 연쇄 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// Logger 인터페이스
class ILogger {
public:
  // 추상 함수
  virtual void write(const char* message) = 0;
  ~ILogger() {}
};
// LoggerManager 클래스
class LoggerManager : public ILogger {
private:
  // 내부 연결 리스트를 만들기 위한 인라인 클래스
  class Pointer {
  public:
    // Logger 객체 변수
    ILogger* logger;
    // 다음 포인터 변수
    Pointer* next = nullptr;
    // 소멸자
    ~Pointer() {
      // logger를 메모리에서 제거한다.
      delete logger;
    }
  };
  // 연결리스트 포인터 변수
  Pointer* first = nullptr;
  Pointer* end = nullptr;
public:
  // 로그 작성 함수(재정의)
  virtual void write(const char* message) {
    // 연결리스트 처음 포인터
    Pointer* n = first;
    // 연결리스트 순서대로 루프
    while (n != nullptr) {
      // 등록된 Logger에 write 함수 호출
      n-&amp;gt;logger-&amp;gt;write(message);
      // 다음 포인터로 이동
      n = n-&amp;gt;next;
    }
  }
  // Logger 설정
  void setLogger(ILogger* logger) {
    // 포인터 인스턴스 생성
    Pointer* p = new Pointer();
    // Logger 설정
    p-&amp;gt;logger = logger;
    // 만약 리스트의 객체가 0개라면 first에 넣는다.
    if (first == nullptr) {
      first = p;
      end = p;
      return;
    }
    // 객체가 0개가 아니면 리스트를 연결한다.
    end-&amp;gt;next = p;
    end = p;
  }
  // 소멸자
  ~LoggerManager() {
    // 리스트 처음 객체
    Pointer* d = first;
    // 연결리스트 순서대로 루프
    while (d != nullptr) {
      // swap
      Pointer* b = d-&amp;gt;next;
      // 포인터 삭제
      delete d;
      // swap
      d = b;
    }
  }
};
// ConsoleLogger 클래스
class ConsoleLogger : public ILogger {
public:
  // 로그 작성(함수 재정의)
  virtual void write(const char* message) {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;ConsoleLogger - &quot; &amp;lt;&amp;lt; message &amp;lt;&amp;lt; endl;
  }
};
// FileLogger 클래스
class FileLogger : public ILogger {
public:
  // 로그 작성(함수 재정의)
  virtual void write(const char* message) {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;FileLogger - &quot; &amp;lt;&amp;lt; message &amp;lt;&amp;lt; endl;
  }
};

// 실행 함수
int main() {
  // LoggerManager 인스턴스 생성
  LoggerManager manager;
  // ConsoleLogger 인스턴스 추가
  manager.setLogger(new ConsoleLogger());
  // FileLogger 인스턴스 추가
  manager.setLogger(new FileLogger());
  // 로그 작성
  manager.write(&quot;hello world&quot;);

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tPldY/btrjQEFq864/KlK4NhAk98CBxE2fnuUJlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tPldY/btrjQEFq864/KlK4NhAk98CBxE2fnuUJlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tPldY/btrjQEFq864/KlK4NhAk98CBxE2fnuUJlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtPldY%2FbtrjQEFq864%2FKlK4NhAk98CBxE2fnuUJlk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 소스 코드를 작성하다 보니 완전히 연결리스트 알고리즘이 되어 버렸네요. 연결리스트 알고리즘을 잘 아시는 분들은 쉽게 눈에 들어올꺼 같은데, 알고리즘이 약하신 분들에게는 굉장히 복잡해 보일 수도 있겠습니다.&lt;/p&gt;
&lt;p&gt;내용은 제가 LoggerManager에 ConsoleLogger와 FileLogger의 인스턴스를 넣었습니다. 그리고 write 함수를 호출하니 순서대로 콘솔에 출력이 되네요.&lt;/p&gt;
&lt;p&gt;즉, setLogger 함수에 인스턴스를 넣은 수만큼 write 함수에서 연쇄적으로 출력하는 패턴입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 책임 연쇄 패턴 예제&quot;&gt;import java.util.LinkedList;
import java.util.List;
// Logger 인터페이스
interface ILogger {
  // 추상 함수
  void write(String msg);
}
// LoggerManager 클래스
class LoggerManager implements ILogger {
  // 연결 리스트 변수
  private List&amp;lt;ILogger&amp;gt; loggers = new LinkedList&amp;lt;&amp;gt;();
  // Logger 설정
  public void setLogger(ILogger logger) {
    // Logger 추가
    loggers.add(logger);
  }
  // 로그 작성 함수(재정의)
  public void write(String msg) {
    // list에 추가되었던 Logger
    for (var l : loggers) {
      // write 함수 호출
      l.write(msg);
    }
  }
}
// ConsoleLogger 클래스
class ConsoleLogger implements ILogger {
  // 로그 작성(함수 재정의)
  public void write(String msg) {
    // 콘솔 출력
    System.out.println(&quot;ConsoleLogger - &quot; + msg);
  }
}
// FileLogger 클래스
class FileLogger implements ILogger {
  // 로그 작성(함수 재정의)
  public void write(String msg) {
    // 콘솔 출력
    System.out.println(&quot;FileLogger - &quot; + msg);
  }
}
// 실행 함수 클래스
public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // LoggerManager 인스턴스 생성
    var manager = new LoggerManager();
    // ConsoleLogger 인스턴스 추가
    manager.setLogger(new ConsoleLogger());
    // FileLogger 인스턴스 추가
    manager.setLogger(new FileLogger());
    // 로그 작성
    manager.write(&quot;hello world&quot;);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3qnW7/btrjV9EasP1/yOe17g0SzIc9kWO2rX6sg0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3qnW7/btrjV9EasP1/yOe17g0SzIc9kWO2rX6sg0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3qnW7/btrjV9EasP1/yOe17g0SzIc9kWO2rX6sg0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3qnW7%2FbtrjV9EasP1%2FyOe17g0SzIc9kWO2rX6sg0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Java에는　연결 리스트의 LinkedList 클래스가 있습니다. 즉, Java에는 연결 리스트가 구현이 되어 있어서 사용하면 됩니다.&lt;/p&gt;
&lt;p&gt;물론 꼭 LinkedList일 필요는 없이 ArrayList여도 상관은 없습니다. 즉, 책임 연쇄 패턴(Chain of responsibility pattern)은 포인터로 연결해야 한다라고 하지만, C/C++에서도 vector 객체를 사용해도 구현 자체는 문제가 없습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 책임 연쇄 패턴 예제&quot;&gt;using System;
// Logger 추상 클래스
abstract class ALogger
{
  // 다음 Logger 포인터 프로퍼티
  protected ALogger Next
  {
    get; private set;
  }
  // 다음 포인터 Logger 설정
  public ALogger SetNextLogger(ALogger logger)
  {
    // 프로퍼티에 인스턴스 설정
    Next = logger;
    // 인스턴스 리턴
    return logger;
  }
  // 다음 인스턴스에 Write 함수 호출
  public virtual void Write(string data)
  {
    // 다음 포인터가 null 아니면
    if (Next != null)
    {
      // Write 함수 실행
      Next.Write(data);
    }
  }
}
// ConsoleLogger 클래스
class ConsoleLogger : ALogger
{
  // 함수 재정의
  public override void Write(string data)
  {
    // 콘솔 출력
    Console.WriteLine(&quot;ConsoleLogger - &quot; + data);
    // 다음 포인터의 인스턴스의 함수 호출
    base.Write(data);
  }
}
// FileLogger 클래스
class FileLogger : ALogger
{
  // 함수 재정의
  public override void Write(string data)
  {
    // 콘솔 출력
    Console.WriteLine(&quot;FileLogger - &quot; + data);
    // 다음 포인터의 인스턴스의 함수 호출
    base.Write(data);
  }
}
// MailLogger 클래스
class MailLogger : ALogger
{
  // 함수 재정의
  public override void Write(string data)
  {
    // 콘솔 출력
    Console.WriteLine(&quot;MailLogger - &quot; + data);
    // 다음 포인터의 인스턴스의 함수 호출
    base.Write(data);
  }
}


// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // ConsoleLogger 인스턴스 생성
    var logger = new ConsoleLogger();
    // SetNextLogger 함수로 순서대로 FileLogger 인스턴스와 MailLogger 인스턴스 추가
    logger.SetNextLogger(new FileLogger())
          .SetNextLogger(new MailLogger());
    // 로그 작성
    logger.Write(&quot;Hello world&quot;);
    // 아무 키나 누르면 종료
    Console.WriteLine(&quot;Press Any key...&quot;);
    Console.ReadLine();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oovdM/btrjR8TM1Mj/McYaIz2VoMfxKUHbwgibkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oovdM/btrjR8TM1Mj/McYaIz2VoMfxKUHbwgibkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oovdM/btrjR8TM1Mj/McYaIz2VoMfxKUHbwgibkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoovdM%2FbtrjR8TM1Mj%2FMcYaIz2VoMfxKUHbwgibkK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;굳이 LoggerManager라는 클래스가 필요없이 Logger 클래스에 다음 포인터를 연결해서 최초에 생성된 인스턴스의 함수를 호출하면 연쇄적으로 실행하는 방법도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;책임 연쇄 패턴은 클래스의 결합도를 낮추어서 여러가지 응용에 꽤 좋은 패턴입니다만, 의외로 사용 빈도가 낮은 패턴입니다. 적용할 만한 사양이 많지가 않습니다.&lt;/p&gt;
&lt;p&gt;대부분 퍼사드 패턴과 전략 패턴으로 해결되는 사양들이 많다보니...&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 책임 연쇄 패턴(Chain of responsibility pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/456</guid>
      <comments>https://nowonbun.tistory.com/456#entry456comment</comments>
      <pubDate>Thu, 4 Nov 2021 19:09:49 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 3-1. 전략 패턴(Strategy pattern)</title>
      <link>https://nowonbun.tistory.com/451</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 전략 패턴(Strategy pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이번 글부터는 행위 패턴에 대한 설명입니다.&lt;/p&gt;
&lt;p&gt;생성 패턴은 프로그램에서 인스턴스를 어떻게 생성하는지에 대한 형태이고 구조 패턴은 인터페이스와 추상 클래스, 그리고 일반 클래스 간의 구조적 정의에 대한 형태입니다.&lt;/p&gt;
&lt;p&gt;행위 패턴은 클래스와 알고리즘을 실제로 프로그램에서 어떻게 사용할지에 대한 방법을 설명하는 패턴입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;전략 패턴은 사용되는 클래스에서 주입되는 클래스의 형태에 따라 나오는 결과를 달리하는 패턴입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wjrVc/btrjLw1yyQ3/L0ytXBpsm9z2w3z09EgIMK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wjrVc/btrjLw1yyQ3/L0ytXBpsm9z2w3z09EgIMK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wjrVc/btrjLw1yyQ3/L0ytXBpsm9z2w3z09EgIMK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwjrVc%2FbtrjLw1yyQ3%2FL0ytXBpsm9z2w3z09EgIMK%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Strategy_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Strategy_pattern&lt;/a&gt;&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 전략 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// 전략 패턴의 인터페이스
class IStrategy {
public:
  // 함수 추상화
  virtual int calc(int data) = 0;
  virtual ~IStrategy() { }
};
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class NormalStrategy : public IStrategy {
public:
  // 함수 재정의
  virtual int calc(int data) {
    // 입력값에 10을 곱해서 리턴
    return data * 10;
  }
};
// SpecialStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class SpecialStrategy : public IStrategy {
public:
  // 함수 재정의
  virtual int calc(int data) {
    // 입력값에 100을 곱해서 리턴
    return data * 100;
  }
};
// Process 클래스
class Process {
private:
  // 전략 패턴의 맴버 변수
  IStrategy* strategy = nullptr;
public:
  // 전략 패턴 인스턴스 입력
  void setStrategy(IStrategy* strategy) {
    // 맴버 변수에 인스턴스를 설정
    this-&amp;gt;strategy = strategy;
  }
  // 출력 함수
  void print(int data) {
    // 전략 패턴의 맴버 변수가 null이 아니면
    if (this-&amp;gt;strategy != nullptr) {
      // calc 함수를 이용해 data를 재설정
      data = this-&amp;gt;strategy-&amp;gt;calc(data);
    }
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;data - &quot; &amp;lt;&amp;lt; data &amp;lt;&amp;lt; endl;
  }
};

// 실행 함수
int main() {
  // 인스턴스 생성
  Process process;
  // 전략 패턴의 인스턴스 입력 없이 10의 데이터를 출력
  process.print(10);
  // NormalStrategy 인스턴스 생성
  NormalStrategy normal;
  // process 인스턴스에 전략 패턴 설정
  process.setStrategy(&amp;normal);
  // 데이터를 출력
  process.print(10);
  // SpecialStrategy 인스턴스 생성
  SpecialStrategy special;
  // process 인스턴스에 전략 패턴 설정
  process.setStrategy(&amp;special);
  // 데이터를 출력
  process.print(10);

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dy2Ms3/btrjRi1ycbv/SdSU5wbMG70ExeeBx5Bok0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dy2Ms3/btrjRi1ycbv/SdSU5wbMG70ExeeBx5Bok0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dy2Ms3/btrjRi1ycbv/SdSU5wbMG70ExeeBx5Bok0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdy2Ms3%2FbtrjRi1ycbv%2FSdSU5wbMG70ExeeBx5Bok0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;아주 단순한 구조입니다. Process 클래스에 전략 패턴을 설정하지 않으면 그대로 10이 출력이 되고 NormalStrategy 인스턴스를 입력하면 100, SpecialStrategy 인스턴스를 입력하면 1000이 됩니다.&lt;/p&gt;
&lt;p&gt;사양에 따라 다르겠지만 전략 패턴의 클래스를 플라이웨이트 패턴과 같이 사용하게 되면 클래스의 재사용율이 매우 높아지고, 성능 개선에도 큰 장점이 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 전략 패턴 예제&quot;&gt;import java.util.HashMap;
import java.util.Map;
// 전략 패턴의 인터페이스
interface IStrategy {
  // 함수 추상화
  int calc(int data);
}
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class NormalStrategy implements IStrategy {
  // 함수 재정의
  public int calc(int data) {
    // 입력값에 10을 곱해서 리턴
    return data * 10;
  }
}
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class SpecialStrategy implements IStrategy {
  // 함수 재정의
  public int calc(int data) {
    // 입력값에 10을 곱해서 리턴
    return data * 100;
  }
}
// Process 클래스
class Process {
  // flyweight 패턴의 맵
  private Map&amp;lt;Class&amp;lt;? extends IStrategy&amp;gt;, IStrategy&amp;gt; flyweight = new HashMap&amp;lt;&amp;gt;();
  // 전략 패턴의 맴버 변수
  private IStrategy strategy = null;
  // 맴버 변수
  private int data = 0;
  // flyweight 패턴의 전략 패턴 인스턴스 취득 함수
  private IStrategy getStrategy(Class&amp;lt;? extends IStrategy&amp;gt; clz) {
    // flyweight 맵에 클래스 타입이 있는지 확인
    if (!flyweight.containsKey(clz)) {
      try {
        // 없으면 인스턴스를 생성해서 입력한다.
        flyweight.put(clz, clz.getDeclaredConstructor().newInstance());
      } catch (Exception e) {
        // Exception 처리를 RuntimeException 처리로 변환
        throw new RuntimeException(e);
      }
    }
    // 전략 패턴 인스턴스 취득
    return flyweight.get(clz);
  }
  // 전략 패턴 타입 입력
  public void setStrategy(Class&amp;lt;? extends IStrategy&amp;gt; clz) {
    // flyweight 맵에서 인스턴스 취득
    this.strategy = getStrategy(clz);
  }
  // 데이터 입력
  public void setData(int data) {
    // 맴버 변수 설정
    this.data = data;
  }
  // 출력 함수
  public void print() {
    // 출력 값의 임시 변수
    int val = data;
    // 전략 패턴이 설정되어 있으면
    if (this.strategy != null) {
      // 출력 값을 패턴의 계산값으로 변경
      val = this.strategy.calc(val);
    }
    // 콘솔 출력
    System.out.println(&quot;Data - &quot; + val);
  }
}
// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Process 인스턴스 생성
    Process process = new Process();
    // 데이터를 10 입력
    process.setData(10);
    // 콘솔 출력
    process.print();
    // 전략 패턴 타입 설정
    process.setStrategy(NormalStrategy.class);
    // 콘솔 출력
    process.print();
    // 전략 패턴 타입 설정
    process.setStrategy(SpecialStrategy.class);
    // 콘솔 출력
    process.print();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CH6Iz/btrjNcaHxdP/h9upUHDXqkenHJDWzB43pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CH6Iz/btrjNcaHxdP/h9upUHDXqkenHJDWzB43pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CH6Iz/btrjNcaHxdP/h9upUHDXqkenHJDWzB43pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCH6Iz%2FbtrjNcaHxdP%2Fh9upUHDXqkenHJDWzB43pk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 전략 패턴에 flyweight 패턴을 추가하여 전략 패턴의 인스턴스를 취득할 때, 메모리 할당을 최소화하고 클래스의 재사용을 높였습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 전략 패턴 예제&quot;&gt;using System;
using System.Collections.Generic;
// 전략 패턴의 인터페이스
interface IStrategy
{
  // 함수 추상화
  int Calc(int data);
}
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class NormalStrategy : IStrategy
{
  // 함수 재정의
  public int Calc(int data)
  {
    // 입력값에 10을 곱해서 리턴
    return data * 10;
  }
}
// NormalStrategy의 전략 패턴 클래스, IStrategy 인터페이스를 상속
class SpecialStrategy : IStrategy
{
  // 함수 재정의
  public int Calc(int data)
  {
    // 입력값에 10을 곱해서 리턴
    return data * 100;
  }
}
// Process 클래스
class Process
{
  // flyweight 패턴의 맵
  private Dictionary&amp;lt;Type, IStrategy&amp;gt; flyweight = new Dictionary&amp;lt;Type, IStrategy&amp;gt;();
  // 전략 패턴의 맴버 변수
  private IStrategy strategy = null;
  // 맴버 변수
  private int data = 0;
  // flyweight 패턴의 전략 패턴 인스턴스 취득 함수
  private IStrategy GetStrategy(Type clz)
  {
    // flyweight 맵에 클래스 타입이 있는지 확인
    if (!flyweight.ContainsKey(clz))
    {
      // 없으면 인스턴스를 생성해서 입력한다.
      flyweight.Add(clz, Activator.CreateInstance(clz) as IStrategy);
    }
    // 전략 패턴 인스턴스 취득
    return flyweight[clz];
  }
  // 전략 패턴 타입 입력
  public void SetStrategy(Type clz)
  {
    // flyweight 맵에서 인스턴스 취득
    this.strategy = GetStrategy(clz);
  }
  // 데이터 입력
  public void SetData(int data)
  {
    // 맴버 변수 설정
    this.data = data;
  }
  // 출력 함수
  public void Print()
  {
    // 출력 값의 임시 변수
    int val = data;
    // 전략 패턴이 설정되어 있으면
    if (this.strategy != null)
    {
      // 출력 값을 패턴의 계산값으로 변경
      val = this.strategy.Calc(val);
    }
    // 콘솔 출력
    Console.WriteLine(&quot;Data - &quot; + val);
  }
}
// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // Process 인스턴스 생성
    Process process = new Process();
    // 데이터를 10 입력
    process.SetData(10);
    // 콘솔 출력
    process.Print();
    // 전략 패턴 타입 설정
    process.SetStrategy(typeof(NormalStrategy));
    // 콘솔 출력
    process.Print();
    // 전략 패턴 타입 설정
    process.SetStrategy(typeof(SpecialStrategy));
    // 콘솔 출력
    process.Print();
    // 아무 키나 누르면 종료
    Console.WriteLine(&quot;Press Any key...&quot;);
    Console.ReadLine();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QyoJa/btrjO2ZXFv4/wIZb3heGZkislJttdnBUF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QyoJa/btrjO2ZXFv4/wIZb3heGZkislJttdnBUF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QyoJa/btrjO2ZXFv4/wIZb3heGZkislJttdnBUF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQyoJa%2FbtrjO2ZXFv4%2FwIZb3heGZkislJttdnBUF0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;전략 패턴는 최대한 클래스의 결합도를 낮추고 재사용성을 높여서 성능을 개선하는 것이 주요 목표입니다. 그리고 클래스는 최대한 나누는 작업으로 인해 프로그램의 UT 테스트와 개별 테스트가 용이하다는 장점이 있습니다.&lt;/p&gt;
&lt;p&gt;그런데 이 전략 패턴의 단점은 너무 지나치게 클래스를 쪼개여서 전략패턴으로 분산화시키면 가독성이 떨어진다는 단점이 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 클래스 작성이 지나치게 늘어나서 프로젝트 관리에도 어렵고 프로젝트 난이도가 상승하는 부분이 생기게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 전략 패턴(Strategy pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/451</guid>
      <comments>https://nowonbun.tistory.com/451#entry451comment</comments>
      <pubDate>Wed, 3 Nov 2021 18:37:34 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 59. 윈도우 폼(Window form)에서 컨트럴(Control)의 이벤트 설정하는 방법</title>
      <link>https://nowonbun.tistory.com/228</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#의 윈도우 폼(Window form)에서 컨트럴(Control)의 이벤트 설정하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전 글에서 윈도우 폼에 컨트럴을 추가하고 Control 클래스를 상속받아서 컨트럴을 만드는 방법에 대해서 설명했습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/177&quot; target=&quot;_blank&quot;&gt;[C#] 58. 원도우 폼(Window form)에서 컨트럴(Control) 다루기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;컨트럴이라고 폼에서 동적으로 움직이는 객체를 이야기합니다. 유저로부터 입력을 받거나(TextBox), 마우스로부터 클릭의 액션을 받거나(Button), 데이터를 표시하는(GridView)　등의 여러가지 컨트럴이 있습니다.&lt;/p&gt;
&lt;p&gt;이런 컨트럴들이 상태가 변화를 할 때에 발생하는 처리를 이벤트라고 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;기본적으로 컨트럴의 이벤트를 받는 방법은 크게 두가지 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;첫번째는 객체의 오버라이드(override)하는 방법이 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // 버튼 클래스를 상속
  class CustomButton: Button
  {
    // Click 이벤트 함수를 재정의
    protected override void OnClick(EventArgs e)
    {
      // 부모 클래스의 Click 처리를 한다. (event 함수 실행)
      base.OnClick(e);
      // 콘솔 출력
      Console.WriteLine(&quot;Click!!!&quot;);
    }
  }
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수
    private CustomButton button = null;
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 클래스의 인스턴스 생성
      this.button = new CustomButton();
      // 위치 설정
      this.button.Location = new System.Drawing.Point(27, 40);
      // 컨트럴 이름 설정
      this.button.Name = &quot;Button&quot;;
      // 컨트럴 크기 설정
      this.button.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      this.button.TabIndex = 0;
      // 버튼의 Text 설정
      this.button.Text = &quot;Button&quot;;
      // 폼에 Control 추가
      this.Controls.Add(this.button);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YYyyP/btrjEWfc4nL/x4sUKSspRKBh82WhkL4fek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YYyyP/btrjEWfc4nL/x4sUKSspRKBh82WhkL4fek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YYyyP/btrjEWfc4nL/x4sUKSspRKBh82WhkL4fek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYYyyP%2FbtrjEWfc4nL%2Fx4sUKSspRKBh82WhkL4fek%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;버튼을 클릭하게 되면 콘솔에 Click의 값이 출력이 됩니다.&lt;/p&gt;
&lt;p&gt;그런데 위처럼 이벤트를 설정하게 되면 기본적으로 제공하는 컨트럴을 사용하기 위해서는 모든 컨트럴 클래스를 상속받아서 정의해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;두번째는 event 키워드를 이용한 이벤트 설정입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // 버튼 클래스를 상속
  class CustomButton: Button
  {
    
  }
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수
    private CustomButton button = null;
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 클래스의 인스턴스 생성
      this.button = new CustomButton();
      // 위치 설정
      this.button.Location = new System.Drawing.Point(27, 40);
      // 컨트럴 이름 설정
      this.button.Name = &quot;Button&quot;;
      // 컨트럴 크기 설정
      this.button.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      this.button.TabIndex = 0;
      // 버튼의 Text 설정
      this.button.Text = &quot;Button&quot;;
      // 폼에 Control 추가
      this.Controls.Add(this.button);
      // 클릭 이벤트
      this.button.Click += Button_Click;
    }
    // 클릭 이벤트의 델리게이트 함수
    private void Button_Click(object sender, EventArgs e)
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Button Click!&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/og76o/btrjLwS7cXu/AvkUSw6UP58NXQ740IS7dK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/og76o/btrjLwS7cXu/AvkUSw6UP58NXQ740IS7dK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/og76o/btrjLwS7cXu/AvkUSw6UP58NXQ740IS7dK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fog76o%2FbtrjLwS7cXu%2FAvkUSw6UP58NXQ740IS7dK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;event를 이용한 클릭 이벤트를 추가하는 방법입니다. event 키워드에 대해서는 이전에 자세히 설명한 게 있으니 참조해 주세요.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/120&quot; target=&quot;_blank&quot;&gt;[C#] 24. 이벤트(event) 키워드 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;두 방법 중에 어떤 방법이 더 좋은가 했을 때는 사양별로 차이가 있습니다. &lt;/p&gt;
&lt;p&gt;일단 override와 event 키워드를 통한 이벤트 중에서 어떤 형태로 움직이는지 설명하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // 버튼 클래스를 상속
  class CustomButton: Button
  {
    // Click 이벤트 함수를 재정의
    protected override void OnClick(EventArgs e)
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Debugger 1 &quot;);
      // 부모 클래스의 Click 처리를 한다. (event 함수 실행)
      base.OnClick(e);
      // 콘솔 출력
      Console.WriteLine(&quot;Debugger 3 &quot;);
    }
  }
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 초기화
    private CustomButton button = null;
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 클래스의 인스턴스 생성
      this.button = new CustomButton();
      // 위치 설정
      this.button.Location = new System.Drawing.Point(27, 40);
      // 컨트럴 이름 설정
      this.button.Name = &quot;Button&quot;;
      // 컨트럴 크기 설정
      this.button.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      this.button.TabIndex = 0;
      // 버튼의 Text 설정
      this.button.Text = &quot;Button&quot;;
      // 폼에 Control 추가
      this.Controls.Add(this.button);
      // 클릭 이벤트
      this.button.Click += Button_Click;
    }
    // 클릭 이벤트의 델리게이트 함수
    private void Button_Click(object sender, EventArgs e)
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Debugger 2&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFOFmL/btrjJ7GiR6S/kNzygTpd37GZAfQTjPWwF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFOFmL/btrjJ7GiR6S/kNzygTpd37GZAfQTjPWwF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFOFmL/btrjJ7GiR6S/kNzygTpd37GZAfQTjPWwF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFOFmL%2FbtrjJ7GiR6S%2FkNzygTpd37GZAfQTjPWwF0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실행 순서를 보면 재정의한 OnClick 함수가 호출이 되는데, 그 다음으로 event 키워드의 이벤트 함수가 호출이 됩니다. 그리고 다시 OnClick 함수의 콘솔 출력이 실행됩니다.&lt;/p&gt;
&lt;p&gt;즉, 외부 클래스의 이벤트를 발생하기 전에 재정의한 OnClick 함수가 호출이 되고 base.OnClick을 통해 외부 event 키워드의 함수를 실행하고 다시 돌아와서 재정의한 OnClick 함수의 스택에서 종료가 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;만약에 base.OnClick 함수를 지우면 어떻게 될까요? 외부에 등록된 event 키워드의 함수들이 실행이 되지 않습니다.&lt;/p&gt;
&lt;p&gt;그리고 base.OnClick의 함수 호출하는 곳에 EventArgs 클래스로 정보를 주고 받는 데, 파라미터의 값을 재정의 한다면? OnClick의 데이터와 event에서 발생한 함수간에 데이터를 주고 받을 수도 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
　　// 데코레이터 패턴의 CustromEventArgs 클래스
  class CustromEventArgs : EventArgs
  {
    // 맴버 변수
    private EventArgs args;
    // 생성자
    public CustromEventArgs(EventArgs args)
    {
      this.args = args;
    }
    // 프로퍼티 추가
    public string Data { get; set; }
  }
  // 버튼 클래스를 상속
  class CustomButton1 : Button
  {
    // Click 이벤트 함수를 재정의
    protected override void OnClick(EventArgs e)
    {
      // 파라미터 e의 재생성
      var args = new CustromEventArgs(e);
      // Data 값을 설정
      args.Data = &quot;CustomButton1&quot;;
      // 부모 클래스의 Click 처리를 한다. (event 함수 실행)
      base.OnClick(args);
    }
  }
  // 버튼 클래스를 상속
  class CustomButton2 : Button
  {
    // Click 이벤트 함수를 재정의
    protected override void OnClick(EventArgs e)
    {
      // 파라미터 e의 재생성
      var args = new CustromEventArgs(e);
      // Data 값을 설정
      args.Data = &quot;CustomButton2&quot;;
      // 부모 클래스의 Click 처리를 한다. (event 함수 실행)
      base.OnClick(args);
    }
  }
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수 버튼들.
    private Button button1 = null;
    private Button button2 = null;
    // Button 클래스의 초기 설정 함수
    private Button setInitButton(Button button, string name, int point)
    {
      // 위치 설정
      button.Location = new System.Drawing.Point(27, point);
      // 컨트럴 이름 설정
      button.Name = name;
      // 컨트럴 크기 설정
      button.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      button.TabIndex = 0;
      // 버튼의 Text 설정
      button.Text = &quot;Button&quot;;
      // 리턴
      return button;
    }
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 인스턴스 생성 후 초기 설정
      this.button1 = setInitButton(new CustomButton1(), &quot;Button1&quot;, 40);
      this.button2 = setInitButton(new CustomButton2(), &quot;Button2&quot;, 70);
      // 폼에 Control 추가
      this.Controls.Add(button1);
      this.Controls.Add(button2);
      // 이벤트 추가(델리게이트 타입이 같기 때문에 같은 함수를 추가해도 된다.)
      this.button1.Click += Button_Click;
      this.button2.Click += Button_Click;
    }
    // 클릭 이벤트의 델리게이트 함수
    private void Button_Click(object sender, EventArgs e)
    {
      // 형변환
      CustromEventArgs args = e as CustromEventArgs;
      // 콘솔 출력
      Console.WriteLine(args.Data);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEDOIB/btrjKONgAgP/miY90wFpGgRwgbHVZHz1MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEDOIB/btrjKONgAgP/miY90wFpGgRwgbHVZHz1MK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEDOIB/btrjKONgAgP/miY90wFpGgRwgbHVZHz1MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEDOIB%2FbtrjKONgAgP%2FmiY90wFpGgRwgbHVZHz1MK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제를 보시면 button1은 CustomButton1 클래스의 인스턴스를 생성하고 button2는 CustomButton2 클래스의 인스턴스를 생성했습니다.&lt;/p&gt;
&lt;p&gt;그리고 Click 이벤트를 추가할 때는 같은 델리게이트 타입이기 때문에 같은 함수(Button_Click)를 설정하는 게 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다시 실행하여 위에 버튼을 클릭하면 CustomButton1의 값을 출력하고, 아래의 버튼을 클릭하면 CustomButton2의 값을 콘솔에 출력하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉, 재정의 함수(override)와 이벤트의 델리게이트 함수 간에 데이터를 위의 예처럼 설정할 수 있는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런데 여기서 델리게이트 함수를 보시면 object sender가 있습니다.&lt;/p&gt;
&lt;p&gt;이게 무엇이냐면 클릭한 인스턴스의 객체입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수 버튼들.
    private Button button1 = null;
    private Button button2 = null;
    // Button 클래스의 초기 설정 함수
    private Button setInitButton(Button button, string name, int point)
    {
      // 위치 설정
      button.Location = new System.Drawing.Point(27, point);
      // 컨트럴 이름 설정
      button.Name = name;
      // 컨트럴 크기 설정
      button.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      button.TabIndex = 0;
      // 버튼의 Text 설정
      button.Text = &quot;Button&quot;;
      // 리턴
      return button;
    }
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 인스턴스 생성 후 초기 설정
      this.button1 = setInitButton(new Button(), &quot;Button1&quot;, 40);
      this.button2 = setInitButton(new Button(), &quot;Button2&quot;, 70);
      // 폼에 Control 추가
      this.Controls.Add(button1);
      this.Controls.Add(button2);
      // 이벤트 추가(델리게이트 타입이 같기 때문에 같은 함수를 추가해도 된다.)
      this.button1.Click += Button_Click;
      this.button2.Click += Button_Click;
    }
    // 클릭 이벤트의 델리게이트 함수
    private void Button_Click(object sender, EventArgs e)
    {
      // 형변환
      var button = sender as Button;
      // 콘솔 출력
      Console.WriteLine(button.Name);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tUrcy/btrjEUobuHJ/YOUNqAn3ZP7jD1lXVRZr8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tUrcy/btrjEUobuHJ/YOUNqAn3ZP7jD1lXVRZr8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tUrcy/btrjEUobuHJ/YOUNqAn3ZP7jD1lXVRZr8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtUrcy%2FbtrjEUobuHJ%2FYOUNqAn3ZP7jD1lXVRZr8K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;즉, 따로 EventArgs의 클래스를 재생성해서 값을 보낼 필요가 없이, Button의 클래스를 상속받으면 상속받은 Button에 전달할 데이터를 포함하는게 훨신 편합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 이벤트의 델리게이트 함수는 람다식 함수로 표현이 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // Form 클래스를 상속
  public partial class Form1 : Form
  {
    // 맴버 변수 버튼
    private Button button1 = null;
    // Button 클래스의 초기 설정 함수
    private Button setInitButton(Button button, string name, int point)
    {
      // 위치 설정
      button.Location = new System.Drawing.Point(27, point);
      // 컨트럴 이름 설정
      button.Name = name;
      // 컨트럴 크기 설정
      button.Size = new System.Drawing.Size(75, 23);
      // 탭 Index 설정
      button.TabIndex = 0;
      // 버튼의 Text 설정
      button.Text = &quot;Button&quot;;
      // 리턴
      return button;
    }
    // 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
      // 버튼 인스턴스 생성 후 초기 설정
      this.button1 = setInitButton(new Button(), &quot;Button1&quot;, 40);
      // 폼에 Control 추가
      this.Controls.Add(button1);
      // 이벤트 추가(람다식으로 추가)
      this.button1.Click += (sender, e) =&amp;gt;
      {
        // 콘솔 출력
        Console.WriteLine(&quot;Click!!!&quot;);
      };
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brVE8S/btrjJ77m950/3hr61wLxUhRU5VlbE3Gw8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brVE8S/btrjJ77m950/3hr61wLxUhRU5VlbE3Gw8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brVE8S/btrjJ77m950/3hr61wLxUhRU5VlbE3Gw8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrVE8S%2FbtrjJ77m950%2F3hr61wLxUhRU5VlbE3Gw8k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;정리하면 일반적으로 이벤트는 클래스 외부에서는 event 키워드를 이용한 이벤트 추가, 클래스 내부에서는 override의 재정의를 이용한 이벤트를 추가합니다.&lt;/p&gt;
&lt;p&gt;override로 재정의한 함수에서는 base.OnClick(e);의 전후의 스탭으로 외부 이벤트와의 실행 순서를 결정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;파라미터는 기본적으로 object 타입과 EventArgs타입, 혹은 EventArgs를 상속받은 타입입니다만 object타입은 이벤트의 주체가 되는 인스턴스의 값, 즉, Button 클릭이면 Button 인스턴스의 값이 있습니다.&lt;/p&gt;
&lt;p&gt;여러 곳에서 재사용이 많은 이벤트 함수라면 당연히 델리게이트 함수로 작성을 해야 하지만, 일회성 실행이라면 람다식으로 작성하는 게 코딩 룰입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#의 윈도우 폼(Window form)에서 컨트럴(Control)의 이벤트 설정하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/228</guid>
      <comments>https://nowonbun.tistory.com/228#entry228comment</comments>
      <pubDate>Tue, 2 Nov 2021 21:15:39 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 2-7. 퍼사드 패턴 (Facade pattern)</title>
      <link>https://nowonbun.tistory.com/450</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 퍼사드 패턴(Facade pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;퍼사드 패턴은 디자인 패턴에서 가장 많이 사용되는 패턴 중의 하나로, 우리가 디자인 패턴을 모르고 가장 자연스럽게 작성되는 패턴이지 않을까 싶습니다.&lt;/p&gt;
&lt;p&gt;이 패턴을 간단한게 설명하면 이전에 생성된 객체와 함수를 사양의 흐름에 맞게 배치하는 구조입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFzoYh/btrjCZ4ewTB/8RdNYvI6KErpqTe5v61oQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFzoYh/btrjCZ4ewTB/8RdNYvI6KErpqTe5v61oQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFzoYh/btrjCZ4ewTB/8RdNYvI6KErpqTe5v61oQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFzoYh%2FbtrjCZ4ewTB%2F8RdNYvI6KErpqTe5v61oQK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Facade_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Facade_pattern&lt;/a&gt;&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 퍼사드 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// ControllerA 클래스
class ControllerA {
public:
  // 함수
  void init() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;ControllerA init&quot; &amp;lt;&amp;lt; endl;
  }
  // 함수
  void run() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;ControllerA run&quot; &amp;lt;&amp;lt; endl;
  }
  // 함수
  void close() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;ControllerA close&quot; &amp;lt;&amp;lt; endl;
  }
};
// ControllerB 클래스
class ControllerB {
public:
  // 함수
  void get() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;ControllerB get&quot; &amp;lt;&amp;lt; endl;
  }
  // 함수
  void put() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;ControllerB put&quot; &amp;lt;&amp;lt; endl;
  }
};
// Facade 패턴 클래스
class WorkFlow {
private:
  // 퍼사드 패턴에서 사용될 맴버 변수
  ControllerA cona;
  ControllerB conb;
public:
  // runA 타입의 함수
  void runA() {
    // 사양 순서대로 함수를 실행
    this-&amp;gt;cona.init();
    this-&amp;gt;conb.get();
    this-&amp;gt;cona.run();
    this-&amp;gt;cona.close();
  }
  // runB 타입의 함수
  void runB() {
    // 사양 순서대로 함수를 실행
    this-&amp;gt;conb.put();
    this-&amp;gt;cona.run();
  }
};

// 실행 함수
int main() {
  // 인스턴스 생성
  WorkFlow work;
  // 콘솔 출력
  cout &amp;lt;&amp;lt; &quot;runA type execute.&quot; &amp;lt;&amp;lt; endl;
  // Facade 패턴의 클래스의 runA 함수를 실행
  work.runA();
  // 콘솔 출력
  cout &amp;lt;&amp;lt; endl &amp;lt;&amp;lt; &quot;runB type execute.&quot; &amp;lt;&amp;lt; endl;
  // Facade 패턴의 클래스의 runB 함수를 실행
  work.runB();
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpiTM8/btrjIZWpYv2/sASNOOqiVuHM14YMFVwQeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpiTM8/btrjIZWpYv2/sASNOOqiVuHM14YMFVwQeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpiTM8/btrjIZWpYv2/sASNOOqiVuHM14YMFVwQeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpiTM8%2FbtrjIZWpYv2%2FsASNOOqiVuHM14YMFVwQeK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제를 설명하면, main 함수에서는, 퍼사드 패턴의 클래스에서 설정해 놓은 runA 던가 runB 함수를 실행하는 것으로, 사양에 의해 차례대로 실행하는 구조입니다.&lt;/p&gt;
&lt;p&gt;즉, Facade 클래스에서는 처리되는 순서를 설정해서, main 함수에서는 runA 함수를 호출하는 것으로 처리가 개시되는 형태의 구조로 되어 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 퍼사드 패턴 예제&quot;&gt;// 인터 페이스
interface INode {
  // 추상 메소드
  void print();
}
// INode를 상속받은 Node1클래스
class Node1 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;Node1 class&quot;);
  }
}
// INode를 상속받은 Node2클래스
class Node2 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;Node2 class&quot;);
  }
}
// Facade 패턴이 포함되어 있는 클래스
class Controller {
  // 맴버 변수
  private INode node = null;
  // 맴버 변수의 인스턴스 생성
  private void createNode1() {
    // 인스턴스 생성
    node = new Node1();
  }
  // 맴버 변수의 인스턴스 생성
  private void createNode2() {
    // 인스턴스 생성
    node = new Node2();
  }
  // 실행
  private void execute() {
    // 맴버 변수가 null이 아니라면
    if (node != null) {
      // print 함수 실행
      node.print();
    } else {
      // null 인 경우 에러 처리
      throw new NullPointerException(&quot;Please class init.&quot;);
    }
  }
  // Node1클래스의 인스턴스를 생성하고 실행
  public void execType1() {
    // 인스턴스 생성
    createNode1();
    // 실행
    execute();
  }
  // Node2클래스의 인스턴스를 생성하고 실행
  public void execType2() {
    // 인스턴스 생성
    createNode2();
    // 실행
    execute();
  }
}
// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Contorller 인스턴스 생성
    var controller = new Controller();
    // execType1 함수를 실행
    controller.execType1();
    // execType2 함수를 실행
    controller.execType2();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk20oo/btrjKOTT1do/rABLPKp0PTPowdBwm9rvGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk20oo/btrjKOTT1do/rABLPKp0PTPowdBwm9rvGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk20oo/btrjKOTT1do/rABLPKp0PTPowdBwm9rvGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk20oo%2FbtrjKOTT1do%2FrABLPKp0PTPowdBwm9rvGk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;퍼사드 패턴이라고 따로 클래스를 생성할 필요는 없고 Controller의 형태에서 객체의 각 처리는 private로 처리하고, 클래스 외부에서는 퍼사드 패턴으로 접근이 가능하도록 execType1함수나 execType2함수처럼 만드는 것이 일반적입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 퍼사드 패턴 예제&quot;&gt;using System;
using System.Reflection;
using System.Linq;

// Node 클래스
class Node
{
  // 프로퍼티
  public string Data { get; set; }
}
// 추상 클래스
abstract class AController
{
  // 퍼사드 패턴의 함수
  public void Route(string url)
  {
    // 파라미터의 데이터를 /기준으로 나눈다.
    var route = url.Split('/');
    // 파라미터
    object data = null;
    // 나눈 데이터 개수가 1보다 클경우
    if (route.Length &amp;gt; 1)
    {
      // ?로 문자열을 나눈다.
      var ps = route[1].Split('?');
      // ps 개수가 0보다 큰경우
      if (ps.Length &amp;gt; 0)
      {
        // 파라미터 인스턴스를 생성한다.예: Node 클래스의 인스턴스 생성
        data = Type.GetType(ps[0]).GetConstructors(BindingFlags.Instance | BindingFlags.Public)
                   .FirstOrDefault()?.Invoke(null);
      }
      // 파라미터 데이터가 null이 아니고 나눈 파라미터의 개수가 1보다 큰 경우
      if (data != null &amp;&amp; ps.Length &amp;gt; 1)
      {
        // 프로퍼티 취득
        var properties = data.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
        // &amp;로 문자열을 나눈다.
        foreach (var p in ps[1].Split('&amp;'))
        {
          // =로 문자열을 나눈다.
          var o = p.Split('=');
          // 2개일 경우
          if (o.Length == 2)
          {
            // 프로퍼티에 값을 넣는다.
            properties.Where(x =&amp;gt; string.Equals(x.Name, o[0], StringComparison.OrdinalIgnoreCase))
                      .FirstOrDefault()?.SetValue(data, o[1]);
          }
        }

      }
    }
    // 나눈 개수가 1보다 클경우
    if (route.Length &amp;gt; 0)
    {
      // 메소드 찾기
      var methods = this.GetType().GetMethods(BindingFlags.Instance | BindingFlags.Public);
      // 실행
      methods.Where(x =&amp;gt; string.Equals(x.Name, route[0], StringComparison.OrdinalIgnoreCase))
             .FirstOrDefault()?.Invoke(this, new object[] { data });
    }

  }
}
// AController 클래스를 상속받는다.
class Controller : AController
{
  // 함수
  public void Index(Node node)
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Contorller Index is executed, node data - &quot; + node.Data);
  }
}

// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(String[] args)
  {
    // Contorller 인스턴스 생성
    var control = new Controller();
    // url를 입력하여 실행한다.
    control.Route(&quot;Index/Node?data=hello world&quot;);
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r2WSK/btrjI1zWhRU/XsEAxgyJzTjkcEK5EKYatK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r2WSK/btrjI1zWhRU/XsEAxgyJzTjkcEK5EKYatK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r2WSK/btrjI1zWhRU/XsEAxgyJzTjkcEK5EKYatK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr2WSK%2FbtrjI1zWhRU%2FXsEAxgyJzTjkcEK5EKYatK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실무에서 사용되는 퍼사드 패턴으로 예제를 만들었습니다.&lt;/p&gt;
&lt;p&gt;사실 위에는 퍼사드 패턴만 있는 것이 아니고 인터프리터 패턴도 같이 사용되고 있습니다. 위에서 Main 함수에서 우리가 Web에서 자주 사용되는 url의 값을 넣었습니다.&lt;/p&gt;
&lt;p&gt;사실 문자열 자르기는 정규식을 사용해서 만들어야 하는데.. 개인적으로 정규식이 굉장히 잘하는 분야도 아니고 생각하기 귀찮아서 그냥 Split으로 잘랐습니다.&lt;/p&gt;
&lt;p&gt;즉, MVC 모델에서 위와 같이 웹 브라우저에서 호출이 오면 Route를 거쳐서 함수를 찾게 됩니다. 그리고 파라미터에 맞는 파라미터 클래스의 인스턴스도 생성하고 호출을 하게 되겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;우리는 그런 Facade 패턴으로 만들어져 있는 프레임워크에서 요청 메서드만 작성하면 됩니다. 즉, 프레임워크의 MVC 구조는 Facade 패턴으로 구현이 되어 있는 것입니다.&lt;/p&gt;
&lt;p&gt;사실 Facade 패턴은 이 패턴을 모르더라도 이미 프로그램을 작성할 때, 많이 작성하는 방법입니다.&lt;/p&gt;
&lt;p&gt;그러나 조금 이론적인 흐름을 알게 되면 위처럼 Reflection 기능까지 추가해서 응용이 가능한 패턴을 구현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 퍼사드 패턴(Facade pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/450</guid>
      <comments>https://nowonbun.tistory.com/450#entry450comment</comments>
      <pubDate>Tue, 2 Nov 2021 19:29:56 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 2-6. 프록시 패턴 (Proxy pattern)</title>
      <link>https://nowonbun.tistory.com/449</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 프록시 패턴(Proxy pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;프록시 패턴은 어떻게 보면 데코레이터 패턴과 유사한 구조를 구성하고 있습니다만, 데코레이터 패턴은 상속받은 인터페이스에 생성자에서 같은 인터페이스를 상속 받은 인스턴스를 받아 내용을 추가하는 내용이라면, 프록시 패턴은 상속받은 인터페이스에 클래스 내부에서 같은 인터페이스를 상속받은 인스턴스를 생성하는 패턴입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx4MJO/btrjxBaLyPN/ZoWrFKPNdrYVMUGjOFE2VK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx4MJO/btrjxBaLyPN/ZoWrFKPNdrYVMUGjOFE2VK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx4MJO/btrjxBaLyPN/ZoWrFKPNdrYVMUGjOFE2VK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx4MJO%2FbtrjxBaLyPN%2FZoWrFKPNdrYVMUGjOFE2VK%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Proxy_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Proxy_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 프록시 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// INode 인터페이스
class INode {
public:
  // 추상 함수
  virtual void print() = 0;
  virtual ~INode() {};
};
// INode를 상속 받은 Node 클래스
class Node : public INode {
public:
  // 함수 재정의
  virtual void print() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;Node Class&quot; &amp;lt;&amp;lt; endl;
  }
};
// Proxy 클래스
class NodeProxy : public INode {
private:
  // 맴버 변수
  INode* node;
public:
  // 생성자
  NodeProxy() {
    node = new Node();
  }
  // 소멸자
  ~NodeProxy() {
    delete node;
  }
  // 함수 재정의
  virtual void print() {
    // Node 인스턴스의 print 함수 호출
    node-&amp;gt;print();
  }
};

// 실행 함수
int main() {
  // 인스턴스 생성
  INode* node = new NodeProxy();
  // 함수 호출
  node-&amp;gt;print();
  // 메모리 삭제
  delete node;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boiuwo/btrjvRL4h0k/ohykYik41Y2WUiK5DmZo60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boiuwo/btrjvRL4h0k/ohykYik41Y2WUiK5DmZo60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boiuwo/btrjvRL4h0k/ohykYik41Y2WUiK5DmZo60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fboiuwo%2FbtrjvRL4h0k%2FohykYik41Y2WUiK5DmZo60%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 구조가 프록시 패턴의 기본 구조입니다. NodeProxy 클래스의 생성자에서 INode 인터페이스를 상속 받은 Node 클래스의 인스턴스를 생성합니다. 그리고 print 함수에서는 Node 인스턴스의 print 함수를 호출합니다.&lt;/p&gt;
&lt;p&gt;보통은 Node 클래스를 NodeProxy 클래스의 인라인 클래스로도 만듭니다.&lt;/p&gt;
&lt;p&gt;위처럼만 보면 굳이 왜 이렇게까지 만들까 하는 생각이 들겠네요. 그냥 Proxy클래스가 아닌 Node 클래스를 그냥 사용해도 상관없을 것 같은데...&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 프록시 패턴 예제&quot;&gt;// 인터페이스
interface INode {
  // 추상 메소드
  void print();
}

// INode를 상속받은 Node1클래스
class Node1 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;Node1 class&quot;);
  }
}

// INode를 상속받은 Node2클래스
class Node2 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;Node2 class&quot;);
  }
}

// Proxy 패턴 클래스
class NodeProxy implements INode {
  // 맴버 변수
  private INode node;

  // 생성자 - 파라미터가 없으면 true
  public NodeProxy() {
    this(true);
  }

  // 생성자
  public NodeProxy(boolean check) {
    // 파라미터 check에 의해 생성되는 인스턴스가 다르다.
    if (check) {
      // Node1 인스턴스 생성
      node = new Node1();
    } else {
      // Node2 인스턴스 생성
      node = new Node2();
    }
  }
  // 함수 재정의
  public void print() {
    // node 인스턴스의 print 함수 실행
    node.print();
  }
}

// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // 파라미터가 없는 NodeProxy 인스턴스 생성
    INode node = new NodeProxy();
    // print 함수 호출
    node.print();
    // 파라미터가 false인 NodeProxy 인스턴스 생성
    node = new NodeProxy(false);
    // print 함수 호출
    node.print();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZR8lB/btrjvSEdglM/fpuK2mtiu0WKCwAKadg7J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZR8lB/btrjvSEdglM/fpuK2mtiu0WKCwAKadg7J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZR8lB/btrjvSEdglM/fpuK2mtiu0WKCwAKadg7J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZR8lB%2FbtrjvSEdglM%2FfpuK2mtiu0WKCwAKadg7J0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Proxy 클래스의 생성자 파라미터에 의해 내부 맴버 변수의 INode node에 생성된 인스턴스가 다릅니다.&lt;/p&gt;
&lt;p&gt;즉, 파라미터나 데이터에 의해 인스턴스를 구분을 할때 사용되는 패턴이라고 할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 프록시 패턴 예제&quot;&gt;using System;
// 인터페이스
interface INode
{
  // 추상 메소드
  void Print();
}
// Proxy 패턴 클래스
class NodeProxy : INode
{
  // INode를 상속받은 Node1클래스
  private class Node1 : INode
  {
    // 함수 재정의
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Node1 class&quot;);
    }
  }

  // INode를 상속받은 Node2클래스
  private class Node2 : INode
  {
    // 함수 재정의
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Node2 class&quot;);
    }
  }

  // 맴버 변수
  private INode node;
  private bool check;
  // 생성자
  public NodeProxy(bool check = true)
  {
    this.check = check;
  }

  // 함수 재정의
  public void Print()
  {
    if(node == null)
    {
      // 파라미터 check에 의해 생성되는 인스턴스가 다르다.
      if (check)
      {
        // Node1 인스턴스 생성
        node = new Node1();
      }
      else
      {
        // Node2 인스턴스 생성
        node = new Node2();
      }
    }
    // node 인스턴스의 print 함수 실행
    node.Print();
  }
}

// 실행 클래스
class Program
{
  // 실행 함수
  static void Main(String[] args)
  {
    // 파라미터가 없는 NodeProxy 인스턴스 생성
    INode node = new NodeProxy();
    // print 함수 호출
    node.Print();
    // 파라미터가 false인 NodeProxy 인스턴스 생성
    node = new NodeProxy(false);
    // print 함수 호출
    node.Print();
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btezrE/btrjDCTL6HW/TFWtmUBV6qK1ZQdOlcTzKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btezrE/btrjDCTL6HW/TFWtmUBV6qK1ZQdOlcTzKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btezrE/btrjDCTL6HW/TFWtmUBV6qK1ZQdOlcTzKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtezrE%2FbtrjDCTL6HW%2FTFWtmUBV6qK1ZQdOlcTzKk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이번에는 Node1 클래스와 Node2 클래스를 NodeProxy클래스의 인라인 클래스로 작성했습니다.&lt;/p&gt;
&lt;p&gt;그리고 인스턴스를 생성자에서 생성하는 것이 아닌 Print 함수에서 생성합니다. 즉, 만약 Node 클래스가 많은 데이터를 가지고 있거나 리소스를 사용하는 클래스라고 한다면, 위처럼 생성자가 아닌 함수 호출할 때 인스턴스를 생성함으로 성능에 고려한 설계를 할 수 있는 장점도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;제 생각으로 패턴 구조로는 생성 패턴에서는 Factory 메서드 패턴과 Flyweight 패턴, 데코레이터 패턴을 자주 사용하는 상황에서 굳이 프록시 패턴이 필요할까 생각이 많이 드는 패턴입니다. 그래서 그런지 실제적으로 많이 사용되는 패턴은 아닙니다.&lt;/p&gt;
&lt;p&gt;또, 그냥 비슷하게 인터페이스를 상속 안받고 맴버 변수에 여러 클래스의 인스턴스를 넣어서 경우로 많이 사용합니다. 그러나 사양에 따라 프록시 패턴이 더 최적인 경우도 많이 있으니 사양에 맞게 패턴을 적용하면 좋을 것 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 프록시 패턴(Proxy pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/449</guid>
      <comments>https://nowonbun.tistory.com/449#entry449comment</comments>
      <pubDate>Mon, 1 Nov 2021 19:41:30 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 2-5. 플라이웨이트 패턴 (Flyweight pattern)</title>
      <link>https://nowonbun.tistory.com/447</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 플라이웨이트 패턴(Flyweight pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;플라이웨이트의 영어의 뜻은 경량화하다라는 뜻입니다. 그러니깐 인스턴스의 생성을 최소화하여서 메모리의 사용을 최대한 아끼는 방법입니다.&lt;/p&gt;
&lt;p&gt;구조 패턴의 싱글톤 버전이라고 생각하면 쉽습니다. 그러나 singleton처럼 static을 이용하는 것은 아니고 보통 Map(Dictionary)와 같이 사용합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HsGjp/btrjk0nm6Hj/kgKRLCqciXqcti1vnmAlfK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HsGjp/btrjk0nm6Hj/kgKRLCqciXqcti1vnmAlfK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HsGjp/btrjk0nm6Hj/kgKRLCqciXqcti1vnmAlfK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHsGjp%2Fbtrjk0nm6Hj%2FkgKRLCqciXqcti1vnmAlfK%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Flyweight_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Flyweight_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 플라이웨이트 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;map&amp;gt;
using namespace std;
// INode 인터페이스
class INode {
public:
  // 추상 함수
  virtual void print() = 0;
  virtual ~INode() {};
};
// Node 인스턴스를 만드는 Builder 클래스
class NodeBuilder {
private:
  // 인라인 클래스(외부에서는 참조할 수 없다.)
  // INode 인터페이스를 상속
  class Node : public INode {
  private:
    // 맴버 변수
    char data;
    int count = 0;
  public:
    // 생성자
    Node(char data) {
      // data를 넣는다.
      this-&amp;gt;data = data;
    }
    // 출력 함수
    virtual void print() {
      // 출력, data는 입력 받은 값이고 count는 Builder에서 호출될 때마다 count가 올라간다.
      cout &amp;lt;&amp;lt; this-&amp;gt;data &amp;lt;&amp;lt; &quot; Node counting - &quot; &amp;lt;&amp;lt; this-&amp;gt;count &amp;lt;&amp;lt; endl;
    }
    // 카운팅 함수
    void counting() {
      // count 변수값을 1증가
      this-&amp;gt;count++;
    }
  };
  // flyweight 맵
  map&amp;lt;char, Node*&amp;gt; flyweight;
public:
  // Node 클래스를 생성하는 함수
  Node* getNode(char data) {
    // data 값으로 flyweight 맵에서 data를 키로 하는 Node 인스턴스가 있는지 확인
    if (this-&amp;gt;flyweight.find(data) == this-&amp;gt;flyweight.end()) {
      // 없으면 인스턴스를 생성한다.
      this-&amp;gt;flyweight.insert(make_pair(data, new Node(data)));
    }
    // flyweight 맵에서 data를 키로 Node 인스턴스를 취득
    Node* node = this-&amp;gt;flyweight.at(data);
    // Node 클래스의 count를 하나 증가한다.
    node-&amp;gt;counting();
    // 반환
    return node;
  }
  // 소멸자
  ~NodeBuilder() {
    // flyweight 맵에 있는 인스턴스를 모두 메모리 해제한다.
    for (map&amp;lt;char, Node*&amp;gt;::iterator ptr = this-&amp;gt;flyweight.begin(); ptr != this-&amp;gt;flyweight.end(); ptr++) {
      // 메모리 해제
      delete ptr-&amp;gt;second;
    }
  }
};
// 실행 함수
int main() {
  // NodeBuilder 인스턴스 생성
  NodeBuilder builder;
  // a의 키로 Node 인스턴스를 취득한다.
  INode* node = builder.getNode('a');
  // 출력
  node-&amp;gt;print();
  // b의 키로 Node 인스턴스를 취득한다.
  node = builder.getNode('b');
  // 출력
  node-&amp;gt;print();
  // a의 키로 Node 인스턴스를 취득한다.
  node = builder.getNode('a');
  // 출력
  node-&amp;gt;print();

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6bDqS/btrjjNCk4x2/IieaAG45MTEy2D6oisVDn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6bDqS/btrjjNCk4x2/IieaAG45MTEy2D6oisVDn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6bDqS/btrjjNCk4x2/IieaAG45MTEy2D6oisVDn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6bDqS%2FbtrjjNCk4x2%2FIieaAG45MTEy2D6oisVDn1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Builder라는 클래스에서 getNode를 통해 Node 인스턴스를 취득합니다.&lt;/p&gt;
&lt;p&gt;그런데 a의 키로 넣었을 때는 count가 2가 되었습니다. 그 뜻은 a를 두번 호출했으나 같은 인스턴스를 2번 리턴한 뜻입니다. 즉, 두번 이상 호출이 되면 인스턴스를 새로 생성하지 않고 첫번째 호출 되었을 때 생성된 인스턴스를 map에 넣어서 같은 인스턴스를 취득하는게 플라이웨이트 패턴입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 플라이웨이트 패턴 예제&quot;&gt;import java.util.HashMap;
import java.util.Map;

// 인터 페이스
interface INode {
  // 추상 메서드
  void print();
}

// INode를 상속한 ANode 클래스
class ANode implements INode {
  // 추상 메서드를 재정의 한다.
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;The memory address of ANode class - &quot; + super.hashCode());
  }
}

// INode를 상속한 BNode 클래스
class BNode implements INode {
  // 추상 메서드를 재정의 한다.
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;The memory address of BNode class - &quot; + super.hashCode());
  }
}

// 팩토리 클래스
class NodeFactory {
  // flywieght 패턴
  private Map&amp;lt;Class&amp;lt;? extends INode&amp;gt;, INode&amp;gt; flyweight = new HashMap&amp;lt;&amp;gt;();

  // Node 인스턴스를 취득한다.
  public INode getNode(Class&amp;lt;? extends INode&amp;gt; clz) {
    // flywieght변수의 map 안에 key가 없으면
    if (!flyweight.containsKey(clz)) {
      // 파라미터 타입이 ANode면 ANode의 인스턴스를 생성해서 넣는다.
      if (clz == ANode.class) {
        flyweight.put(clz, new ANode());
      }
      // 파라미터 타입이 BNode면 BNode의 인스턴스를 생성해서 넣는다.
      else if (clz == BNode.class) {
        flyweight.put(clz, new BNode());
      } else {
        // 그 외의 예외처리한다.
        throw new UnsupportedOperationException();
      }
    }
    // 한 번 생성한 것은 재사용한다.
    return flyweight.get(clz);
  }
}

// 실행 클래스
class Program {
  // 실행 함수
  public static void main(String[] args) {
    // 팩토리 클래스 생성
    var factory = new NodeFactory();

    // ANode 인스턴스 취득
    factory.getNode(ANode.class).print();
    // BNode 인스턴스 취득
    factory.getNode(BNode.class).print();
    // ANode 인스턴스 취득
    factory.getNode(ANode.class).print();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wRVG9/btrjkzXRTkH/zLg4sUPGQv542JfrVec8bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wRVG9/btrjkzXRTkH/zLg4sUPGQv542JfrVec8bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wRVG9/btrjkzXRTkH/zLg4sUPGQv542JfrVec8bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwRVG9%2FbtrjkzXRTkH%2FzLg4sUPGQv542JfrVec8bK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;사실 플라이웨이트 패턴은 위의 Factory 메서드 패턴과 같이 자주 사용합니다. Factory에서 인스턴스를 생성하고 한번 생성된 인스턴스는 재사용을 하는 것입니다.&lt;/p&gt;
&lt;p&gt;그래도 Factory 메서드 패턴이다 보니 Class를 추가할 때마다 Factory 함수를 수정해야 하는 불편함이 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 플라이웨이트 패턴 예제&quot;&gt;using System;
using System.Collections.Generic;
// IDao 인터페이스
interface IDao
{
  // 추상 함수
  void Select();
}
// IDao 인터페이스를 상속 받은 ADao
class ADao: IDao
{
  // 함수 재정의
  public void Select()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;ADao was selected!&quot;);
  }
}
// IDao 인터페이스를 상속 받은 BDao
class BDao : IDao
{
  // 함수 재정의
  public void Select()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;BDao was selected!&quot;);
  }
}
// FactoryDao 클래스
class FactoryDao
{
  // 플라이웨이트 패턴 Dictionary
  private Dictionary&amp;lt;Type, IDao&amp;gt; flyweight = new Dictionary&amp;lt;Type, IDao&amp;gt;();
  // Dao 인스턴스를 취득
  public T GetDao&amp;lt;T&amp;gt;() where T : IDao
  {
    // 제네릭 타입으로 플라이웨이트 딕셔너리에 인스턴스가 있는지 확인
    if (!flyweight.ContainsKey(typeof(T)))
    {
      // 없으면 Reflection 기능을 이용해서 인스턴스 생성
      flyweight.Add(typeof(T), (IDao)Activator.CreateInstance(typeof(T)));
    }
    // 인스턴스 리턴
    return (T)flyweight[typeof(T)];
  }
}

class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // FactoryDao 인스턴스 생성
    var factory = new FactoryDao();
    // ADao 인스턴스를 받아와서 Select 함수 실행
    factory.GetDao&amp;lt;ADao&amp;gt;().Select();
    // BDao 인스턴스를 받아와서 Select 함수 실행
    factory.GetDao&amp;lt;BDao&amp;gt;().Select();
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lNJ0N/btrjjOgX8at/aScIoTtb7DZNmBDwlLfUb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lNJ0N/btrjjOgX8at/aScIoTtb7DZNmBDwlLfUb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lNJ0N/btrjjOgX8at/aScIoTtb7DZNmBDwlLfUb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlNJ0N%2FbtrjjOgX8at%2FaScIoTtb7DZNmBDwlLfUb1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제에서는 Java의 예와 비슷한데 FactoryDao안을 Reflection과 Generic기능을 이용해서 인스턴스를 취득하게 만들었습니다.&lt;/p&gt;
&lt;p&gt;이 패턴이 어디서 사용하는가 하면, ORM 프레임워크에서 Dao를 취득하는 함수에서 사용하는 방법입니다. 특히 Spring에서 의존성 주입으로 Dao를 취득할 때 프레임워크에서 위와 같은 구조로 인스턴스를 넘기게 되는 것입니다.&lt;/p&gt;
&lt;p&gt;즉, 한번 생성된 인스턴스는 계속해서 재사용한다. 라는 뜻이지요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 플라이웨이트 패턴(Flyweight pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/447</guid>
      <comments>https://nowonbun.tistory.com/447#entry447comment</comments>
      <pubDate>Fri, 29 Oct 2021 19:44:21 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 58. 원도우 폼(Window form)에서 컨트럴(Control) 다루기</title>
      <link>https://nowonbun.tistory.com/177</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#의 원도우 폼(Window form)에서 컨트럴(Control) 다루기에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전 글에서 Window form을 만드는 방법에 대해서 간략하게 설명했습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/174&quot; target=&quot;_blank&quot;&gt;[C#] 57. 윈도우 폼(Window form)을 작성하는 방법, 그리고 윈도우 메시지와 큐&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;일반적으로 우리가 윈도우 프로그램을 만들게 되면 가장 많이 사용하는 클래스 객체는 아마도 컨트럴(Control) 객체입니다.&lt;/p&gt;
&lt;p&gt;이 컨트럴 객체는 기본적으로 .Net framework에서 제공을 하고 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgH21J/btrjeuCoZTJ/HfMUSVDdoh0klYa6KjmkF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgH21J/btrjeuCoZTJ/HfMUSVDdoh0klYa6KjmkF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgH21J/btrjeuCoZTJ/HfMUSVDdoh0klYa6KjmkF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgH21J%2FbtrjeuCoZTJ%2FHfMUSVDdoh0klYa6KjmkF1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;사실 이 기본적인 컨트럴(Control)만 사용해도 거의 모든 윈도우 프로그램을 만들 수 있습니다. 사양에 따라 필요한 컨트럴(Control)를 개발할 일도 있겠지만, 제 생각에는 많은 프로젝트가 기본적으로 제공하는 컨트럴만으로도 충분히 개발이 가능하다고 생각됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 폼에 컨트럴을 추가하는 방법은 디자인 화면에서 드래그 엔 드롭(Drag and drop)을 이용해서 추가할 수 있고 또는 Form1.Designer.cs 페이지에서 소스를 작성해서 추가할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfgOdg/btrjctjVR32/KGcep9lcSkqhA8mmUxt7y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfgOdg/btrjctjVR32/KGcep9lcSkqhA8mmUxt7y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfgOdg/btrjctjVR32/KGcep9lcSkqhA8mmUxt7y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfgOdg%2FbtrjctjVR32%2FKGcep9lcSkqhA8mmUxt7y1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여러가지 간단하게 컨트럴을 추가하는 부분은 역시 디자인에서 작업을 하는게 좋지만, 역시 디자인에서 추가를 하게 되면 세밀한 설정은 힘든 부분이 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 Form1.Designer.cs에서 추가하는 방법입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;// Designer 코드는 왜인지 namespace 선언이 되어 있지 않습니다.
// 자주 사용하는 라이브러리의 namespace를 선언을 하는 것이 좋습니다.
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // 초기에 생성되면 이런 저런 주석등이 작성되어 있는 데 깔끔하게 지우고 시작하는 것이 좋습니다.
  partial class Form1
  {
    // 리소스 관리 변수(이건 건드릴 일 없으니 그대로 냅두자)
    private System.ComponentModel.IContainer components = null;
    // button 컨트럴 맴버 변수
    private Button button = null;
    // 초기화 함수
    private void InitializeComponent()
    {
      // 인스턴스 추가(위에 using을 사용했는데도, Visual studio에서 자동으로 namespace가 붙네요.)
      this.button = new System.Windows.Forms.Button();
      // 레이 아웃 설정
      this.SuspendLayout();
      // 
      // button
      // 
      // 버튼 위치 설정(GDI 좌표계, 윈도우 왼쪽 위 상단이 0,0입니다. 한 픽셀마다 1씩 이동한다.)
      this.button.Location = new System.Drawing.Point(12, 12);
      // 컨트럴 이름 설정
      this.button.Name = &quot;TestBtn&quot;;
      // 버튼 안의 텍스트
      this.button.Text = &quot;TestBtn&quot;;
      // 컨트럴 크기 설정
      this.button.Size = new System.Drawing.Size(75, 23);
      // TabIndex 설정
      this.button.TabIndex = 0;
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      // 폼 크기 설정
      this.ClientSize = new System.Drawing.Size(253, 186);
      // 폼에 컨트럴 추가
      this.Controls.Add(this.button);
      // 폼 이름
      this.Name = &quot;Form1&quot;;
      // 폼 상단의 Text
      this.Text = &quot;TestButton&quot;;
      // 폼 레이아웃 설정
      this.ResumeLayout(false);
    }
    // 초기에는 이게 클래스의 위쪽에 작성되어 있는데, 그렇게 중요한 함수가 아니기 때문에 아래쪽으로 옮깁니다.
    protected override void Dispose(bool disposing)
    {
      // 프로그램이 닫힐 때, 작동되는 리소스 해제.
      if (disposing &amp;&amp; (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;제가 Window 폼에서 컨트럴을 작성하는 방법은 일단 Designer 소스 화면에서 사용할 컨트럴 맴버 변수와 인스턴스 생성, 그리고 Name 설정(이게 가장 중요함), Text 설정, 그리고 Controls.Add를 통해 Window 폼에 컨트럴 설정을 추가합니다. 다음에 다시 디자인 모드 화면으로 와서 컨트럴을 윈도우 폼에 알맞게 배치, 크기 설정을 합니다.&lt;/p&gt;
&lt;p&gt;그리고 다시 소스로 돌아와서 정밀한(?) 설정을 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oGjhY/btrjiuayBec/lvgg8MzsDRNL4c993DhwT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oGjhY/btrjiuayBec/lvgg8MzsDRNL4c993DhwT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oGjhY/btrjiuayBec/lvgg8MzsDRNL4c993DhwT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoGjhY%2FbtrjiuayBec%2Flvgg8MzsDRNL4c993DhwT0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;왜 이렇게 복잡하게 작성할까 싶겠지만, 솔직히 Visual Studio에서 제공하는 디자인 모드는 굉장히 편하기는 합니다. 그런데 너무 자동으로 설정하는 부분이 많아서 놓치는 부분이 많아 집니다.&lt;/p&gt;
&lt;p&gt;예를 들면 Name 항목입니다. 이 Name 항목이 Window Form에서 사소한 것 같아도 컨트럴을 식별하기 위해 굉장히 중요한 부분입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;우리가 맴버 변수를 선언해서 맴버 변수에서 직접 컨트럴 인스턴스를 취득하면 문제가 없을 것 같지만, 그런 Form 클래스 안에서만 입니다. 다른 컨트럴에서는 어떨까요? Form의 인스턴스를 넘겨서 맴버 변수를 전부 프로퍼티로 해서 취득해 오지 않는 이상 어떤 컨트럴이 어떤 컨트럴인지 알 수가 없습니다.&lt;/p&gt;
&lt;p&gt;이것에 관해서는 밑에서 자세히 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇기 때문에 디자인 모드에서 작성하는 것도 중요하지만 Form.Designer.cs 소스 파일에서도 제대로 설정이 되어있나, 임의의 변수명이 아닌 조금은 구별하기 쉬운 변수명으로 작성이 되었나 정도를 확인하기 위해서 소스에서도 설정하는 것이 중요합니다.&lt;/p&gt;
&lt;p&gt;여기까지 Form에서 Control를 추가하는 것을 설명했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 이제 Control를 자세히 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;Window에서 제공하는 모든 컨트럴은 Control 클래스를 상속 받았습니다. 마치 클래스가 Object 클래스를 상속받는 것처럼 말입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bntYQ1/btrjfEEMm4S/OlE0LgqrIbvaWOjrC1bPzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bntYQ1/btrjfEEMm4S/OlE0LgqrIbvaWOjrC1bPzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bntYQ1/btrjfEEMm4S/OlE0LgqrIbvaWOjrC1bPzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbntYQ1%2FbtrjfEEMm4S%2FOlE0LgqrIbvaWOjrC1bPzk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZryZw/btrjhtXhTpc/GjofPfnKOGo1yhCsDJwM7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZryZw/btrjhtXhTpc/GjofPfnKOGo1yhCsDJwM7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZryZw/btrjhtXhTpc/GjofPfnKOGo1yhCsDJwM7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZryZw%2FbtrjhtXhTpc%2FGjofPfnKOGo1yhCsDJwM7k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Button만 봐도 ButtonBase를 상속받고 ButtonBase는 Control를 상속받았습니다.&lt;/p&gt;
&lt;p&gt;즉, 우리가 이 Control를 상속받으면 컨트럴을 만들 수가 있다는 뜻입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 클래스를 추가하고 컨트럴을 만들어 봅시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;TestControl.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // TestControl 생성
  class TestControl : Control
  {
    // 프로퍼티 재정의
    public override string Text
    {
      // 취득은 가능하지만
      get =&amp;gt; base.Text;
      // 설정은 못함.
      set { }
    }
    // 생성자
    public TestControl()
    {
      // 초기 Text 설정
      base.Text = &quot;Hello World&quot;;
      // 초기 사이즈 설정
      base.Size = new Size(100, 22);
    }
    // 그리기 이벤트
    protected override void OnPaint(PaintEventArgs e)
    {
      base.OnPaint(e);
      // 폼 취득
      var form = FindForm();
      // Text 그리기
      e.Graphics.DrawString(base.Text, form.Font, Brushes.Red, new PointF(0, 0));
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사실 OnPaint를 알려먼 GDI에 대해서 알아야 하는데, GDI는 다른 글에서 자세히 설명하고 여기서는 간단하게 위처럼 작성합시다.&lt;/p&gt;
&lt;p&gt;TestControl은 인스턴스 외부에서는 설정할 수 없습니다. 설정한다고 해도 아무런 처리가 일어나지 않게 되죠. 그럼 어디서 설정할까 생성자에서 Hello world로 설정했습니다.&lt;/p&gt;
&lt;p&gt;그리고 다시 Visual studio의 디자이인 모드 화면으로 돌아가 보겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmyz73/btrjcub7Igh/5kGfg90OUYjXBJxD1JYZF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmyz73/btrjcub7Igh/5kGfg90OUYjXBJxD1JYZF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmyz73/btrjcub7Igh/5kGfg90OUYjXBJxD1JYZF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdmyz73%2Fbtrjcub7Igh%2F5kGfg90OUYjXBJxD1JYZF0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그럼 Toolbox에 TestControl이 추가된 것을 확인 할 수 있습니다. (만약 없으면 F5를 누르고 디버깅하면 생깁니다.)&lt;/p&gt;
&lt;p&gt;그럼 다시 Button처럼 드래그 앤 드롭(Drag and drop)으로 컨트럴을 추가합니다. 그리고 Form.Designer.cs에서도 변수명과 Name등의 설정을 합시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System.Windows.Forms;

namespace WindowsFormsApp
{
  partial class Form1
  {
    // 리소스 관리 변수(이건 건드릴 일 없으니 그대로 냅두자)
    private System.ComponentModel.IContainer components = null;
    // button 컨트럴 맴버 변수
    private Button button = null;
    // 새로 생성한 컨트럴 맴버 변수
    private TestControl testCtl = null;
    // 초기화 함수
    private void InitializeComponent()
    {
      // 인스턴스 생성
      this.button = new System.Windows.Forms.Button();
      this.testCtl = new WindowsFormsApp.TestControl();
      // 레이 아웃 설정
      this.SuspendLayout();
      // 
      // button
      // 
      // 버튼 위치 설정(GDI 좌표계, 윈도우 왼쪽 위 상단이 0,0입니다. 한 픽셀마다 1씩 이동한다.)
      this.button.Location = new System.Drawing.Point(27, 40);
      // 버튼 안의 텍스트
      this.button.Name = &quot;TestBtn&quot;;
      // 컨트럴 크기 설정
      this.button.Size = new System.Drawing.Size(75, 23);
      // TabIndex 설정
      this.button.TabIndex = 0;
      // 컨트럴에 사용될 Text 이름 설정
      this.button.Text = &quot;TestBtn&quot;;
      // 
      // testCtl
      // 
      // 위치 설정
      this.testCtl.Location = new System.Drawing.Point(27, 12);
      // 컨트럴 이름 설정
      this.testCtl.Name = &quot;testCtl&quot;;
      // 크기 설정
      this.testCtl.Size = new System.Drawing.Size(100, 22);
      // TabIndex 설정
      this.testCtl.TabIndex = 1;
      // Text 설정(의미가 없다)
      this.testCtl.Text = &quot;Hello World&quot;;
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      // 크기 설정
      this.ClientSize = new System.Drawing.Size(253, 186);
      // 컨트럴 추가
      this.Controls.Add(this.button);
      this.Controls.Add(this.testCtl);
      // 폼 이름 설정
      this.Name = &quot;Form1&quot;;
      // 폼 상단의 Text
      this.Text = &quot;TestButton&quot;;
      // 폼 레이아웃 설정
      this.ResumeLayout(false);
    }

    // 초기에는 이게 클래스의 위쪽에 작성되어 있는데, 그렇게 중요한 함수가 아니기 때문에 아래쪽으로 옮깁니다.
    protected override void Dispose(bool disposing)
    {
      // 프로그램이 닫힐 때, 작동되는 리소스 해제.
      if (disposing &amp;&amp; (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위처럼 설정하고 실행하면 윈도우 폼에서 라벨 같은 컨트럴이 생기는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EfRTZ/btrjb3luKEj/z4QnaaNmphnLymT3pPAFz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EfRTZ/btrjb3luKEj/z4QnaaNmphnLymT3pPAFz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EfRTZ/btrjb3luKEj/z4QnaaNmphnLymT3pPAFz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEfRTZ%2Fbtrjb3luKEj%2Fz4QnaaNmphnLymT3pPAFz0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이제부터 제가 만든 컨트럴에서 button 이벤트를 받아오겠습니다.&lt;/p&gt;
&lt;p&gt;button 이벤트에서 클릭하면 텍스트 내용이 Hello world에서 Click!으로 바뀌는 내용입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;TestControl.cs&quot;&gt;using System;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  // TestControl 생성
  class TestControl : Control
  {
    // 프로퍼티 재정의
    public override string Text
    {
      // 취득은 가능하지만
      get =&amp;gt; base.Text;
      // 설정은 못함.
      set { }
    }
    // 생성자
    public TestControl()
    {
      // 초기 Text 설정
      base.Text = &quot;Hello World&quot;;
      // 초기 사이즈 설정
      base.Size = new Size(100, 22);
    }
    // 생성자에 넣으면 Form에 컨트럴이 추가되기 전이기 때문에 초기 설정 이외에는 OnCreateControl 이벤트에 넣는다.
    protected override void OnCreateControl()
    {
      base.OnCreateControl();
      // 폼 취득
      var form = FindForm();
      // 폼에서 TestBtn의 이름을 가진 버튼을 취득한다.
      var button = (from Control c in form.Controls where c is Button &amp;&amp; &quot;TestBtn&quot;.Equals(c.Name) select c).FirstOrDefault();
      // null 이 아니면
      if (button != null)
      {
        // 이벤트 추가
        button.Click += Button_Click;
      }
    }
    // 버튼 클릭 이벤트
    private void Button_Click(object sender, EventArgs e)
    {
      // 텍스트를 바꿉니다.
      base.Text = &quot;Click!&quot;;
      // OnPaint 호출
      Invalidate();
    }
    // 그리기
    protected override void OnPaint(PaintEventArgs e)
    {
      base.OnPaint(e);
      // 폼 취득
      var form = FindForm();
      // Text 그리기
      e.Graphics.DrawString(base.Text, form.Font, Brushes.Red, new PointF(0, 0));
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpm2cN/btrjdN3iVf3/7ULqQe10cS0UB1hAw42EB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpm2cN/btrjdN3iVf3/7ULqQe10cS0UB1hAw42EB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpm2cN/btrjdN3iVf3/7ULqQe10cS0UB1hAw42EB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbpm2cN%2FbtrjdN3iVf3%2F7ULqQe10cS0UB1hAw42EB0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실행을 해서 버튼을 클릭하면 TestControl의 Text가 Hello world부터 Click!으로 바뀌는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;위 소스에서 보면 Control에서 Form의 인스턴스를 취득하는 함수는 FindForm()입니다. 물론 생성자에서 컨트럴을 생성할 때, Form의 인스턴스를 파라미터로 넘기는 것도 가능하지만, Window form를 만들때 소스 코딩 규약을 지키지 않으면 Visual studio에서 디자인 모드가 에러가 발생할 수 있습니다.&lt;/p&gt;
&lt;p&gt;그래서 일반적으로는 Control에서는 Form의 인스턴스를 FindForm()함수를 이용해서 취득합니다. 그렇다면 Form의 인스턴스에서 버튼 인스턴스를 어떻게 찾을까?&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇습니다. 위에서 설정한 Control의 Name으로 찾습니다. 물론 버튼의 인스턴스를 프로퍼티로 public을 설정하면 Name으로 찾을 필요는 없지만, 실제로 윈도우 프로그램을 개발하다보면 많은 컨트럴을 사용하게 됩니다.&lt;/p&gt;
&lt;p&gt;그럼 그 모든 컨트럴을 프로퍼티로 생성하게 되면 꽤 소스가 지저분해 지겠네요. 또, FindForm으로 취득해 오는 타입은 Form 클래스 타입입니다. 그렇다면 Form1로 강제 캐스팅을 해서 형 변환도 해야 할 것입니다.&lt;/p&gt;
&lt;p&gt;위 예제에서는 Dialog 형식으로 하나의 폼(SMI:Single Document Interface)이지만 MDI(Multiple Document Interface)이라면 많은 Form을 사용하게 됩니다. &lt;/p&gt;
&lt;p&gt;다중 Form 환경에서 TestControl를 사용한다고 생각하면 강제 캐스팅을 하게 되면 충분히 에러가 발생할 여지가 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇기 때문에 컨트럴에서 폼의 인스턴스를 취득하는 방법은 FindForm()함수를 통해서, 다른 컨트럴을 찾는 방법에 대해서는 Name을 이용해서 찾는 방법이 안전합니다.&lt;/p&gt;
&lt;p&gt;그래서 컨트럴에서 Name을 설정하는 것은 컨트럴 식별을 위해 꽤 중요한 작업입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#의 원도우 폼(Window form)에서 컨트럴(Control) 다루기에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/177</guid>
      <comments>https://nowonbun.tistory.com/177#entry177comment</comments>
      <pubDate>Fri, 29 Oct 2021 13:33:52 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 2-4. 데코레이터 패턴 (Decorator pattern)</title>
      <link>https://nowonbun.tistory.com/446</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 데코레이터 패턴(Decorator pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;데코레이터의 영어 뜻은 장식하다, 꾸미다라는 뜻입니다. 그런 의미로 데코레이터 패턴은 인터페이스에서 상속받은 클래스들의 기능을 확장하기 위한 패턴이라고 할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m7jVe/btri5SKYYab/HOQvuJnCReUZdBSnwvbIdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m7jVe/btri5SKYYab/HOQvuJnCReUZdBSnwvbIdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m7jVe/btri5SKYYab/HOQvuJnCReUZdBSnwvbIdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm7jVe%2Fbtri5SKYYab%2FHOQvuJnCReUZdBSnwvbIdk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Decorator_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Decorator_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 데코레이터 패턴 예제&quot;&gt;#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;ctime&amp;gt;
using namespace std;
// INode 인터페이스
class INode {
public:
  // 함수 추상화
  virtual void print() = 0;
  virtual ~INode() { }
};
// INode 인터페이스를 상속받은 클래스
class Node : public INode {
public:
  // 함수 재정의
  virtual void print() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;Node print()&quot; &amp;lt;&amp;lt; endl;
  }
};
// INode에 대한 데코레이터 추상 클래스, INode를 상속
class NodeDecorator : public INode {
private:
  // 데코레이터 맴버 변수
  INode* node;
protected:
  // 데코레이터 맴버 변수 취득
  INode* getNode() {
    // 맴버 변수 리턴
    return this-&amp;gt;node;
  }
public:
  // 생성자
  NodeDecorator(INode* node) {
    // 맴버 변수 설정
    this-&amp;gt;node = node;
  }
  // 소멸자
  ~NodeDecorator() {
    // 맴버 변수 메모리 삭제
    delete this-&amp;gt;node;
  }
};
// NodeDecorator 테코레이터 추상 클래스 상속, 시간을 추가
class NodeTimeDecorator : public NodeDecorator {
public:
  // 생성자 설정
  NodeTimeDecorator(INode* node) : NodeDecorator(node) {

  }
  // 기본적으로 INode를 상속받기에 print 함수를 재정의, 기존 node-&amp;gt;print 결과에 시간을 추가
  void print() {
    // 현재 시간 취득
    time_t t = time(nullptr); 
    // tm 클래스로 변환
    tm* now = localtime(&amp;t);
    // 현재 시간 콘솔에 출력
    cout &amp;lt;&amp;lt; (now-&amp;gt;tm_year + 1900) &amp;lt;&amp;lt; '-' &amp;lt;&amp;lt; (now-&amp;gt;tm_mon + 1) &amp;lt;&amp;lt; '-' &amp;lt;&amp;lt; now-&amp;gt;tm_mday &amp;lt;&amp;lt; &quot;  &quot;;
    // 데코레이터 맴버 변수 print 함수 출력
    NodeDecorator::getNode()-&amp;gt;print();
  }
};
// NodeDecorator 테코레이터 추상 클래스 상속, 로그 시작과 끝을 추가
class NodeLogDecorator : public NodeDecorator {
public:
  // 생성자 설정
  NodeLogDecorator(INode* node) : NodeDecorator(node) {

  }
  // 기본적으로 INode를 상속받기에 print 함수를 재정의, 기존 node-&amp;gt;print 결과에 전행, 후행에 log를 추가
  void print() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;*****start log******&quot; &amp;lt;&amp;lt; endl;
    // 데코레이터 맴버 변수 print 함수 출력
    NodeDecorator::getNode()-&amp;gt;print();
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;*****end log******&quot; &amp;lt;&amp;lt; endl;
  }
};
// 실행 함수
int main() {
  // Node 인스턴스 생성
  INode* node = new Node();
  // NodeTimeDecorator를 추가하고 node 변수의 인스턴스 주소 변경
  node = new NodeTimeDecorator(node);
  // NodeLogDecorator를 추가하고 node 변수의 인스턴스 주소 변경
  node = new NodeLogDecorator(node);
  // node 인스턴스의 print 함수 호출
  node-&amp;gt;print();
  // 메모리 삭제
  delete node;

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6GSAh/btri6697ziE/rOhKKlUfxgk3DzrlNF1oXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6GSAh/btri6697ziE/rOhKKlUfxgk3DzrlNF1oXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6GSAh/btri6697ziE/rOhKKlUfxgk3DzrlNF1oXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6GSAh%2Fbtri6697ziE%2FrOhKKlUfxgk3DzrlNF1oXk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제에서 처음의 Node 클래스에서는 단순한 Node-&amp;gt;print()의 출력이 있을 뿐입니다.&lt;/p&gt;
&lt;p&gt;그런데 NodeTimeDecorator의 데코레이터을 추가하고 거기에 NodeLogDecorator의 데코레이터를 추가했습니다.&lt;/p&gt;
&lt;p&gt;결과는 NodeLogDecorator의 print가 호출이 되고 NodeTimeDecorator의 print가 호출이 되고 최종적으로 Node 클래스의 print가 호출이 되었습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 데코레이터 패턴 예제&quot;&gt;// INode 인터페이스
interface INode {
  // 추상 함수
  String output();
}


// INode 인터페이스를 상속받은 Node 클래스
class Node implements INode {
  // 함수 재정의
  public String output() {
    // 값 리턴
    return &quot;Node&quot;;
  }
}

// INode 인터페이스를 상속받은 데코레이터 추상 클래스
abstract class ANodeDecorator implements INode {
  // 맴버 변수
  private INode node;
  // 생성자
  public ANodeDecorator(INode node) {
    // 맴버 변수 설정
    this.node = node;
  }
  // 맴버 변수 리턴
  protected INode getNode() {
    // 리턴
    return this.node;
  }
}
// ANodeDecorator 데코레이터 추상 클래스를 상속
class CheckANodeDecorator extends ANodeDecorator implements INode {
  // 생성자에서 INode의 인스턴스 설정
  public CheckANodeDecorator(INode node) {
    super(node);
  }
  // 함수 재정의
  public String output() {
    // 값 리턴 - 뒤에 Decorator 라는 문자열을 추가한다.
    return super.getNode().output() + &quot; Decorator&quot;;
  }
}

public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Node 인스턴스 생성
    INode node = new Node();
    // 반복문을 통해서 CheckANodeDecorator를 다섯번 생성
    for (int i = 0; i &amp;lt; 5; i++) {
      // CheckANodeDecorator 인스턴스 생성, INode 인스턴스 입력
      node = new CheckANodeDecorator(node);
    }
    // 콘솔 출력
    System.out.println(node.output());
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UEu97/btrjcAIzY8Q/dRUkJ9b2topsksrkPtGpNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UEu97/btrjcAIzY8Q/dRUkJ9b2topsksrkPtGpNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UEu97/btrjcAIzY8Q/dRUkJ9b2topsksrkPtGpNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUEu97%2FbtrjcAIzY8Q%2FdRUkJ9b2topsksrkPtGpNk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;데코레이터 구조는 추상 클래스에서 인터페이스를 상속받고 생성자에서 상속받은 인터페이스의 인스턴스를 받습니다.&lt;/p&gt;
&lt;p&gt;그리고 데코레이터 추상 클래스를 상속받은 클래스에서는 인터페이스의 정의를 따라 작성하면서 맴버 변수로 있는 상속받은 인터페이스의 인스턴스를 실행합니다.&lt;/p&gt;
&lt;p&gt;그리면 데코레이터 클래스는 INode를 상속받은 모든 인스턴스의 추상화된 함수에 데이터를 추가할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 데코레이터 패턴 예제&quot;&gt;using System;
// INode 인터페이스
interface INode
{
  // 추상 함수
  int output();
}

// INode 인터페이스를 상속받은 Node 클래스
class Node : INode
{
  // 맴버 변수
  private int data;
  // 생성자
  public Node(int data)
  {
    // 맴버 변수 설정
    this.data = data;
  }
  // 함수 재정의
  public int output()
  {
    // 값 리턴
    return this.data;
  }
  // ToString 재정의
  public override String ToString()
  {
    // output의 값을 출력
    return this.output().ToString();
  }
}

// INode 인터페이스를 상속받은 데코레이터 추상 클래스
abstract class ANodeDecorator : INode
{
  // 맴버 변수
  private INode node;
  // 생성자
  public ANodeDecorator(INode node)
  {
    // 맴버 변수 설정
    this.node = node;
  }
  // 맴버 변수 리턴
  protected INode getNode()
  {
    // 리턴
    return this.node;
  }
  // ToString 재정의
  public override String ToString()
  {
    // output의 값을 출력
    return this.output().ToString();
  }
  // 인터페이스의 output 함수 추상화
  public abstract int output();

}
// ANodeDecorator 데코레이터 추상 클래스를 상속
class MultiplyDecorator : ANodeDecorator, INode
{
  // 생성자에서 INode의 인스턴스 설정
  public MultiplyDecorator(INode node) : base(node) { }
  // 함수 재정의
  public override int output()
  {
    // 값 리턴 - 10의 값을 곱한다.
    return base.getNode().output() * 10;
  }
}
// ANodeDecorator 데코레이터 추상 클래스를 상속
class DivisionDecorator : ANodeDecorator, INode
{
  // 생성자에서 INode의 인스턴스 설정
  public DivisionDecorator(INode node) : base(node) { }
  // 함수 재정의
  public override int output()
  {
    // 값 리턴 - 10의 값을 나눈다.
    return base.getNode().output() / 10;
  }
}

class Program
{
  // 열거형 타입
  enum CalType
  {
    Multiply,
    Division
  }
  // 팩토리 메서드 패턴
  static INode GetNodeFactory(int data, CalType? calType = null)
  {
    // Node 인스턴스 생성
    INode node = new Node(data);
    // 파라미터에서 CalType.Multiply이 오면
    if (calType == CalType.Multiply)
    {
      // MultiplyDecorator 데코레이터로 인스턴스 생성
      node = new MultiplyDecorator(node);
    }
    // 파라미터에서 CalType.Division이 오면
    else if (calType == CalType.Division)
    {
      // MultiplyDecorator 데코레이터로 인스턴스 생성
      node = new DivisionDecorator(node);
    }
    // 인스턴스 리턴
    return node;
  }
  // 실행 함수
  static void Main(string[] args)
  {
    // 열거형 파라미터가 없이 팩토리 메서드 함수를 호출하여 INode의 값을 취득, ToString으로 콘솔 출력
    Console.WriteLine(GetNodeFactory(100));
    // 열거형 파라미터가 CalType.Multiply로 팩토리 메서드 함수를 호출하여 INode의 값을 취득, ToString으로 콘솔 출력 (MultiplyDecorator 데코레이터 인스턴스)
    Console.WriteLine(GetNodeFactory(100, CalType.Multiply));
    // 열거형 파라미터가 CalType.Division로 팩토리 메서드 함수를 호출하여 INode의 값을 취득, ToString으로 콘솔 출력 (DivisionDecorator 데코레이터 인스턴스)
    Console.WriteLine(GetNodeFactory(100, CalType.Division));

    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dU9Wpm/btrjcABNLYP/aEzkqCEet0qvA0YgWvoyJ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dU9Wpm/btrjcABNLYP/aEzkqCEet0qvA0YgWvoyJ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dU9Wpm/btrjcABNLYP/aEzkqCEet0qvA0YgWvoyJ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdU9Wpm%2FbtrjcABNLYP%2FaEzkqCEet0qvA0YgWvoyJ1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 데코레이터 패턴에 팩토리 메서드 패턴을 추가한 것입니다.&lt;/p&gt;
&lt;p&gt;GetNodeFactory 함수에서 CalType 타입의 파라미터의 결과에 따라 취득하는 인스턴스의 종료가 다르네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;개인적으로 구조 패턴 중에서 파사드 패턴(Facade pattern) 다음으로 가장 많이 사용되는 패턴이지 않을까 싶네요. 예를 들면 프레임 워크나 .Net Framework에서 제공하는 기본 클래스를 사양에 맞게 바꾸는 경우가 많은데 그럴 경우 사용하면 프로그램이 굉장히 편해지는 것을 느낄 수 있습니다.&lt;/p&gt;
&lt;p&gt;특히나 로그 처리 클래스나 기타 데이터베이스 데이터 처리 클래스에서 많이 사용하는 듯 싶네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 데코레이터 패턴(Decorator pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/446</guid>
      <comments>https://nowonbun.tistory.com/446#entry446comment</comments>
      <pubDate>Thu, 28 Oct 2021 16:16:11 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 57. 윈도우 폼(Window form)을 작성하는 방법, 그리고 윈도우 메시지와 큐</title>
      <link>https://nowonbun.tistory.com/174</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 윈도우 폼(Window form)을 작성하는 방법, 그리고 윈도우 메시지와 큐에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;C#이란 언어는 MS사에서 개발한 언어로써 Window OS에 특화되어 있습니다. 즉, C# 언어로 우리가 사용하는 윈도우 환경에서 윈도우 프로그램을 개발할 수 있습니다.&lt;/p&gt;
&lt;p&gt;Java나 그밖에 python등을 통해서도 윈도우 프로그램을 못 만드는 것은 아니지만, 아무래도 윈도우가 MS사의 제품이기 때문에 C#에서 Window API를 가져다 쓰는게 더 쉽습니다.&lt;/p&gt;
&lt;p&gt;사실 C# 언어를 배우는 것이 거의 윈도우 프로그램을 만들기 위한 것이라고 해도 틀리지 않습니다. 요즘 게임 엔진으로 많이 사용되는 Unity도 기본적으로 사용되는 언어는 C#입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 간단하게 윈도우 폼을 작성해 보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;Visual studio를 켜서 프로젝트 작성를 실행합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crgWYO/btri7QxTW9q/jKUpKZKUaElgq8NS5EsTC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crgWYO/btri7QxTW9q/jKUpKZKUaElgq8NS5EsTC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crgWYO/btri7QxTW9q/jKUpKZKUaElgq8NS5EsTC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcrgWYO%2Fbtri7QxTW9q%2FjKUpKZKUaElgq8NS5EsTC0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그럼 Window Forms App 항목이 두개가 나옵니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqnMVI/btri67zQ1Sk/3lW6pNkIasnMsJHJ6Nrxjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqnMVI/btri67zQ1Sk/3lW6pNkIasnMsJHJ6Nrxjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqnMVI/btri67zQ1Sk/3lW6pNkIasnMsJHJ6Nrxjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqnMVI%2Fbtri67zQ1Sk%2F3lW6pNkIasnMsJHJ6Nrxjk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;하나는 Core 용이고, 하나는 .Net framework 용입니다. Core을 사용해도 상관은 없습니다만, Window form은 기본적으로 Window에서 실행하는 프로그램인데 굳이, Core를 선택할 필요는 없습니다.&lt;/p&gt;
&lt;p&gt;최근에 사용하는 Window 10에는 기본적으로 .Net framework가 설치 되어 있으니깐요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 프로젝트 명을 입력하고 프로젝트 작성을 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctfiYQ/btri7RDy8ob/iwuoESSw359jctqVDGpZTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctfiYQ/btri7RDy8ob/iwuoESSw359jctqVDGpZTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctfiYQ/btri7RDy8ob/iwuoESSw359jctqVDGpZTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctfiYQ%2Fbtri7RDy8ob%2FiwuoESSw359jctqVDGpZTK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그럼 Visual studio에 기본적으로 Window form이 생성되어 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnYCfX/btri6nXhMzn/vvgolaFpGGAQ0vQVW26H20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnYCfX/btri6nXhMzn/vvgolaFpGGAQ0vQVW26H20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnYCfX/btri6nXhMzn/vvgolaFpGGAQ0vQVW26H20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnYCfX%2Fbtri6nXhMzn%2FvvgolaFpGGAQ0vQVW26H20%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그대로 F5(Starting debugging)을 누르면 윈도우에 윈도우 프로그램이 실행되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lS45e/btri7RjgBMa/yMvFmaqv1I8ao0SbeWmcEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lS45e/btri7RjgBMa/yMvFmaqv1I8ao0SbeWmcEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lS45e/btri7RjgBMa/yMvFmaqv1I8ao0SbeWmcEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlS45e%2Fbtri7RjgBMa%2FyMvFmaqv1I8ao0SbeWmcEK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;사실 여기까지만 해도 윈도우 프로그램 하나를 만든 것과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 우리가 윈도우 폼에 버튼을 하나 추가 해보도록 합시다.&lt;/p&gt;
&lt;p&gt;Visual studio에 옆을 보면 Toolbox가 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/71xdK/btri3Vm7CcM/lUGhQKrgL2lSl2I8tDWx7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/71xdK/btri3Vm7CcM/lUGhQKrgL2lSl2I8tDWx7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/71xdK/btri3Vm7CcM/lUGhQKrgL2lSl2I8tDWx7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F71xdK%2Fbtri3Vm7CcM%2FlUGhQKrgL2lSl2I8tDWx7k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;거기서 Button을 찾아서 폼으로 마우스로 끌어다가 놓습니다.(Drag-and-drop)&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wKBXf/btri5IgkaWz/UZt53axxwLqbLByN4OL3K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wKBXf/btri5IgkaWz/UZt53axxwLqbLByN4OL3K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wKBXf/btri5IgkaWz/UZt53axxwLqbLByN4OL3K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwKBXf%2Fbtri5IgkaWz%2FUZt53axxwLqbLByN4OL3K0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그럼 폼에 버튼이 생긴 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 버튼을 더블 클릭을 합니다.&lt;/p&gt;
&lt;p&gt;그러면 소스 화면으로 바뀌는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1futT/btri66VhHAg/Dt5HnmgFo5lnHmciOG47Uk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1futT/btri66VhHAg/Dt5HnmgFo5lnHmciOG47Uk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1futT/btri66VhHAg/Dt5HnmgFo5lnHmciOG47Uk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1futT%2Fbtri66VhHAg%2FDt5HnmgFo5lnHmciOG47Uk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;무언지는 모르겠지만 button1_Click이라는 함수가 있네요. 여기에 클릭을 할 때 동작하는 소스을 넣는 것 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  public partial class Form1 : Form
  {
    // 폼 생성자
    public Form1()
    {
      InitializeComponent();
    }
    // 버튼 클릭 이벤트
    private void button1_Click(object sender, EventArgs e)
    {
      // 메시지 박스
      MessageBox.Show(&quot;Hello world&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;button1_Click 함수 안에 MessageBox.Show(&quot;Hello world&quot;)를 입력하고 다시 F5(Starting debugging)를 누르고 디버깅을 합시다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;실행을 하면 아까처럼 윈도우 프로그램이 생성이 되고 그 안에 버튼이 있는 것을 확인할 수 있습니다. 버튼을 클릭하면 메시지 박스에 Hello world라는 문자가 보이는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPLTTR/btri6nCZl8I/9KtRikXZU4JdLwDlmr6L0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPLTTR/btri6nCZl8I/9KtRikXZU4JdLwDlmr6L0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPLTTR/btri6nCZl8I/9KtRikXZU4JdLwDlmr6L0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPLTTR%2Fbtri6nCZl8I%2F9KtRikXZU4JdLwDlmr6L0k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여기까지가 기본적인 윈도우 프로그램 생성 흐름입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;소스에 대해서 조금 자세히 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;옆의 탐색기를 보면 Form1.cs파일이 있고, Form1.Desiner.cs파일과 Form1.resx 파일이 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 Program.cs 파일이 있네요.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/708vg/btri1duPOp0/mp9M0xOkoY2SmWabL4akCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/708vg/btri1duPOp0/mp9M0xOkoY2SmWabL4akCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/708vg/btri1duPOp0/mp9M0xOkoY2SmWabL4akCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F708vg%2Fbtri1duPOp0%2Fmp9M0xOkoY2SmWabL4akCk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;먼저 Program.cs 파일을 열어보면 아래와 같은 소스가 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  static class Program
  {
    // 단일 스레드 아파트 먼트(Single-threaded apartment) 어노테이션 설정
    [STAThread]
    // 실행함수
    static void Main()
    {
      // 이건 Visual 스타일의 설정하는 것, 즉 설정하지 않으면 조금 옛날 분위기의 윈도우 프로그램이 생성
      Application.EnableVisualStyles();
      // 이건 Text 랜더링에 대한 설정인데, 문자에 대한 간격 설정과 기타 등등의 설정, Default는 false로 설정
      Application.SetCompatibleTextRenderingDefault(false);
      // 윈도우 프로그램의 메시지를 돌리는 함수
      Application.Run(new Form1());
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기본적으로 위 형태의 소스 구조는 Console때 사용하던 구조와 같습니다. 즉, 프로그램이 Main 함수에서 실행하는 것입니다.&lt;/p&gt;
&lt;p&gt;다시 말해서, 콘솔 프로젝트에서 System.Windows.Forms 라이브러리를 연결하고 위와 똑같은 소스를 작성하면 똑같이 윈도우 프로그램이 실행된다는 뜻입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDNthb/btri5k0749A/LYh0HxQ69XvHNHf79GMx91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDNthb/btri5k0749A/LYh0HxQ69XvHNHf79GMx91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDNthb/btri5k0749A/LYh0HxQ69XvHNHf79GMx91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDNthb%2Fbtri5k0749A%2FLYh0HxQ69XvHNHf79GMx91%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boqRn0/btri1dPaJGJ/2R4y3Lb0SkgVmmfd4Zpxu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boqRn0/btri1dPaJGJ/2R4y3Lb0SkgVmmfd4Zpxu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boqRn0/btri1dPaJGJ/2R4y3Lb0SkgVmmfd4Zpxu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboqRn0%2Fbtri1dPaJGJ%2F2R4y3Lb0SkgVmmfd4Zpxu1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여기서 다시 생각나는 궁금증이 콘솔 창이 없는데? 라고 생각할 수 있는데, 탐색기에서 프로젝트를 오른쪽 마우스 클릭하면 Properties라는 메뉴가 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kPubJ/btri1cpc1Wn/7J9RWNIXUKav4T4rDxuxik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kPubJ/btri1cpc1Wn/7J9RWNIXUKav4T4rDxuxik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kPubJ/btri1cpc1Wn/7J9RWNIXUKav4T4rDxuxik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkPubJ%2Fbtri1cpc1Wn%2F7J9RWNIXUKav4T4rDxuxik%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;클릭하면 설정 화면이 나오는데, 거기서 Output type이 Windows application으로 되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;일단 배포할 때는 Windows application으로 변경해야 하지만 우리는 일단 개발을 해야 하니 Console Application으로 바꿉시다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kVLk0/btriZrAk8Lk/6qUXsAzTGgKKAW2aSBB0bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kVLk0/btriZrAk8Lk/6qUXsAzTGgKKAW2aSBB0bK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kVLk0/btriZrAk8Lk/6qUXsAzTGgKKAW2aSBB0bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkVLk0%2FbtriZrAk8Lk%2F6qUXsAzTGgKKAW2aSBB0bK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;다시 F5(Starting debugging)를 누르고 디버깅을 하면 Console 창이 나오는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sW5tX/btri4Xyewpi/ewvRUcnCQx1HVutrKKw95k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sW5tX/btri4Xyewpi/ewvRUcnCQx1HVutrKKw95k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sW5tX/btri4Xyewpi/ewvRUcnCQx1HVutrKKw95k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsW5tX%2Fbtri4Xyewpi%2FewvRUcnCQx1HVutrKKw95k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;다시 돌아와서 Program.cs 파일에서는 new Form1()으로 인스턴스를 생성해서 Application.Run으로 실행하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;이 Application.Run은 후반부에 다시 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;new Form1으로 인스턴스가 생성되었으니 Form1.cs를 확인해야 겠네요.&lt;/p&gt;
&lt;p&gt;그런데 아마 Form1.cs파일을 클릭하면 디자인 창이 나올 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 오른쪽 마우스로 View Code로 소스를 봅시다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qVZKJ/btri6oIEUhH/Cj6wOqnS6wRGmTKTqY4Bhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qVZKJ/btri6oIEUhH/Cj6wOqnS6wRGmTKTqY4Bhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qVZKJ/btri6oIEUhH/Cj6wOqnS6wRGmTKTqY4Bhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqVZKJ%2Fbtri6oIEUhH%2FCj6wOqnS6wRGmTKTqY4Bhk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;아까 우리가 버튼을 클릭하면 실행하는 소스가 있네요. 확실히 구조는 Form1의 클래스이고 Form 클래스를 상속받았습니다.&lt;/p&gt;
&lt;p&gt;생성자에서는 InitializeComponent함수가 실행되고 밑에는 뜬금없는 함수가 있어서 클릭하면 실행을 하네요. 이것만 보면 사실 프로그램이 이해가 안됩니다. 모든게 자동으로 처리되나? 프로그램은 자동이 없습니다. 다 이유가 있는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 Form1.Desiner.cs 파일을 봅시다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.Designer.cs&quot;&gt;namespace WindowsFormsApp
{
  partial class Form1
  {
    /// &amp;lt;summary&amp;gt;
    /// Required designer variable.
    /// &amp;lt;/summary&amp;gt;
    private System.ComponentModel.IContainer components = null;

    /// &amp;lt;summary&amp;gt;
    /// Clean up any resources being used.
    /// &amp;lt;/summary&amp;gt;
    /// &amp;lt;param name=&quot;disposing&quot;&amp;gt;true if managed resources should be disposed; otherwise, false.&amp;lt;/param&amp;gt;
    // 폼이 사라질 때 발생하는 함수(재정의 함수)
    protected override void Dispose(bool disposing)
    {
      if (disposing &amp;&amp; (components != null))
      {
        components.Dispose();
      }
      base.Dispose(disposing);
    }

    #region Windows Form Designer generated code

    /// &amp;lt;summary&amp;gt;
    /// Required method for Designer support - do not modify
    /// the contents of this method with the code editor.
    /// &amp;lt;/summary&amp;gt;
    // 생성자에서 호출된 함수
    private void InitializeComponent()
    {
      // 버튼 인스턴스 생성
      this.button1 = new System.Windows.Forms.Button();
      // 폼 레이아웃 설정
      this.SuspendLayout();
      // 
      // button1
      // 
      // 버튼 위치 설정
      this.button1.Location = new System.Drawing.Point(24, 26);
      // 버튼 이름
      this.button1.Name = &quot;button1&quot;;
      // 버튼 크기
      this.button1.Size = new System.Drawing.Size(75, 23);
      // 버튼 탭 인덱스
      this.button1.TabIndex = 0;
      // 버튼 안의 Text
      this.button1.Text = &quot;button1&quot;;
      // 버튼 색상 설정(기본 윈도우 색상)
      this.button1.UseVisualStyleBackColor = true;
      // 이벤트 추가
      this.button1.Click += new System.EventHandler(this.button1_Click);
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      // 폼 크기 설정
      this.ClientSize = new System.Drawing.Size(253, 186);
      // 폼에 컨트럴 추가
      this.Controls.Add(this.button1);
      // 폼 이름
      this.Name = &quot;Form1&quot;;
      // 폼 상단의 Text
      this.Text = &quot;Form1&quot;;
      // 폼 레이아웃 설정
      this.ResumeLayout(false);
    }
    #endregion
    // button1 맴버 변수
    private System.Windows.Forms.Button button1;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Form1 클래스가 partial로 분할이 되어 있습니다.&lt;/p&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://nowonbun.tistory.com/129&quot; target=&quot;_blank&quot;&gt;[C#] 54. namespace와 using 그리고 partial의 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;그리고 button1 클래스가 맴버 변수로 있고 InitializeComponent 함수 안에서 인스턴스 생성되네요.&lt;/p&gt;
&lt;p&gt;그리고 여러가지 복잡한 설정이 있는데 무시하고 this.button1.Click 이란 함수를 보니, 이벤트를 추가하는 함수였네요.&lt;/p&gt;
&lt;p&gt;즉, Form1.cs파일에 있는 함수는 button에서 클릭을 누르면 발생하는 이벤트 함수였던 것입니다.&lt;/p&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://nowonbun.tistory.com/120&quot; target=&quot;_blank&quot;&gt;[C#] 24. 이벤트(event) 키워드 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그러면 Window의 속성을 변경하려면 Form.Designer.cs에서 수정을 해야 할까?&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CBvHf/btri5lS4OGw/nCjLKtdhbhWXBUiQF0X3l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CBvHf/btri5lS4OGw/nCjLKtdhbhWXBUiQF0X3l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CBvHf/btri5lS4OGw/nCjLKtdhbhWXBUiQF0X3l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCBvHf%2Fbtri5lS4OGw%2FnCjLKtdhbhWXBUiQF0X3l0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Visual studio는 고맙게도 디자인 화면에서 오른쪽 하단의 프로퍼티 설정을 보시면 속성을 설정할 수 있는 항목이 있습니다. 친절하게 하단에 속성 설명까지 나와 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;대충 Form의 구조는 알겠네요. 일단 Form1은 Form 클래스를 상속받았습니다.&lt;/p&gt;
&lt;p&gt;즉, Form에서 기본적으로 윈도우에 폼을 그리고 여러가지 설정을 하는데, 기타 우리가 설정을 바꾸어야 하는 부분을 상속받아서 재정의를 하는 형식으로 프로그램이 구성되어 있네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그러면 Window란 어떤 방식으로 프로그램이 움직일까 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;Window는 기본적으로 스레드 환경에서 무한 루프 중입니다. 가장 비교하기 쉬운 예제가 플립북이 있습니다.&lt;/p&gt;
&lt;p&gt;플립북이란 우리가 어렸을 때, 교과서에 첫장에 그림을 그리고 두번째 장에 조금 움직이는 그림을 그리고, 세번째 장에 더 움직이는 그림을 그리고해서 마지막에 책장을 넘기면 마치 그림이 움직이는 것처럼 보이는 것이 있습니다.&lt;/p&gt;
&lt;p&gt;윈도우도 마찬가지고 스레드로 연속적으로 화면에 그리는 행위입니다. 그게 1초에 20장이 그려지면 20프레임(20FPS) 40장이 그려지면 40프레임(FPS)으로 표현하는 것입니다. 윈도우 폼은 우리가 FPS를 설정할 수는 없지만, 그런 식으로 그려지는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;계속적으로 그려지는 것은 알겠는데, 그럼 버튼 클릭의 이벤트나 처리는 다른 스레드에서 실행되는 것인가? 아닙니다.&lt;/p&gt;
&lt;p&gt;여러 스레드를 사용하게 되면 동기화 문제가 발생합니다. 각 스레드에서 어느 지점에서 서로 데이터를 주고 받아야 하는지에 대한 문제입니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/427&quot; target=&quot;_blank&quot;&gt;[C#] 38. lock 키워드와 deadlock(교착 상태)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;동기화 문제가 발생하기 때문에 기본적으로 단일 스레드로 무한 루프를 돌고 있습니다.&lt;/p&gt;
&lt;p&gt;예로 button1_Click에서 MessageBox.Show()를 사용했는데 이번에는 for문을 0에서 1000까지 Thread.Sleep를 주고 실행해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  public partial class Form1 : Form
  {
    // 폼 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
    }
    // 버튼 클릭 이벤트
    private void button1_Click(object sender, EventArgs e)
    {
      // 반복문
      for(var i = 0; i &lt; 1000; i++)
      {
        // 콘솔 출력
        Console.WriteLine(i);
        // 1초 대기
        System.Threading.Thread.Sleep(1000);
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7Xvt4/btriYWG0mgg/KoKY53JhgAVaR4iyyrIr9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7Xvt4/btriYWG0mgg/KoKY53JhgAVaR4iyyrIr9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7Xvt4/btriYWG0mgg/KoKY53JhgAVaR4iyyrIr9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7Xvt4%2FbtriYWG0mgg%2FKoKY53JhgAVaR4iyyrIr9k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;프로그램을 실행해서 버튼을 눌러보면 뒤에 Console에서 i값이 출력되는 것을 확인할 수 있습니다. 그런데 윈도우를 조작하려면 멈춰 있습니다. 조금 시간이 지나면 응답 없음으로 바뀝니다.&lt;/p&gt;
&lt;p&gt;즉, 단일 스레드라서 함수 등에서 위와 같이 인터럽트가 걸리면 프로그램이 멈춰버립니다. 그렇다면 윈도우 프로그램에서는 시간이 걸리는 작업이 안되는 것인가? 그건 Window 스레드를 이용하면 되는 데, 그건 다른 글에서 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다시 돌아와서 윈도우는 단일 스레드의 무한 루프에서 돈다. 그럼 위와 같은 클릭이나 이벤트 등은 어떻게 처리가 되는 것인가 했을 때, 윈도우 메시지 큐가 있습니다. (큐 알고리즘인 FILO 구조입니다.)&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsKbfc/btri67GD15A/eSbsejwSQ0d57qa9Yp3rf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsKbfc/btri67GD15A/eSbsejwSQ0d57qa9Yp3rf1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsKbfc/btri67GD15A/eSbsejwSQ0d57qa9Yp3rf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsKbfc%2Fbtri67GD15A%2FeSbsejwSQ0d57qa9Yp3rf1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://www.researchgate.net/figure/Window-based-messaging-process-The-function-prototype-of-PostThreadMessage-is-BOOL_fig3_339679677&quot; target=&quot;_blank&quot;&gt;https://www.researchgate.net&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;설명이 어려운데 그러니깐 위처럼 윈도우가 돌고 있습니다. 시스템에 윈도우 생성!하면 시스템에서 윈도우로 메시지를 보냅니다. 예를 들면 그려라, 무슨 함수를 실행해라 이렇게 말입니다.&lt;/p&gt;
&lt;p&gt;그럼 우리 프로그램에서는 메시지를 받고 어느 위치에 이 윈도우는 있습니다. 하고 알려주면 시스템에서 모니터를 통해 윈도우를 그리는 것입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Form1.cs&quot;&gt;using System;
using System.Windows.Forms;

namespace WindowsFormsApp
{
  public partial class Form1 : Form
  {
    // 폼 생성자
    public Form1()
    {
      // 초기화
      InitializeComponent();
    }
    // 버튼 클릭 이벤트
    private void button1_Click(object sender, EventArgs e) { }
    // 메시지 큐 함수의 재정의
    protected override void WndProc(ref Message m)
    {
      base.WndProc(ref m);
      // 콘솔 출력
      Console.WriteLine(&quot;0x{0:X4}&quot;, m.Msg);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dKXRsK/btri67NqObU/7OtbKdo36kYuXKoNvm5AW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dKXRsK/btri67NqObU/7OtbKdo36kYuXKoNvm5AW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dKXRsK/btri67NqObU/7OtbKdo36kYuXKoNvm5AW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdKXRsK%2Fbtri67NqObU%2F7OtbKdo36kYuXKoNvm5AW1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;WndProc라는 함수를 Form 클래스에서 재정의한 후 Console에 메시지를 출력하면 무슨 데이터가 끊임없이 출력되는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;이게 메시지 번호인데 사실 C#에서는 메시지 큐를 직접적으로 컨트럴할 일은 없습니다.&lt;/p&gt;
&lt;p&gt;그래서 이 식별자에 대한 값이 C#에는 정의되지 않았는데 C++(MFC)에는 정의되어 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bakxdf/btri0cppQr3/2w5TB8rjt0X1LtHrXp2sD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bakxdf/btri0cppQr3/2w5TB8rjt0X1LtHrXp2sD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bakxdf/btri0cppQr3/2w5TB8rjt0X1LtHrXp2sD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbakxdf%2Fbtri0cppQr3%2F2w5TB8rjt0X1LtHrXp2sD1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 식별 코드로 시스템에서 윈도우 폼으로 끊임없이 메시지를 보내면 윈도우에서는 그 메시지를 받고 처리를 하는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;사실 C#에서는 WndProc까지 재정의해서 사용할 일은 없습니다.&lt;/p&gt;
&lt;p&gt;저도 실무로는 Window 프로그램 프로젝트 한번 해봤는데... 그 때는 제가 C#를 자세히 몰라서 WndProc에 덕지덕지 붙여서 개발했던 게 기억이 나네요. C#에서는 거의 모든 메시지가 이벤트 혹은 virtual로 재정의할 수 있게끔 클래스가 만들어져 있습니다.&lt;/p&gt;
&lt;p&gt;개인적인 경험에 의해, C++(MFC)는 그런 기능이 없습니다. 그래서 이벤트나 특정 처리를 다 message에서 처리했던 게 기억이 납니다. Window message는 하나의 프로그램에서만 사용되는 게 아니라 시스템 전체에서 운영이 되는 것이기 때문에 간혹 message로 인스턴스를 넘기고 데이터를 넘기면, 받는 쪽에서 제대로 데이터를 받지를 못하고 객체를 찾아다녔던 게 기억이 많이 남네요.&lt;/p&gt;
&lt;p&gt;어쩔 때는 다른 프로그램으로 데이터가 잘 못 날라가서 프로그램이 이상해 질 때도 있었습니다. 참고로 예전에 스타크래프트나 디아블로 게임 맵핵이 다 이런 Window message를 이용해서 프로그램 내의 값을 변조하는 것입니다. 특정 값이 변하면 맵이 켜진다거나..등등..&lt;/p&gt;
&lt;p&gt;C++(MFC)에 비하면 C#은 정말 혁명적으로 윈도우 프로그램 개발이 쉬워진 것은 맞습니다. 아마 이런 부분 때문에 C++(MFC)이 C#보다 상대적으로 어렵다라고 표현하는 것 같네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 윈도우 폼(Window form)을 작성하는 방법, 그리고 윈도우 메시지와 큐에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/174</guid>
      <comments>https://nowonbun.tistory.com/174#entry174comment</comments>
      <pubDate>Wed, 27 Oct 2021 20:27:20 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 2-3. 브릿지 패턴(Bridge pattern)</title>
      <link>https://nowonbun.tistory.com/445</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 브릿지 패턴(Bridge pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;브릿지 패턴이라는 것은 개념적으로 추상층의 처리와 구현부의 처리를 독립적으로 사용할 수 있게 하는 방법입니다. 쉽게 말해서 추상층, 즉 인터페이스에서 함수에 대한 처리에 대한 정의를 하면, 상속을 받은 구현부, 즉 클래스에서는 입력받은 인스턴스에 따라 다른 결과를 만들어 내는 구조입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3AsAx/btri45v83jf/vQizPsUfa8QD3uCS2VSTsk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3AsAx/btri45v83jf/vQizPsUfa8QD3uCS2VSTsk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3AsAx/btri45v83jf/vQizPsUfa8QD3uCS2VSTsk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3AsAx%2Fbtri45v83jf%2FvQizPsUfa8QD3uCS2VSTsk%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Bridge_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Bridge_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 브릿지 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;
// INode 인터페이스
class INode {
public:
  // 추상 함수
  virtual void print() = 0;
  virtual ~INode() { }
};
// IBridge 인터페이스
class IBridge {
public:
  // 추상 함수
  virtual void exec() = 0;
  virtual ~IBridge() { }
};
// INode 인터페이스를 상속받은 Node1 클래스
class Node1 : public INode {
public:
  // 함수 재정의
  virtual void print() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;Node1 - print&quot; &amp;lt;&amp;lt; endl;
  }
};
// INode 인터페이스를 상속받은 Node2 클래스
class Node2 : public INode {
public:
  // 함수 재정의
  virtual void print() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;Node2 - print&quot; &amp;lt;&amp;lt; endl;
  }
};
// IBridge 인터페이스를 상속받은 Bridge 클래스
class Bridge : public IBridge {
private:
  // 브릿지 패턴에 사용될 맴버 변수
  INode* node;
public:
  // 생성자, INode의 인스턴스를 받는다.
  Bridge(INode* node) {
    this-&amp;gt;node = node;
  }
  // 실행
  virtual void exec() {
    // INode 인스턴스의 print 함수 실행
    this-&amp;gt;node-&amp;gt;print();
  }
  // 소멸자
  ~Bridge() {
    // 메모리 해제
    delete node;
  }
};
// 실행 함수
int main() {
  // Bridge 인스턴스 생성 (Node1 클래스의 인스턴스를 넣는다.)
  Bridge bridge1(new Node1());
  // 함수 실행
  bridge1.exec();
  // Bridge 인스턴스 생성 (Node2 클래스의 인스턴스를 넣는다.)
  Bridge bridge2(new Node2());
  // 함수 실행
  bridge2.exec();

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpAGaZ/btri5lyKF9l/6qXVssg4X63xJwJa3GYqQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpAGaZ/btri5lyKF9l/6qXVssg4X63xJwJa3GYqQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpAGaZ/btri5lyKF9l/6qXVssg4X63xJwJa3GYqQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpAGaZ%2Fbtri5lyKF9l%2F6qXVssg4X63xJwJa3GYqQk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 결과를 보면 Bridge의 클래스에 어떤 인스턴스를 넣느냐에 결과가 달라집니다. 즉, Node1 인스턴스를 넣으면 Node1이 콘솔에 출력이 되고 Node2 인스턴스를 넣으면 Node2가 콘솔에 출력이 됩니다.&lt;/p&gt;
&lt;p&gt;Bridge 클래스는 INode 클래스의 실행에 대한 정의를 구현하는 것이고 그 데이터 값에 따라 결과는 다르게 나오는 것입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 브릿지 패턴 예제&quot;&gt;// INode 인터페이스
interface INode {
  // 추상 함수
  String output();
}
// IBridge 인터페이스
interface IBridge {
  // 추상 함수
  void print();
}
// INode 인터페이스를 상속받은 Node 클래스
class Node1 implements INode {
  // 함수 재정의
  public String output() {
    // 값 리턴
    return &quot;Node1&quot;;
  }
}
// INode 인터페이스를 상속받은 Node 클래스
class Node2 implements INode {
  // 함수 재정의
  public String output() {
    // 값 리턴
    return &quot;Node2&quot;;
  }
}
// IBridge 인터페이스를 상속받은 Bridge 클래스
class Bridge implements IBridge {
  // 브릿지 패턴에 사용될 맴버 변수
  private INode node;

  // 생성자, INode의 인스턴스를 받는다.
  public Bridge(INode node) {
    this.node = node;
  }

  // 함수 재정의
  public void print() {
    // 브릿지 패턴의 맴버 변수의 output 함수의 결과 값을 콘솔 출력
    System.out.println(this.node.output());
  }
}
public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Bridge 인스턴스 생성, Node1의 인스턴스를 넣는다.
    var bridge1 = new Bridge(new Node1());
    // print 함수 실행
    bridge1.print();
    // Bridge 인스턴스 생성, Node2의 인스턴스를 넣는다.
    var bridge2 = new Bridge(new Node2());
    // print 함수 실행
    bridge2.print();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZZ097/btri5mj7mEv/q2En5VDj8t5E4YW9caAOGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZZ097/btri5mj7mEv/q2En5VDj8t5E4YW9caAOGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZZ097/btri5mj7mEv/q2En5VDj8t5E4YW9caAOGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZZ097%2Fbtri5mj7mEv%2Fq2En5VDj8t5E4YW9caAOGk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;자바의 형태도 C++과 비슷한 구조입니다. 브릿지 패턴은 거의 규격화된 패턴이랄까? 다른 형식으로 응용할 만한 구조는 없네요..&lt;/p&gt;
&lt;p&gt;제가 모르는 것일 수도 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 브릿지 패턴 예제&quot;&gt;using System;
// Controller 인터페이스
public interface Controller
{
  // 추상 함수
  void Execute(Model model);
}
// Model 인터페이스
public interface Model
{
  // 추상 함수
  String GetData();
}
// MVC 형태의 프레임워크에서 Model 클래스(파라미터)
public class ParameterModel : Model
{
  // 맴버 변수
  private string data;
  // 생성자
  public ParameterModel(string data)
  {
    // 맴버 변수 데이터에 넣는다.
    this.data = data;
  }
  // 함수 재정의, 데이터 취득 함수
  public String GetData()
  {
    // data 리턴
    return this.data;
  }
}
// MVC 형태의 프레임워크에서 Controller 클래스
public class MainController : Controller
{
  // 함수 재정의, model를 받는다.
  public void Execute(Model model)
  {
    // 콘솔 출력, model의 데이터를 취득
    Console.WriteLine(&quot;Execute - &quot; + model.GetData());
  }
}

class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // Controller 인스턴스
    var controller = new MainController();
    // 웹 요청시의 파라미터 model 인스턴스
    var model = new ParameterModel(&quot;Hello world&quot;);
    // Controller 실행
    controller.Execute(model);
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bW6hDo/btri6Fjasjg/WjeXK8CokcyM1ksGfbPThK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bW6hDo/btri6Fjasjg/WjeXK8CokcyM1ksGfbPThK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bW6hDo/btri6Fjasjg/WjeXK8CokcyM1ksGfbPThK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbW6hDo%2Fbtri6Fjasjg%2FWjeXK8CokcyM1ksGfbPThK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이 브릿지 패턴은 우리가 자주 사용하는 MVC 형태의 프레임워크에서 자주 보이는 패턴입니다.&lt;/p&gt;
&lt;p&gt;Client(브라우져)로부터 요청이 오면 요청 파라미터로 Model 클래스로 인스턴스를 생성하고 Controller를 호출하여 실제 우리가 작성하는 구현부는 Controller의 Execute 함수입니다.&lt;/p&gt;
&lt;p&gt;추상 부분은 다 프레임워크에 구현이 되어 있죠.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 브릿지 패턴(Bridge pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/445</guid>
      <comments>https://nowonbun.tistory.com/445#entry445comment</comments>
      <pubDate>Wed, 27 Oct 2021 20:22:14 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 2-2. 컴포지트 패턴(Composite pattern)</title>
      <link>https://nowonbun.tistory.com/444</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 컴포지트 패턴(Composite pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;컴포지트 패턴, 일명 합성 패턴이라 불리는 패턴으로 하나의 클래스와 복합 클래스(즉, 리스트)를 동일한 구성을 하여 사용하는 방법입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eawaMN/btri7RXROi6/I7ZTzPvom70xbnlIaIZQbk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eawaMN/btri7RXROi6/I7ZTzPvom70xbnlIaIZQbk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eawaMN/btri7RXROi6/I7ZTzPvom70xbnlIaIZQbk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeawaMN%2Fbtri7RXROi6%2FI7ZTzPvom70xbnlIaIZQbk%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Composite_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Composite_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 컴포지트 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;
// INode 인터페이스
class INode {
public:
  // 추상 함수
  virtual void print() = 0;
  // 소멸자 추상화
  virtual ~INode() { }
};
// Node 클래스, INode를 상속
class Node : public INode {
public:
  // print 함수 재정의
  virtual void print() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;Node - print execute&quot; &amp;lt;&amp;lt; endl;
  }
};
// NodeComposite 클래스, INode를 재정의
class NodeComposite : public INode {
private:
  // 맴버 변수
  vector&amp;lt;INode*&amp;gt; _vector;
public:
  // INode 타입의 인스턴스를 입력 받는 함수
  void add(INode* node) {
    // vector에 추가한다.
    this-&amp;gt;_vector.push_back(node);
  }
  // print 함수 재정의
  virtual void print() {
    // vector에 저장되어 있는 인스턴스의 print 함수를 실행
    for (INode* n : this-&amp;gt;_vector) {
      // print 함수 호출
      n-&amp;gt;print();
    }
  }
  // 소멸자
  virtual ~NodeComposite() {
    // vector에 있는 인스턴스 모두 해제
    for (INode* n : this-&amp;gt;_vector) {
      delete n;
    }
  }
};

int main() {
  // NodeComposite 인스턴스 생성
  NodeComposite composite;
  // composite 인스턴스에 Node 인스턴스를 넣는다.
  composite.add(new Node());
  composite.add(new Node());
  composite.add(new Node());
  // print 함수 호출
  composite.print();

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1z9BP/btriZqO0h7m/3KGPtSS7ReZiViYbI8ZbiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1z9BP/btriZqO0h7m/3KGPtSS7ReZiViYbI8ZbiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1z9BP/btriZqO0h7m/3KGPtSS7ReZiViYbI8ZbiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1z9BP%2FbtriZqO0h7m%2F3KGPtSS7ReZiViYbI8ZbiK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;컴포지트 패턴의 기본적인 형태입니다. 공통된 인터페이스에 하나는 단일 실행에 대한 print함수를 재정의했고, 하나는 복수 객체에 대한 print함수를 재정의해서 실행했습니다.&lt;/p&gt;
&lt;p&gt;그러니깐 컴포지트 클래스는 동일한 인터페이스에 상속을 받아 리스트 형식의 맴버 변수를 만들고 add 함수를 통해 같은 인터페이스를 상속한 인스턴스를 넣고 동일한 함수명이 실행되는 형태의 패턴입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 컴포지트 패턴 예제&quot;&gt;import java.util.ArrayList;

// INode 인터페이스
interface INode {
  // 추상 함수
  void print();
}
// INode 인터페이스를 상속받은 Node 클래스
class Node implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;Node - print()&quot;);
  }
}
// INode 인터페이스를 상속받은 NodeComposite 클래스
class NodeComposite extends ArrayList&amp;lt;INode&amp;gt; implements INode {
  // 함수 재정의
  public void print() {
    // List에 담긴 인스턴스 취득
    for (var node : this) {
      // 동일한 이름의 함수를 실행
      node.print();
    }
  }
}
public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // Composite 인스턴스 생성
    var composite = new NodeComposite();
    // Node 인스턴스 생성하고 저장
    composite.add(new Node());
    composite.add(new Node());
    composite.add(new Node());
    // 컴포지트 인스턴스의 함수 실행
    composite.print();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clp4Lo/btri66Vhlfo/OpKr0pZ7OCWQAgJDSDLVWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clp4Lo/btri66Vhlfo/OpKr0pZ7OCWQAgJDSDLVWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clp4Lo/btri66Vhlfo/OpKr0pZ7OCWQAgJDSDLVWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclp4Lo%2Fbtri66Vhlfo%2FOpKr0pZ7OCWQAgJDSDLVWK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;List를 맴버 변수에 넣고 구현하지만, List를 상속받고 구현하는 것도 가능합니다.&lt;/p&gt;
&lt;p&gt;개인적으로 List를 상속받는 편이 따로 add 함수나 remove 함수를 구현할 필요가 없고 편하다고 생각합니다만, 사양에 맞춰서 맴버 함수로 List의 함수를 가리거나 어댑터 패턴으로 다른 형태로 변환할 경우가 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 컴포지트 패턴 예제&quot;&gt;using System;
using System.Collections.Generic;

// INode 인터페이스
public interface INode
{
  // 추상 함수
  void Print();
}
// INode 인터페이스를 상속받은 Node1 클래스
public class Node1 : INode
{
  // 함수 재정의
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Node1&quot;);
  }
}
// INode 인터페이스를 상속받은 Node2 클래스
public class Node2 : INode
{
  // 함수 재정의
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Node2&quot;);
  }
}

// 컴포지트 패턴은 한데 묶어야 하는 특성으로 List의 특성을 상속 받고, 인터페이스의 기능을 사용한다.
public class CompositeNode : List&amp;lt;INode&amp;gt;, INode
{
  // 함수 재정의
  public void Print()
  {
    // List에 있는 인스턴스 취득
    foreach (var node in this)
    {
      // 동일한 이름의 함수를 실행
      node.Print();
    }
  }
}

class Program
{
  static void Main(string[] args)
  {
    // composite 인스턴스 생성
    var composite = new CompositeNode();
    // INode 인터페이스를 상속받은 인스턴스를 생성하고 저장
    composite.Add(new Node1());
    composite.Add(new Node2());
    // 컴포지트 인스턴스의 함수 실행
    composite.Print();
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/srYNQ/btri5Hn8jwg/XUgk6xeRYKskWlclHvdZRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/srYNQ/btri5Hn8jwg/XUgk6xeRYKskWlclHvdZRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/srYNQ/btri5Hn8jwg/XUgk6xeRYKskWlclHvdZRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsrYNQ%2Fbtri5Hn8jwg%2FXUgk6xeRYKskWlclHvdZRk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;꼭 하나의 클래스에 대해서만 컴포지트 패턴이 아닙니다.&lt;/p&gt;
&lt;p&gt;INode를 상속받은 모든 인스턴스를 동일한 구조의 Composite 클래스에서 일괄적으로 실행하기 위한 목적입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 컴포지트 패턴(Composite pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/444</guid>
      <comments>https://nowonbun.tistory.com/444#entry444comment</comments>
      <pubDate>Wed, 27 Oct 2021 20:20:44 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 2-1. 어댑터 패턴(Adapter pattern)</title>
      <link>https://nowonbun.tistory.com/436</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 어댑터 패턴(Adapter pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;어댑터 패턴부터는 구조 패턴입니다. 구조 패턴이란 여러 클래스나 객체를 조합하여 더 큰 구조를 만드는 패턴입니다. 이전 생성 패턴에서는 new를 이용한 인스턴스를 생성하는 형태가 중심이었다면 구조 패턴에서는 클래스나 객체의 구조를 어떻게 구성하는 것에 더 중점을 두는 패턴입니다.&lt;/p&gt;
&lt;p&gt;어댑터 패턴은 인터페이스로 묶이지 않은 다른 클래스를 형태를 같은 인터페이스의 형태로 변환하는 것이 목표입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chsnMZ/btriRX6ZMRn/R2DPXfntQZ4f9mW0OSXbH0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chsnMZ/btriRX6ZMRn/R2DPXfntQZ4f9mW0OSXbH0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chsnMZ/btriRX6ZMRn/R2DPXfntQZ4f9mW0OSXbH0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchsnMZ%2FbtriRX6ZMRn%2FR2DPXfntQZ4f9mW0OSXbH0%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Adapter_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Adapter_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 어댑터 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;
// INode 인터페이스
class INode {
public:
  // 추상 함수
  virtual string getData() = 0;
  // 소멸자 추상화
  virtual ~INode() { }
};
// INode 인터페이스를 상속 받는다.
class Node1 : public INode {
public:
  // getData 함수 재정의
  virtual string getData() {
    // String 데이터를 return 한다.
    return &quot;Node1 Class - getData()&quot;;
  }
};
// Node2 클래스, INode 인터페이스를 상속 받지 않았다.
class Node2 {
public:
  // getData 함수
  string getData() {
    // String 데이터를 return 한다.
    return &quot;Node2 Class - getData()&quot;;
  }
};
// Node2 클래스의 어댑터 패턴 (Node2가 INode 인터페이스 안에서 사용할 수 있게 한다.)
class Node2Adapder : public INode {
private:
  // 맴버 변수
  Node2* node;
public:
  // 생성자, Node2의 인스턴스를 받는다.
  Node2Adapder(Node2* node) {
    // 맴버 변수 설정
    this-&amp;gt;node = node;
  }
  // 소멸자
  ~Node2Adapder() {
    // 맴버 변수 Node2를 해제한다.
    delete this-&amp;gt;node;
  }
  // getData 함수 재정의
  virtual string getData() {
    // Node2 인스턴스의 getData를 호출
    return this-&amp;gt;node-&amp;gt;getData();
  }
};
// 실행 함수
int main() {
  // 백터 선언 (INode 인터페이스를 상속 받은 인스턴스만)
  vector&amp;lt;INode*&amp;gt; v;
  // Node1은 INode를 상속 받았으므로 OK
  v.push_back(new Node1());
  // Node2는 INode를 상속받지 않았지만 Adapter 패턴으로 INode 인터페이스에 포함하게 한다.
  v.push_back(new Node2Adapder(new Node2()));
  // 반복문으로 INode 인스턴스 취득
  for (INode* n : v) {
    // getData 데이터를 호출하고 콘솔에 출력
    cout &amp;lt;&amp;lt; n-&amp;gt;getData() &amp;lt;&amp;lt; endl;
    // 메모리 해재
    delete n;
  }

  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mG0sT/btriYnwm2TV/BCk3BToozrLKTRFRCW1B2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mG0sT/btriYnwm2TV/BCk3BToozrLKTRFRCW1B2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mG0sT/btriYnwm2TV/BCk3BToozrLKTRFRCW1B2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmG0sT%2FbtriYnwm2TV%2FBCk3BToozrLKTRFRCW1B2K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제를 보면 제가 main 함수에서 INode 인터페이스의 타입의 객체들을 vector를 통해 관리하기 위해 선언했습니다.&lt;/p&gt;
&lt;p&gt;그런데 Node2 클래스는 INode 인터페이스를 상속 받은 받은 클래스가 아니여서 INode 인터페이스 그룹으로 묶을 수가 없습니다. 클래스의 구조는 비슷한데 말입니다. &lt;/p&gt;
&lt;p&gt;Node2를 INode에 상속받게 하면 아주 간단하게 될지도 해결될 지도 모르겠습니다만, 상황에 따라서 Node2클래스를 수정하면 안된다고 한다면 위처럼 Adapter 클래스를 만들어서 Node2 클래스를 마치 INode에 상속 받은 것처럼 만들 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 어댑터 패턴 예제&quot;&gt;import java.util.ArrayList;
// INode 인터페이스
interface INode {
  // 추상 함수
  void print();
}
// INodeAnother 인터페이스
interface INodeAnother {
  // 추상 함수
  String output();
}
// INode 인터페이스를 상속받은 Node1 클래스
class Node1 implements INode {
  // 함수 재정의
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;Node1 - print()&quot;);
  }
}
// INodeAnother 인터페이스를 상속받은 Node2 클래스
class Node2 implements INodeAnother {
  // 함수 재정의
  public String output() {
    // String 값을 리턴
    return &quot;Node2 - output()&quot;;
  }
}
// INodeAnother 인터페이스의 adapter 패턴, INode 인터페이스를 상속
class INodeAnotherAdapter implements INode {
  // 맴버 변수
  private INodeAnother node;
  // 생성자
  public INodeAnotherAdapter(INodeAnother node) {
    this.node = node;
  }
  // 함수 재정의
  public void print() {
    // INodeAnother 인터페이스의 output 함수의 값을 받아 출력
    System.out.println(node.output());
  }
}
public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // INode의 리스트 생성
    var list = new ArrayList&amp;lt;INode&amp;gt;();
    // Node1 인스턴스 생성
    list.add(new Node1());
    // Node2 인스턴스를 INodeAnotherAdapter 어댑터를 통해 변환
    list.add(new INodeAnotherAdapter(new Node2()));
    // 반복문 추출
    for (var node : list) {
      // print 함수 출력
      node.print();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qOkm6/btriUFxMMwY/Wfq5fOW2Rz9U13lYepTpl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qOkm6/btriUFxMMwY/Wfq5fOW2Rz9U13lYepTpl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qOkm6/btriUFxMMwY/Wfq5fOW2Rz9U13lYepTpl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqOkm6%2FbtriUFxMMwY%2FWfq5fOW2Rz9U13lYepTpl1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Adapter 패턴이 꼭 클래스만 적용하라는 법은 없습니다. 위처럼 Interface 어댑터를 만들어서 INodeAnother를 상속받은 클래스는 INode 인터페이스를 상속받은 어댑터 클래스로 변환하는 게 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 어댑터 패턴 예제&quot;&gt;using System;
using System.Collections.Generic;

// INode 인터페이스
public interface INode
{
  // 추상 함수
  void Print();
}
// Node2 클래스
public class Node2
{
  // String 값을 리턴하는 함수
  public string Output()
  {
    return &quot;Node2 - Output()&quot;;
  }
}
// INode 인터페이스를 상속받은 Node 클래스
public class Node1 : INode
{
  // 출력 함수
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Node1 - Print()&quot;);
  }
}
// 이번엔 상속을 통해 Adapter를 구현헀다.
public class Adapter : Node2, INode
{
  // 함수 재정의
  public void Print()
  {
    // 콘솔 출력
    Console.WriteLine(base.Output());
  }
}
class Program
{
  // 실행 함수
  static void Main(string[] args)
  {
    // INode의 리스트 생성
    var list = new List&amp;lt;INode&amp;gt;();
    // Node1 클래스의 인스턴스 생성
    list.Add(new Node1());
    // Node2 클래스를 상속받은 Adapter 클래스를 통해 Adapter 패턴을 구현
    list.Add(new Adapter());
    // 반복문을 통해 추출
    foreach (var node in list)
    {
      // 함수 호출
      node.Print();
    }
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bao4UT/btriXA3US0Z/iBmvNFcUPkA4jTiySYAlk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bao4UT/btriXA3US0Z/iBmvNFcUPkA4jTiySYAlk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bao4UT/btriXA3US0Z/iBmvNFcUPkA4jTiySYAlk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbao4UT%2FbtriXA3US0Z%2FiBmvNFcUPkA4jTiySYAlk1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;어댑터 패턴은 기본적으로 생성자에서 형을 변환할 클래스를 받아서 새로운 클래스로 감싸는 것을 말합니다.&lt;/p&gt;
&lt;p&gt;그런데 꼭 생성자에서 받을 필요없이 위처럼 상속을 통해서도 어댑터 패턴을 구현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 어댑터 패턴(Adapter pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/436</guid>
      <comments>https://nowonbun.tistory.com/436#entry436comment</comments>
      <pubDate>Tue, 26 Oct 2021 17:02:27 +0900</pubDate>
    </item>
    <item>
      <title>[Design pattern] 1-5. 프로토타입 패턴 (Prototype pattern)</title>
      <link>https://nowonbun.tistory.com/435</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 프로토타입 패턴(Prototype pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;프로토타입은 패턴 자체는 굉장히 단순한 패턴입니다만, 개념적으로 포인터와 스택, 힙 메모리에 대한 개념을 잘 모르신다면 이해하기 어려울 수도 있는 패턴입니다.&lt;/p&gt;
&lt;p&gt;우리가 프로그램 상에서 클래스의 인스턴스를 생성하게 되면 변수에 포인터 주소가 입력되고 주소에 따른 인스턴스가 힙 메모리에 할당이 됩니다. 그래서 변수로 새로운 객체를 생성하지 않고 등호(equal: =) 기호로 새로운 변수명으로 인스턴스의 주소 값을 넘기면 두개의 변수에서 하나의 인스턴스를 가르키기 때문에 두 변수의 처리에 있어서 데이터 영향이 생깁니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 데이터 프로퍼티
    public int Data { get; set; }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // node.Data의 프로퍼티에 1이란 값을 넣는다.
      node.Data = 1;
      // 새로운 변수를 생성하고 node의 인스턴스 주소를 넣는다.
      var nodeClone = node;
      // nodeClone.Data에 2의 값을 넣는다.
      nodeClone.Data = 2;
      // 이 때, node.Data의 값은 1일까 2일까?
      Console.WriteLine(node.Data);
      
      // 메모리 주소 출력
      Console.WriteLine(node.GetHashCode());
      Console.WriteLine(nodeClone.GetHashCode());
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cox8sX/btriCFDyE24/mRcLdl50lLtlVwuKhcm7I0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cox8sX/btriCFDyE24/mRcLdl50lLtlVwuKhcm7I0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cox8sX/btriCFDyE24/mRcLdl50lLtlVwuKhcm7I0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcox8sX%2FbtriCFDyE24%2FmRcLdl50lLtlVwuKhcm7I0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그렇죠.. 당연히 영향이 가죠.. 결과를 보시면 node 변수와 nodeClone 변수의 메모리 주소가 같다는 걸 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TeNZp/btriz7ateIe/ExFAntdkXXouIePFuPxLLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TeNZp/btriz7ateIe/ExFAntdkXXouIePFuPxLLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TeNZp/btriz7ateIe/ExFAntdkXXouIePFuPxLLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTeNZp%2Fbtriz7ateIe%2FExFAntdkXXouIePFuPxLLk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위의 그림같은 구조로 두개의 변수에 하나의 인스턴스가 묶여 있어서 그렇습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/67&quot; target=&quot;_blank&quot;&gt;[C#] 10. 인스턴스 생성(new)과 메모리 할당(Stack 메모리와 Heap 메모리) 그리고 null&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/303&quot; target=&quot;_blank&quot;&gt;[Java] 10. 메모리 할당(stack 메모리와 heap 메모리 그리고 new)과 Call by reference(포인터에 의한 참조)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇다면 반대로 저 인스턴스의 주소 값을 복사하지 말고 클래스 안의 데이터는 모두 같게 하고 클래스를 복제할 수 없을까?&lt;/p&gt;
&lt;p&gt;위 예제 클래스는 워낙 단순한 구조라 새로 new Node하고 Data 값을 복사하는 것으로 끝나지만, 복잡한 클래스이고 대부분의 맴버 함수가 private으로 설정되어 있을 경우에는 단순하게 복사하는 게 안되겠네요.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YqdFf/btrizyGpqKs/3u8sniaWKmwsaKv6pxmf9k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YqdFf/btrizyGpqKs/3u8sniaWKmwsaKv6pxmf9k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YqdFf/btrizyGpqKs/3u8sniaWKmwsaKv6pxmf9k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYqdFf%2FbtrizyGpqKs%2F3u8sniaWKmwsaKv6pxmf9k%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Prototype_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Prototype_pattern&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 프로토타입 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// 예제 클래스
class Node {
private:
  // 맴버 변수
  int _data;
public:
  // 생성자
  Node(int data) {
    // 맴버 변수에 데이터를 넣는다.
    this-&amp;gt;_data = data;
  }
  // 출력 함수
  void print() {
    // 콘솔 출력
    cout &amp;lt;&amp;lt; &quot;data = &quot; &amp;lt;&amp;lt; this-&amp;gt;_data &amp;lt;&amp;lt; endl;
  }
  // 프로토타입(prototype)의 메모리 복사
  Node* clone()
  {
    // 새로운 인스턴스 리턴
    return new Node(*this);
  }
};
// 실행 함수
int main() {
  // 인스턴스 생성
  Node* node = new Node(1);
  // 인스턴스 복사
  Node* nodeClone = node-&amp;gt;clone();
  // 출력 함수 호출
  node-&amp;gt;print();
  nodeClone-&amp;gt;print();
  
  // 메모리 주소 출력
  cout &amp;lt;&amp;lt; node &amp;lt;&amp;lt; endl;
  cout &amp;lt;&amp;lt; nodeClone &amp;lt;&amp;lt; endl;
  // 메모리 해제
  delete node;
  delete nodeClone;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciOTix/btrizzk3a2p/VTzuRrw1wnfH3RDFQbzQ40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciOTix/btrizzk3a2p/VTzuRrw1wnfH3RDFQbzQ40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciOTix/btrizzk3a2p/VTzuRrw1wnfH3RDFQbzQ40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciOTix%2Fbtrizzk3a2p%2FVTzuRrw1wnfH3RDFQbzQ40%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;C/C++에서는 단순하게 clone 함수에서 새로운 인스턴스를 생성하는 new 키워드를 사용하고 this를 통해서 메모리를 넣으면 인스턴스가 복사가 됩니다.&lt;/p&gt;
&lt;p&gt;결과를 보시면 data에 들어가 있는 값은 같지만 메모리 주소가 다른 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 프로토타입 패턴 예제&quot;&gt;// 프로토타입을 사용하기 위해서는 Cloneable 인터페이스를 상속받아야 한다.
class Node implements Cloneable {
  // 맴버 변수
  private int data;
  // 생성자
  public Node(int data) {
    // 맴버 변수에 값 설정
    this.data = data;
  }
  // 출력 함수
  public void print() {
    // 콘솔 출력
    System.out.println(&quot;data = &quot; + data);
  }
  // 프로토타입(prototype)의 메모리 복사
  public Node clone() {
    try {
      // 메모리 복사한다.
      return (Node) super.clone();
    } catch (CloneNotSupportedException e) {
      // Cloneable 인터페이스를 상속 받지 않으면 에러가 발생한다.
      return null;
    }
  }
}

public class Program {
  // 실행 함수
  public static void main(String[] args) {
    // 인스턴스 생성
    var node = new Node(1);
    // 인스턴스 복사
    var nodeClone = node.clone();
    // 출력 함수 호출
    node.print();
    nodeClone.print();
    // 메모리 주소 출력
    System.out.println(node.hashCode());
    System.out.println(nodeClone.hashCode());
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TzxQW/btriB5P1U9a/vc6M5bpyCRMKPMLSzncQK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TzxQW/btriB5P1U9a/vc6M5bpyCRMKPMLSzncQK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TzxQW/btriB5P1U9a/vc6M5bpyCRMKPMLSzncQK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTzxQW%2FbtriB5P1U9a%2Fvc6M5bpyCRMKPMLSzncQK1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;자바의 경우는 Cloneable 인터페이스를 상속받아야 프로토타입 함수 clone 함수를 사용할 수 있습니다. 그리고 Object 클래스에서는 clone이 protected의 접근 제한자로 설정되어 있기 때문에 public으로 재정의해야 합니다.&lt;/p&gt;
&lt;p&gt;자바도 맴버 변수의 값은 같은 것으로 확인이 됩니다만 메모리 주소는 다르니 서로 다른 인스턴스라는 것을 확인 할 수 있네요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 프로토타입 패턴 예제&quot;&gt;using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 맴버 변수
    private int data;
    // 생성자
    public Node(int data)
    {
      // 맴버 변수에 값을 설정
      this.data = data;
    }
    // 출력 함수
    public void Print()
    {
      // 콘솔에 출력
      Console.WriteLine(&quot;data = &quot; + this.data);
    }
    // 프로토타입(prototype)의 메모리 복사
    public Node Clone()
    {
      // Object 클래스에 protected 형태로 존재한다.
      return this.MemberwiseClone() as Node;
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node(1);
      // 인스턴스 복사
      var nodeClone = node.Clone();
      // 출력 함수 호출
      node.Print();
      nodeClone.Print();
      // 메모리 주소 출력
      Console.WriteLine(node.GetHashCode());
      Console.WriteLine(nodeClone.GetHashCode());
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSjoe6/btriBdHL7N5/E8CEdAMT3B4PeCW62YFeck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSjoe6/btriBdHL7N5/E8CEdAMT3B4PeCW62YFeck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSjoe6/btriBdHL7N5/E8CEdAMT3B4PeCW62YFeck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSjoe6%2FbtriBdHL7N5%2FE8CEdAMT3B4PeCW62YFeck%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;C#에서는 java와 다르게 따로 상속받아야 할 인터페이스는 없습니다. java와 마찬가지로 Object 클래스에 MemberwiseClone의 함수가 protected로 설정되어 있기 때문에 public으로 재정의해야 합니다.&lt;/p&gt;
&lt;p&gt;역시나 맴버 변수의 값은 같은 것으로 확인이 되지만 메모리 주소가 다른 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 프로토타입 패턴(Prototype pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/435</guid>
      <comments>https://nowonbun.tistory.com/435#entry435comment</comments>
      <pubDate>Fri, 22 Oct 2021 20:11:46 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 56. 코딩 규약</title>
      <link>https://nowonbun.tistory.com/510</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#의 코딩 규약에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 코딩 규약에 대해서 간략하게 설명하면, 코딩 규약은 우리가 프로그램을 작성할 때 지켜야하는 규칙이라고 할 수 있습니다. 프로그램 성능과 효율성과는 관련은 없고 여러 명에서 프로그램을 만들 때 소스를 알아보기 쉽게 정해 놓은 것입니다.&lt;/p&gt;
&lt;p&gt;간단하게 예를 들면, 함수 명명법을 보면 「동사+ 명사」 형태로 보통 「GetData」 식으로 작성을 합니다. 그러나 DataGet 이런 식으로 함수명을 만든다 해도 동작이 안되는 것은 아닙니다. 아니 「abcdefghijklm」 이라고 함수 명을 지어도 동작하는 데는 문제없습니다.&lt;/p&gt;
&lt;p&gt;그러나 「GetData」이라고 함수명을 작성하면 함수의 소스를 보지 않더라도 이 함수는 「어떤 데이터를 가져오는 처리구나」 라고 예상할 수 있지만 이상하게 함수명을 만든다면 함수명 만으로 어떤 동작을 하는 지 알 수가 없습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 MS에서는 C# 코딩을 할 때, 함수 명명법, 소스 내 띄어쓰기 등의 규칙을 정해 놓고 사용하기를 권장하고 있습니다. 이것을 코딩 규약이라고 합니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/inside-a-program/coding-conventions&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;MS 코딩 표준&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;명명 규칙&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote style=&quot;border: 1px solid;border-left: 5px solid;&quot;&gt;
&lt;p&gt;정규화된 클래스의 이름이 한 줄에 길게 표시 되지 않게 합니다. 즉, 포함되어 있는 모든 namespace를 설정할 필요는 없습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;코드 명명법&quot;&gt;// 잘못된 명명 방법
System.Diagnostics.PerformanceCounterCategory category = new System.Diagnostics.PerformanceCounterCategory(); 
// 지시문은 var로 대체하여 최대한 간결하게 사용하고 선언문의 네임스페이스는 using을 사용하여 한줄의 코딩 표기를 최대한 줄인다.
using System.Diagnostics;
var category = new PerformanceCounterCategory();
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;레이 아웃 규칙&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote style=&quot;border: 1px solid;border-left: 5px solid;&quot;&gt;
&lt;p&gt;좋은 레이 아웃 규칙은 코딩의 구문을 강조하고 읽기 쉽게 작성하는 것입니다.&lt;/p&gt;
&lt;p&gt;1. 기본 코드 편집기 설정 (4 자 들여 쓰기, 공백으로 저장된 탭)을 사용합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;단축 키로는 Ctrl + K + D입니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; width=&quot;700&quot; height=&quot;784&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;813&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://t1.daumcdn.net/cfile/tistory/996E06435D1E0CD037?original&quot; data-phocus=&quot;https://t1.daumcdn.net/cfile/tistory/996E06435D1E0CD037?original&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/cfile/tistory/996E06435D1E0CD037&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Ft1.daumcdn.net%2Fcfile%2Ftistory%2F996E06435D1E0CD037&quot; width=&quot;700&quot; height=&quot;784&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;813&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;링크 - &lt;a href=&quot;https://docs.microsoft.com/en-us/visualstudio/ide/reference/options-text-editor-csharp-formatting?view=vs-2019&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.microsoft.com/en-us/visualstudio/ide/reference/options-text-editor-csharp-formatting?view=vs-2019&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;2. 한 줄에 하나의 문장만 씁니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;코딩법&quot;&gt;// 잘못된 코딩 방법
if( a &amp;gt; 0) for(int i = 0; i &amp;lt; 10; i++) Console.WriteLine(i);
// 올바른 코딩 방법
if (a &amp;gt; 0)
{
  for (int i = 0; i &amp;lt; 10; i++)
  {
    Console.WriteLine(i);
  } 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;3. 연속적인 체인식 함수 선언이 자동으로 들여 쓰기되지 않으면 탭 간격을 한 칸 들여 씁니다.(4 칸)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;gt; 특히 Linq 식의 경우는, .Where .Select의 연속된 처리식이 가능합니다만 라인을 바꿀 때는 꼭 들여쓰기를 통해 구분을 정합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;체인 명명법&quot;&gt;List.Where(x =&amp;gt; x.IsCheck)
     .Select의(x =&amp;gt; x.Data);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;4. 메서드 정의와 프로퍼티 정의 사이에 적어도 하나의 공백 행을 추가합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;gt; 이게 무슨 말인지 진짜 고민을 많이 했습니다.&lt;/p&gt;
&lt;p&gt;아마도 메서드 이름과 파라미터 사이에 공백을 넣으라는 뜻인것 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;공백&quot;&gt;public void SetData (int a)
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;5. 괄호를 사용하여 표현식에서 절을 명백하게 만듭니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;gt; if 나 for의 경우 괄호없이 코딩(한줄 코딩)이 가능하나 절대 추천하지 않습니다. 왠만하면 괄호를 넣어서 확실하게 구분을 하는 편이 좋습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;코멘트 규칙&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote style=&quot;border: 1px solid;border-left: 5px solid;&quot;&gt;
&lt;p&gt;1. 코드 줄 끝에 주석을 넣지말고 별도의 줄에 주석을 넣습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;주석 표기법&quot;&gt;// 잘못된 표기법 
if ( a == 0 ) // a가 0일 경우
{
}  
// 올바른 표기법 
// a가 0일 경우
if ( a == 0 )
{
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2. 대문자로 주석 텍스트를 시작합니다.(영어만 해당되는 사항입니다.)&lt;/p&gt;
&lt;p&gt;3. 마침표가 있는 설명 텍스트를 끝냅니다.(영어만 해당되는 사항입니다.)&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;4. 주석 구분 기호 (//)와 주석 텍스트 사이에 하나의 공백을 넣습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;주석 표기법&quot;&gt;//잘못된 표기법
// 올바른 표기법
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;언어 규칙&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote style=&quot;border: 1px solid;border-left: 5px solid;&quot;&gt;
&lt;p&gt;1. 문자열 데이터 형식&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;1-1. 가능한한 string interpolation(보간법)을 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;보간법&quot;&gt;int data = 10;
Console.WriteLine($&quot;Hello world {data}&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/511&quot; target=&quot;_blank&quot;&gt;[C#] String 보간법(interpolation)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;1-2. 많은 양의 String 객체를 합칠 경우에는 StringBuilder를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;StringBuilder 사용&quot;&gt;var phrase = &quot;lalalalalalalalalalalalalalalalalalalalalalalalalalalalalala&quot;;
var manyPhrases = new StringBuilder();
for (var i = 0; i &amp;lt; 10000; i++)
{
  manyPhrases.Append(phrase);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;2. 암시적 변수형 타입&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;2-1. 변수 타입이 정확하거나 별도의 구분이 필요없을 경우는 var를 사용하세요.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/407&quot; target=&quot;_blank&quot;&gt;[C#] 26. var 키워드와 dynamic 키워드&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;2-2. 타입이 불분명한 경우, dynamic 타입과 object타입의 경우는 var을 사용하지 않습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;2-3. 변수 이름에는 변수 형태(자료형)를 암시하는 명명을 하지 않습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;변수 명명법&quot;&gt;// 잘못된 경우
var inputInt = 10; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;2-4. for와 foreach에서는 반드시 var 타입을 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;반복문 변수 명명법&quot;&gt;for (var i=0 ; i&amp;lt;100 ; i++) 
{
}
foreach (var data in List)
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;3. Unsigned Data Type&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;일반적으로 Unsigned Type은 사용하지 않습니다. 예로 int형으로 충분히 가능한 코딩을 unsigned int를 선언할 필요는 없습니다.&lt;/p&gt;
&lt;p&gt;4. 배열&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;최대한 간결하게 선언합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;배열 선언&quot;&gt;string[] vowels1 = { &quot;a&quot;, &quot;e&quot;, &quot;i&quot;, &quot;o&quot;, &quot;u&quot; };
var vowels2 = new string[] { &quot;a&quot;, &quot;e&quot;, &quot;i&quot;, &quot;o&quot;, &quot;u&quot; };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;5. 델리게이트&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;최대한 간결하게 선언하고 클래스에 종속되지 않게 사용한다.(클래스의 종속 관계는 사양에 따라 달라질 수 있다.)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;델리게이트 선언&quot;&gt;using System;

namespace Example
{
  // 델리게이트는 class의 외부에서도 생성가능
  public delegate void Del(string message);

  class Program
  {
    // 델리게이트 예제 함수
    public static void DelMethod(string str)
    {
      Console.WriteLine(&quot;DelMethod argument: {0}&quot;, str);
    }
    // 실행 함수
    static void Main(string[] args)
    {
      Del exampleDel2 = DelMethod;
      Del exampleDel1 = new Del(DelMethod);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;6. 예외 처리&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;6.1 예외 처리는 try~catch를 사용합니다..(다른 예외 처리도 있었나???)&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;6.2 Dispose 메소드 호출 코드는 using를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;using 사용법&quot;&gt;Font font1 = new Font(&quot;Arial&quot;, 10.0f);
try
{
  byte charset = font1.GdiCharSet;
}
finally
{
  if (font1 != null)
  {
    ((IDisposable)font1).Dispose();
  }
}
// try - finally 문을 using으로 대체 가능하다.
using (Font font2 = new Font(&quot;Arial&quot;, 10.0f))
{
  byte charset = font2.GdiCharSet;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;7. &amp;amp;&amp;amp;와 || 연산자&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;성능과 가독성을 높이고 싶으면 가능하면 &amp;amp;와 |대신에 &amp;amp;&amp;amp;와 ||를 사용합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;(|와 ||는 용법이 다른게 아닌가? bit 연산의 경우는 |가 맞지만, 아마 true, false를 구분하려면 가능한 ||를 사용하라는 듯. 아니 용법이 전혀 다르다고...)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;논리 연산자와 비트 연산자&quot;&gt;// 잘못된 표기
if(a == 1 &amp;amp; b == 1)
{
}
// 올바른 표기
if(a == 1 &amp;amp;&amp;amp; b == 1)
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;8. 이벤트&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;나중에 제거할 필요가 없는 event handler의 경우 람다식을 추천하고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;(분명히 람다식이 편한 것은 맞는데... 너무 람다식 위주로 가면 가독성이 떨어지는데...)&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;람다식 추천&quot;&gt;// 잘못된 사용법
public Form1()
{
  this.Click += new EventHandler(Form1_Click);
}
void Form1_Click(object sender, EventArgs e)
{
  MessageBox.Show(((MouseEventArgs)e).Location.ToString());
}
// 올바른 사용법
public Form2()
{
  this.Click += (s, e) =&amp;gt;
  {
    MessageBox.Show(((MouseEventArgs)e).Location.ToString());
  }; 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;9. 정적 맴버&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;반드시 클래스 명을 붙힙니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;정적 변수 사용법&quot;&gt;public class Test
{
  public static int Data;
  public void Print()
  {
    // 잘못된 표기법
    Data = 0;
    // 올바른 표기법
    Test.Data = 1;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;10. Linq식&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;10-1. 변수명을 결과값이 유추되는 값을 명명합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;10-2. select 결과에 익명 클래스를 사용할 경우 속성의 첫글자를 대문자를 사용합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;10-3. 결과의 변수값이 모호할 경우 명확하게 바꿉니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;10-4. 결과의 변수 타입은 var를 사용합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;10-5. 들여쓰기는 from에 맞춤니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-&amp;gt; 개인적으로 Linq식은 위에 했던 이야기 반복된 이야기 하는 것같습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;그 밖에 코딩 명명 규칙&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote style=&quot;border: 1px solid;border-left: 5px solid;&quot;&gt;
&lt;p&gt;예전에 제가 코딩 규칙을 공부할 때와 현재 코딩 규칙이 많이 다른 듯합니다. 제가 기억나는 코딩 규칙을 추가로 작성합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;1. 클래스의 맴버 변수는 반드시 private로 선언합니다.&lt;/p&gt;
&lt;p&gt;2. 맴버 변수의 첫 글자는 소문자이며 명사로만 구성합니다.&lt;/p&gt;
&lt;p&gt;3. 프로퍼티의 첫 글자는 대문자입니다.&lt;/p&gt;
&lt;p&gt;4. 함수는 「동사 + 명사」의 형태이고 첫 글자와 절 구분은 반드시 대문자입니다. 예 GetData, CreateBuildExcelSheet...&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;참고로 자바의 경우는 「동사 + 명사」 의 형태는 같은데 첫 글자는 소문자, 절 구분은 대문자입니다. 다른 규약이니 참고하시면 됩니다. 예) getData, createBuildExcelSheet..&lt;/p&gt;
&lt;p&gt;6. 괄호 앞은 반드시 개행이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;참고로 자바의 경우는 괄호 앞에 개행을 넣지 않는게 규칙입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;개행 규칙&quot;&gt;// C#의 경우는 
if( a == 1)
{
}
// Java의 경우는 
if ( a == 1) { 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;7. 클래스는 되도록 데코레이터 패턴으로 작성한다. (interface에서 추상 클래스를 상속받고, 클래스를 상속 받는다.)&lt;/p&gt;
&lt;p&gt;8. 클래스 명과 파일명은 일치하게 작성한다.&lt;/p&gt;
&lt;p&gt;9. 네임 스페이스 명과 폴더명은 일치하게 작성한다.&lt;/p&gt;
&lt;p&gt;10. goto문은 사용하지 않는다.&lt;/p&gt;
&lt;p&gt;11. if - else 문보다는 if break, continue 혹은 if return문을 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;반복문 내의 분기 규칙&quot;&gt;// 잘못된 사용법
for(var i=0; i&amp;lt;100; i++)
{
  if(i%2 == 0)
  {
    Console.WriteLine(&quot;짝수입니다.&quot;);
  }
  else
  {
    Console.WriteLine(&quot;홀수입니다.&quot;);
  }
}
// 올바른 사용법 
for(var i=0; i&amp;lt;100; i++)
{
  if(i%2 == 0)
  {
    Console.WriteLine(&quot;짝수입니다.&quot;);
    continue;
  }
  Console.WriteLine(&quot;홀수입니다.&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;12. 가능하면 한 줄에 하나의 처리식만 작성한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;한줄 코딩 금지&quot;&gt;String data = &quot;&quot;;
// 잘못된 사용법
Console.WriteLine((String.IsNullOrEmpty(data) ? &quot;null&quot; : data)?.Length);
// 올바른 사용법
String buffer;
int length = 0;
if (String.IsNullOrEmpty(data))
{
  buffer = &quot;null&quot;;
}
else
{
  buffer = data;
}
Console.WriteLine(buffer.Length);
&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;그 밖의 명명 규칙의 경우는 제가 예전에 코딩 표준을 공부할 때 있었던 내용인데 지금은 없네요. 왜 없어졌는 지는 모르겠는데, 프로젝트 할 때에 필요한 규칙이니 알아두면 좋습니다.&lt;/p&gt;
&lt;p&gt;위의 C#의 코딩 규약은 성능에는 영향의 거의 없습니다. 있는 경우도 있으나, 보통은 가독성을 올리기 위한 방법이 대부분입니다.&lt;/p&gt;
&lt;p&gt;그리고 실무에서의 프로젝트는 혼자서 프로젝트를 진행하기 보다는 여러 명에서 작성하는 경우가 많으니 반드시 지키는 것이 서로 간의 불필요한 코드 논쟁을 줄이고 소스 저장도(Git)에도 불필요한 스탭이 생기지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;물론 큰 프로젝트나 여러 기업이 붙어서 하는 경우에는 별도의 코드 규약이 있는 경우도 있는데 보통은 이 코딩 기본 규약을 좀 더 엄격히 설정한 경우가 많습니다. 사내 SE던가 학교에서 진행하는 조별 프로젝트나 개별 프로젝트에서는 이 규약을 무시하고 작성하는 경우가 많습니다.&lt;/p&gt;
&lt;p&gt;그것이 잘못되었다는 건 아니지만 규약을 맞추지 못하면 그만큼 오픈 API등를 사용하는데 제약이 걸리는 경우도 있고, 작성한 라이브러리를 배포하기도 애매해 집니다. 나아가 가독성이 매우 떨어지는 경우가 발생하기 때문에 코드 리뷰등에도 오해나 문제가 발생할 수도 있겠네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 코딩 규약이 정말 사소하고 쉽게 무시될 수 있는 부분이기도 하지만 프로젝트의 중요도에 따라 아주 중요한 문제가 될 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#의 코딩 규약에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <category>c#</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/510</guid>
      <comments>https://nowonbun.tistory.com/510#entry510comment</comments>
      <pubDate>Thu, 21 Oct 2021 17:28:11 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 55. 값의 초기화 및 기본 데이터 값(default)를 설정하는 방법 그리고 원시 데이터의 null 처리, ?와 ??의 사용법</title>
      <link>https://nowonbun.tistory.com/140</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#의 값의 초기화 및 기본 데이터 값(default)를 설정하는 방법 그리고 원시 데이터의 null 처리, ?와 ??의 사용법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;우리가 프로그램을 작성하다 보면 변수의 초기값을 설정해야 하는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;예를 들면 int 타입의 맴버 변수를 생성하면 초기 데이터를 -1로 할 것인가? 0으로 할 것인가에 대한 고민을 할 경우가 있습니다. 물론 -1로 설정할 지, 0으로 설정할 지에 대해서는 사양에 따라 설정을 해야하는 것이 기본입니다만 그래서 int a = 0;식의 설정보다는 조금 더 프로그램 같은 설정이 없을까?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
namespace Example
{
  // 예제 클래스
  class Node
  {
    // 맴버 변수의 data에 int 타입의 기본 데이터
    private int data = default(int);
    // 프로퍼티
    public int Data
    {
      get
      {
        // 맴버 변수 data를 리턴
        return this.data;
      }
    }
  }
  // Program 클래스
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // node의 맴버 변수 data를 콘솔 출력
      Console.WriteLine(node.Data);

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X28RI/btritNJmcU4/mRv3a6yAhK9F43RgrYPVG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X28RI/btritNJmcU4/mRv3a6yAhK9F43RgrYPVG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X28RI/btritNJmcU4/mRv3a6yAhK9F43RgrYPVG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX28RI%2FbtritNJmcU4%2FmRv3a6yAhK9F43RgrYPVG0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제를 보면 int형의 data 맴버 변수에 default 키워드를 사용해서 int의 초기 값을 선언했습니다. 콘솔에 출력을 하니 0의 값이 나오네요.&lt;/p&gt;
&lt;p&gt;즉, default는 해당 데이터 타입(자료형)의 초기 데이터의 값을 리턴하는 키워드입니다.&lt;/p&gt;
&lt;p&gt;원시 데이터의 경우는 초기 int의 경우 0을, float의 경우는 0.0을 입력하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 여기서 맴버 변수의 값에 default(int)를 빼고 출력하면 어떠한 값이 나올까요?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
namespace Example
{
  // 예제 클래스
  class Node
  {
    // 맴버 변수
    private int data;
    // 프로퍼티
    public int Data
    {
      get
      {
        // 맴버 변수 data를 리턴
        return this.data;
      }
    }
  }
  // Program 클래스 분할
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // node의 맴버 변수 data를 콘솔 출력
      Console.WriteLine(node.Data);

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cu6vRX/btrion6hV83/AQaCugjnB9BRGrXutaCKi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cu6vRX/btrion6hV83/AQaCugjnB9BRGrXutaCKi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cu6vRX/btrion6hV83/AQaCugjnB9BRGrXutaCKi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcu6vRX%2Fbtrion6hV83%2FAQaCugjnB9BRGrXutaCKi0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;똑같이 0이 나옵니다. 그럼 default를 설정할 필요가 없는 것인가? 정답은 필요없습니다.&lt;/p&gt;
&lt;p&gt;기본적으로 맴버 변수는 기본 초기 값이 설정이 됩니다. 즉, default(int)를 넣지 않아도 default(int)값이 넣는 것처럼 표시가 됩니다. 그런데 사양에 따라서 int값에 null를 허용하고 싶을 수도 있습니다.&lt;/p&gt;
&lt;p&gt;즉, 기본 값이 0이 아닌 null을 말이죠.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
namespace Example
{
  // 예제 클래스
  class Node
  {
    // 원시 데이터에 ?를 넣으면 null을 허용한다.
    private int? data;
    // 프로퍼티
    public int? Data
    {
      get
      {
        // 맴버 변수 data를 리턴
        return this.data;
      }
    }
  }
  // Program 클래스
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // node의 맴버 변수 data가 null이라면
      if(node.Data == null)
      {
        // 콘솔 출력
        Console.WriteLine(&quot;node.Data is null!&quot;);
      }
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEatnN/btritMDHcdL/CGj7QKim33uCRceklmRdnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEatnN/btritMDHcdL/CGj7QKim33uCRceklmRdnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEatnN/btritMDHcdL/CGj7QKim33uCRceklmRdnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEatnN%2FbtritMDHcdL%2FCGj7QKim33uCRceklmRdnk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그렇습니다. 자료형에다가 ?를 넣으면 원시 데이터에 null를 허용하는 것입니다. 그리고 default(int?)는 null입니다. 기본 초기값이 null이 들어갑니다.&lt;/p&gt;
&lt;p&gt;원시 데이터가 아닌 클래스의 경우는 어떨까요?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
namespace Example
{
  // 예제 클래스
  class Node
  {
    // string은 원시 데이터가 아니고 클래스이다.
    private string data;
    // 프로퍼티
    public string Data
    {
      get
      {
        // 맴버 변수 data를 리턴
        return this.data;
      }
    }
  }
  // Program 클래스
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // node의 맴버 변수 data가 null이라면
      if(node.Data == null)
      {
        // 콘솔 출력
        Console.WriteLine(&quot;node.Data is null!&quot;);
      }
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0kdy5/btripFST1hx/bwlkLvRDKBEbe1JvCfOMb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0kdy5/btripFST1hx/bwlkLvRDKBEbe1JvCfOMb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0kdy5/btripFST1hx/bwlkLvRDKBEbe1JvCfOMb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0kdy5%2FbtripFST1hx%2FbwlkLvRDKBEbe1JvCfOMb1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그렇습니다. class는 기본적으로 null를 허용하기 때문에 ?를 넣지 않아도 기본 default(string)은 null의 값입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그럼 원시 데이터와 class의 차이는 무엇인가 했을때, 기본적으로 C#에서는 원시 데이터를 구조체(struct)로 인식하고 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k7Q1Y/btrio2U6zCu/VqGZXEWxbDjakgzuktqrAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k7Q1Y/btrio2U6zCu/VqGZXEWxbDjakgzuktqrAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k7Q1Y/btrio2U6zCu/VqGZXEWxbDjakgzuktqrAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk7Q1Y%2Fbtrio2U6zCu%2FVqGZXEWxbDjakgzuktqrAk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;즉, struct는 null를 허용하지 않습니다.&lt;/p&gt;
&lt;p&gt;제가 struct으로 구조체를 생성하면 사용법은 기본적으로 class와 비슷하지만 null이 허용되지 않습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSp3zZ/btrisMqqDqN/rKzZd0c2YodgFP4DIPT7K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSp3zZ/btrisMqqDqN/rKzZd0c2YodgFP4DIPT7K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSp3zZ/btrisMqqDqN/rKzZd0c2YodgFP4DIPT7K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSp3zZ%2FbtrisMqqDqN%2FrKzZd0c2YodgFP4DIPT7K0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;struct에서 null를 허용하게 사용하려면 ?를 사용해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 또 궁금한 점이 맴버 변수는 default를 사용하지 않아도 클래스는 null이 입력이 되고 구조체(Struct)는 기본 값이 설정되는 것을 알았는데 default라는 키워드는 의미가 없지 않을까하고 생각되네요.&lt;/p&gt;
&lt;p&gt;그러나 default 키워드는 제네릭(Generic)에서 사용됩니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/406&quot; target=&quot;_blank&quot;&gt;[C#] 30. 제네릭(Generic) 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;제네릭이라는 것은 클래스 내부에서 객체를 설정하는 것이 아니고 외부에서 설정하는 것이라고 했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  // 제네릭 클래스
  class Node&amp;lt;T&amp;gt;
  {
    // 기본 default 값을 리턴하는 함수
    public T GetDefault()
    {
      return default(T);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node 클래스에 제네릭 데이터를 int를 넣는다.
      var node1 = new Node&amp;lt;int&amp;gt;();
      // 값이 null 아니면
      if (node1.GetDefault() != null)
      {
        // 콘솔 출력
        Console.WriteLine(node1.GetDefault());
      }
      else
      {
        // 콘솔 출력
        Console.WriteLine(&quot;node1 is null!&quot;);
      }
      // Node 클래스에 제네릭 데이터를 string를 넣는다.
      var node2 = new Node&amp;lt;string&amp;gt;();
      // 값이 null 아니면
      if (node2.GetDefault() != null)
      {
        // 콘솔 출력
        Console.WriteLine(node2.GetDefault());
      }
      else
      {
        // 콘솔 출력
        Console.WriteLine(&quot;node2 is null&quot;);
      }
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nIvIF/btrit5wg0Tr/gwQNaABjHyxYk2ijPvqUB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nIvIF/btrit5wg0Tr/gwQNaABjHyxYk2ijPvqUB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nIvIF/btrit5wg0Tr/gwQNaABjHyxYk2ijPvqUB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnIvIF%2Fbtrit5wg0Tr%2FgwQNaABjHyxYk2ijPvqUB1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;즉, 제네릭에서는 클래스가 설정될 지 구조체가 설정될 지, 외부에서 설정하기 때문에 내부의 제네릭(Generic)값은 default로 값을 설정해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;추가로 위에서 구조체를 클래스처럼 null를 허용하게 하려면 ?를 사용한다고 설명했습니다.&lt;/p&gt;
&lt;p&gt;추가적으로 ?의 용법이 하나 더 있는데, 바로 삼항 다항식의 null 체크입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // string 타입에 null를 넣는다.
      string node = null;
      // node의 값에 공백을 제거한다.
      Console.WriteLine(node.Trim());
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYGvrg/btriq54PtBQ/nZkeINnoFveFpbA49CHGj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYGvrg/btriq54PtBQ/nZkeINnoFveFpbA49CHGj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYGvrg/btriq54PtBQ/nZkeINnoFveFpbA49CHGj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYGvrg%2Fbtriq54PtBQ%2FnZkeINnoFveFpbA49CHGj0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;당연히 위처럼 작성하면 100% 에러가 발생합니다. 왜냐하면 node의 변수는 null인데 인스턴스의 함수를 실행하라고 요청을 했습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/67&quot; target=&quot;_blank&quot;&gt;[C#] 10. 인스턴스 생성(new)과 메모리 할당(Stack 메모리와 Heap 메모리) 그리고 null&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇기 때문에 보통은 if(node != null) { }의 null 체크 분기식을 소스에 넣습니다.&lt;/p&gt;
&lt;p&gt;그런데 ?를 사용하면 위의 if(node != null) { }의 의미의 분기식이 포함됩니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // string 타입에 null를 넣는다.
      string node = null;
      // node의 값이 null이 아니면 에 공백을 제거한다.
      Console.WriteLine(node?.Trim());
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJkDHd/btriuVGIbir/sFNELWpRlYXkCuXHX3ek10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJkDHd/btriuVGIbir/sFNELWpRlYXkCuXHX3ek10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJkDHd/btriuVGIbir/sFNELWpRlYXkCuXHX3ek10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJkDHd%2FbtriuVGIbir%2FsFNELWpRlYXkCuXHX3ek10%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;결과는 node?.Trim()을 하면 사실 공백 없는 string의 값이 아니고 계속 null이 리턴됩니다. Console.WriteLine에서는 null값이 들어오면 공백없는 개행이 추가되네요.&lt;/p&gt;
&lt;p&gt;?의 의미는 null이 아니면 실행이라는 것을 알겠는데 null이라면 다른 처리를 하고 싶습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // string 타입에 null를 넣는다.
      string node = null;
      // node의 값이 null이 아니면 에 공백을 제거한다. null이라면 다른 string 값을 출력하라
      Console.WriteLine(node?.Trim()??&quot;This is null!&quot;);
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFW8Pj/btriuUHNfBa/By21SdyrJkrTKBvMgKd611/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFW8Pj/btriuUHNfBa/By21SdyrJkrTKBvMgKd611/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFW8Pj/btriuUHNfBa/By21SdyrJkrTKBvMgKd611/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFW8Pj%2FbtriuUHNfBa%2FBy21SdyrJkrTKBvMgKd611%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위에서 node?.Trim()은 node가 null이기 때문에 null이라고 이야기했습니다. ??는 node?.Trim()가 null이기 때문에 다른 값으로 치환하게 됩니다. 즉, 만약 node에 값이 있다면 그 값이 출력이 되는 것입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // string 타입에 문자열을 넣는다.
      string node = &quot;Hello world      &quot;;
      // node의 값이 null이 아니면 에 공백을 제거한다.
      Console.WriteLine(node?.Trim()??&quot;This is null!&quot;);
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9NMzM/btriuUHNfOn/g3Y58QRH2Ku1rqq7LDbDdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9NMzM/btriuUHNfOn/g3Y58QRH2Ku1rqq7LDbDdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9NMzM/btriuUHNfOn/g3Y58QRH2Ku1rqq7LDbDdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9NMzM%2FbtriuUHNfOn%2Fg3Y58QRH2Ku1rqq7LDbDdK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;여기까지 C#의 값의 초기화 및 기본 데이터 값(default)를 설정하는 방법 그리고 원시 데이터의 null 처리, ?와 ??의 사용법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/140</guid>
      <comments>https://nowonbun.tistory.com/140#entry140comment</comments>
      <pubDate>Thu, 21 Oct 2021 17:26:01 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 54. namespace와 using 그리고 partial의 사용법</title>
      <link>https://nowonbun.tistory.com/129</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#의 namespace와 using 그리고 partial의 사용법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;namespace&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;C#에서의 가장 최소 단위의 객체(Object)를 클래스라고 설명했습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/80&quot; target=&quot;_blank&quot;&gt;[C#] 09. 클래스 생성하는 방법(생성자, 소멸자)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;우리가 실행 함수(Main)를 사용하더라도 그 함수는 반드시 클래스에 포함되어야 합니다. 즉, 함수 만을 사용할 수 없습니다.&lt;/p&gt;
&lt;p&gt;그래서 우리가 프로그램을 다루다 보면 많은 라이브러리를 연결하고 많은 소스를 작성하게 됩니다. 그럴 경우, 클래스 명이 모두 다르게 작성할 수 있을까? 참고로 클래스는 오버로드(overload 같은 이름의 다른 처리)가 적용되지 않습니다.&lt;/p&gt;
&lt;p&gt;같은 이름의 클래스를 작성하면 반드시 에러가 발생합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vcysn/btriuVGHFGZ/pe6n4fjUyF6LOYZkqw2Uw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vcysn/btriuVGHFGZ/pe6n4fjUyF6LOYZkqw2Uw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vcysn/btriuVGHFGZ/pe6n4fjUyF6LOYZkqw2Uw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvcysn%2FbtriuVGHFGZ%2Fpe6n4fjUyF6LOYZkqw2Uw0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그렇다면 우리가 프로그램을 작성할 때는 그것을 조심하게 작성한다고 해도, 오픈 라이브러리를 연결해 쓸 때, 그 라이브러리 안에 있는 클래스 명이 절대 중복되지 않을까? 가능성이 충분히 있습니다.&lt;/p&gt;
&lt;p&gt;그럼 우리는 라이브러리를 연결해야 사용해야 하는데 클래스가 중복되어 실행이 되지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그것을 방지하기 위해 namespace가 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
// Test1의 이름인 namespace
namespace Test1
{
  // Node 클래스
  class Node
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Test1.Node&quot;);
    }
  }
}
// Test2의 이름인 namespace
namespace Test2
{
  // Node 클래스
  class Node
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Test2.Node&quot;);
    }
  }
}
// Example의 이름인 namespace
namespace Example
{
  // 실행 함수가 있는 클래스
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Test1의 namespace 안에 있는 Node 클래스
      var test1 = new Test1.Node();
      // 함수 실행
      test1.Print();
      // Test2의 namespace 안에 있는 Node 클래스
      var test2 = new Test2.Node();
      // 함수 실행
      test2.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/REVzR/btriop33BIt/d59sHiUY4q5BjsV5mxSD21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/REVzR/btriop33BIt/d59sHiUY4q5BjsV5mxSD21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/REVzR/btriop33BIt/d59sHiUY4q5BjsV5mxSD21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FREVzR%2Fbtriop33BIt%2Fd59sHiUY4q5BjsV5mxSD21%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;namespace를 두개로 나누어서 class를 생성하면 같은 class 이름이라도 생성이 가능합니다. 정확히는 Test1.Node 이름과 Test2.Node 이름이기 때문에 다른 이름입니다.&lt;/p&gt;
&lt;p&gt;이렇게 각자의 namespace를 유니크로 지정해 놓으면 다른 라이브러리와 클래스 이름이 충돌할 일이 줄어듭니다.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;using&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;그런데 이 namespace는 중복 선언이 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
// A의 이름인 namespace
namespace A
{
  // B의 이름인 namespace
  namespace B
  {
    // C의 이름인 namespace
    namespace C
    {
      // 클래스
      class Node
      {
        // 콘솔 출력 함수
        public void Print()
        {
          // 콘솔 출력
          Console.WriteLine(&quot;Hello world&quot;);
        }
      }
    }
  }
}
// Example의 이름인 namespace
namespace Example
{
  // 실행 함수가 있는 클래스
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // A의 namespace 안에, B의 namespace 안에, C의 namespace 안에 있는 Node 클래스(namespace가 생략됨)
      var test1 = new A.B.C.Node();
      // 함수 실행
      test1.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AQKg4/btriuiIS3fN/EFuwiZDwCPMakxLogPgKo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AQKg4/btriuiIS3fN/EFuwiZDwCPMakxLogPgKo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AQKg4/btriuiIS3fN/EFuwiZDwCPMakxLogPgKo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAQKg4%2FbtriuiIS3fN%2FEFuwiZDwCPMakxLogPgKo0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제에서는 namespace를 A,B,C로 표현했지만 만약 이 명명이 길다면, 인스턴스를 생성할 때, 구문이 길어 지겠네요.&lt;/p&gt;
&lt;p&gt;그것을 축약하기 위해서 using 키워드를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
// A의 이름인 namespace
namespace A
{
  // B의 이름인 namespace
  namespace B
  {
    // C의 이름인 namespace
    namespace C
    {
      // 클래스
      class Node
      {
        // 콘솔 출력 함수
        public void Print()
        {
          // 콘솔 출력
          Console.WriteLine(&quot;Hello world&quot;);
        }
      }
    }
  }
}
// Example의 이름인 namespace
namespace Example
{
  // 여기 이후의 소스는 A.B.C의 네임스페이스 생략이 가능하다.
  using A.B.C;
  // 실행 함수가 있는 클래스
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // A의 namespace 안에, B의 namespace 안에, C의 namespace 안에 있는 Node 클래스
      var test1 = new Node();
      // 함수 실행
      test1.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3neuO/btriq7axjoS/bo0FogTs61U0tErNSVxHs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3neuO/btriq7axjoS/bo0FogTs61U0tErNSVxHs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3neuO/btriq7axjoS/bo0FogTs61U0tErNSVxHs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3neuO%2Fbtriq7axjoS%2Fbo0FogTs61U0tErNSVxHs1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;using을 사용해서 namespace를 지정하면 using을 작성한 코드 라인 이후에는 A.B.C를 추가하지 않아도 A.B.C의 클래스를 사용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;위 예제에서는 제가 설명하기 쉽게 하나의 페이지로 설명했으나 보통은 클래스 별로 파일을 나누고 namespace별로 폴더를 구분하기 때문에 using을 소스의 가장 최상단에 표기합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇다면 최초의 예제처럼 같은 이름의 클래스는 어떻게 처리할까?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
// Test1의 이름인 namespace
namespace Test1
{
  // Node 클래스
  class Node
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Test1.Node&quot;);
    }
  }
}
// Test2의 이름인 namespace
namespace Test2
{
  // Node 클래스
  class Node
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Test2.Node&quot;);
    }
  }
}
// Example의 이름인 namespace
namespace Example
{
  // 여기 이후의 소스는 Test1의 네임스페이스 생략이 가능하다.
  using Test1;
  // 여기 이후의 소스는 Test2의 네임스페이스 생략이 가능하다.
  using Test2;
  // 실행 함수가 있는 클래스
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Test1의 namespace 안에 있는 Node 클래스
      var test1 = new Test1.Node();
      // 함수 실행
      test1.Print();
      // Test2의 namespace 안에 있는 Node 클래스
      var test2 = new Test2.Node();
      // 함수 실행
      test2.Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8thOM/btripEGruw0/ku31dJukl0NyBO1SQtFwwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8thOM/btripEGruw0/ku31dJukl0NyBO1SQtFwwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8thOM/btripEGruw0/ku31dJukl0NyBO1SQtFwwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8thOM%2FbtripEGruw0%2Fku31dJukl0NyBO1SQtFwwk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그렇습니다. 이 경우에는 Node의 클래스 구분이 되지 않기 때문에 using를 사용했어도 앞에 namespace 이름을 넣어야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 클래스가 static으로 이루어져 있고 클래스의 안에 함수가 모두 static으로 이루어져 있을 경우도 있습니다.&lt;/p&gt;
&lt;p&gt;일명 유틸 함수 클래스라고도 이야기하죠.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
// Util의 이름인 namespace
namespace Util
{
  // Common 정적 클래스
  static class Common
  {
    // 콘솔 출력 함수
    public static void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Hello world&quot;);
    }
  }
}
// Example의 이름인 namespace
namespace Example
{
  // Util namespace의 Common 클래스
  using static Util.Common;
  using static System.Console;
  // 실행 함수가 있는 클래스
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // using static이 선언되었기 때문에 내부 함수처럼 사용 가능하다.
      Print();

      // 아무 키나 누르시면 종료합니다.
      WriteLine(&quot;Press any key...&quot;);
      ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUkmt6/btriq6imPYS/rAJIUD6Yt5hRRaPLN6w6BK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUkmt6/btriq6imPYS/rAJIUD6Yt5hRRaPLN6w6BK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUkmt6/btriq6imPYS/rAJIUD6Yt5hRRaPLN6w6BK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUkmt6%2Fbtriq6imPYS%2FrAJIUD6Yt5hRRaPLN6w6BK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그렇습니다. namespace와 클래스 명까지 생략이 가능합니다.&lt;/p&gt;
&lt;p&gt;아래에 우리가 습관처럼 사용했던 Console.WriteLine도 Console를 생략했습니다.&lt;/p&gt;
&lt;h2&gt;&lt;b&gt;&lt;span&gt;partial&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;원래는 명명법에 의해 namespace는 폴더명, class는 파일 명과 일치시켜야 하는 규약이 있습니다.&lt;/p&gt;
&lt;p&gt;하나의 파일에 모든 소스 내용을 작성하지는 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런데 하나의 파일에 하나의 클래스를 작성한다고 해도 사양에 따라 그 클래스의 크기가 엄청나게 길어지는 경우도 있습니다. 클래스 하나가 몇만줄 할 수도 있다는 뜻입니다.&lt;/p&gt;
&lt;p&gt;그럴 경우, 마찬가지로 가독성이 매우 떨어지기 때문에 파일을 분할해서 클래스를 작성하고 싶은 경우도 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.1.cs&quot;&gt;using System;
namespace Example
{
  // Program 클래스 분할
  partial class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 함수 호출
      Print();

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.2.cs&quot;&gt;using System;
namespace Example
{
  // Program 클래스 분할
  partial class Program
  {
    // 콘솔 출력 함수
    public static void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Hello world&quot;);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ldv7n/btrit5bZjxV/4tqz6qQ00KL8WC48SLYKN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ldv7n/btrit5bZjxV/4tqz6qQ00KL8WC48SLYKN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ldv7n/btrit5bZjxV/4tqz6qQ00KL8WC48SLYKN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLdv7n%2Fbtrit5bZjxV%2F4tqz6qQ00KL8WC48SLYKN1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;partial 키워드를 사용하면 두개의 파일에 하나의 클래스를 분할해서 작성하는 것이 가능합니다. 여기서 주의할 점은 partial이 없는 클래스에 partial를 사용해서 클래스를 확장한다는 개념은 아닙니다.&lt;/p&gt;
&lt;p&gt;그러니깐 String 클래스에 partial class String을 사용한다고 해도 확장이 되는 개념이 아닙니다. 사실 클래스가 맘대로 확장이 되면 안되는 것이지요. 반대로 이야기하면 partial 키워드를 사용하면 어디서든 확장이 된다는 개념입니다.&lt;/p&gt;
&lt;p&gt;즉, 라이브러리 배포용(dll)을 개발할 때에는 사용을 주의해야 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#의 namespace와 using 그리고 partial의 사용법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <category>c#</category>
      <category>MSDN</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/129</guid>
      <comments>https://nowonbun.tistory.com/129#entry129comment</comments>
      <pubDate>Thu, 21 Oct 2021 17:23:18 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 53. Reflection 기능을 사용하는 방법 - Attribute</title>
      <link>https://nowonbun.tistory.com/496</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 Reflection 기능을 사용하는 방법 - Attribute에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;지금까지 Reflection 기능을 소개했는데, 간단하게 요약하면 소스에 클래스 할당이나 함수 호출하는 정적인 방법을 데이터등에 의해 동적으로 클래스가 할당되거나 호출을 호출하는 방법입니다.&lt;/p&gt;
&lt;p&gt;사실 C#에는 dynamic 자료형이 있고, 여러가지 패턴에 의한 프레임워크를 만들거나 코어 클래스를 만드는 것이 아니면 사실 사용할 일은 많이 있지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런데 Reflection 기능 중에서 Attribute에 의한 클래스 구분이나 메소드 함수 호출 방식은 정말 많이 사용합니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/128&quot; target=&quot;_blank&quot;&gt;[C#] 31. 어트리뷰트(Attribute)를 사용하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;C#의 어트리뷰트(Attribute)는 메타 데이터로서의 역할 뿐인데, Reflection의 기능과 같이 사용되면 단순 메타 데이터의 기능이 아닌 프로그램을 제어하는 기능으로 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

// Class 타입의 어트리뷰트 생성
[AttributeUsage(AttributeTargets.Class)]
class DefaultData : Attribute
{
  // 어트리뷰트 필드 데이터
  public String Name { get; set; }
  // 생성자
  public DefaultData(String name)
  {
    this.Name = name;
  }
}

// Node1 클래스에 DefaultData 어트리뷰트 설정
[DefaultData(&quot;Process1&quot;)]
class Node1
{

}

// Node2 클래스에 DefaultData 어트리뷰트 설정
[DefaultData(&quot;Process2&quot;)]
class Node2
{

}
class Program
{
  static void Main(string[] args)
  {
    // Node1 클래스의 DefaultData 어트리뷰트 취득
    foreach (var attr in typeof(Node1).GetCustomAttributes(typeof(DefaultData), false) as DefaultData[])
    {
      // 콘솔 출력
      Console.WriteLine(attr.Name);
    }
    // Node2 클래스의 DefaultData 어트리뷰트 취득
    foreach (var attr in typeof(Node2).GetCustomAttributes(typeof(DefaultData), false) as DefaultData[])
    {
      // 콘솔 출력
      Console.WriteLine(attr.Name);
    }

    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YZiJ7/btriiuqXqnJ/fqjG5FsCi9nblsnEpkCpc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YZiJ7/btriiuqXqnJ/fqjG5FsCi9nblsnEpkCpc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YZiJ7/btriiuqXqnJ/fqjG5FsCi9nblsnEpkCpc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYZiJ7%2FbtriiuqXqnJ%2FfqjG5FsCi9nblsnEpkCpc0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 간단하게 Node1 클래스와 Node2 클래스의 어트리뷰트를 취득하는 예제입니다. 다른 Reflection 예제와 별반 차이가 없어보입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Linq;

// Class 타입의 어트리뷰트 생성
[AttributeUsage(AttributeTargets.Class)]
class DefaultData : Attribute
{
  // 어트리뷰트 필드 데이터
  public String Name { get; set; }
  // 생성자
  public DefaultData(String name)
  {
    this.Name = name;
  }
}
// Method 타입의 어트리뷰트 생성
[AttributeUsage(AttributeTargets.Method)]
class RunFlag : Attribute
{

}

// Node 클래스에 DefaultData 어트리뷰트 설정
[DefaultData(&quot;Hello world&quot;)]
class Node
{
  // Print 함수에 RunFlag 어트리뷰트 추가
  [RunFlag]
  public void Run()
  {
    Print();
  }
  public void Print()
  {
    // 클래스 객체의 타입을 가져온다.
    Type clz = this.GetType();
    // 클래스의 어트리뷰트를 가져온다.
    foreach (dynamic obj in clz.GetCustomAttributes(false))
    {
      // 어트리뷰트 중에 DefaultData 타입이면,
      if (obj.GetType() == typeof(DefaultData))
      {
        // 설정 Name값을 출력한다.
        Console.WriteLine(obj.Name);
      }
    }
  }
}
class Program
{
  static void Main(string[] args)
  {
    // 인스턴스 생성
    var node = new Node();
    // Node 클래스의 메서드의
    typeof(Node).GetMethods()
          // 어트리뷰트가 RunFlag가 있는 함수를 필터
          .Where(x =&amp;gt; x.GetCustomAttributes(false).Where(y =&amp;gt; y.GetType() == typeof(RunFlag)).Any())
          // 리스트로 출력한 후
          .ToList()
          .ForEach(x =&amp;gt;
          {
            // 함수명 출력
            Console.WriteLine(&quot;Execute - &quot; + x.Name);
            // 함수를 실행
            x.Invoke(node, null);
          });
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dbq5m/btrio3dLOyK/Z8odDKwzx2DLmmKGnvAy2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dbq5m/btrio3dLOyK/Z8odDKwzx2DLmmKGnvAy2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dbq5m/btrio3dLOyK/Z8odDKwzx2DLmmKGnvAy2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDbq5m%2Fbtrio3dLOyK%2FZ8odDKwzx2DLmmKGnvAy2K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실제로는 이 어트리뷰트의 타입으로 함수의 실행 여부나 순서를 설정하는 경우가 많이 있습니다.&lt;/p&gt;
&lt;p&gt;예를 들면, 웹 프로젝트에서 어트리뷰트를 설정하면, 접속하는 유저의 권한에 따라 함수를 실행할 수 있는 지 없는 지에 대한 검사, 데이터에 따른 함수의 실행 순서를 설정 등을 할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Linq;

// Method 타입의 어트리뷰트 생성
[AttributeUsage(AttributeTargets.Method)]
class ExecuteOrder : Attribute
{
  // 순서 맴버 변수
  public int Order { get; set; }
  // 생성자 (필수 설정)
  public ExecuteOrder(int order)
  {
    this.Order = order;
  }
}
// 예제 클래스
class Node
{
  // 함수에 어트리뷰트 설정
  [ExecuteOrder(3)]
  public void Test1()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Test1&quot;);
  }
  // 함수에 어트리뷰트 설정
  [ExecuteOrder(1)]
  public void Test2()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Test2&quot;);
  }
  // 함수에 어트리뷰트 설정
  [ExecuteOrder(4)]
  public void Test3()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Test3&quot;);
  }
  // 함수에 어트리뷰트 설정
  [ExecuteOrder(2)]
  public void Test4()
  {
    // 콘솔 출력
    Console.WriteLine(&quot;Test4&quot;);
  }
}
class Program
{
  static void Main(string[] args)
  {
    // 인스턴스 생성
    var node = new Node();
    // Node 클래스의 메서드의
    typeof(Node).GetMethods()
          // 어트리뷰트가 RunFlag가 있는 함수를 필터
          .Where(x =&amp;gt; x.GetCustomAttributes(false).Where(y =&amp;gt; y.GetType() == typeof(ExecuteOrder)).Any())
          // 어트리뷰트의 Order 값에 따른 정렬
          .OrderBy(x =&amp;gt; (x.GetCustomAttributes(false).Where(y =&amp;gt; y.GetType() == typeof(ExecuteOrder)).First() as ExecuteOrder).Order)
          // 리스트로 출력한 후
          .ToList()
          .ForEach(x =&amp;gt;
          {
            // 함수명 출력
            Console.WriteLine(&quot;Execute - &quot; + x.Name);
            // 함수를 실행
            x.Invoke(node, null);
          });
    // 아무 키나 누르시면 종료합니다.
    Console.WriteLine(&quot;Press any key...&quot;);
    Console.ReadKey();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2aDCw/btrinsZDYWv/9JhhJX95N6fpUJd0ap68H1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2aDCw/btrinsZDYWv/9JhhJX95N6fpUJd0ap68H1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2aDCw/btrinsZDYWv/9JhhJX95N6fpUJd0ap68H1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2aDCw%2FbtrinsZDYWv%2F9JhhJX95N6fpUJd0ap68H1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 Node 클래스에 ExecuteOrder가 설정된 함수를 가져와서 실행 순서가 설정된 순서대로 함수가 실행됩니다.&lt;/p&gt;
&lt;p&gt;위 예제는 제가 임의로 어트리뷰트에 int형을 넣어서 순서를 설정했습니다만, 데이터 베이스나 유저의 입력되는 값에 의해 순서를 결정하게 되면 그것이 바로 인터프리터 패턴(interpreter pattern)이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;어트리뷰트는 위의 예제처럼 Reflection의 동적 실행을 위해서 사용하는 경우가 많이 있습니다. 사실 단순히 메타 데이터를 위한 것이라면 그냥 주석을 사용하는 게 더 깔끔합니다.&lt;/p&gt;
&lt;p&gt;Reflection과 어트리뷰트를 잘 이용한다면, 여러가지 디자인 패턴의 알고리즘을 작성할 수 있고, 크게는 이런식으로 프레임워크를 작성할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 Reflection 기능을 사용하는 방법 - Attribute에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/496</guid>
      <comments>https://nowonbun.tistory.com/496#entry496comment</comments>
      <pubDate>Wed, 20 Oct 2021 18:52:08 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 52. Reflection 기능을 사용하는 방법 - Property와 event</title>
      <link>https://nowonbun.tistory.com/495</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 Reflection 기능을 사용하는 방법 - Property와 event에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;C#의 Reflection에 대해서 Class와 Method, Variable(변수)에 대해서 설명했었습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/488&quot; target=&quot;_blank&quot;&gt;[C#] 49. Reflection 기능을 사용하는 방법 - Class&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/490&quot; target=&quot;_blank&quot;&gt;[C#] 50. Reflection 기능을 사용하는 방법 - Method&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/491&quot; target=&quot;_blank&quot;&gt;[C#] 51. Reflection 기능을 사용하는 방법 - Variable&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Reflection은 클래스의 구성요소에 대한 동적으로 데이터를 취득 및 호출하는 방법입니다.&lt;/p&gt;
&lt;p&gt;Java에서의 Class의 구성요소가 보통 함수와 맴버 변수 밖에 없지만 C#에서는 프로퍼티와 이벤트 등의 특수 기능(?)을 함수가 있습니다.&lt;/p&gt;
&lt;p&gt;프로퍼티는 함수 기능과 변수의 기능을 합친 요소이고 이벤트는 함수를 복수로 중첩하여 호출할 수 있는 기능입니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/405&quot; target=&quot;_blank&quot;&gt;[C#] 20. 프로퍼티 (Property)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/120&quot; target=&quot;_blank&quot;&gt;[C#] 24. 이벤트(event) 키워드 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 프로퍼티의 Reflection은 함수의 Reflection과 많이 비슷합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Reflection;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 맴버 변수
    private int data;
    // 프로퍼티
    public int Data
    {
      // set 함수
      set
      {
        // 콘솔 출력
        Console.WriteLine(&quot;Data property set&quot;);
        // 맴버 변수에 값을 입력
        this.data = value;
      }
      // set 함수
      get
      {
        // 콘솔 출력
        Console.WriteLine(&quot;Data property get&quot;);
        // 맴버 변수의 값을 리턴
        return this.data;
      }
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // Node 클래스의 type을 취득
      Type type = typeof(Node);
      // 프로퍼티 Data의 Reflection 정보를 취득
      PropertyInfo property = type.GetProperty(&quot;Data&quot;);
      // Data 프로퍼티 set 함수를 호출, 값을 10 입력
      property.SetValue(node, 10);
      // Data 프로퍼티 get 함수를 호출하여 값을 취득
      var value = property.GetValue(node);
      // 콘솔 출력
      Console.WriteLine(value);

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csx4W0/btricDH0mGq/jUyguHLOoI4RQ1YFUsopdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csx4W0/btricDH0mGq/jUyguHLOoI4RQ1YFUsopdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csx4W0/btricDH0mGq/jUyguHLOoI4RQ1YFUsopdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcsx4W0%2FbtricDH0mGq%2FjUyguHLOoI4RQ1YFUsopdk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 Node 클래스의 Data 프로퍼티의 set 함수와 get 함수를 호출하는 Reflection입니다.&lt;/p&gt;
&lt;p&gt;프로퍼티 자체가 OOP를 위한 get set 함수이기 때문에 함수의 Reflection과 크게 다른게 없습니다. 프로퍼티도 private를 설정할 수 있고, static로 설정할 수 있지만, 보통은 public으로 설정하고 public이 아니라도, 함수 Reflection과 같이 BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static의 옵션으로 취득할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;event의 경우는 함수를 중첩시켜서 한번에 호출하는 방식입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Reflection;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 델리게이트
    public event EventHandler ExecuteEvent;
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Node node = new Node();
      // Node 클래스의 type을 취득
      Type type = typeof(Node);
      // ExecuteEvent의 Event reflection를 취득
      EventInfo eventInfo = type.GetEvent(&quot;ExecuteEvent&quot;);
      // 이벤트에 추가 시킬 함수
      MethodInfo methodInfo = typeof(Program).GetMethod(&quot;Print_Event&quot;, BindingFlags.NonPublic | BindingFlags.Static);
      // 함수를 Delegate로 변환
      Delegate d = Delegate.CreateDelegate(eventInfo.EventHandlerType, null, methodInfo);
      // 이벤트에 추가
      eventInfo.AddEventHandler(node, d);
      // 이벤트에 추가
      eventInfo.AddEventHandler(node, d);
      // 이벤트에 추가
      eventInfo.AddEventHandler(node, d);
      // ExecuteEvent 이벤트에 대한 실행 필드 취득
      var backingField = typeof(Node).GetField(&quot;ExecuteEvent&quot;, BindingFlags.Instance | BindingFlags.NonPublic);
      // node 인스턴스로 부터 핸들러 취득
      var handler = (EventHandler)backingField.GetValue(node);
      // 실행
      handler(&quot;Execute&quot;, new EventArgs());

      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadLine();
    }
    // 콘솔 출력 이벤트
    static void Print_Event(object sender, EventArgs e)
    {
      // 콘솔 출력
      Console.WriteLine(sender);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q3BWg/btrigH3IWFW/eNZ2glj5IAzXSYFNHi9JK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q3BWg/btrigH3IWFW/eNZ2glj5IAzXSYFNHi9JK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q3BWg/btrigH3IWFW/eNZ2glj5IAzXSYFNHi9JK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQ3BWg%2FbtrigH3IWFW%2FeNZ2glj5IAzXSYFNHi9JK1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제에서는 GetEvent 함수를 통해서 Node 클래스의 event를 취득합니다.&lt;/p&gt;
&lt;p&gt;이벤트에서는 Delegate 형식으로 이벤트를 추가하게 되는데, Delegate.CreateDelegate를 통해서 함수를 델리게이트(함수 포인터) 형식으로 변환해야 합니다.&lt;/p&gt;
&lt;p&gt;그리고 다시 event reflection를 통해서 이벤트를 추가합니다. 저는 3번을 추가 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;사실 이벤트는 클래스 내부에서 실행해야 합니다. 하지만 그렇게 하면 Reflection이 아니죠. 외부에서 event를 호출할 수 있게 실행 Field 데이터를 취득해야 합니다. 이 부분이 재미있는게 여기에서는 Node 클래스에서 event를 멤버 변수처럼 작성했는데 멤버 변수로 인식을 합니다.&lt;/p&gt;
&lt;p&gt;접근 제한자가 public인데 private로 인식이 됩니다. 즉, Field를 취득해서 EventHandler의 델리게이트 형식으로 강제 캐스팅을 하면 함수처럼 호출이 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 Reflection 기능을 사용하는 방법 - Property와 event에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <category>c#</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/495</guid>
      <comments>https://nowonbun.tistory.com/495#entry495comment</comments>
      <pubDate>Tue, 19 Oct 2021 20:47:07 +0900</pubDate>
    </item>
    <item>
      <title>[Centos]  CentOs에서 젠킨스(Jenkins)를 설치하는 방법</title>
      <link>https://nowonbun.tistory.com/154</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 CentOs에서 젠킨스(Jenkins)를 설치하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;젠킨스(Jenkins)란 소프트웨어 개발 할 때에 사용되는 통합 서비스를 하는 툴, 즉 CI(Continuous Integration)이라고 합니다.&lt;/p&gt;
&lt;p&gt;통합 서비스를 하는 툴이라는 뜻은 우리가 프로젝트 개발을 할 때, 한명이 아닌 다수의 인원과 개발을 할 때, 하나의 브런치로 통합하여 업로드하는 툴을 이야기합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 소스를 통합해주는 툴로서는 최근에 자주 사용되는 Git이 있고 SVN등등이 있습니다. 이건 소스를 통합해주는 툴로써 CI툴과는 차이가 있습니다.&lt;/p&gt;
&lt;p&gt;각자의 로컬 PC에서 개발을 하면 우리가 Git을 통해서 커밋 및 Pull request를 진행합니다. 하지만 이건 각자의 환경에서 개발한 것이므로 모두의 환경에서 통합이 된 것이라고 할 수는 없습니다.&lt;/p&gt;
&lt;p&gt;즉, 내 로컬에서는 오류가 없었는데, 막상 환경이 다른 개발 환경(Dev env)아니 실 서버(Prod env)에 가면 에러가 발생하는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;대표적으로 데이터 베이스 접속 설정이나 파일 경로의 차이가 있을 수 있겠네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 이 젠킨스를 통해서 통합 환경에서 재 Deploy를 하고 최종적으로 서버스를 안전하게 구동하기 위한 툴로 생각하면 좋습니다.&lt;/p&gt;
&lt;p&gt;사실 설명은 이런데.. 위의 에러 처리까지 다 작성하려면 꽤나 복잡한 작업을 통해야 하고, 그냥 소스를 재통합해서 해당 서버에서 재빌드 후 Deploy하는 기능으로 보면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 Centos에 이 Jenkins를 설치하는 방법에는 두가지 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;yum을 통해서 install하는 방법이 있고 소스를 그대로 Deploy해서 사용하는 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;저도 안해봐서 자세히 설명하기는 어렵지만 yum을 통해서 하는 방법은 설치는 편한데 권한 설정이 조금 복잡하더라고요. 그래서 저는 여기서 그냥 소스를 받아와서 설치하는 방법으로 하겠습니다.(제 개발서버는 한개라 두번을 하기가 힘들어요.)&lt;/p&gt;
&lt;p&gt;Jenkins를 설치하기 위해서는 java가 필요하고 tomcat과 git, 그리고 maven를 설치해야 합니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/137&quot; target=&quot;_blank&quot;&gt;[CentOS] Java 설치하기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/138&quot; target=&quot;_blank&quot;&gt;[CentOS] tomcat 설치하기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;git과 maven은 따로 글이 없기 때문에 설치하는 방법을 소개하겠습니다.&lt;/p&gt;
&lt;p&gt;git은 그냥 yum으로 설치합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell&quot; data-type=&quot;yum install&quot;&gt;yum install git
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DCUr7/btrh9CgZhoN/lfq7g0Pbt99QkoVCOMoEv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DCUr7/btrh9CgZhoN/lfq7g0Pbt99QkoVCOMoEv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DCUr7/btrh9CgZhoN/lfq7g0Pbt99QkoVCOMoEv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDCUr7%2Fbtrh9CgZhoN%2Flfq7g0Pbt99QkoVCOMoEv0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;maven은 yum으로 설치하면 조금 귀찮은 일이 많이 생기기 때문에 그냥 소스를 받아서 압축을 풀고 PATH 설정을 합니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://maven.apache.org/download.cgi&quot; target=&quot;_blank&quot;&gt;https://maven.apache.org/download.cgi&lt;/a&gt;
&lt;pre&gt;&lt;code class=&quot;shell&quot; data-type=&quot;maven install&quot;&gt;## 다운로드
wget https://dlcdn.apache.org/maven/maven-3/3.8.3/binaries/apache-maven-3.8.3-bin.tar.gz　--no-check-certificate
## 압축풀기
tar -zxvf apache-maven-3.8.3-bin.tar.gz
## 심복릭 링크 설정
ln -s apache-maven-3.8.3 maven
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HPpQq/btrh5YykROk/S6tkkRUcTas8XNFie6TbYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HPpQq/btrh5YykROk/S6tkkRUcTas8XNFie6TbYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HPpQq/btrh5YykROk/S6tkkRUcTas8XNFie6TbYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHPpQq%2Fbtrh5YykROk%2FS6tkkRUcTas8XNFie6TbYK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 마지막으로 /etc/profile의 환경설정입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;shell&quot; data-type=&quot;/etc/profile&quot;&gt;export MAVEN_HOME=/usr/local/maven
PATH=$PATH:$HOME/bin:$MAVEN_HOME/bin

export PATH
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MtcID/btrh3qvlF7J/qdAW2AcgbnDVMf5PGkPWD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MtcID/btrh3qvlF7J/qdAW2AcgbnDVMf5PGkPWD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MtcID/btrh3qvlF7J/qdAW2AcgbnDVMf5PGkPWD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMtcID%2Fbtrh3qvlF7J%2FqdAW2AcgbnDVMf5PGkPWD0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7M32w/btriaTPPuiI/t3nmZlqRsOfTpa6tm8Osv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7M32w/btriaTPPuiI/t3nmZlqRsOfTpa6tm8Osv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7M32w/btriaTPPuiI/t3nmZlqRsOfTpa6tm8Osv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7M32w%2FbtriaTPPuiI%2Ft3nmZlqRsOfTpa6tm8Osv1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이렇게 java, tomcat, git과 maven을 설치 했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 Jenkins 홈페이지에 가서 Jenkins를 다운로드 받습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://www.jenkins.io/download/&quot; target=&quot;_blank&quot;&gt;https://www.jenkins.io/download/&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZYlaJ/btriaVUpsQl/1rhFtBKuQbr7ahnScgkN81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZYlaJ/btriaVUpsQl/1rhFtBKuQbr7ahnScgkN81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZYlaJ/btriaVUpsQl/1rhFtBKuQbr7ahnScgkN81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZYlaJ%2FbtriaVUpsQl%2F1rhFtBKuQbr7ahnScgkN81%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;최신 버전을 받아도 되는데 저는 조금 더 안정적인 LTS 버전을 다운 받겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEg0i4/btriadHLFoi/ZIRD3c9FKjWKXevBZHDv9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEg0i4/btriadHLFoi/ZIRD3c9FKjWKXevBZHDv9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEg0i4/btriadHLFoi/ZIRD3c9FKjWKXevBZHDv9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEg0i4%2FbtriadHLFoi%2FZIRD3c9FKjWKXevBZHDv9k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 webapps로 가서 ROOT 폴더 안을 전부 지우고 파일을 이동시킵니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbA5M3/btrh2helpx1/5iL5Ep9JLGy2TqzWuuLyW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbA5M3/btrh2helpx1/5iL5Ep9JLGy2TqzWuuLyW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbA5M3/btrh2helpx1/5iL5Ep9JLGy2TqzWuuLyW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbA5M3%2Fbtrh2helpx1%2F5iL5Ep9JLGy2TqzWuuLyW1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이제 jar xvf 명령어로 war 파일의 압축을 풉니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EkRfD/btrh2hSXaZD/RQ8pLZqVKedC5JckmYWaV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EkRfD/btrh2hSXaZD/RQ8pLZqVKedC5JckmYWaV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EkRfD/btrh2hSXaZD/RQ8pLZqVKedC5JckmYWaV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEkRfD%2Fbtrh2hSXaZD%2FRQ8pLZqVKedC5JckmYWaV1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이제 압축이 풀렸으면 tomcat을 기동합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boF2L6/btriaUgTWui/KtVEM9FkMdWVtDaiC4SkN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boF2L6/btriaUgTWui/KtVEM9FkMdWVtDaiC4SkN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boF2L6/btriaUgTWui/KtVEM9FkMdWVtDaiC4SkN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboF2L6%2FbtriaUgTWui%2FKtVEM9FkMdWVtDaiC4SkN1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;저는 설치 중에 에러가 발생했네요...&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eti63K/btrh5YrxNfu/SKHDZX99HRvuR4IxuEKDck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eti63K/btrh5YrxNfu/SKHDZX99HRvuR4IxuEKDck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eti63K/btrh5YrxNfu/SKHDZX99HRvuR4IxuEKDck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feti63K%2Fbtrh5YrxNfu%2FSKHDZX99HRvuR4IxuEKDck%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;사실 예전에 제가 할떄는 catalina.sh에 JAVA_OPTS=&quot;$JAVA_OPTS -Djava.awt.headless=true&quot;만 추가하면 해결이 됐는데.. 지금은 도저히 안되네요..&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wBGY6/btriacPDvf3/FxIJsmyV8cwYGAKpSxT741/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wBGY6/btriacPDvf3/FxIJsmyV8cwYGAKpSxT741/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wBGY6/btriacPDvf3/FxIJsmyV8cwYGAKpSxT741/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwBGY6%2FbtriacPDvf3%2FFxIJsmyV8cwYGAKpSxT741%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그러던 중 발견한 블로그..&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://it00.tistory.com/39&quot; target=&quot;_blank&quot;&gt;https://it00.tistory.com/39&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;openjdk 8과 11에서만 동작한다고...;;;&lt;/p&gt;
&lt;p&gt;그래서 openjdk를 버전 다운 시켰습니다.. 감사합니다...&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYN7uF/btrh3ppFjhn/C2o8itl8kK997EqAziEex1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYN7uF/btrh3ppFjhn/C2o8itl8kK997EqAziEex1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYN7uF/btrh3ppFjhn/C2o8itl8kK997EqAziEex1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYN7uF%2Fbtrh3ppFjhn%2FC2o8itl8kK997EqAziEex1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;자바가 요즘에 버전마다 되는게 있고 안되는게 있고 이게 너무 심해서... 버전업 한 번 할때마다.. 머리털이 다 빠진다는...&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;다시 기동했습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l2Jzh/btrh3p4eCer/dfNrOFQYWYOSlVndJJQGB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l2Jzh/btrh3p4eCer/dfNrOFQYWYOSlVndJJQGB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l2Jzh/btrh3p4eCer/dfNrOFQYWYOSlVndJJQGB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl2Jzh%2Fbtrh3p4eCer%2FdfNrOFQYWYOSlVndJJQGB1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;정상 작동하네요.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EEj6v/btrh50iznAM/0szTQfH8SpLSwEb5vKTszK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EEj6v/btrh50iznAM/0szTQfH8SpLSwEb5vKTszK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EEj6v/btrh50iznAM/0szTQfH8SpLSwEb5vKTszK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEEj6v%2Fbtrh50iznAM%2F0szTQfH8SpLSwEb5vKTszK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이제 저 위치에 가보면 패스워드가 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pc9EY/btrh2idhwz5/TCqhvGglvt3unbk9XFrB70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pc9EY/btrh2idhwz5/TCqhvGglvt3unbk9XFrB70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pc9EY/btrh2idhwz5/TCqhvGglvt3unbk9XFrB70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpc9EY%2Fbtrh2idhwz5%2FTCqhvGglvt3unbk9XFrB70%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;입력을 하면 플러그인을 설치하라고 나옵니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsH8Hq/btrh3pb5xLH/sWJVGd1iy7KOhR3jyJxE01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsH8Hq/btrh3pb5xLH/sWJVGd1iy7KOhR3jyJxE01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsH8Hq/btrh3pb5xLH/sWJVGd1iy7KOhR3jyJxE01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsH8Hq%2Fbtrh3pb5xLH%2FsWJVGd1iy7KOhR3jyJxE01%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;플러그인은 따로 설치가 가능하니깐 일단 제안하는 플러그인만 설치합시다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbk9ad/btrh3pDeuM0/YYgkkZQRTvzUaY9YjUakq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbk9ad/btrh3pDeuM0/YYgkkZQRTvzUaY9YjUakq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbk9ad/btrh3pDeuM0/YYgkkZQRTvzUaY9YjUakq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbk9ad%2Fbtrh3pDeuM0%2FYYgkkZQRTvzUaY9YjUakq1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그러면 처음 관리자 아이디와 패스워드 설정화면이 나옵니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxHp6W/btrh7xAutDg/yOx5a6MkOKxARfP45jHIU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxHp6W/btrh7xAutDg/yOx5a6MkOKxARfP45jHIU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxHp6W/btrh7xAutDg/yOx5a6MkOKxARfP45jHIU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxHp6W%2Fbtrh7xAutDg%2FyOx5a6MkOKxARfP45jHIU1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 URL 설정인데, 도메인을 가지고 있으면 도메인 명을 넣으면 되고, 저는 테스트 서버이기 때문에 그냥 아이피를 넣겠습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X7OND/btrh9QlJ64v/b8X5FAjfFdPgValXdPhxq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X7OND/btrh9QlJ64v/b8X5FAjfFdPgValXdPhxq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X7OND/btrh9QlJ64v/b8X5FAjfFdPgValXdPhxq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX7OND%2Fbtrh9QlJ64v%2Fb8X5FAjfFdPgValXdPhxq0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;설정이 완료되었습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbEX9c/btrh9R516J8/K1FlXBMN2Rvxon0S9ivb7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbEX9c/btrh9R516J8/K1FlXBMN2Rvxon0S9ivb7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbEX9c/btrh9R516J8/K1FlXBMN2Rvxon0S9ivb7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbEX9c%2Fbtrh9R516J8%2FK1FlXBMN2Rvxon0S9ivb7k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이제 github를 연결해서 작성해 보겠습니다.&lt;/p&gt;
&lt;p&gt;본인 github에 접속해서 프로그램 토큰 키를 가져와야 합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I9Guy/btrh9C86O04/kytK0SIwKXJid74w5PucLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I9Guy/btrh9C86O04/kytK0SIwKXJid74w5PucLk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I9Guy/btrh9C86O04/kytK0SIwKXJid74w5PucLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI9Guy%2Fbtrh9C86O04%2FkytK0SIwKXJid74w5PucLk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Settings 메뉴로 이동해서  Developer settings로 이동합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNTn2N/btrh9RLJkhP/tmK3JRR6afVQEDKK9r24ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNTn2N/btrh9RLJkhP/tmK3JRR6afVQEDKK9r24ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNTn2N/btrh9RLJkhP/tmK3JRR6afVQEDKK9r24ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNTn2N%2Fbtrh9RLJkhP%2FtmK3JRR6afVQEDKK9r24ak%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 Personal access tokens 메뉴로 이동해서 토큰을 하나 생성합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pROBJ/btrh8DNWJjo/yKK1o20Nu6kP7YQz6qBBQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pROBJ/btrh8DNWJjo/yKK1o20Nu6kP7YQz6qBBQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pROBJ/btrh8DNWJjo/yKK1o20Nu6kP7YQz6qBBQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpROBJ%2Fbtrh8DNWJjo%2FyKK1o20Nu6kP7YQz6qBBQ1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Note에 토큰 이름을 작성하고 repo와 hook, org를 선택합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRDcCB/btrh7ye5irL/yjsrdXOQDWzfpoexUxgFmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRDcCB/btrh7ye5irL/yjsrdXOQDWzfpoexUxgFmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRDcCB/btrh7ye5irL/yjsrdXOQDWzfpoexUxgFmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRDcCB%2Fbtrh7ye5irL%2FyjsrdXOQDWzfpoexUxgFmK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;필요한 게 있으면 더 클릭해도 됩니다.&lt;/p&gt;
&lt;p&gt;생성을 하게 되면 tokens 키가 생성이 됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHCcSt/btriadgHGED/QLXI9oACGtbnzNYXKYokk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHCcSt/btriadgHGED/QLXI9oACGtbnzNYXKYokk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHCcSt/btriadgHGED/QLXI9oACGtbnzNYXKYokk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHCcSt%2FbtriadgHGED%2FQLXI9oACGtbnzNYXKYokk0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;다시 젠킨스(jenkins)로 돌아옵니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 Item을 만듭니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ojJ0k/btriaUnF6Ty/cfI8THp1P8VLCgDuA5a5n0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ojJ0k/btriaUnF6Ty/cfI8THp1P8VLCgDuA5a5n0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ojJ0k/btriaUnF6Ty/cfI8THp1P8VLCgDuA5a5n0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FojJ0k%2FbtriaUnF6Ty%2FcfI8THp1P8VLCgDuA5a5n0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;적당하게 프로젝트 이름을 넣고 Freestyle project를 선택합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5uxr4/btriaefCbPK/oUefzMG0JmkBe1OZsKQSI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5uxr4/btriaefCbPK/oUefzMG0JmkBe1OZsKQSI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5uxr4/btriaefCbPK/oUefzMG0JmkBe1OZsKQSI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5uxr4%2FbtriaefCbPK%2FoUefzMG0JmkBe1OZsKQSI1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 가져올 Github 주소를 넣습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KZeyz/btriaU82Szo/Ij6MTT0KLaaVIjGxfB0PFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KZeyz/btriaU82Szo/Ij6MTT0KLaaVIjGxfB0PFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KZeyz/btriaU82Szo/Ij6MTT0KLaaVIjGxfB0PFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKZeyz%2FbtriaU82Szo%2FIj6MTT0KLaaVIjGxfB0PFK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 Credentials를 추가합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rjZMY/btrh2iK6j3j/P4wDnhaTZro7cOs42T2ls0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rjZMY/btrh2iK6j3j/P4wDnhaTZro7cOs42T2ls0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rjZMY/btrh2iK6j3j/P4wDnhaTZro7cOs42T2ls0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrjZMY%2Fbtrh2iK6j3j%2FP4wDnhaTZro7cOs42T2ls0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 종류를 Secret Text 타입으로 하고 Secret를 아까 Github에 받았던 tokens 키를 넣습니다.&lt;/p&gt;
&lt;p&gt;그리고 Jenkins에서는 commit를 할 때 넣을 ID명을 넣습니다. (자신이 commit 되었을 때 식별할 수 있는 이름을 넣으면 됩니다.)&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lgImG/btrh9ScOy8g/K4hZIIYcKpQsIP69fAOB0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lgImG/btrh9ScOy8g/K4hZIIYcKpQsIP69fAOB0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lgImG/btrh9ScOy8g/K4hZIIYcKpQsIP69fAOB0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlgImG%2Fbtrh9ScOy8g%2FK4hZIIYcKpQsIP69fAOB0K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;브런치는 계획된 브런치를 넣으면 됩니다. 저는 그냥 master 넣습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwdC9z/btrh9RLJvNT/DytYbJaKiGKT2hFFJCItr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwdC9z/btrh9RLJvNT/DytYbJaKiGKT2hFFJCItr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwdC9z/btrh9RLJvNT/DytYbJaKiGKT2hFFJCItr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwdC9z%2Fbtrh9RLJvNT%2FDytYbJaKiGKT2hFFJCItr0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 Build가 끝나면 처리할 shell 명령어를 넣습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brf1kB/btriadufbTz/4Ab0VICtBrU1Hiyn3fx1Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brf1kB/btriadufbTz/4Ab0VICtBrU1Hiyn3fx1Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brf1kB/btriadufbTz/4Ab0VICtBrU1Hiyn3fx1Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbrf1kB%2FbtriadufbTz%2F4Ab0VICtBrU1Hiyn3fx1Dk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;shell&quot; data-type=&quot;build batch&quot;&gt;# 톰캣 서버 중지
/****/catalina.sh stop
sleep1s
# 빌드
mvn package -Dmaven.test.skip=true
sleep 1s
# war 파일 복사
cp -rf ./target/nowonbun*.war /****/nowonbun.war
sleep 1s
# war 파일 삭제
rm -rf ./target/nowonbun*.war
sleep 1s
# 톰캣 Deploy 폴더로 이동
cd /****/
# 기존 파일 삭제
rm -rf ROOT
sleep 1s
# ROOT 폴더 생성
mkdir ROOT
sleep 1s
# war 파일 이동
mv nowonbun.war ./ROOT/nowonbun.war
sleep 1s
# ROOT 폴더 이동
cd ROOT
# 압축 풀기
jar xvf nowonbun.war
sleep 1s
# war 파일 삭제
rm -rf nowonbun.war
sleep 1s
# 기타 환경 파일 설정
cd WEB-INF
cd classes
cp -rf /home/shared/env/nowonbun/* .
sleep 1s
# 톰캣 서버기동
/****/catalina.sh start
sleep 1s
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wgOIx/btrh5ZYhnLg/91XdKdpymfRtk2vzK5vySK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wgOIx/btrh5ZYhnLg/91XdKdpymfRtk2vzK5vySK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wgOIx/btrh5ZYhnLg/91XdKdpymfRtk2vzK5vySK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwgOIx%2Fbtrh5ZYhnLg%2F91XdKdpymfRtk2vzK5vySK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 제가 임의로 tomcat를 새로 설정해서 Deploy하는 예제를 작성했습니다.&lt;/p&gt;
&lt;p&gt;위처럼 쉘 명령어를 사용하면 git로부터 pull이 완료되면 실행됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이제 실행 버튼을 누릅니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/paNi9/btrh5Y58Py1/GADBbeyGrs8ONPR5vkMkVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/paNi9/btrh5Y58Py1/GADBbeyGrs8ONPR5vkMkVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/paNi9/btrh5Y58Py1/GADBbeyGrs8ONPR5vkMkVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpaNi9%2Fbtrh5Y58Py1%2FGADBbeyGrs8ONPR5vkMkVk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그러면 콘솔 결과를 보면 pull이 완료되고 shell이 실행되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGK8I1/btriac9W0pN/k0kXLKoD7DfOhGgYYAjrak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGK8I1/btriac9W0pN/k0kXLKoD7DfOhGgYYAjrak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGK8I1/btriac9W0pN/k0kXLKoD7DfOhGgYYAjrak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGK8I1%2Fbtriac9W0pN%2Fk0kXLKoD7DfOhGgYYAjrak%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;실제 서버에서 확인해 보니 소스가 tomcat 서버에 제대로 압축이 풀리는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Y6iw/btrh7xAuMiq/4Kyj7AxHewD0O1QTzmcZ8K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Y6iw/btrh7xAuMiq/4Kyj7AxHewD0O1QTzmcZ8K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Y6iw/btrh7xAuMiq/4Kyj7AxHewD0O1QTzmcZ8K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Y6iw%2Fbtrh7xAuMiq%2F4Kyj7AxHewD0O1QTzmcZ8K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 CentOs에서 (Jenkins)를 설치하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Development note/Linux</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/154</guid>
      <comments>https://nowonbun.tistory.com/154#entry154comment</comments>
      <pubDate>Mon, 18 Oct 2021 16:54:39 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] 1-4. 추상 팩토리 패턴 (Abstract factory pattern)</title>
      <link>https://nowonbun.tistory.com/434</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 디자인 패턴의 추상 팩토리 패턴(Abstract factory pattern)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;디자인 패턴의 생성 패턴 중에서 가장 복잡한 패턴인 추상 팩토리 패턴입니다.&lt;/p&gt;
&lt;p&gt;구조는 복잡하지만 자세히 보면 팩토리 메서트 패턴에서 팩토리를 클래스로 만들고 그 위로 추상 인터페이스를 만들어서 사양에 따라 팩토리를 취득하고 그 팩토리 안에서 클래스를 취득하는 구조입니다.&lt;/p&gt;
&lt;p&gt;그러니깐 팩토리 메서드 패턴이 중첩되어 있다라고 생각하면 쉽습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIiDr5/btrhUUV2aCA/3JgsyEEkSkrzJ2irwmlRG1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIiDr5/btrhUUV2aCA/3JgsyEEkSkrzJ2irwmlRG1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIiDr5/btrhUUV2aCA/3JgsyEEkSkrzJ2irwmlRG1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIiDr5%2FbtrhUUV2aCA%2F3JgsyEEkSkrzJ2irwmlRG1%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;span style=&quot;font-size:0.7em&quot;&gt;출처 - &lt;a href=&quot;https://en.wikipedia.org/wiki/Abstract_factory_pattern&quot; target=&quot;_blank&quot;&gt;https://en.wikipedia.org/wiki/Abstract_factory_pattern&lt;/a&gt;&lt;/span&gt;
&lt;pre&gt;&lt;code class=&quot;cpp&quot; data-type=&quot;C/C++ 추상 팩토리 패턴 예제&quot;&gt;#pragma once
#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;
// 추상 클래스
class IDao {
public:
  // 추상 메서드
  virtual string getData() = 0;
};
// 추상 팩토리 클래스
class IFactory {
public:
  // 추상 메서드 (IDao 타입의 클래스를 리턴한다.)
  virtual IDao* getTypeDao() = 0;
};
// ATypeDAO 클래스, IDao 추상 클래스를 상속
class ATypeDAO : public IDao {
public:
  // 함수 재정의
  virtual string getData() {
    // string 값 리턴
    return &quot;ATypeDAO - getData()&quot;;
  }
};
// 팩토리 클래스, IFactory 추상 팩토리 클래스를 상속
class AFactory : public IFactory {
public:
  // 함수 재정의해서 ATypeDAO 클래스의 인스턴스를 리턴한다.
  virtual IDao* getTypeDao() {
    return new ATypeDAO();
  }
};
// BTypeDAO 클래스, IDao 추상 클래스를 상속 
class BTypeDAO : public IDao {
public:
  // 함수 재정의
  virtual string getData() {
    // string 값 리턴
    return &quot;BTypeDAO - getData()&quot;;
  }
};
// 팩토리 클래스, IFactory 추상 팩토리 클래스를 상속
class BFactory : public IFactory {
public:
  // 함수 재정의해서 BTypeDAO 클래스의 인스턴스를 리턴한다.
  virtual IDao* getTypeDao() {
    return new BTypeDAO();
  }
};
// 팩토리 패턴, 파라미터 값의 의해 팩토리 클래스의 인스턴스를 리턴한다.
IFactory* getFactory(int type) {
  // 0의 값이라면 AFactory 클래스의 인스턴스를 리턴
  if (type == 0) {
    return new AFactory();
  }
  // 0의 값이 아니라면 AFactory 클래스의 인스턴스를 리턴
  else {
    return new BFactory();
  }
}
// 실행 함수
int main() {
  // 팩토리 함수로부터 팩토리 인스턴스를 받는다.
  IFactory* factory = getFactory(0);
  // 함수를 통해서 ATypeDAO 클래스의 인스턴스를 받는다.(여기서는 빌드 패턴(여기서 다시 팩토리 패턴을 넣어도 된다.))
  IDao* dao = factory-&amp;gt;getTypeDao();
  // 콘솔에 출력
  cout &amp;lt;&amp;lt; dao-&amp;gt;getData() &amp;lt;&amp;lt; endl;
  // 메모리 해제
  delete dao;
  delete factory;
  // 팩토리 함수로부터 팩토리 인스턴스를 받는다.
  factory = getFactory(1);
  // 함수를 통해서 BTypeDAO 클래스의 인스턴스를 받는다.(여기서는 빌드 패턴(여기서 다시 팩토리 패턴을 넣어도 된다.))
  dao = factory-&amp;gt;getTypeDao();
  // 콘솔에 출력
  cout &amp;lt;&amp;lt; dao-&amp;gt;getData() &amp;lt;&amp;lt; endl;
  // 메모리 해제
  delete dao;
  delete factory;
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LJIed/btrhTSdlkBF/5nSCkfkb9qbA8bSqj08Fv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LJIed/btrhTSdlkBF/5nSCkfkb9qbA8bSqj08Fv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LJIed/btrhTSdlkBF/5nSCkfkb9qbA8bSqj08Fv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLJIed%2FbtrhTSdlkBF%2F5nSCkfkb9qbA8bSqj08Fv0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제에서 보면 Factory 클래스를 getFactory라는 함수로 인스턴스를 받았습니다.&lt;/p&gt;
&lt;p&gt;다시 Factory 클래스에서는 getTypeDao를 통해서 인스턴스를 받습니다. 저는 여기서 빌드 패턴을 통해서 IDao를 받지만 getTypeDao에 파라미터를 넣고 다시 팩토리 메서드 패턴을 사용할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;java&quot; data-type=&quot;Java 추상 팩토리 패턴 예제&quot;&gt;// 실행 함수가 있는 클래스
public class Program {
  // 팩토리 메서드 패턴으로 팩토리 클래스를 받는다.
  private static IFactory getFactory(String type) {
    // 입력 값이 A라면 AFactory 클래스의 인스턴스를 리턴
    if (&quot;A&quot;.equals(type.toUpperCase())) {
      return new AFactory();
    // 입력 값이 B라면 BFactory 클래스의 인스턴스를 리턴
    } else if (&quot;B&quot;.equals(type.toUpperCase())) {
      return new BFactory();
    }
    // 조건에 맞지 않으면 null
    return null;
  }
  // 실행 함수
  public static void main(String[] args) {
    // 팩토리 메서드 패턴으로 팩토리 클래스를 받는다.
    var factory = getFactory(&quot;A&quot;);
    // 여기서 다시 getType 함수를 통해서 AType1Dao 클래스의 인스턴스를 받고 getData() 함수를 콘솔에 출력
    System.out.println(factory.getTypeDao(1).getData());
    // 여기서 다시 getType 함수를 통해서 AType2Dao 클래스의 인스턴스를 받고 getData() 함수를 콘솔에 출력
    System.out.println(factory.getTypeDao(2).getData());
    
    // 팩토리 메서드 패턴으로 팩토리 클래스를 받는다.
    factory = getFactory(&quot;B&quot;);
    // 여기서 다시 getType 함수를 통해서 BType1Dao 클래스의 인스턴스를 받고 getData() 함수를 콘솔에 출력
    System.out.println(factory.getTypeDao(1).getData());
    // 여기서 다시 getType 함수를 통해서 BType2Dao 클래스의 인스턴스를 받고 getData() 함수를 콘솔에 출력
    System.out.println(factory.getTypeDao(2).getData());
  }
}
// 인터페이스
interface IDao {
  // 최종 클래스에서 받은 데이터
  String getData();
}
// 추상 팩토리 클래스 인터페이스
interface IFactory {
  // 여기도 역시 팩토리 메서드를 통해서 IDao 클래스의 인스턴스를 받음
  IDao getTypeDao(int type);
}
// AType1Dao 클래스, IDao 인터페이스를 상속
class AType1Dao implements IDao {
  // 함수 재정의
  @Override
  public String getData() {
    // 결과값 리턴
    return &quot;AType1Dao - getData()&quot;;
  }
}
// AType2Dao 클래스, IDao 인터페이스를 상속
class AType2Dao implements IDao {
  // 함수 재정의
  @Override
  public String getData() {
    // 결과값 리턴
    return &quot;AType2Dao - getData()&quot;;
  }
}
// 팩토리 클래스, IFactory 인터페이스를 상속
class AFactory implements IFactory {
  // 함수 재정의
  @Override
  // 팩토리 메서드 패턴을 통해서 IDao 타입의 클래스를 취득
  public IDao getTypeDao(int type) {
    // 값이 1이라면 AType1Dao 클래스의 인터페이스를 리턴
    if (type == 1) {
      return new AType1Dao();
    // 값이 2이라면 AType1Dao 클래스의 인터페이스를 리턴
    } else if (type == 2) {
      return new AType2Dao();
    }
    // 조건에 맞지 않으면 null
    return null;
  }
}
// BType1Dao 클래스, IDao 인터페이스를 상속
class BType1Dao implements IDao {
  // 함수 재정의
  @Override
  public String getData() {
    // 결과값 리턴
    return &quot;BType1Dao - getData()&quot;;
  }
}
// BType2Dao 클래스, IDao 인터페이스를 상속
class BType2Dao implements IDao {
  // 함수 재정의
  @Override
  public String getData() {
    // 결과값 리턴
    return &quot;BType2Dao - getData()&quot;;
  }
}
// 팩토리 클래스, IFactory 인터페이스를 상속
class BFactory implements IFactory {
  // 함수 재정의
  @Override
  // 팩토리 메서드 패턴을 통해서 IDao 타입의 클래스를 취득
  public IDao getTypeDao(int type) {
    // 값이 1이라면 BType1Dao 클래스의 인터페이스를 리턴
    if (type == 1) {
      return new BType1Dao();
    // 값이 2이라면 BType1Dao 클래스의 인터페이스를 리턴
    } else if (type == 2) {
      return new BType2Dao();
    }
    // 조건에 맞지 않으면 null
    return null;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biYiVt/btrhSZqIJRz/KHlLk6rTLBIl2FQtIjHgxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biYiVt/btrhSZqIJRz/KHlLk6rTLBIl2FQtIjHgxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biYiVt/btrhSZqIJRz/KHlLk6rTLBIl2FQtIjHgxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiYiVt%2FbtrhSZqIJRz%2FKHlLk6rTLBIl2FQtIjHgxK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 Java로 작성된 추상 팩토리 패턴 예제입니다.&lt;/p&gt;
&lt;p&gt;C/C++과는 다르게 팩토리 클래스 안에서 빌드 패턴 대신에 다시 팩토리 메서드 패턴으로 인스턴스를 취득합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;C# 추상 팩토리 패턴 예제&quot;&gt;using System;

namespace Example
{
  // 인터페이스
  interface IDao
  {
    // 콘솔 출력
    void Print();
  }
  // 추상 팩토리 클래스 인터페이스
  interface IFactory
  {
    // 추상 메서드 (IDao 타입의 클래스를 리턴한다.)
    IDao GetTypeDao();
  }
  // ATypeDAO 클래스, IDao 추상 클래스를 상속
  class ATypeDao : IDao
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;ATypeDao - GetData()&quot;);
    }
  }
  // 팩토리 클래스, IFactory 추상 팩토리 클래스를 상속
  class AFactory : IFactory
  {
    // ATypeDao 클래스의 인스턴스를 리턴
    public IDao GetTypeDao()
    {
      // ATypeDao 클래스 인스턴스 생성
      return new ATypeDao();
    }
  }
  // BTypeDAO 클래스, IDao 추상 클래스를 상속
  class BTypeDao : IDao
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;BTypeDao - GetData()&quot;);
    }
  }
  // 팩토리 클래스, IFactory 추상 팩토리 클래스를 상속
  class BFactory : IFactory
  {
    // BTypeDao 클래스의 인스턴스를 리턴
    public IDao GetTypeDao()
    {
      // BTypeDao 클래스 인스턴스 생성
      return new BTypeDao();
    }
  }
  // 실행 함수가 있는 클래스
  public class Program
  {
    // 팩토리 메서드 패턴으로 팩토리 클래스 인스턴스를 취득하는 함수
    private static IFactory GetFactory(String type)
    {
      // 파라미터 값이 A의 경우
      if (&quot;A&quot;.Equals(type, StringComparison.OrdinalIgnoreCase))
      {
        // AFactory 클래스 인스턴스를 생성해서 리턴
        return new AFactory();
      }
      // 파라미터 값이 B의 경우
      else if (&quot;B&quot;.Equals(type, StringComparison.OrdinalIgnoreCase))
      {
        // BFactory 클래스 인스턴스를 생성해서 리턴
        return new BFactory();
      }
      // 조건에 맞지 않으면 null
      return null;
    }
    // 실행 함수
    public static void Main(string[] args)
    {
      // 팩토리 메서드 패턴 함수로 팩토리를 취득한다.
      var factory = GetFactory(&quot;A&quot;);
      // 취득된 팩토리에서 ATypeDao 인스턴스의 Print 함수를 실행
      factory.GetTypeDao().Print();
      // 팩토리 메서드 패턴 함수로 팩토리를 취득한다.
      factory = GetFactory(&quot;B&quot;);
      // 취득된 팩토리에서 BTypeDao 인스턴스의 Print 함수를 실행
      factory.GetTypeDao().Print();
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rY6B7/btrhTRS1GgB/99PtcnY0RuM5KyWqigXv7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rY6B7/btrhTRS1GgB/99PtcnY0RuM5KyWqigXv7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rY6B7/btrhTRS1GgB/99PtcnY0RuM5KyWqigXv7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrY6B7%2FbtrhTRS1GgB%2F99PtcnY0RuM5KyWqigXv7k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 C/C++과 같이 팩토리 클래스에서 빌드 패턴으로 인스턴스를 취득 후에 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;제가 Factory 클래스 말고 일반 클래스를 Dao라는 클래스 명으로 사용했습니다.&lt;/p&gt;
&lt;p&gt;왜냐하면 이 추상 팩토리 패턴이 ORM 프레임워크에서 가장 많이 사용되는 패턴이기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;예를 들면 데이터 베이스의 각 테이블의 Dao 클래스를 만듭니다. 그런데 사양에 따라서 이게 Oracle이 될 수 있고, Mssql이 될 수 있고, Mysql(MariaDB)가 될 수도 있습니다.&lt;/p&gt;
&lt;p&gt;각 데이터 베이스 시스템의 테이블의 설계 구조는 같다는 가정하에 이 추상 팩토리 패턴을 사용하면 사양에 따라 Oracle용 Dao 생성 팩토리를 생성할 수 있고, Mssql용 생성 팩토리를 생성할 수 있는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그 밖에 데이터 관리나 생성, PDF 생성이나 Excel 생성등에서 사양에 따른 장치를 구분할 때, 해당 클래스의 구조는 같게 만든다고 할 때, 자주 사용되는 패턴입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 디자인 패턴의 추상 팩토리 패턴(Abstract factory pattern)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/Design Pattern</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/434</guid>
      <comments>https://nowonbun.tistory.com/434#entry434comment</comments>
      <pubDate>Fri, 15 Oct 2021 18:50:03 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 51. Reflection 기능을 사용하는 방법 - Variable</title>
      <link>https://nowonbun.tistory.com/491</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 Reflection 기능을 사용하는 방법 - Variable에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전 글에서 Reflection에 대해 클래스와 함수 사용법에 대해 설명했습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/488&quot; target=&quot;_blank&quot;&gt;[C#] 49. Reflection 기능을 사용하는 방법 - Class&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/490&quot; target=&quot;_blank&quot;&gt;[C#] 50. Reflection 기능을 사용하는 방법 - Method&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Reflection이라는 것은 클래스나 함수에서 소스에 인스턴스 생성식(new)이나 함수 호출식(method())이 아닌 동적으로 Reflection 기능을 이용해 생성 및 호출하는 방법을 뜻합니다.&lt;/p&gt;
&lt;p&gt;변수도 같은 내용입니다. 특히 C#에서는 이 클래스 안에서의 변수는 객체 지향 개발 방식(OOP)으로 인해 변수는 private로 생성하는 게 일반적입니다.&lt;/p&gt;
&lt;p&gt;그러나 사양에 의해 강제적으로 변수의 값을 변경해야 하거나 유닛 테스트(NUnit)를 실시할 때 디버깅의 자료로서 사용하는 경우가 많이 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
　　// 예제 클래스
  class Node
  {
    // 맴버 변수는 private
    private int data;
    // 생성자
    public Node(int data)
    {
      // 생성자에서 맴버 변수에 값을 넣는다.
      this.data = data;
    }
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(data);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node(10);
      // 결과는 10
      node.Print();
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadKey();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OPzYO/btrhTcWs1wZ/pBmJcxWx5gQKKDp47LG9Z1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OPzYO/btrhTcWs1wZ/pBmJcxWx5gQKKDp47LG9Z1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OPzYO/btrhTcWs1wZ/pBmJcxWx5gQKKDp47LG9Z1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOPzYO%2FbtrhTcWs1wZ%2FpBmJcxWx5gQKKDp47LG9Z1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 아주 일반적인 코딩 방법으로 Node 클래스의 인스턴스를 생성할 때, 생성자에 10이라는 값을 넣어서 Print 함수를 통해 콘솔에 출력하였습니다.&lt;/p&gt;
&lt;p&gt;당연히 Main 함수에서는 Node의 인스턴스의 맴버 변수인 data를 참조할 수 없습니다. 생성자에서 값을 넣는 것 이외에는 값을 변경하거나 값을 취득하지 못하고 Print함수를 통해 출력만 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Reflection;

namespace Example
{
　　// 예제 클래스
  class Node
  {
    // 맴버 변수는 private
    private int data;
    // 생성자
    public Node(int data)
    {
      // 생성자에서 맴버 변수에 값을 넣는다.
      this.data = data;
    }
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(data);
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node(10);
      // Node 클래스의 Type을 취득
      var nodeClz = typeof(Node);
      // Node 클래스의 data 변수를 취득(private나 protected 타입 | 인스턴스타입(static이 아닌 맴버변수)
      var field = nodeClz.GetField(&quot;data&quot;, BindingFlags.NonPublic | BindingFlags.Instance);
      // 인스턴스 맴버 변수의 값을 취득
      var data = field.GetValue(node);
      // 콘솔 출력
      Console.WriteLine(data); ;
      // 인스턴스 맴버 변수의 값을 설정
      field.SetValue(node, 100);
      // 함수 호출
      node.Print();

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadKey();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chFTJj/btrhMJIvoCL/VoJceqh6QaUAhryQVxaKH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chFTJj/btrhMJIvoCL/VoJceqh6QaUAhryQVxaKH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chFTJj/btrhMJIvoCL/VoJceqh6QaUAhryQVxaKH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchFTJj%2FbtrhMJIvoCL%2FVoJceqh6QaUAhryQVxaKH1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위에서 typeof(Node)로 Node 클래스의 Type 구조를 취득한 후에 data의 맴버 변수를 취득했습니다.&lt;/p&gt;
&lt;p&gt;그리고 콘솔에 출력을 하니 맴버 변수의 값이 출력되네요. 역시 SetValue를 통해 값을 설정하고 Print 함수를 호출하니 변경된 값이 출력이 되었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 보면 Reflection를 통해 함수를 취득하는 경우와 매우 흡사합니다. 사실 똑같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Reflection;

namespace Example
{
　　// 예제 클래스
  class Node
  {
    // 프로퍼티
    public int Data
    {
      get; set;
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // Node 클래스의 Type을 취득
      var nodeClz = typeof(Node);
      // Node 클래스의 모든 변수를 취득
      foreach (var field in nodeClz.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
      {
        // 변수명과 값 출력
        Console.WriteLine($&quot;{field.Name} = {field.GetValue(node)} &quot;);
        // 변수명에 Data가 포함되어 있으면
        if (field.Name.Contains(&quot;Data&quot;))
        {
          // 설정
          field.SetValue(node, 1000);
        }
      }
      // node 인스턴스의 프로퍼티 Data를 출력
      Console.WriteLine(node.Data);

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press any key...&quot;);
      Console.ReadKey();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVR6SF/btrhL65jD07/uBZAZcqvyFevTjRiiugdM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVR6SF/btrhL65jD07/uBZAZcqvyFevTjRiiugdM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVR6SF/btrhL65jD07/uBZAZcqvyFevTjRiiugdM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVR6SF%2FbtrhL65jD07%2FuBZAZcqvyFevTjRiiugdM1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 Node 클래스에 맴버 변수가 없습니다. 단지 프로퍼티만 있습니다.&lt;/p&gt;
&lt;p&gt;그런데 제가 Reflection를 통해서 Node 클래스의 변수를 취득하니 Data의 backingfield 라는 변수가 있네요. 즉, 프로퍼티에서 get, set으로 설정하면 컴파일해서 사용되면 자동으로 private 변수가 생성되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉, 프로퍼티만 작성해도 내부에서는 private 맴버 변수가 생성되서 OOP 규약에 맞추는 것을 확인할 수 있네요.&lt;/p&gt;
&lt;p&gt;값을 설정하고 프로퍼티로 값을 취득하니 값이 바뀌어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;역시나 Reflection은 C#의 접근자를 무시하고 데이터를 취득하고 설정할 수 있습니다.&lt;/p&gt;
&lt;p&gt;일반 프로그램을 작성할 때는 사용하기를 비추천합니다. 단지 NUnit 테스트나 디버깅 프로그램등을 만들 때 사용된다면 유용하겠네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 Reflection 기능을 사용하는 방법 - Variable에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/491</guid>
      <comments>https://nowonbun.tistory.com/491#entry491comment</comments>
      <pubDate>Fri, 15 Oct 2021 14:03:43 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 50. Reflection 기능을 사용하는 방법 - Method</title>
      <link>https://nowonbun.tistory.com/490</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 Reflection 기능을 사용하는 방법 - Method에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전 글에서 Reflection의 기능을 이용해서 클래스의 인스턴스를 생성하는 방법에 대해 설명했습니다.&lt;/p&gt;
&lt;p&gt;link - &lt;a href=&quot;https://nowonbun.tistory.com/488&quot; target=&quot;_blank&quot;&gt;[C#] 49. Reflection 기능을 사용하는 방법 - Class&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Reflection이라는 것은 간단하게 설명하면 기존에 소스에서 new 키워드를 사용하여 인스턴스를 생성하는 방식에서 String의 값의 요소에 의해 동적으로 인스턴스가 생성되는 것을 이야기합니다.&lt;/p&gt;
&lt;p&gt;함수(Method)도 기존에 소스 상에서 함수명을 작성하여 호출하는 방식이 아닌 클래스 내에서 함수를 탐색하여 동적으로 실행하도록 하는 방법을 뜻합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Hello world&quot;);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var node = new Node();
      // 함수 호출
      node.Print();
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvYH5T%2FbtrhInSrRCR%2FTmAi9KKO00U6g99xwxRgn0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 방식은 기존 방식으로 Print 함수를 호출하는 방법입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Hello world&quot;);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Node node = new Node();
      // Node 클래스의 타입을 취득
      Type type = typeof(Node);
      // Node 클래스에서 Print 함수를 취득
      var method = type.GetMethod(&quot;Print&quot;);
      // Print 함수에 인스턴스를 넣어 실행한다.
      method.Invoke(node, null);
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvYH5T%2FbtrhInSrRCR%2FTmAi9KKO00U6g99xwxRgn0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 Reflection로 함수를 찾아서 실행하는 방식입니다. 소스 상에서는 String의 데이터로 Print의 값으로 함수를 실행하게 되네요.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Linq;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 출력 함수
    public void A()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;A&quot;);
    }
    // 출력 함수
    public void B()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;B&quot;);
    }
    // 출력 함수
    public void C()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;C&quot;);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Node node = new Node();
      // Node 클래스의 타입을 취득
      Type type = typeof(Node);
      // Node 클래스를 모두 취득하기
      foreach(var method in type.GetMethods().Where(x =&amp;gt; x.GetParameters().Length == 0　&amp;&amp; x.ReturnType == typeof(void)))
      {
        // 함수를 실행하기
        method.Invoke(node, null);
      }
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpt2MS/btrhMfsLFNm/uyTBMkTEzk0j29FKwkVeyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpt2MS/btrhMfsLFNm/uyTBMkTEzk0j29FKwkVeyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpt2MS/btrhMfsLFNm/uyTBMkTEzk0j29FKwkVeyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbpt2MS%2FbtrhMfsLFNm%2FuyTBMkTEzk0j29FKwkVeyK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 GetMethods를 이용해서 Node 클래스에 있는 모든 함수를 취득해 왔습니다.&lt;/p&gt;
&lt;p&gt;그런데 C#에서의 모든 클래스는 Object 클래스를 상속받습니다. 그렇기 때문에 Object의 함수들도 실행이 됩니다. 그래서 파라미터가 없고 return 타입이 void인 것만 걸러서 실행을 했습니다.&lt;/p&gt;
&lt;p&gt;결과는 A,B,C의 함수가 실행됐습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;저는 예제를 위해서 위처럼 작성했지만, Reflection을 이용하면 유저의 입력 값 또는 데이터 베이스나 기타 파일의 값에 의해 실행할 수 있는 함수를 제어할 수 있겠네요. 간단하게 인터프리트 패턴(Interpret pattern)을 만들 수 있습니다.&lt;/p&gt;
&lt;p&gt;link -  &lt;a href=&quot;https://nowonbun.tistory.com/466&quot; target=&quot;_blank&quot;&gt;[Design pattern] 인터프리트 패턴(Interpret pattern) (※스택 계산기 예제)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 Reflection을 이용하면 public으로 된 함수 뿐 아니라 private, protected의 접근 제한자도 접근할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Reflection;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력 함수(private 제한자로 설정)
    private void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Hello world&quot;);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Node node = new Node();
      // Node 클래스의 타입을 취득
      Type type = typeof(Node);
      // Node 클래스에서 Print 함수를 취득, private, protected, public에 관계없이 Instance 함수를 취득한다.
      var method = type.GetMethod(&quot;Print&quot;, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
      // Print 함수에 인스턴스를 넣어 실행한다.
      method.Invoke(node, null);
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvYH5T%2FbtrhInSrRCR%2FTmAi9KKO00U6g99xwxRgn0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제에서 Print 함수는 private로 설정이 되어 있는 데도 Main 함수에서 실행이 되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;우리가 완벽한 캡슐화로 프로젝트를 작성해도 Unit 테스트를 위해서 중간 함수가 제대로 작동하는 지 테스트를 해야 하는 경우가 있습니다.&lt;/p&gt;
&lt;p&gt;그럴 경우, Reflection을 이용한 TestClass를 작성해서 클래스의 Unit 테스트를 하게 된다면 소스를 건드리지 않고 테스트 클래스를 만드는 것도 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 static과 파라미터가 있는 함수에도 접근이 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Reflection;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 콘솔 출력 함수(private 제한자로 설정)
    private static void Print(String str)
    {
      // 콘솔 출력
      Console.WriteLine(str);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node 클래스의 타입을 취득
      Type type = typeof(Node);
      // Node 클래스에서 Print 함수를 취득, private, protected, public에 관계없이 Static 함수를 취득한다.
      var method = type.GetMethod(&quot;Print&quot;, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
      // Print 함수는 static 함수이기 때문에 인스턴스 값이 필요없다., 파라미터는 배열 타입인데, 파라미터가 한개 이기 때문에 하나의 값만 넣는다.
      method.Invoke(null, new object[] { &quot;Hello world&quot; });
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vYH5T/btrhInSrRCR/TmAi9KKO00U6g99xwxRgn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvYH5T%2FbtrhInSrRCR%2FTmAi9KKO00U6g99xwxRgn0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 Node 클래스의 Print의 static 함수입니다. static 함수는 인스턴스에 상관없이 호출이 되는 함수입니다.&lt;/p&gt;
&lt;p&gt;그래서 Invoke함수에서 함수를 호출할 때, 인스턴스의 값 대신에 null를 넣습니다. 파라미터의 값은 Invoke 함수의 두번째 파라미터로 배열 형식으로 입력이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Reflection의 기능를 잘 이용하면 프로그램을 꽤 유연하게 작성할 수 있는 장점이 있습니다.&lt;/p&gt;
&lt;p&gt;역시 Class와 마찬가지고 디자인 패턴과 관계가 많은 기능이고 특히, NUnit의 Unit 테스트에서 가장 많이 사용되는 기능이기도 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런데 Reflection기능은 Visual studio의 디버그 환경에서 잡아주지를 않기 때문에, 버그의 위험성과 가독성의 많이 떨어지는 단점이 있습니다.&lt;/p&gt;
&lt;p&gt;그리고 일반 정적인 호출보다는 탐색하고 인스턴스를 넣어 실행하는 구조이기 때문에 성능에도 많은 영향을 끼치게 됩니다. 역시 사양에 맞게 사용하는 것이 가장 좋을 듯 싶습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 Reflection 기능을 사용하는 방법 - Method에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/490</guid>
      <comments>https://nowonbun.tistory.com/490#entry490comment</comments>
      <pubDate>Thu, 14 Oct 2021 15:41:59 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 49. Reflection 기능을 사용하는 방법 - Class</title>
      <link>https://nowonbun.tistory.com/488</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 Reflection 기능을 사용하는 방법 - Class에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Reflection 기능이란 인스턴스를 동적으로 할당하거나 함수나 필드 및 속성에 동적으로 호출할 수 있는 기능이라고 설명은 되어있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/reflection&quot; target=&quot;_blank&quot;&gt;https://docs.microsoft.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;이게 설명이 어렵네요. 인스턴스를 동적으로 할당한다라는 뜻은 우리가 클래스의 인스턴스를 생성한다고 할 때, 보통 new라는 키워드를 사용합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 프로퍼티
    public int Data { get; set; }
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(Data);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      Node node = new Node();
      // 프로퍼티에 데이터 입력
      node.Data = 10;
      // 콘솔 출력 함수 호출
      node.Print();
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dE1kET/btrhA1QkrBJ/2eFqgs9t7F5dHiYduPmZHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dE1kET/btrhA1QkrBJ/2eFqgs9t7F5dHiYduPmZHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dE1kET/btrhA1QkrBJ/2eFqgs9t7F5dHiYduPmZHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdE1kET%2FbtrhA1QkrBJ%2F2eFqgs9t7F5dHiYduPmZHk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;클래스의 인스턴스는 기본적으로 new를 이용해서 생성한다라는 건, 이전에 충분히 설명했습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/67&quot; target=&quot;_blank&quot;&gt;[C#] 10. 인스턴스 생성(new)과 메모리 할당(Stack 메모리와 Heap 메모리) 그리고 null&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;인스턴스를 생성할 때, 소스에 new Node라고 작성합니다. Reflection에서는 이걸 정적 표현이라고 하네요.&lt;/p&gt;
&lt;p&gt;즉, 데어터나 분기에 따른 다른 인스턴스를 생성하고 싶으면 if를 사용해서 소스에 작성을 해야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  // 인터페이스
  interface INode
  {
    // 추상 함수
    void Print();
  }
  // 예제 클래스
  class Node1 : INode
  {    
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Node1&quot;);
    }
  }
  // 예제 클래스
  class Node2 : INode
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Node2&quot;);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 임의의 데이터
      string input = &quot;Node1&quot;;
      // 인스턴스 생성
      INode node;
      if (&quot;Node1&quot;.Equals(input))
      {
        // Node1 클래스의 인스턴스 생성
        node = new Node1();
      }
      else
      {
        // Node2 클래스의 인스턴스 생성
        node = new Node2();
      }
      // 콘솔 출력 함수 호출
      node.Print();
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lfv63/btrhFQ7PGWt/7KsEwwDfnXSOQv9E3R9tHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lfv63/btrhFQ7PGWt/7KsEwwDfnXSOQv9E3R9tHk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lfv63/btrhFQ7PGWt/7KsEwwDfnXSOQv9E3R9tHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flfv63%2FbtrhFQ7PGWt%2F7KsEwwDfnXSOQv9E3R9tHk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제를 보면 type이라는 임의의 데이터를 두어서 Node1이면 Node1의 인스턴스를 생성하고 그 외에는 Node2의 인스턴스를 생성합니다.&lt;/p&gt;
&lt;p&gt;근데 여기서 사양에 따라 Node3의 클래스가 생긴다면? if를 더 분기를 해서 인스턴스 생성하는 구간을 만들게 될 것입니다. 물론 클래스가 생기면 생길수록 Main 함수가 계속 수정이 필요하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  // 인터페이스
  interface INode
  {
    // 추상 함수
    void Print();
  }
  // 예제 클래스
  class Node1 : INode
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Node1&quot;);
    }
  }
  // 예제 클래스
  class Node2 : INode
  {
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Node2&quot;);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node2의 Type을 취득
      Type type = Type.GetType(&quot;Example.Node2&quot;);
      // 인스턴스 생성
      INode node = (INode)Activator.CreateInstance(type);
      // 콘솔 출력 함수 호출
      node.Print();
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLJaJS/btrhGO9u8wK/fkVOFBqzgqNZbZyPZyTEm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLJaJS/btrhGO9u8wK/fkVOFBqzgqNZbZyPZyTEm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLJaJS/btrhGO9u8wK/fkVOFBqzgqNZbZyPZyTEm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLJaJS%2FbtrhGO9u8wK%2FfkVOFBqzgqNZbZyPZyTEm0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이런 식으로 만들게 되면 클래스가 생성되더라도 Type.GetType의 String 값에 따라 인스턴스가 생성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;또 우리가 클래스의 생성자를 private로 생성할 수 있습니다.&lt;/p&gt;
&lt;p&gt;생성자를 private로 설정을 하게 되면 new로 인스턴스를 생성할 수 없습니다. 생성자가 내부에서만 생성할 수 있게 설정되어서 그렇죠.. 대표적으로 싱글톤 패턴이 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/431&quot; target=&quot;_blank&quot;&gt;[Design Pattern] 1-1. 싱글톤 패턴 (Singleton pattern)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그런데 Reflection을 사용하게 되면 private로 설정이 되어 있어도 인스턴스 생성이 가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Reflection;
using System.Linq;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 생성자를 print로
    private Node()
    {

    }
    // 콘솔 출력 함수
    public void Print()
    {
      // 콘솔 출력
      Console.WriteLine(&quot;Node&quot;);
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node2의 Type을 취득
      Type type = Type.GetType(&quot;Example.Node&quot;);
      // 생성자 취득
      var constructor = type.GetConstructors(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).Where(x =&amp;gt; x.GetParameters().Length == 0).First();
      // 인스턴스 생성
      dynamic node = constructor.Invoke(null);
      // 콘솔 출력 함수 호출
      node.Print();

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgXSYm/btrhDGkA4Ul/AzLZ0SuUKJGz0KkkaZIEH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgXSYm/btrhDGkA4Ul/AzLZ0SuUKJGz0KkkaZIEH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgXSYm/btrhDGkA4Ul/AzLZ0SuUKJGz0KkkaZIEH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgXSYm%2FbtrhDGkA4Ul%2FAzLZ0SuUKJGz0KkkaZIEH1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;GetParameters 함수를 써서 생성자의 종류를 취득하고 where로 파라미터가 없는 생성자를 하나 취득합니다. 취득하면 Invoke 함수를 써서 인스턴스를 생성합니다.&lt;/p&gt;
&lt;p&gt;즉, 파라미터가 있는 생성자는 파라미터의 개수나 타입으로 취득할 수 있고 생성도 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 Reflection 기능을 사용하는 방법 - Class에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/488</guid>
      <comments>https://nowonbun.tistory.com/488#entry488comment</comments>
      <pubDate>Wed, 13 Oct 2021 18:00:48 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 48. Operator(연산자) 오버로드 사용법</title>
      <link>https://nowonbun.tistory.com/429</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#의 Operator(연산자) 오버로드 사용법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;C#에서 Operator(연산자)란 더하기(+), 빼기(-)의 기호입니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/71&quot; target=&quot;_blank&quot;&gt;[C#] 04. 연산자&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;어떤 프로그램을 배우더라도 가장 맨처음 보게 되는 것이 연산자이지 않을까 싶습니다. &lt;/p&gt;
&lt;p&gt;그리고 오버로드(overrode)의 뜻은 프로그램에서 재정의라는 뜻입니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/89&quot; target=&quot;_blank&quot;&gt;[C#] 12. 클래스 상속과 재정의(override)하는 방법과 override와 new의 차이&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;즉, Operator(연산자) 오버로드라는 것은 연산자를 재정의하는 뜻입니다. 즉, 우리가 사용하는 더하기(+)나 빼기(-)를 단순한 숫자 연산이 아닌 다른 처리로 재정의 할 수 있다는 뜻입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;

namespace Example
{
  // 예제 클래스
  class Node
  {
    // 데이터 프로퍼티
    public int Data
    {
      get; set;
    }
    // 생성자
    public Node(int data)
    {
      this.Data = data;
    }

    // 연산자 더하기(+) 재정의 -&gt; 하나의 Node 인스턴스에 +를 사용할 경우
    public static Node operator +(Node a)
    {
      // 인스턴스 생성(프로퍼티의 값은 같으나 인스턴스가 다른 클래스)
      return new Node(a.Data);
    }
    // 연산자 더하기(+) 재정의 -&gt; +의 연산자에 앞과 뒤에 Node 인스턴스가 배치된 경우
    public static Node operator +(Node a, Node b)
    {
      // 연산자 앞 인스턴스의 Data 값에 뒤 인스턴스의 Data를 더한다.
      a.Data = a.Data + b.Data;
      // 인스턴스를 리턴
      return a;
    }
    // 연산자 증감(++) 재정의 -&gt; 전치, 후치 관계없다.
    public static Node operator ++(Node a)
    {
      // 새로운 인스턴스를 생성하며 받은 Data 값을 1 증가한다.
      return new Node(a.Data + 1);
    }
    // Operator(연산자)의 재정의(override)는 익명 함수로 설정 가능하다.
    // 새로운 인스턴스를 생성하며 Data 값을 뺀다.(참고, 더하기(+)는 인스턴스 생성이 아니지만 빼기(-)는 인스턴스 생성이다.)
    public static Node operator -(Node a, Node b) =&gt; new Node(a.Data - b.Data);
    // 묵시적 형 변환 (묵시적은 강제 캐스팅을 하지 않아도 변환된다.)
    public static implicit operator int(Node node)
    {
      // node 인스턴스의 Data 값을 리턴한다.
      return node.Data;
    }
    // 명시적 형 변환 (명시적은 강제 캐스팅을 해야 한다.)
    public static explicit operator Node(int data)
    {
      // int 형 값을 node 인스턴스를 생성해서 리턴한다.
      return new Node(data);
    }
    // Object 클래스의 ToString 함수를 재정의
    public override String ToString()
    {
      // Data 값을 String 값으로 리턴
      return Data.ToString();
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Node 클래스의 명시적 형 변환으로 인해 explicit operator Node 가 호출. 인스턴스가 생성된다.
      Node node = (Node)10;
      
      // Node 클래스의 묵시적 형 변환으로 인해 implicit operator int 가 호출, int 형으로 Data 값이 나온다.
      int data = node;
      // operator + 새로운 인스턴스 생성
      Node node2 = +node;
      // operator + node의 인스턴스에 node2의 값을 더한다.
      // node와 node3은 같은 인스턴스
      var node3 = node + node2;
      // 인스턴스 주소 값 출력
      Console.WriteLine(&quot;node pointer = &quot; + node.GetHashCode());
      // node2는 새로운 인스턴스가 생겼기 때문에 다른 인스턴스 주소값
      Console.WriteLine(&quot;node2 pointer = &quot; + node2.GetHashCode());
      // node3은 node 인스턴스의 주소 값 복사를 했기 때문에 node와 주소값이 같다.
      Console.WriteLine(&quot;node3 pointer = &quot; + node3.GetHashCode());
      // 연산자 증감(++) 재정의 새로운 인스턴스가 출력이 되기 때문에 node2는 영향이 없다.
      Console.WriteLine((++node2).Data);
      
      // node 인스턴스의 ToString
      Console.WriteLine(&quot;node result = &quot; + node);
      // node2 인스턴스의 ToString
      Console.WriteLine(&quot;node2 result = &quot; + node2);
      // node2에는 새로운 인스턴스가 생성되어 기존 node2.Data에서 node.Data를 뺀다.
      node2 -= node;
      // 콘솔 출력
      Console.WriteLine(&quot;node2 result = &quot; + node2);

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xGDPZ/btrhzs6xtek/R6ftj88jkPotF39l7ZvPcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xGDPZ/btrhzs6xtek/R6ftj88jkPotF39l7ZvPcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xGDPZ/btrhzs6xtek/R6ftj88jkPotF39l7ZvPcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxGDPZ%2Fbtrhzs6xtek%2FR6ftj88jkPotF39l7ZvPcK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;프로그램에서 쉽게 사용되는 연산자의 재정의입니다.&lt;/p&gt;
&lt;p&gt;기존의 정수형(int)과 실수형(float)에서는 더하기(+)나 빼기(-)를 넣으면 사칙 연산이 됩니다만, 클래스에서는 위처럼 재정의하여 사용하게 되면 코드를 많이 줄이는 효과를 줄 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 연산기호는 모든 기호가 재정의되는 것은 아닙니다.&lt;/p&gt;
&lt;p&gt;참고 - &lt;a href=&quot;https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/operator-overloading&quot; target=&quot;_blank&quot;&gt;https://docs.microsoft.com/&lt;/a&gt;&lt;/p&gt;
&lt;table class=&quot;table table-striped table-bordered table-condensed&quot;&gt;
    &lt;thead&gt;
        &lt;tr style=&quot;background-color:#DAD7D7&quot;&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;+x, -x, !x, ~x, ++, --, true, false&lt;/td&gt;
            &lt;td&gt;파라미터가 하나인 연산자로써, 재정의가 가능하다.　여기서 true, false는 두개를 동시에 써야하는 operator로써 bool 형식으로 묵시적 형 변환이 가능한 재정의이다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;x + y, x - y, x * y, x / y, x % y, x &amp; y, x | y, x ^ y, x &lt;&lt; y, x &gt;&gt; y, x == y, x != y, x &lt; y, x &gt; y, x &lt;= y, x &gt;= y&lt;/td&gt;
            &lt;td&gt;파라미터가 두개인 연산자로써, 재정의가 가능하다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;x &amp;&amp; y, x || y&lt;/td&gt;
            &lt;td&gt;재정의 불가, true, false 재정의로 인식된다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;a[i], a?[i]&lt;/td&gt;
            &lt;td&gt;연산자 재정의가 아닌 인덱서로 인식된다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;(T)x&lt;/td&gt;
            &lt;td&gt;형변환 재정의, explicit 명시적, implicit 묵시적&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;+=, -=, *=, /=, %=, &amp;=, |=, ^=, &lt;&lt;=, &gt;&gt;=&lt;/td&gt;
            &lt;td&gt;재정의 불가, 단, 파라미터 두개의 연산자 재정의로 실행되어 대입된다.&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td&gt;^x, x = y, x.y, x?.y, c ? t : f, x ?? y, x ??= y, x..y, x-&gt;y, =&gt;, f(x), as, await, checked, unchecked, default, delegate, is, nameof, new, sizeof, stackalloc, switch, typeof, with&lt;/td&gt;
            &lt;td&gt;재정의 불가&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;위 표를 참조하여 연산자 재정의가 허용됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;실무에서는 사실 연산자 재정의를 잘 사용하지 않습니다. 예전 C++에서는 전처리문으로 여러가지 매크로를 만들기 위해서 사용한 적이 있던 걸로 기억되는데 C#에서는 잘 사용하지 않네요.&lt;/p&gt;
&lt;p&gt;아마도 가독성의 문제성 때문이지 않을까 생각됩니다. 연산자를 재정의해서 사용하게 되면, 무언가 소스를 해석하기에 암호문처럼 바뀌어 버리지 않을까라고 생각되네요.&lt;/p&gt;
&lt;p&gt;예를 들면 +연산자에 빼기 연산을 넣고 -연산자에 더하기 연산을 넣어버리면, 그냥 기호 +,-만 보기에는 더하기 빼기가 헷갈리지 않을까라고 생각되네요. 그렇게 만드는 사람은 없겠지만..&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#의 Operator(연산자) 오버로드 사용법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/429</guid>
      <comments>https://nowonbun.tistory.com/429#entry429comment</comments>
      <pubDate>Tue, 12 Oct 2021 15:56:05 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 47. IEnumerable와 IEnumerator, 그리고 yield 키워드</title>
      <link>https://nowonbun.tistory.com/425</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은　IEnumerable와 IEnumerator, 그리고 yield 키워드에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이전에 제가 Linq 식을 설명하면서 IEnumerable에 대해서 간단하게 설명한 적이 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/406&quot; target=&quot;_blank&quot;&gt;[C#] 30. 제네릭(Generic) 사용법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;IEnumerable의 인터페이스는 반복자 패턴과 관계 있는 패턴으로 우리가 반복문 키워드 foreach에서 사용되는 인터페이스입니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/463&quot; target=&quot;_blank&quot;&gt;[Design pattern] 반복자 패턴 (Iterator pattern)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;IEnumerable의 인터페이스는 GetEnumerator 함수가 정의되어 있어서 IEnumerator로 반환을 하게 됩니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wYQwl/btrhhSE8aDP/Qd59WIkwrc5jEumLUZMdjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wYQwl/btrhhSE8aDP/Qd59WIkwrc5jEumLUZMdjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wYQwl/btrhhSE8aDP/Qd59WIkwrc5jEumLUZMdjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwYQwl%2FbtrhhSE8aDP%2FQd59WIkwrc5jEumLUZMdjK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 IEnumerator의 경우는 foreach에서 사용될 패턴의 동작 인터페이스로 현재 값에 대한 프로퍼티 Current, 포인트 이동과 값이 존재하는 지에 대한 함수 MoveNext, 그리고 포인터의 초기화를 하게 되는 함수 Reset로 이루어져 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJCcxl/btrhd6qTa7h/zS2d7hvlCTpO4l3hbKi0P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJCcxl/btrhd6qTa7h/zS2d7hvlCTpO4l3hbKi0P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJCcxl/btrhd6qTa7h/zS2d7hvlCTpO4l3hbKi0P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJCcxl%2Fbtrhd6qTa7h%2FzS2d7hvlCTpO4l3hbKi0P0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Collections;

namespace Example
{
  // IEnumerable를 상속 받은 List
  class List : IEnumerable
  {
    // IEnumerator를 상속 받은 Node 
    private class Node : IEnumerator
    {
      // 데이터를 1부터 10까지의 배열
      private int[] data = new int[]
      {
        1,2,3,4,5,6,7,8,9,10
      };
      // 현제 위치 포지션
      private int pos = 0;
      // 현재 포지션 값 더하기
      public object Current
      {
        get
        {
          // pos-1를 취득한다.
          return data[pos - 1];
        }
      }
      // 포지션 이동
      public bool MoveNext()
      {
        // 포지션이 배열 크기를 넘어서면 false
        if (pos &amp;gt;= data.Length)
        {
          return false;
        }
        // 포지션 이동
        pos++;
        // true
        return true;
      }
      // 포지션 위치 초기화
      public void Reset()
      {
        // 포지션 이동 0
        pos = 0;
      }
    }
    // 인스턴스 생성
    private IEnumerator node = new Node();
    // GetEnumerator를 리턴
    public IEnumerator GetEnumerator()
    {
      node.Reset();
      return node;
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 리스트 인스턴스 생성
      var list = new List();
      // 반복문으로 list의 GetEnumerator 함수를 통해서 리스트 형식으로 가져온다.
      foreach (int item in list)
      {
        // MoveNext와 Current를 통해서 값을 콘솔에 표시
        Console.WriteLine(item);
      }
      
      // 반복문으로 list의 GetEnumerator 함수를 통해서 리스트 형식으로 가져온다.
      foreach (int item in list)
      {
        // MoveNext와 Current를 통해서 값을 콘솔에 표시
        Console.WriteLine(item);
      }
      
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buRwmL/btrhjss9kk9/EL0QdKpbTbclKlE6ATULE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buRwmL/btrhjss9kk9/EL0QdKpbTbclKlE6ATULE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buRwmL/btrhjss9kk9/EL0QdKpbTbclKlE6ATULE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuRwmL%2Fbtrhjss9kk9%2FEL0QdKpbTbclKlE6ATULE1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;foeach의 반복문 키워드는 IEnumerable의 인스턴스를 상속받은 클래스 값을 사용할 수 있습니다. 우리가 보통 List나 Array를 foreach에 넣어서 사용하게 되는데, 이 클래스가 IEnumerable의 인스턴스를 상속받은 것과 같은 의미입니다.&lt;/p&gt;
&lt;p&gt;IEnumerable의 인터페이스는 GetEnumerator 함수가 정의되어 있고 IEnumerator의 인터페이스 상속받은 클래스를 반환합니다.&lt;/p&gt;
&lt;p&gt;IEnumerator의 인터페이스에서는 foreach에서 사직을 하게 되면 Reset 함수를 호출하여 포인터를 초기화 하고 foreach에서 다음 포인터로 넘어갈 때마다 MoveNext의 함수를 호출하고 item으로 데이터를 넘길 때면 Current 프로퍼티를 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 조금 이상한 코드가 보이는데 MoveNext가 먼저 호출이 되고 Current를 호출이 되는데 MoveNext의 반환값은 현재값이 null인지 아닌 지에 대한 체크하고 포인터를 이동합니다. &lt;/p&gt;
&lt;p&gt;즉, Current에서는 pos - 1로 리턴하고 MoveNext 함수에서는 현재 포인터의 대한 값의 null 여부를 체크하기 때문에 return 값은 pos &amp;gt;= data.Lengt인지 확인하고 pos++로 위치를 이동하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 우리는 yield 키워드를 사용하면 IEnumerator를 좀 더 쉽게 만들 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Collections;

namespace Example
{
  // IEnumerable를 상속 받은 List
  class List : IEnumerable
  {
    // 데이터를 1부터 10까지의 배열
    private int[] data = new int[]
    {
      1,2,3,4,5,6,7,8,9,10
    };
    // GetEnumerator를 리턴
    public IEnumerator GetEnumerator()
    {
      // yield 키워드를 통해서 위 data 배열이 순서대로 return 된다.
      yield return data[0];
      yield return data[1];
      yield return data[2];
      yield return data[3];
      yield return data[4];
      yield return data[5];
      yield return data[6];
      yield return data[7];
      yield return data[8];
      yield return data[9];
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 리스트 인스턴스 생성
      var list = new List();
      // 반복문으로 list의 GetEnumerator 함수를 통해서 리스트 형식으로 가져온다.
      foreach (int item in list)
      {
        // MoveNext와 Current를 통해서 값을 콘솔에 표시
        Console.WriteLine(item);
      }

      // 반복문으로 list의 GetEnumerator 함수를 통해서 리스트 형식으로 가져온다.
      foreach (int item in list)
      {
        // MoveNext와 Current를 통해서 값을 콘솔에 표시
        Console.WriteLine(item);
      }

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buRwmL/btrhjss9kk9/EL0QdKpbTbclKlE6ATULE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buRwmL/btrhjss9kk9/EL0QdKpbTbclKlE6ATULE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buRwmL/btrhjss9kk9/EL0QdKpbTbclKlE6ATULE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuRwmL%2Fbtrhjss9kk9%2FEL0QdKpbTbclKlE6ATULE1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;IEnumerator 인터페이스를 상속받은 클래스와 같은 결과의 값이 표시됩니다.&lt;/p&gt;
&lt;p&gt;yield의 경우는 호출될 때마다 return의 값을 다르게 할 수 있습니다. 즉, GetEnumerator() 호출이 되면 yield 키워드를 파악해서 하나의 함수로 되어 있는 연결리스트를 생성합니다.&lt;/p&gt;
&lt;p&gt;그리고 MoveNext가 호출이 될 때마다 다음 단계의 yield까지 실행이 되는 방법입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;또, IEnumerator 인터페이스는 foreach에서만 사용하는 게 아니고 Linq에서도 사용가능합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Example
{
  // IEnumerable를 상속 받은 List
  class List : IEnumerable&amp;lt;int&amp;gt;
  {
    // 데이터를 1부터 10까지의 배열
    private int[] data = new int[]
    {
      1,2,3,4,5,6,7,8,9,10
    };
    // GetEnumerator를 리턴 (IEnumerable&amp;lt;int&amp;gt; 인터페이스)
    public IEnumerator&amp;lt;int&amp;gt; GetEnumerator()
    {
      // 데이터의 개수만큼
      yield return data[0];
      yield return data[1];
      yield return data[2];
      yield return data[3];
      yield return data[4];
      yield return data[5];
      yield return data[6];
      yield return data[7];
      yield return data[8];
      yield return data[9];
    }
    // GetEnumerator를 리턴 (IEnumerable 인터페이스)
    IEnumerator IEnumerable.GetEnumerator()
    {
      // 오버로딩으로 인한 두 함수가 나누어져 있기 때문에 위 함수를 호출하면 됩니다.
      return GetEnumerator();
    }
  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 리스트 인스턴스 생성
      var list = new List();
      // 위 yield으로 되어 있는 리스트를 Linq 식으로 리스트로 만들 수 있다
      var ret = list.Where(x =&amp;gt; x &amp;gt; 5).OrderByDescending(x =&amp;gt; x);
      // 결과를 foreach
      foreach (var item in ret)
      {
        // 콘솔 출력
        Console.WriteLine(item);
      }

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m9zT5/btrhnbxoQaO/k1LKbGUx8LA5UppXh4hHNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m9zT5/btrhnbxoQaO/k1LKbGUx8LA5UppXh4hHNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m9zT5/btrhnbxoQaO/k1LKbGUx8LA5UppXh4hHNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm9zT5%2FbtrhnbxoQaO%2Fk1LKbGUx8LA5UppXh4hHNK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;사실 yield 키워드는 자주 사용하지 않습니다. 저도 문법은 알고 있지만 상당히 생소한 문법입니다. 물론 개발하는 사람에 따라 다르기는 하지만, 저의 경우는 다른 언어(?)에는 없는 문법이고 굳이 익숙하지 않은 문법으로 가독성을 안좋게 할 이유는 없다고 생각합니다.&lt;/p&gt;
&lt;p&gt;사실 보통 IEnumerable나 IEnumerator를 상속받아서 개발하는 것 자체가 거의 별로 없습니다. 상황에 따라 캐쉬 알고리즘을 만든다거나 List 알고리즘을 더 효율적으로 개선할 수 있겠지만, .Net Framework에서 제공하는 List 알고리즘 자체가 꽤 우수하고 새로운 알고리즘을 만드는 것으로 버그에 대한 안정성을 보장할 수 없어서 입니다.&lt;/p&gt;
&lt;p&gt;Array도 있고 그렇다고 List를 쓴다고 시스템이 그렇게 느려질 것 같지도 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 IEnumerable와 IEnumerator, 그리고 yield 키워드에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/425</guid>
      <comments>https://nowonbun.tistory.com/425#entry425comment</comments>
      <pubDate>Mon, 11 Oct 2021 17:43:36 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 46. Nuget 사용법(외부 라이브러리)과 데이터 베이스(MariaDB(Mysql)) 사용법 그리고 트랜젝션(Transaction)</title>
      <link>https://nowonbun.tistory.com/171</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 Nuget 사용법(외부 라이브러리)과 데이터 베이스(MariaDB(Mysql)) 사용법 그리고 트랜젝션(Transaction)에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;C#이 언어적으로 장점이라고 생각되는 부분은 .Net framework 안에 기본적인 라이브러리가 정말 많이 포함되어 있다는 것입니다. 그 외에도 Visual studio의 IDE 툴도 굉장히 편하고, 같은 제품군(MS 제품군)에 대해서 호완성도 뛰어나다는 장점도 있습니다.&lt;/p&gt;
&lt;p&gt;그런데 많은 라이브러리를 포함하고 있다고 해도, 실무에서 사용되는 모든 라이브러리를 가지고 있지 않습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;예를 들면, 기본적으로 C#에서는 MsSQL 데이터 베이스에 대한 접속 라이브러리를 가지고 있습니다. 따로 외부 라이브러리가 필요가 없습니다. 그렇기 때문에 다른 종류의 데이터 베이스의 라이브러리는 .Net framework에 없습니다.&lt;/p&gt;
&lt;p&gt;한마디로 C#를 사용하면 MsSql를 사용하기를 권장하는 것입니다. 그런데 실무에서는 사양에 따른, 혹은 여러가지 이유로 개발 언어는 C#으로 개발해도 데이터 베이스는 Oracle(오라클)이나 Mysql(MariaDB)를 사용해야 할 경우도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그렇다면 외부 라이브러리를 사용해야 하는데 MariaDB 홈페이지에 들어가면 C# 라이브러리 파일이 있습니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://mariadb.com/kb/en/other-net-connectors/&quot; target=&quot;_blank&quot;&gt;mariadb net-connectors&lt;/a&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6vCJo/btrg8g8bltM/5pr3nSBEiN1XWvzOT9lzD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6vCJo/btrg8g8bltM/5pr3nSBEiN1XWvzOT9lzD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6vCJo/btrg8g8bltM/5pr3nSBEiN1XWvzOT9lzD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6vCJo%2Fbtrg8g8bltM%2F5pr3nSBEiN1XWvzOT9lzD0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이렇게 다운로드해서 사용해도 괜찮은데, 이렇게 하면 버젼 관리라던가 배포(Deploy)나 여러 사람과 프로젝트 공유를 할 때, 라이브러리 관리등의 문제가 발생하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 프로젝트 라이브러리 관리 툴이 있는데 Java에서는 maven이 있는 것처럼 C#에서는 Nuget이 있습니다.&lt;/p&gt;
&lt;p&gt;Nuget은 Visual studio가 설치되어 있으면 별도로 설치할 필요는 없습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciIei8/btrg9D2TGdy/OqntPK5fARGECKS0tZT3ZK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciIei8/btrg9D2TGdy/OqntPK5fARGECKS0tZT3ZK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciIei8/btrg9D2TGdy/OqntPK5fARGECKS0tZT3ZK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciIei8%2Fbtrg9D2TGdy%2FOqntPK5fARGECKS0tZT3ZK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;프로젝트에서 References의 항목에서 마우스 오른쪽 클릭을 해서 Context 메뉴를 보면 Manage Nuget 항목이 있습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eaep57/btrg8heWycD/UyuNM7lUngy8Inmh1a8CbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eaep57/btrg8heWycD/UyuNM7lUngy8Inmh1a8CbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eaep57/btrg8heWycD/UyuNM7lUngy8Inmh1a8CbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feaep57%2Fbtrg8heWycD%2FUyuNM7lUngy8Inmh1a8CbK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;Browse 탭에서 검색을 하면 설치할 수 있는 라이브러리가 표시됩니다.&lt;/p&gt;
&lt;p&gt;우리는 MariaDB를 사용할 것이기 때문에 MySql.Data 라이브러리를 클릭하고 Version을 확인하고 Install를 누릅니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PVPma/btrhdBQfW1G/C8p5fqIbCO2veHqP9K5sGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PVPma/btrhdBQfW1G/C8p5fqIbCO2veHqP9K5sGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PVPma/btrhdBQfW1G/C8p5fqIbCO2veHqP9K5sGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPVPma%2FbtrhdBQfW1G%2FC8p5fqIbCO2veHqP9K5sGk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그러면 약관이 표시가 되고 Accept를 눌러서 설치를 시작합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcODpu/btrhdwaou9C/GGhxM9bKAwZxK9k2OMZoi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcODpu/btrhdwaou9C/GGhxM9bKAwZxK9k2OMZoi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcODpu/btrhdwaou9C/GGhxM9bKAwZxK9k2OMZoi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcODpu%2Fbtrhdwaou9C%2FGGhxM9bKAwZxK9k2OMZoi0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;그리고 References의 항목을 열면 라이브러리가 연결되어 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;혹시 라이브러리가 의존성되어 있는 라이브러리가 있다면 (즉, 라이브러리에서 다른 라이브러리를 참조하고 있다면) 필요한 라이브러리도 자동으로 다 연결이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 우리는 MariaDB를 사용할 것인데 왜 Mysql를 다운을 받는가 하면 사실 과거 Mysql를 예전에 무료 데이터 베이스였는데 Oracle에 인수되면서 유료화로 바뀌었습니다.&lt;/p&gt;
&lt;p&gt;그래서 예전 Mysql를 만들던 팀이 다시 독립을 해서 무료 시절의 Mysql의 소스를 포크하여 무료 데이터 베이스인 MariaDB를 만들었습니다.&lt;/p&gt;
&lt;p&gt;그러다 보니 MySql과 MariaDB는 비슷하지만 다른 데이터 베이스라고 생각하면 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;MariaDB를 설치하는 방법은 다른 글에서 소개하고 있으니 참고하세요.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/37&quot; target=&quot;_blank&quot;&gt;[Window] MariaDB를 설치하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;데이터 베이스가 설치 되었으면 간단한 테이블을 만들고 데이터를 넣고 C# 프로그램에서 검색해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;sql&quot; data-type=&quot;query.sql&quot;&gt;-- drop table Test;
-- 테이블 생성
create table Test(
	idx bigint auto_increment,
	data varchar(1024),
	
	primary key(idx)
)
-- 데이터 입력
insert into Test (data) values('Hello world - 1');
insert into Test (data) values('Hello world - 2');
insert into Test (data) values('Hello world - 3');
insert into Test (data) values('Hello world - 4');
insert into Test (data) values('Hello world - 5');
insert into Test (data) values('Hello world - 6');
insert into Test (data) values('Hello world - 7');
insert into Test (data) values('Hello world - 8');
insert into Test (data) values('Hello world - 9');
insert into Test (data) values('Hello world - 10');
insert into Test (data) values('Hello world - 11');
insert into Test (data) values('Hello world - 12');
-- 검색
select * from Test;
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dHKeEP/btrg8b0spF3/AzsoIw6PrkKCOCIrVscjGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dHKeEP/btrg8b0spF3/AzsoIw6PrkKCOCIrVscjGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dHKeEP/btrg8b0spF3/AzsoIw6PrkKCOCIrVscjGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdHKeEP%2Fbtrg8b0spF3%2FAzsoIw6PrkKCOCIrVscjGK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위처럼 데이터 베이스에 테이블을 만들고 간단한 데이터를 입력했습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 데이터를 가지고 일단 C# 프로그램에서 데이터를 취득해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Collections.Generic;
using MySql.Data.MySqlClient;
using System.Data;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // MySqlCommand 클래스의 인스턴스 생성
      var cmd = new MySqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new MySqlConnection(&quot;Data Source=localhost;Database=BlogExample;SSL Mode=None;User Id=root;Password=&quot;);
      // 검색할 쿼리를 넣는다.
      cmd.CommandText = &quot;select * from Test&quot;;
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // 검색해서 가져오는 데이터 변수
      List&amp;lt;List&amp;lt;object&amp;gt;&amp;gt; dataList = null;
      // 검색한 테이블의 Column 변수
      string[] fieldList = null;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 위 쿼리를 접속해서 SqlDataReader를 취득한다.
        var dr = cmd.ExecuteReader();
        // 데이터 변수 인스턴스 생성
        dataList = new List&amp;lt;List&amp;lt;object&amp;gt;&amp;gt;();
        // Column 변수 인스턴스 생성
        fieldList = new string[dr.FieldCount];
        // 테이블의 필드 크기만큼
        for (var i = 0; i &amp;lt; fieldList.Length; i++)
        {
          // 필드 명을 배열에 넣는다.
          fieldList[i] = dr.GetName(i);
        }
        // 레코드의 개수만큼 루프
        while (dr.Read())
        {
          // 레코드 데이터를 넣을 변수 리스트 생성
          var entity = new List&amp;lt;object&amp;gt;();
          // 데이터 변수 리스트에 넣는다.
          dataList.Add(entity);
          // Column의 크기만큼
          for (var i = 0; i &amp;lt; fieldList.Length; i++)
          {
            // 컬럼의 타입을 취득
            var type = dr.GetFieldType(i);
            // int 타입이라면
            if (type == typeof(int))
            {
              // int 타입으로 취득
              entity.Add(dr.GetInt32(i));
            }
            // string 타입이라면
            else if (type == typeof(string))
            {
              // string 타입으로 취득
              entity.Add(dr.GetString(i));
            }
            else
            {
              // object 타입으로 취득
              entity.Add(dr.GetValue(i));
            }
          }
        }
      }
      // 출력을 위해 콘솔 상단에 Column 이름을 출력한다.
      foreach (var field in fieldList)
      {
        // 콘솔 출력
        Console.Write(field);
        // tab 두번 콘솔 출력
        Console.Write(&quot;\t\t&quot;);
      }
      // 개행
      Console.WriteLine();
      // 테이블 Column 명과 데이터를 구분하기 위해 콘솔 출력
      for (int i = 0; i &amp;lt; fieldList.Length; i++)
      {
        // 콘솔 출력
        Console.Write(&quot;-----------------&quot;);
      }
      // 개행
      Console.WriteLine();
      // 데이터의 크기만큼
      foreach (var entity in dataList)
      {
        // Column의 크기만큼
        foreach (var column in entity)
        {
          // 콘솔 출력
          Console.Write(column);
          // tab 두번 콘솔 출력
          Console.Write(&quot;\t\t&quot;);
        }
        // 개행
        Console.WriteLine();
      }

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AFSps/btrhdd3gqM0/oInrckmhCDF77x6PKXhsq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AFSps/btrhdd3gqM0/oInrckmhCDF77x6PKXhsq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AFSps/btrhdd3gqM0/oInrckmhCDF77x6PKXhsq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAFSps%2Fbtrhdd3gqM0%2FoInrckmhCDF77x6PKXhsq1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;사용 방법은 MsSQL에서 접속한 방법이랑 같습니다. &lt;/p&gt;
&lt;p&gt;ExecuteReader로 결과 값을 받고 ExecuteNonQuery로 insert나 update, delete를 실행하는 방법입니다.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/169&quot; target=&quot;_blank&quot;&gt;[C#] 45. 데이터 베이스(MSSQL)에 접속하는 방법&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기서 그럼 조금 궁금한 점이 생깁니다. 쿼리를 실행중에 에러가 발생되면 어떻게 될까요?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using MySql.Data.MySqlClient;
using System.Data;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // MySqlCommand 클래스의 인스턴스 생성
      var cmd = new MySqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new MySqlConnection(&quot;Data Source=localhost;Database=BlogExample;User Id=root;SSL Mode=None;Password=&quot;);
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 테이블 삭제 쿼리
        cmd.CommandText = &quot;delete from Test&quot;;
        // 실행(반환 값이 필요없다.)
        cmd.ExecuteNonQuery();
        // 에러를 발생!
        throw new Exception();
        // 테이블에 데이터 추가
        cmd.CommandText = &quot;insert into Test values(@data)&quot;;
        // @data 파라미터에 데이터를 입력한다.
        cmd.Parameters.Add(new MySqlParameter(&quot;@data&quot;, &quot;Hello world - Addition&quot;));
        // 실행(반환 값이 필요없다.)
        cmd.ExecuteNonQuery();
      }

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sEWC1/btrg8FAbUr7/kICJY9xGmKZGRImsYuHbWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sEWC1/btrg8FAbUr7/kICJY9xGmKZGRImsYuHbWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sEWC1/btrg8FAbUr7/kICJY9xGmKZGRImsYuHbWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsEWC1%2Fbtrg8FAbUr7%2FkICJY9xGmKZGRImsYuHbWk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;일부러 에러를 발생 시켰습니다. 그런데 그 에러가 delete를 실행하고 에러를 발생합니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX6mwA/btrg8bsyxAM/SOt9HrOVPABe4DKnp8qCoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX6mwA/btrg8bsyxAM/SOt9HrOVPABe4DKnp8qCoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX6mwA/btrg8bsyxAM/SOt9HrOVPABe4DKnp8qCoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX6mwA%2Fbtrg8bsyxAM%2FSOt9HrOVPABe4DKnp8qCoK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;프로그램이 에러가 발생했기 때문에 프로그램에서 실행되는 모든 쿼리는 실행 취소가 되어야 하는데 테이블에서 데이터가 모두 삭제가 되어버렸습니다.&lt;/p&gt;
&lt;p&gt;실제 업무에서도 처리중에 에러가 발생되면 프로그램 상에서 실행된 쿼리가 취소가 되어야 하는데 그렇지 않으면 중간에 쿼리 처리가 멈춘 불완전한 데이터가 되어버립니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그래서 트랜젝션 기능이 있는데 그 구역안에 완벽히 실행이 완료가 되어야 모든 쿼리가 실행되는 처리입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using MySql.Data.MySqlClient;
using System.Data;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // MySqlCommand 클래스의 인스턴스 생성
      var cmd = new MySqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new MySqlConnection(&quot;Data Source=localhost;Database=BlogExample;User Id=root;SSL Mode=None;Password=&quot;);
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 트랜젝션 생성
        using (var transaction = cmd.Connection.BeginTransaction())
        {
          try
          {
            // 테이블에 데이터 추가
            cmd.CommandText = &quot;insert into Test values(@data)&quot;;
            // @data 파라미터에 데이터를 입력한다.
            cmd.Parameters.Add(new MySqlParameter(&quot;@data&quot;, &quot;Hello world - Addition&quot;));
            // 실행(반환 값이 필요없다.)
            cmd.ExecuteNonQuery();
            // 에러 발생
            throw new Exception();
            // 트랜젝션 커밋(트랜젝션 안에서 실행된 쿼리를 데이터 베이스에 실행)
            transaction.Commit();
          }
          catch
          {
            // 트랜젝션 롤백(트랜젝션 안에서 실행된 쿼리를 취소)
            transaction.Rollback();
          }
        }
      }
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX6mwA/btrg8bsyxAM/SOt9HrOVPABe4DKnp8qCoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX6mwA/btrg8bsyxAM/SOt9HrOVPABe4DKnp8qCoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX6mwA/btrg8bsyxAM/SOt9HrOVPABe4DKnp8qCoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX6mwA%2Fbtrg8bsyxAM%2FSOt9HrOVPABe4DKnp8qCoK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;insert를 실행했지만 트랜젝션에 의해 중간에 에러가 발생하면 insert 실행되지 않은 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;즉, 트랜젝션은 중간에 많은 쿼리를 실행했다 해도 에러가 발생하면 데이터 베이스에 적용되지 않도록 하는 기능입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 Transaction 기능인데 위처럼 작성되면 무언가 프로그램답지 않은 소스입니다.&lt;/p&gt;
&lt;p&gt;여기서 옵서버 패턴으로 프로그램을 작성해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using MySql.Data.MySqlClient;
using System.Data;
using System.Text;

namespace Example
{
  class Program
  {
    // Transaction 옵서버 패턴
    static T Transaction&amp;lt;T&amp;gt;(MySqlCommand cmd, Func&amp;lt;T&amp;gt; func)
    {
      // 트랜젝션 생성
      using (var transaction = cmd.Connection.BeginTransaction())
      {
        try
        {
          // 함수 실행
          T ret = func();
          // 트랜젝션 커밋(트랜젝션 안에서 실행된 쿼리를 데이터 베이스에 실행)
          transaction.Commit();
          // 함수의 결과를 리턴
          return ret;
        }
        catch
        {
          // 트랜젝션 롤백(트랜젝션 안에서 실행된 쿼리를 취소)
          transaction.Rollback();
          // class라면 null을 struct라면 기본 인스턴스를 리턴
          return default(T);
        }
      }
    }
    // Transaction 옵서버 패턴
    static void Transaction(MySqlCommand cmd, Action action)
    {
      // 트랜젝션 생성
      using (var transaction = cmd.Connection.BeginTransaction())
      {
        try
        {
          // 함수 실행
          action();
          // 트랜젝션 커밋(트랜젝션 안에서 실행된 쿼리를 데이터 베이스에 실행)
          transaction.Commit();
        }
        catch
        {
          // 트랜젝션 롤백(트랜젝션 안에서 실행된 쿼리를 취소)
          transaction.Rollback();
        }
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // MySqlCommand 클래스의 인스턴스 생성
      var cmd = new MySqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new MySqlConnection(&quot;Data Source=localhost;Database=BlogExample;User Id=root;SSL Mode=None;Password=&quot;);
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // Transaction 실행
        Transaction(cmd, () =&amp;gt;
        {
          // 테이블에 데이터 추가
          cmd.CommandText = &quot;insert into Test (data) values(@data)&quot;;
          // @data 파라미터에 데이터를 입력한다.
          cmd.Parameters.Add(new MySqlParameter(&quot;@data&quot;, &quot;Hello world - Addition&quot;));
          // 실행(반환 값이 필요없다.)
          cmd.ExecuteNonQuery();
        });
        // Transaction 실행
        var str = Transaction(cmd, () =&amp;gt;
        {
          // 데이터 검색
          cmd.CommandText = &quot;select * from Test&quot;;
          // 버퍼
          var sb = new StringBuilder();
          // 실행
          using (var dr = cmd.ExecuteReader())
          {
            // 검색 레코드 수만큼
            while (dr.Read())
            {
              // 버퍼에 결과 입력
              sb.AppendLine($&quot;{dr.GetValue(0)}\t{dr.GetValue(1)}&quot;);
            }
          }
          // 버퍼 리턴
          return sb.ToString();
        });
        // 결과 콘솔 출력
        Console.WriteLine(str);
      }
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzgmFP/btrg8FmCC79/9gzYQwZbq6bMskGTkLyk61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzgmFP/btrg8FmCC79/9gzYQwZbq6bMskGTkLyk61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzgmFP/btrg8FmCC79/9gzYQwZbq6bMskGTkLyk61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzgmFP%2Fbtrg8FmCC79%2F9gzYQwZbq6bMskGTkLyk61%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 패턴을 써서 좀 더 프로그램다운 프로그램을 만들었습니다.&lt;/p&gt;
&lt;p&gt;사실 C#에는 ORM 프레임워크라고 Entity 프레임워크가 있습니다. 직접적으로 데이터 베이스에 접속하기 위해 일일히 Transaction 설정하고 Open, Close 등을 만드는 경우도 있지만 대부분은 위처럼 데이터 베이스를 효율적으로 접속하기 위해 패턴으로 짜여져 있는 프레임워크가 있는데 그것이 ORM(Object reference mapping) 프레임워크인 Entity 프레임워크가 있습니다.&lt;/p&gt;
&lt;p&gt;Entity 프레임워크에 대해서는 다른 글에서 상세하게 설명하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 Nuget 사용법과 데이터 베이스(MariaDB(Mysql)) 사용법 그리고 트랜젝션(Transaction)에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/171</guid>
      <comments>https://nowonbun.tistory.com/171#entry171comment</comments>
      <pubDate>Fri, 8 Oct 2021 17:57:47 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 45. 데이터 베이스(MSSQL)에 접속하는 방법</title>
      <link>https://nowonbun.tistory.com/169</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 데이터 베이스(MSSQL)에 접속하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;우리가 프로그램을 작성하다보면 데이터를 저장하는 경우가 많은데, IO를 이용한 파일로 저장하거나 Socket 서버를 이용해서 다른 PC나 다른 프로그램을 이용해서 저장하는 방법 등등이 있습니다.&lt;/p&gt;
&lt;p&gt;그러나 데이터를 저장하기 가장 쉽고, 검색이나 필터를 하기 쉬운 방법으로 데이터 베이스를 이용하는 방법이 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;데이터 베이스의 종류는 엄청나게 많지만 그 중에서 가장 대표적인 것이 Oracle(오라클), MsSQL(Sql-server), MySql 혹은 MariaDB가 가장 많이 사용하는 데이터 베이스입니다.&lt;/p&gt;
&lt;p&gt;그 중에서 MsSQL은 MS(Microsoft)사에서 제공하는 데이터 베이스이고, C#도 MS(Microsoft)사에서 제공하는 프로그램 언어이기 때문에 C#에서는 상대적으로 MsSQL이 다루기가 가장 쉽습니다. 다루기가 쉽다기 보다는 별로의 라이브러리를 연결하지 않아도 사용할 수 있는 데이터 베이스입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;먼저 데이터 베이스를 접속하기 위해서는 데이터 베이스를 설치해야 하는데 그건 다른 글에서 소개하고 있으니 참고하세요.&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://nowonbun.tistory.com/142&quot; target=&quot;_blank&quot;&gt;[MSSQL] MSSQL Express 설치하기&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;데이터 베이스가 설치 되었으면 간단한 테이블을 만들고 데이터를 넣고 C# 프로그램에서 검색해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;sql&quot; data-type=&quot;query.sql&quot;&gt;-- drop table Test;
-- 테이블 작성
create table Test(
	idx int identity(1,1),
	data nvarchar(max)
)
-- 데이터 입력
insert into Test values('Hello world - 1');
insert into Test values('Hello world - 2');
insert into Test values('Hello world - 3');
insert into Test values('Hello world - 4');
insert into Test values('Hello world - 5');
insert into Test values('Hello world - 6');
insert into Test values('Hello world - 7');
insert into Test values('Hello world - 8');
insert into Test values('Hello world - 9');
insert into Test values('Hello world - 10');
insert into Test values('Hello world - 11');
insert into Test values('Hello world - 12');
-- 검색
select * from Test;
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckMp0u/btrg1Lngkqq/PXK07aFKAOMTKHzEvXcgj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckMp0u/btrg1Lngkqq/PXK07aFKAOMTKHzEvXcgj0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckMp0u/btrg1Lngkqq/PXK07aFKAOMTKHzEvXcgj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckMp0u%2Fbtrg1Lngkqq%2FPXK07aFKAOMTKHzEvXcgj0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위처럼 데이터 베이스에 테이블을 만들고 간단한 데이터를 입력했습니다.&lt;/p&gt;
&lt;p&gt;이 데이터를 가지고 일단 C# 프로그램에서 데이터를 취득해 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // SqlCommand 클래스의 인스턴스 생성
      var cmd = new SqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new SqlConnection(&quot;Data Source=localhost;Database=BlogExample;User Id=sa;Password=&quot;);
      // 검색할 쿼리를 넣는다.
      cmd.CommandText = &quot;select * from Test&quot;;
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // 검색해서 가져오는 데이터 변수
      List&amp;lt;List&amp;lt;object&amp;gt;&amp;gt; dataList = null;
      // 검색한 테이블의 Column 변수
      string[] fieldList = null;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 위 쿼리를 접속해서 SqlDataReader를 취득한다.
        var dr = cmd.ExecuteReader();
        // 데이터 변수 인스턴스 생성
        dataList = new List&amp;lt;List&amp;lt;object&amp;gt;&amp;gt;();
        // Column 변수 인스턴스 생성
        fieldList = new string[dr.FieldCount];
        // 테이블의 필드 크기만큼
        for (var i = 0; i &amp;lt; fieldList.Length; i++)
        {
          // 필드 명을 배열에 넣는다.
          fieldList[i] = dr.GetName(i);
        }
        // 레코드의 개수만큼 루프
        while (dr.Read())
        {
          // 레코드 데이터를 넣을 변수 리스트 생성
          var entity = new List&amp;lt;object&amp;gt;();
          // 데이터 변수 리스트에 넣는다.
          dataList.Add(entity);
          // Column의 크기만큼
          for (var i = 0; i &amp;lt; fieldList.Length; i++)
          {
            // 컬럼의 타입을 취득
            var type = dr.GetFieldType(i);
            // int 타입이라면
            if (type == typeof(int))
            {
              // int 타입으로 취득
              entity.Add(dr.GetInt32(i));
            }
            // string 타입이라면
            else if (type == typeof(string))
            {
              // string 타입으로 취득
              entity.Add(dr.GetString(i));
            }
            else
            {
              // object 타입으로 취득
              entity.Add(dr.GetValue(i));
            }
          }
        }
      }
      // 출력을 위해 콘솔 상단에 Column 이름을 출력한다.
      foreach (var field in fieldList)
      {
        // 콘솔 출력
        Console.Write(field);
        // tab 두번 콘솔 출력
        Console.Write(&quot;\t\t&quot;);
      }
      // 개행
      Console.WriteLine();
      // 테이블 Column 명과 데이터를 구분하기 위해 콘솔 출력
      for (int i = 0; i &amp;lt; fieldList.Length; i++)
      {
        // 콘솔 출력
        Console.Write(&quot;-----------------&quot;);
      }
      // 개행
      Console.WriteLine();
      // 데이터의 크기만큼
      foreach (var entity in dataList)
      {
        // Column의 크기만큼
        foreach (var column in entity)
        {
          // 콘솔 출력
          Console.Write(column);
          // tab 두번 콘솔 출력
          Console.Write(&quot;\t\t&quot;);
        }
        // 개행
        Console.WriteLine();
      }

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tcOTL/btrg7Wf0h8N/tKMKF1kYhasg3c4k67h4x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tcOTL/btrg7Wf0h8N/tKMKF1kYhasg3c4k67h4x1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tcOTL/btrg7Wf0h8N/tKMKF1kYhasg3c4k67h4x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtcOTL%2Fbtrg7Wf0h8N%2FtKMKF1kYhasg3c4k67h4x1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제를 보면 데이터 베이스에 있는 Test의 테이블의 데이터를 읽어와서 콘솔에 표시했습니다.&lt;/p&gt;
&lt;p&gt;SqlCommand 클래스의 인스턴스를 생성해서 커넥션을 만들고 ExecuteReader의 함수를 실행해서 데이터를 읽어옵니다.&lt;/p&gt;
&lt;p&gt;그리고 ExecuteReader의 함수의 리턴값은 SqlDataReader의 인스턴스이고 SqlDataReader의 Read함수를 이용해서 데이터를 읽어 옵니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Data;
using System.Data.SqlClient;
using System.Collections.Generic;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // SqlCommand 클래스의 인스턴스 생성
      var cmd = new SqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      cmd.Connection = new SqlConnection(&quot;Data Source=localhost;Database=BlogExample;User Id=sa;Password=&quot;);
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      cmd.CommandType = CommandType.Text;
      // 검색해서 가져오는 데이터 변수
      List&amp;lt;List&amp;lt;object&amp;gt;&amp;gt; dataList = null;
      // 검색한 테이블의 Column 변수
      string[] fieldList = null;
      // Close 설정(스택 영역이 끝나면 자동 커넥션 종료)
      using (cmd.Connection)
      {
        // 커넥션 Open
        cmd.Connection.Open();
        // 테이블 삭제 쿼리
        cmd.CommandText = &quot;delete Test&quot;;
        // 실행(반환 값이 필요없다.)
        cmd.ExecuteNonQuery();
        
        // 테이블에 데이터 추가
        cmd.CommandText = &quot;insert into Test (data) values(@data)&quot;;
        // @data 파라미터에 데이터를 입력한다.
        cmd.Parameters.Add(new SqlParameter(&quot;@data&quot;, &quot;Hello world - Addition&quot;));
        // 실행(반환 값이 필요없다.)
        cmd.ExecuteNonQuery();
        // 테이블에서 데이터 검색
        cmd.CommandText = &quot;select * from Test&quot;;
        // 위 쿼리를 접속해서 SqlDataReader를 취득한다.
        var dr = cmd.ExecuteReader();
        // 데이터 변수 인스턴스 생성
        dataList = new List&amp;lt;List&amp;lt;object&amp;gt;&amp;gt;();
        // Column 변수 인스턴스 생성
        fieldList = new string[dr.FieldCount];
        // 테이블의 필드 크기만큼
        for (var i = 0; i &amp;lt; fieldList.Length; i++)
        {
          // 필드 명을 배열에 넣는다.
          fieldList[i] = dr.GetName(i);
        }
        // 레코드의 개수만큼 루프
        while (dr.Read())
        {
          // 레코드 데이터를 넣을 변수 리스트 생성
          var entity = new List&amp;lt;object&amp;gt;();
          // 데이터 변수 리스트에 넣는다.
          dataList.Add(entity);
          // Column의 크기만큼
          for (var i = 0; i &amp;lt; fieldList.Length; i++)
          {
            // 컬럼의 타입을 취득
            var type = dr.GetFieldType(i);
            // int 타입이라면
            if (type == typeof(int))
            {
              // int 타입으로 취득
              entity.Add(dr.GetInt32(i));
            }
            // string 타입이라면
            else if (type == typeof(string))
            {
              // string 타입으로 취득
              entity.Add(dr.GetString(i));
            }
            else
            {
              // object 타입으로 취득
              entity.Add(dr.GetValue(i));
            }
          }
        }
      }
      // 출력을 위해 콘솔 상단에 Column 이름을 출력한다.
      foreach (var field in fieldList)
      {
        // 콘솔 출력
        Console.Write(field);
        // tab 두번 콘솔 출력
        Console.Write(&quot;\t\t&quot;);
      }
      // 개행
      Console.WriteLine();
      // 테이블 Column 명과 데이터를 구분하기 위해 콘솔 출력
      for (int i = 0; i &amp;lt; fieldList.Length; i++)
      {
        // 콘솔 출력
        Console.Write(&quot;-----------------&quot;);
      }
      // 개행
      Console.WriteLine();
      // 데이터의 크기만큼
      foreach (var entity in dataList)
      {
        // Column의 크기만큼
        foreach (var column in entity)
        {
          // 콘솔 출력
          Console.Write(column);
          // tab 두번 콘솔 출력
          Console.Write(&quot;\t\t&quot;);
        }
        // 개행
        Console.WriteLine();
      }

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C9c3X/btrg8cJGMUx/9DsuPKXBkKjv5SpqTaC41K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C9c3X/btrg8cJGMUx/9DsuPKXBkKjv5SpqTaC41K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C9c3X/btrg8cJGMUx/9DsuPKXBkKjv5SpqTaC41K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC9c3X%2Fbtrg8cJGMUx%2F9DsuPKXBkKjv5SpqTaC41K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제에서는 delete와 insert를 해서 데이터를 삭제하고 추가했습니다.&lt;/p&gt;
&lt;p&gt;그리고 함수는 ExecuteReader의 함수가 아니고 ExecuteNonQuery의 함수를 사용해서 실행했습니다.&lt;/p&gt;
&lt;p&gt;왜냐하면 delete와 insert는 검색의 결과를 가져오는 것이 아니고 실행만 하는 것이 중요하기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위 방법은 연결 지향형 데이터 베이스 접속이라고 합니다. 즉, 데이터 베이스의 데이터를 한번에 다 가져오는게 아니고 Read() 함수를 통해서 하나하나 취득해오는 것이기 때문입니다. 연결 지향형에서는 시스템에 대한 부담은 상대적으로 적을 수 있으나 데이터가 많으면 데이터 베이스의 lock이나 트랙젝션 문제가 있을 수 있습니다.&lt;/p&gt;
&lt;p&gt;그래서 레코드 하나하나식 읽어오면 연결 지향형이 아니고 데이터를 한번에 취득해 오는 비연결 지향형 접속 방식이 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Data;
using System.Data.SqlClient;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // SqlDataAdapter 인스턴스 생성
      var sda = new SqlDataAdapter();
      // SqlCommand 클래스의 인스턴스 생성
      sda.SelectCommand = new SqlCommand();
      // 접속 커넥션의 인스턴스를 생성한다.
      sda.SelectCommand.Connection = new SqlConnection(&quot;Data Source=localhost;Database=BlogExample;User Id=sa;Password=&quot;);
      // 검색할 쿼리를 넣는다.
      sda.SelectCommand.CommandText = &quot;select * from Test&quot;;
      // 검색 쿼리 타입, 일반 쿼리는 Text이고 프로시저는 StoredProcedure이다.
      sda.SelectCommand.CommandType = CommandType.Text;
      // DataSet 인스턴스 생성
      var ds = new DataSet();
      // SqlDataAdapter 인스턴스에 DataSet 데이터를 넣음
      sda.Fill(ds);
      foreach (DataTable table in ds.Tables)
      {
        // 출력을 위해 콘솔 상단에 Column 이름을 출력한다.
        foreach (DataColumn column in table.Columns)
        {
          // 콘솔 출력
          Console.Write(column.ColumnName);
          // tab 두번 콘솔 출력
          Console.Write(&quot;\t\t&quot;);
        }
        // 개행
        Console.WriteLine();
        // 테이블 Column 명과 데이터를 구분하기 위해 콘솔 출력
        for (int i = 0; i &amp;lt; table.Columns.Count; i++)
        {
          // 콘솔 출력
          Console.Write(&quot;-----------------&quot;);
        }
        // 개행
        Console.WriteLine();
        // 테이터의 개수만큼
        foreach (DataRow row in table.Rows)
        {
          // 데이터를 column의 수만큼
          foreach (DataColumn column in table.Columns)
          {
            // 콘솔 출력
            Console.Write(row[column]);
            // tab 두번 콘솔 출력
            Console.Write(&quot;\t\t&quot;);
          }
          // 개행
          Console.WriteLine();
        }
      }

      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C9c3X/btrg8cJGMUx/9DsuPKXBkKjv5SpqTaC41K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C9c3X/btrg8cJGMUx/9DsuPKXBkKjv5SpqTaC41K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C9c3X/btrg8cJGMUx/9DsuPKXBkKjv5SpqTaC41K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC9c3X%2Fbtrg8cJGMUx%2F9DsuPKXBkKjv5SpqTaC41K%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;결과는 연결 지향형으로 데이터를 가져오는 것과 같습니다. 데이터 베이스에 데이터를 가져오는 것은 같습니다만 데이터를 취득하는 방식에 대한 차이입니다.&lt;/p&gt;
&lt;p&gt;이 경우에는 데이터를 한번에 가져와서 DataSet에 가져오기 때문에 데이터가 커지게 되면 프로그램 쪽에 많은 인스턴스 생성으로 인해 오히려 느려지고 메모리 부담이 생기게 되겠네요. 대신에 데이터 베이스에서는 한번에 Select 하고 난 후여서 조금 더 부담이 적겠네요.&lt;/p&gt;
&lt;p&gt;즉, 상황에 맞게 연결 지향형으로 할지 비연결 지향형으로 설정하여 사용하면 되겠네요.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 데이터 베이스(MSSQL)에 접속하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/169</guid>
      <comments>https://nowonbun.tistory.com/169#entry169comment</comments>
      <pubDate>Thu, 7 Oct 2021 14:48:00 +0900</pubDate>
    </item>
    <item>
      <title>[C#] 44. 네트워크 소켓 통신(Socket)을 하는 방법</title>
      <link>https://nowonbun.tistory.com/155</link>
      <description>&lt;p&gt;안녕하세요. 명월입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;이 글은 C#에서 네트워크 소켓 통신(Socket)을 하는 방법에 대한 글입니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;프로그램과 프로그램, 그리고 컴퓨터와 컴퓨터끼리 데이터를 주고 받는 것을 통신이라고 합니다. 통신을 좀 더 자세히 설명하면, 전송하는 패킷(데이터)이 컴퓨터의 랜 카드를 거쳐 랜 케이블로 나갑니다. 랜 케이블로 나간 데이터는 DNS와 라우터 등을 거쳐 도달하고자 하는 PC의 랜 카드에 들어가고 목표로 하는 프로그램에서 패킷(데이터)를 읽어 서로 간에 데이터를 주고 받습니다.&lt;/p&gt;
&lt;p&gt;이 때, 우리는 각 단말 간에 데이터 변환이나 장비 간의 통신 규약에 대해서 모두 개발하지 않습니다. 이러한 통신 규약 등은 모두 OS 측에서 설정(OSI 7계층)되고, 우리는 그 위에 꽂아서 쓴다라는 개념으로 Socket 통신 규약을 이용해 통신합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;링크 - &lt;a href=&quot;https://ko.wikipedia.org/wiki/OSI_%EB%AA%A8%ED%98%95&quot; target=&quot;_blank&quot;&gt;[위키백과] OSI 모형&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Socket 통신 규약은 규칙이 정해져 있습니다.&lt;/p&gt;
&lt;p&gt;먼저 기다리는 측의 PC를 서버라고 하며 Port를 열고 클라이언트의 접속을 기다립니다. 그리고 접속하는 측을 클라이언트라고 하며 서버의 IP와 Port에 접속하여 통신이 연결이 됩니다.&lt;/p&gt;
&lt;p&gt;서버와 클라이언트 간의 통신은 Send, Receive의 형태로 데이터를 주고 받습니다. 그리고 서로 통신이 끝나면 Close로 접속을 끊습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZCNG9/btrg1KN9l0r/qshixGnkKAwxaVLkvLbIC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZCNG9/btrg1KN9l0r/qshixGnkKAwxaVLkvLbIC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZCNG9/btrg1KN9l0r/qshixGnkKAwxaVLkvLbIC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZCNG9%2Fbtrg1KN9l0r%2FqshixGnkKAwxaVLkvLbIC1%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;이 규약을 이용해서 C#에서 소켓 통신을 작성해 보겠습니다.&lt;/p&gt;
&lt;p&gt;먼저 Server를 만들고 Window의 Telnet 프로그램을 이용해서 접속을 확인하고 그리고 사양에 맞추어서 Client를 작성하겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Socket EndPoint 설정(서버의 경우는 Any로 설정하고 포트 번호만 설정한다.)
      var ipep = new IPEndPoint(IPAddress.Any, 10000);
      // 소켓 인스턴스 생성
      using (Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
      {
        // 서버 소켓에 EndPoint 설정
        server.Bind(ipep);
        // 클라이언트 소켓 대기 버퍼
        server.Listen(20);
        // 콘솔 출력
        Console.WriteLine($&quot;Server Start... Listen port {ipep.Port}...&quot;);
        // 클라이언트로부터 접속 대기
        using (var client = server.Accept())
        {
          // 클라이언트 EndPoint 정보 취득
          var ip = client.RemoteEndPoint as IPEndPoint;
          // 콘솔 출력 - 접속 ip와 접속 시간
          Console.WriteLine($&quot;Client : (From: {ip.Address.ToString()}:{ip.Port}, Connection time: {DateTime.Now})&quot;);
          // 클라이언트로 접속 메시지를 byte로 변환하여 송신
          client.Send(Encoding.ASCII.GetBytes(&quot;Welcome server!\r\n&amp;gt;&quot;));
          // 메시지 버퍼
          var sb = new StringBuilder();
          // 통신 바이너리 버퍼
          var binary = new Byte[1024];
          // 무한 루프
          while (true)
          {
            // 클라이언트로부터 메시지 대기
            client.Receive(binary);
            // 클라이언트로 받은 메시지를 String으로 변환
            var data = Encoding.ASCII.GetString(binary);
            // 메시지 공백(\0)을 제거
            sb.Append(data.Trim('\0'));
            // 메시지 총 내용이 2글자 이상이고 개행(\r\n)이 발생하면
            if (sb.Length &amp;gt; 2 &amp;&amp; sb[sb.Length - 2] == '\r' &amp;&amp; sb[sb.Length - 1] == '\n')
            {
              // 메시지 버퍼의 내용을 String으로 변환
              data = sb.ToString().Replace(&quot;\n&quot;, &quot;&quot;).Replace(&quot;\r&quot;, &quot;&quot;);
              // 메시지 내용이 공백이라면 계속 메시지 대기 상태로
              if (String.IsNullOrWhiteSpace(data))
              {
                continue;
              }
              // 메시지 내용이 exit라면 무한 루프 종료(즉, 서버 종료)
              if (&quot;EXIT&quot;.Equals(data, StringComparison.OrdinalIgnoreCase))
              {
                break;
              }
              // 메시지 내용을 콘솔에 표시
              Console.WriteLine(&quot;Message = &quot; + data);
              // 버퍼 초기화
              sb.Length = 0;
              // 메시지에 ECHO를 붙힘
              var sendMsg = Encoding.ASCII.GetBytes(&quot;ECHO : &quot; + data + &quot;\r\n&amp;gt;&quot;);
              // 클라이언트로 메시지 송신
              client.Send(sendMsg);
            }
          }
          // 콘솔 출력 - 접속 종료 메시지
          Console.WriteLine($&quot;Disconnected : (From: {ip.Address.ToString()}:{ip.Port}, Connection time: {DateTime.Now})&quot;);
        }
      }
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cN1By8/btrg1KAyIOR/kpHbC9FqmCdyPDJkGvjXI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cN1By8/btrg1KAyIOR/kpHbC9FqmCdyPDJkGvjXI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cN1By8/btrg1KAyIOR/kpHbC9FqmCdyPDJkGvjXI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcN1By8%2Fbtrg1KAyIOR%2FkpHbC9FqmCdyPDJkGvjXI0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 내용은 윈도우 telnet 프로그램으로 제가 만든 서버 프로그램에 접속하는 예제를 작성했습니다.&lt;/p&gt;
&lt;p&gt;먼저 프로그램을 설명하면 Socket 클래스로 서버 Socket 서버 인스턴스를 생성하였습니다. Bind 함수를 사용하여 대기 포트를 설정합니다.&lt;/p&gt;
&lt;p&gt;Listen으로 동시 접속 대기 설정을 하고 Accept 함수를 통해 클라이언트의 접속을 대기합니다.&lt;/p&gt;
&lt;p&gt;프로그램 상에서는 Accept 함수가 호출이 되면 Client 접속이 발생할 때까지 프로세스가 멈추게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;그리고 telnet 프로그램으로 접속을 하게 되면 Accept함수를 통해서 클라이언트 Socket 인스턴스가 나오고 Send와 Receive 함수를 통해서 서버와 클라이언트로부터 서로 메시지를 주고 받을 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위 예제는 제가 Main 함수에 넣었기 때문에 하나의 클라이언트의 접속만 허용됩니다. 그러니깐 클라이언트 둘 이상이 접속이 되지 않는 상태입니다.&lt;/p&gt;
&lt;p&gt;그러면 멀티 접속을 허용하게 하려면 쓰레드 기능을 넣어야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace Example
{
  class Program
  {
    // 서버 실행 Task 메소드
    static async Task RunServer(int port)
    {
      // Socket EndPoint 설정(서버의 경우는 Any로 설정하고 포트 번호만 설정한다.)
      var ipep = new IPEndPoint(IPAddress.Any, port);
      // 소켓 인스턴스 생성
      using (Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
      {
        // 서버 소켓에 EndPoint 설정
        server.Bind(ipep);
        // 클라이언트 소켓 대기 버퍼
        server.Listen(20);
        // 콘솔 출력
        Console.WriteLine($&quot;Server Start... Listen port {ipep.Port}...&quot;);
        // server Accept를 Task로 병렬 처리(즉, 비동기를 만든다.)
        var task = new Task(() =&amp;gt;
        {
          // 무한 루프
          while (true)
          {
            // 클라이언트로부터 접속 대기
            var client = server.Accept();
            // 접속이 되면 Task로 병렬 처리
            new Task(() =&amp;gt;
            {
              // 클라이언트 EndPoint 정보 취득
              var ip = client.RemoteEndPoint as IPEndPoint;
              // 콘솔 출력 - 접속 ip와 접속 시간
              Console.WriteLine($&quot;Client : (From: {ip.Address.ToString()}:{ip.Port}, Connection time: {DateTime.Now})&quot;);
              // 클라이언트로 접속 메시지를 byte로 변환하여 송신
              client.Send(Encoding.ASCII.GetBytes(&quot;Welcome server!\r\n&amp;gt;&quot;));
              // 메시지 버퍼
              var sb = new StringBuilder();
              // 종료되면 자동 client 종료
              using (client)
              {
                // 무한 루프
                while (true)
                {
                  // 통신 바이너리 버퍼
                  var binary = new Byte[1024];
                  // 클라이언트로부터 메시지 대기
                  client.Receive(binary);
                  // 클라이언트로 받은 메시지를 String으로 변환
                  var data = Encoding.ASCII.GetString(binary);
                  // 메시지 공백(\0)을 제거
                  sb.Append(data.Trim('\0'));
                  // 메시지 총 내용이 2글자 이상이고 개행(\r\n)이 발생하면
                  if (sb.Length &amp;gt; 2 &amp;&amp; sb[sb.Length - 2] == '\r' &amp;&amp; sb[sb.Length - 1] == '\n')
                  {
                    // 메시지 버퍼의 내용을 String으로 변환
                    data = sb.ToString().Replace(&quot;\n&quot;, &quot;&quot;).Replace(&quot;\r&quot;, &quot;&quot;);
                    // 메시지 내용이 공백이라면 계속 메시지 대기 상태로
                    if (String.IsNullOrWhiteSpace(data))
                    {
                      continue;
                    }
                    // 메시지 내용이 exit라면 무한 루프 종료(즉, 서버 종료)
                    if (&quot;EXIT&quot;.Equals(data, StringComparison.OrdinalIgnoreCase))
                    {
                      break;
                    }
                    // 메시지 내용을 콘솔에 표시
                    Console.WriteLine(&quot;Message = &quot; + data);
                    // 버퍼 초기화
                    sb.Length = 0;
                    // 메시지에 ECHO를 붙힘
                    var sendMsg = Encoding.ASCII.GetBytes(&quot;ECHO : &quot; + data + &quot;\r\n&amp;gt;&quot;);
                    // 클라이언트로 메시지 송신
                    client.Send(sendMsg);
                  }
                }
                // 콘솔 출력 - 접속 종료 메시지
                Console.WriteLine($&quot;Disconnected : (From: {ip.Address.ToString()}:{ip.Port}, Connection time: {DateTime.Now})&quot;);
              }
              // Task 실행
            }).Start();
          }
        });
        // Task 실행
        task.Start();
        // 대기
        await task;
      }
    }
    // 실행 함수
    static void Main(string[] args)
    {
    　　// Task로 Socket 서버를 만듬(서버가 종료될 때까지 대기)
      RunServer(10000).Wait();
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G3NHu/btrgYkI9zHu/21clOsD3tbP4kEDeuaAptK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G3NHu/btrgYkI9zHu/21clOsD3tbP4kEDeuaAptK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G3NHu/btrgYkI9zHu/21clOsD3tbP4kEDeuaAptK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG3NHu%2FbtrgYkI9zHu%2F21clOsD3tbP4kEDeuaAptK%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;위 예제는 첫번째 예제에서 Task 쓰레드를 이용해서 멀티 접속이 가능하게 만들었습니다.&lt;/p&gt;
&lt;p&gt;Accept 함수는 클라이언트가 접속하기 전에 쓰레드가 멈추는 형태이기 때문에, 클라이언트로 접속되면 병렬로 다시 Task 쓰레드를 만들고 다시 루프로 Accept로 대기 상태로 들어갑니다.&lt;/p&gt;
&lt;p&gt;여기까지 간단한 서버 프로그램은 만들어 졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;위 프로그램을 기반으로 다시 클라이언트를 만들어 보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;cs&quot; data-type=&quot;Program.cs&quot;&gt;using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

namespace Example
{
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // Socket EndPoint 설정
      var ipep = new IPEndPoint(IPAddress.Parse(&quot;127.0.0.1&quot;), 10000);
      // 소켓 인스턴스 생성
      using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
      {
        // 소켓 접속
        client.Connect(ipep);
        // 접속이 되면 Task로 병렬 처리
        new Task(() =&amp;gt;
        {
          try
          {
            // 종료되면 자동 client 종료
            // 무한 루프
            while (true)
            {
              // 통신 바이너리 버퍼
              var binary = new Byte[1024];
              // 서버로부터 메시지 대기
              client.Receive(binary);
              // 서버로 받은 메시지를 String으로 변환
              var data = Encoding.ASCII.GetString(binary).Trim('\0');
              // 메시지 내용이 공백이라면 계속 메시지 대기 상태로
              if (String.IsNullOrWhiteSpace(data))
              {
                continue;
              }
              // 메시지 내용을 콘솔에 표시
              Console.Write(data);
            }
          }
          catch (SocketException)
          {
            // 접속 끝김이 발생하면 Exception이 발생
          }
          // Task 실행
        }).Start();
        // 유저로부터 메시지 받기 위한 무한 루프
        while (true)
        {
          // 콘솔 입력 받는다.
          var msg = Console.ReadLine();
          // 클라이언트로 받은 메시지를 String으로 변환
          client.Send(Encoding.ASCII.GetBytes(msg + &quot;\r\n&quot;));
          // 메시지 내용이 exit라면 무한 루프 종료(즉, 클라이언트 종료)
          if (&quot;EXIT&quot;.Equals(msg, StringComparison.OrdinalIgnoreCase))
          {
            break;
          }
        }
        // 콘솔 출력 - 접속 종료 메시지
        Console.WriteLine($&quot;Disconnected&quot;);
      }
      // 아무 키나 누르면 종료
      Console.WriteLine(&quot;Press Any key...&quot;);
      Console.ReadLine();
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Fhdla/btrg0pKedlm/fObMtmAMy2Yi6N6sTINjPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Fhdla/btrg0pKedlm/fObMtmAMy2Yi6N6sTINjPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Fhdla/btrg0pKedlm/fObMtmAMy2Yi6N6sTINjPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFhdla%2Fbtrg0pKedlm%2FfObMtmAMy2Yi6N6sTINjPk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&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;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p&gt;서버 프로그램에 맞추어서 클라이언트를 만들었습니다.&lt;/p&gt;
&lt;p&gt;서버와 Send, Receive 함수는 서버와 비슷합니다만, Bind, Listen 대신에 Connect 함수를 써서 서버에 Socket 접속을 합니다.&lt;/p&gt;
&lt;p&gt;클라이언트는 보통 하나의 서버를 접속하기 때문에 따로 병렬 처리를 만들 필요는 없습니다. 있다면 Receive 함수만 Task 쓰레드로 Send, Receive를 분리했습니다.&lt;/p&gt;
&lt;p&gt;사양에 따라 클라이언트도 여러 서버를 동시에 접속할 수 있습니다만, 기본적으로는 하나의 서버의 접속을 합니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;여기까지 C#에서 네트워크 소켓 통신(Socket)을 하는 방법에 대한 글이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.&lt;/p&gt;</description>
      <category>Study/C＃</category>
      <author>v명월v</author>
      <guid isPermaLink="true">https://nowonbun.tistory.com/155</guid>
      <comments>https://nowonbun.tistory.com/155#entry155comment</comments>
      <pubDate>Wed, 6 Oct 2021 17:14:46 +0900</pubDate>
    </item>
  </channel>
</rss>