<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>suhyeon.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 30 Sep 2025 09:39:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>suhyeon.log</title>
            <url>https://velog.velcdn.com/images/jsuhyeon_/profile/974ad964-4680-47ea-a084-09961239d95c/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. suhyeon.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jsuhyeon_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[9월 일기]]></title>
            <link>https://velog.io/@jsuhyeon_/9%EC%9B%94-%EC%9D%BC%EA%B8%B0</link>
            <guid>https://velog.io/@jsuhyeon_/9%EC%9B%94-%EC%9D%BC%EA%B8%B0</guid>
            <pubDate>Tue, 30 Sep 2025 09:39:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>자격증 공부 탓에 9월에 다 끝내려했던 소셜 로그인 인강을 이제서야 듣기 시작했다. ㅎㅎ
나에겐 이제 새 학기 시작이다 !</p>
</blockquote>
<h3 id="9월-30일-화요일-1">9월 30일 화요일 (1)</h3>
<ol>
<li><p>인프런에서 소셜 로그인 인강을 드디어 수강하기 시작했다. MySQL이며 Spring Boot며 언어 버전 설정 다시하고 새로 설치하느라 살짝 오래 걸렸다. Spring Boot는 Gradle 버전밖에 사용해보지 못 했는데, 이 인강에서는 Maven으로 다루기 때문에 일단 따랐다 ...</p>
</li>
<li><p>Java 17을 먼저 설치해둔 상태라 21로 변경했다. 여러 개의 버전을 선택하여 바꾸는 방법으로 두 가지가 있었다. 첫번째는 환경 변수의 Path에서 jdk 파일의 경로를 각각 입력해 원하는 버전을 더 상위에 위치시키는 방법이고, 두번째는 환경 변수의 JAVA_HOME에 원하는 Java 버전의 경로를 입력한 후, Path에서 <code>%JAVA_HOME%\bin</code> 을 입력해두는 방법이다. 매번 헷갈렸었는데 이번 기회에 알게 되었다.</p>
</li>
<li><p>Maven 기반으로 Spring Boot 프로젝트를 생성했다. 소셜 로그인에 대해 다루기 때문에 의존성으로 <code>OAuth2 Client</code>와 <code>Spring Security</code>를 추가하였다. 프로젝트를 생성 후, 압축 파일을 풀어 IntelliJ로 실행했다. 그러나 Maven의 버전과 Java 21의 버전이 서로 호환되지 않아 에러가 발생했다. 해결은 간단했다. <code>pom.xml</code> 파일에서 <code>&lt;properties&gt;</code>를 찾아 아래와 같이 마지막 두 줄만 추가하니까 해결 됐다.</p>
<pre><code>&lt;properties&gt;
 &lt;java.version&gt;21&lt;/java.version&gt;
 &lt;maven.compiler.source&gt;21&lt;/maven.compiler.source&gt;
 &lt;maven.compiler.target&gt;21&lt;/maven.compiler.target&gt;  
&lt;/properties&gt;</code></pre></li>
<li><p>위 문제를 해결하고 재실행하니 다른 문제가 발생하였다. JPA나 JDBC 의존성을 함께 설치했을 때 연결되는 데이터베이스가 없으면 에러가 발생한다. 이 또한 <code>pom.xml</code> 파일에서 <code>&lt;dependencies&gt;</code> 블록 아래에 아래와 같은 코드를 삽입하니 해결 됐다.</p>
<pre><code>&lt;dependency&gt;
 &lt;groupId&gt;com.h2database&lt;/groupId&gt;
 &lt;artifactId&gt;h2&lt;/artifactId&gt;
 &lt;scope&gt;runtime&lt;/scope&gt;
&lt;/dependency&gt;</code></pre></li>
<li><p><code>pom.xml</code> 파일을 수정하고 저장 후 다시 로드할 때엔 오른쪽은 Maven 탭에서 &#39;모든 Maven 프로젝트 다시 로드&#39;를 클릭한다.</p>
</li>
</ol>
<h3 id="9월-30일-화요일-2">9월 30일 화요일 (2)</h3>
<ol start="6">
<li><p>여름 학기에 진행했던 EduScript를 양 교수님께서 목요일에 사용해보고 싶다고 하셔서 중국어 자막 실행을 테스트 했다. 현재 미니 PC 백그라운드에서 서버를 실행하고 로그를 메모장(.txt)에 출력하고 있다. </p>
</li>
<li><p>이때 발생한 문제는 메모장의 인코딩 방식이었다. 예전에도 디버깅 로그에 작성된 이모티콘 때문에 실행이 안된 적이 있다. 그러나 이번엔 중국어 문자를 메모장에 ANSI 방식으로 입력할 수 없어 발생한 에러였다.</p>
</li>
<li><p>그러니까 아래와 같은 흐름이다. 처음에 로그를 utf-8로 저장되도록 .bat 파일을 수정해보았으나, 아무래도 운영체제의 기본 방식이 ANSI로 고정되어 있어 .bat 파일을 아무리 수정해도 해결되지 않았다.</p>
<pre><code>① [클라 -&gt; 서버] 음성 전송 
② [서버] STT 변환 + 번역 
③ [서버] 메모장에 로그 출력 -&gt; 인코딩 에러 
④ [서버 -&gt; 클라] 번역 텍스트를 출력하던 중 번역 중단 </code></pre></li>
<li><p>로그를 저장하지 않고 서버를 실행해보니 중국어 자막이 잘 출력돼서 아예 윈도우의 기본 언어 설정을 utf-8로 변경하였다. 생각보다 쉬운 문제였다 ㅎ...</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[7월 일기]]></title>
            <link>https://velog.io/@jsuhyeon_/7%EC%9B%94-%EC%9D%BC%EA%B8%B0</link>
            <guid>https://velog.io/@jsuhyeon_/7%EC%9B%94-%EC%9D%BC%EA%B8%B0</guid>
            <pubDate>Mon, 07 Jul 2025 14:01:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>6월 20일에 종강을 맞이하고 여행을 다녀와 그간 개발 일기를 작성하지 못 했는데,, 코드 해결하다 머리가 지끈지끈한 기념으로 7월 일기 시작해봄ㅂ니다 ...</p>
</blockquote>
<br>

<h3 id="⭐-7월-7일-월요일">⭐ 7월 7일 월요일</h3>
<ol>
<li><p>지난주부터 음성 녹음 도중 or 연결 시작부터 서버가 끊겼을 때 재연결을 할 수 있도록 로직을 구현하는 것에만 며칠 내내 매달렸다... 되게 쉬워보였는데 대체 왜...! 이렇게 오랫동안 매달렸던 건지 모르겠으나 일단 오늘 두 개 다 해결하긴 했다.</p>
</li>
<li><p>아무튼 골치 아팠던 문제는 디버깅 로그로 확인했을 때 재연결 처리가 <code>reconnecting (1/3) -&gt; reconnecting (2/3) -&gt; reconnecting (3/3) -&gt; disconnected</code>로 처리되길 바랐으나, 계속 <code>reconnecting (1/3) -&gt; disconnected -&gt; reconnecting (2/3) -&gt; ...</code> 이런 식으로 발생한다거나,, <code>reconnecting</code>이 총 3회 이루어지는데 <code>(1/3)</code>을 실행하고 <code>(2/3)</code>는 건너뛰고 <code>(3/3)</code>을 실행한다거나,, 그것도 아니면 웹소켓이 여러개 생성되어 재연결 로직이 뒤엉키는 문제가 발생하였다.</p>
</li>
<li><p>몇 시간을 붙들어 해결해보니 &quot;재생 버튼&quot;을 눌렀을 때 서비스를 호출하고 화면에 진입하고 나서도 서비스를 호출한다는 것이었다. 뿐만 아니라 서버 연결 시도 시 생성하였던 웹소켓을 연결 실패 후 가비지 컬렉터가 처리하지 못 해 그대로 남아있어 새로 생성된 웹소켓과 충돌이 일어나는 등의 원인이 있었다. 아무튼 해결은 했으니 이젠 집에 가야겠다... 막상 해결하고 나니까 며칠을 붙들 만큼의 업무 난이도인건지 아니면 코드가 너무 더러운 나머지 내 머릿속도 더러워진건지 조차도 헷갈린다... 코드 정리의 중요성을 한 번 더 깨닫고 간다.......</p>
</li>
<li><p>내일은 사용자가 서버로부터 &quot;ready&quot;를 수신했을 때 음성 녹음을 시작할 수 있도록 &quot;ready&quot;를 수신할 때까지 로딩되는 로직과 위젯을 만들어보려고 한다. 그전에 서비스 코드 먼저 정리하는 것이 우선이겠지만... 백엔드만 맡았을 땐 몰랐는데 클라이언트는 뭐 이렇게 머리가 더 아픈 기분일까... 헝헝헝 </p>
</li>
<li><p>얼레벌레 해결하고 일기 쓰려니까 정확히 어떻게 해결했는지도 기억이 잘 안난다... 내일은 하나 성공했을 때마다 한줄씩이라도 기록해둬야겠다...!</p>
</li>
</ol>
<br>]]></description>
        </item>
        <item>
            <title><![CDATA[6월 일기]]></title>
            <link>https://velog.io/@jsuhyeon_/6%EC%9B%94-%EC%9D%BC%EA%B8%B0</link>
            <guid>https://velog.io/@jsuhyeon_/6%EC%9B%94-%EC%9D%BC%EA%B8%B0</guid>
            <pubDate>Sat, 07 Jun 2025 11:59:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>무더운 유월입니다...
스을슬 캡스톤 프로젝트가 마무리 되어 가고, 새로운 프로젝트를 준비할 때입니다... (ㅠ.ㅠ)</p>
</blockquote>
<br>

<h3 id="⭐-6월-6일-금요일">⭐ 6월 6일 금요일</h3>
<ol>
<li><p>캡스톤 프로젝트에서는 백엔드를 맡게 되었지만, 이번 새 경진대회 프로젝트에서는 프론트엔드도 맡아야할 것 같아 플러터 강의를 듣기 시작했다.</p>
</li>
<li><p>HTML/CSS/JS 는 그래도 자주 접했어서 ((비교적)) 익숙하다만, 플러터는 처음이라 많이 어색했다. 뿐만 아니라 솔직히 이렇게 가독성 떨어지는 코드를 처음 봤다고 느껴질 정도였다. 그치만... 처음이라 아직  익숙하지 않은 것이라 믿으며... 일단 이런 게 있구나 ~ 정도로만 인강 들으면서 연습하고 있다.</p>
</li>
</ol>
<p><a href="https://nomadcoders.co/flutter-for-beginners/lobby">🔗 Flutter로 웹툰 앱 만들기 (NomadCoder)</a></p>
<br>

<h3 id="⭐-6월-7일-토요일">⭐ 6월 7일 토요일</h3>
<ol>
<li><p>그래도 계속해서 플러터 강의를 듣다 보니까 조금씩은 익숙해지는 것 같다. 아직은 따라치는 단계이기는 하나, 아직 많이 부족하다. 그래도 재미는 있는 것 같다 !!!</p>
</li>
<li><p>Chpater 6에서는 제일 많이 쓰일 법한 내용들을 배웠다. API를 통해 서버로부터 데이터를 받아오고, 해당 데이터를 json으로 파싱한 뒤 필요한 데이터를 추출하는 과정을 배웠다. 특히 <code>FutureBuilder()</code>라는 위젯에 대해 알게 되었다. 서버로부터 전달받은 데이터를 처리하는 비동기 작업 상태에 따라 자동으로 UI를 업데이트하는 위젯이다.</p>
</li>
<li><p>실제 엔드포인트를 통해 데이터를 받아와 출력하는 과정을 배워보았다. 캡스톤 프로젝트를 하며 클라이언트로부터 받아온 정보를 서버에서 처리한 후 다시 콜백하는 과정만 해왔는데, 이번엔 반대로 서버로부터 받아온 정보를 클라이언트에서 처리하는 과정을 배우게 되어 프론트엔드의 구조를 알게 된 것 같아 주고 받는 데이터에 대한 이해도가 높아진 것 같다. </p>
</li>
<li><p>( 강의를 아직 완강하지 못 해서 받아온 데이터를 출력하는 것은 콘솔 창으로만 진행해보았다. )</p>
</li>
</ol>
<br>

<h3 id="⭐-6월-11일-수요일">⭐ 6월 11일 수요일</h3>
<p><img src="https://velog.velcdn.com/images/jsuhyeon_/post/b452276b-ea1c-45f1-9ea3-3682df9675ba/image.png" alt=""></p>
<p><code>mywordbook_screen.dart</code> 에서 <code>WordSetButton</code>을 호출하는 위젯에 <code>onWordbookChanged: ...</code> 로 상태 변화 처리가 되었을 때 단어장 목록을 새로 고침하는 함수(<code>_getWordSet()</code>)를 호출하도록 하였다.</p>
<p><img src="https://velog.velcdn.com/images/jsuhyeon_/post/d9d213d0-ad17-400a-9690-0807877c2045/image.png" alt="">
<code>word_set_button_widget.dart</code> 에서 단어장 환경설정 화면(<code>wordbook_setting_screen.dart</code>)을 호출하는 부분에 필요한 인자(단어장 ID - <code>wordbookId</code>, 현재 제목 - <code>setName</code>)를 전달하고 해당 화면에서 <code>Navigation.pop()</code>을 하면서 데이터를 전달하여 <code>result</code>가 <code>null</code>이 아닐 때에 callback을 처리하도록 하였다.</p>
<p><img src="https://velog.velcdn.com/images/jsuhyeon_/post/8dc12190-5b63-44a5-bfe4-bfbce591200a/image.png" alt="">
<code>wordbook_setting_screen.dart</code>에서 <code>Navigator.pop</code> 한 예시 (단어장 삭제 &amp; 단어장 제목 수정 시 사용)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[5월 일기]]></title>
            <link>https://velog.io/@jsuhyeon_/5%EC%9B%94-%EC%9D%BC%EA%B8%B0</link>
            <guid>https://velog.io/@jsuhyeon_/5%EC%9B%94-%EC%9D%BC%EA%B8%B0</guid>
            <pubDate>Fri, 09 May 2025 10:12:12 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>따사로운 5월 수현이의 성장 일기입니당 :)
하루하루 코딩을 하며 어떤 멍청한 일과 잘한 일이 있었는지 회고해보는 시간을 가져보아요 ...~</p>
</blockquote>
<br>

<h3 id="⭐-5월-9일-금요일">⭐ 5월 9일 금요일</h3>
<h4 id="오늘-한-일">오늘 한 일</h4>
<ol>
<li><p><code>api/</code>에 <code>image_generation.py</code> 파일을 두었다고 생각했는데,, 알고보니 <code>api/__pycache__/</code>에 파일을 저장해둔 것이었다.. <code>from api.image_generation import __</code> 했을 때 자꾸 import 에러가 떠서 개짜증난 상태였는데, 내 실수였다. 허허</p>
</li>
<li><p>전날 번역 라이브러리 잘못 설치했다가 버전 호환 문제로 이전에 백업해둔 코드를 다시 가져와서 수정하였지만 여전히 해결되지 않았고, 여러 차례 이것저것 실행해본 결과 얻어낸 원인은 PowerShell이 내부적으로 경로를 바꾸지 않아 가상 환경 안의 Python이 아니라 시스템 전역의 Python이 실행된다는 것이었다. uvicorn 또한 사용되는 위치가 가상환경인지, 시스템 전역 환경인지 알아야 한다.</p>
</li>
<li><p>openai와 httpx 버전 충돌이 잦았는데 openai는 1.3.9를, httpx는 0.27.0을 사용해야 충돌이 없었다. </p>
</li>
<li><p>기본 제공 단어장에서 이미지 프롬프팅할 때 한국어가 아닌 영어로 번역된 문장이 필요하여 데이터베이스의 <code>basic_word</code> 테이블에 <code>association_kor</code> 칼럼을 추가하였다.</p>
</li>
<li><p>샘플 데이터 50개를 통한 이미지 생성 테스트 → 샘플 데이터 5개만 진행 ..</p>
</li>
</ol>
<br>

<h3 id="⭐-5월-12일-월요일">⭐ 5월 12일 월요일</h3>
<h4 id="오늘-한-일-1">오늘 한 일</h4>
<ol>
<li><p>메인 서버의 기본 제공 단어장 기능을 만들었다. 네 개의 API를 생성하였으며, 구성은 다음과 같다. 
① 메인 화면에서 기본 제공 단어장 버튼을 클릭했을 때 뜨는 단어장 목록 조회
② 기본 단어장의 목록을 클릭했을 때 뜨는 day 목록 조회
③ day에 들어갔을 때 해당 day에 저장된 단어들 간단히 조회 
④ &quot;학습 시작&quot;을 눌렀을 때 차례로 단어들의 상세 정보 (발음, 연상 예문, 이미지, 영어 예문, 한글 번역 예문 등)를 출력
이렇게 네 가지 기능을 한다.</p>
</li>
<li><p>①에서는 단어장 목록에 해당 목록에 저장돼있는 day 리스트의 목록 개수를 출력해야 했다. 이때 Spring Data JPA의 쿼리 메서드 방식을 사용했다. JPA가 메서드 이름을 분석하여 자동으로 쿼리를 만들어주는 방식이다. <code>By</code> 뒤에 테이블명과 칼럼명을 적어주면 된다.
<code>int countByBasicBasicId(Long basicId);</code>
= <code>SELECT COUNT(*) FROM daylist WHERE basic.basic_id = :basicId</code>
(②에서 daylist에 저장된 단어 개수를 출력하는 과정도 동일하게 진행하였다.)</p>
</li>
<li><p>④에서 필요한 데이터는 단어, 의미, 발음, 연상 예문 등이었는데, 여기서 <strong>단어</strong>와 <strong>발음</strong> 칼럼은 단어의 공통 정보를 저장하는 <code>word_info</code> 테이블에 있어, <code>dayword</code>에서 참조하고 있는 <code>basic_word_id</code>와 <code>daylist_id</code> 외래키를 통해 접근하였다. </p>
</li>
<li><p>③에서 daylist에 해당하는 단어 목록을 조회할 때 쓴 <code>findAllByDaylistDaylistId()</code> 메서드를 ④에서도 사용하였고, 이 메서드를 통해 <code>daylist_id</code>에 해당하는 단어 목록을 <code>List&lt;Daylist&gt;</code> 형태로 받아왔고, 이 <code>Daylist</code> 타입 리스트를 통해 <code>basic_word</code>와 <code>word_info</code> 테이블에 접근하여, 단어의 상세 데이터들을 반환하였다. </p>
</li>
<li><p>단어 공통 정보 테이블엔 세 개 국가의 발음 기호(미국, 영국, 호주)가 저장되어 있으며, 사용자로부터 API로 <code>country</code>를 전달 받아 해당하는 국가에 발음 기호를 반환하도록 구현하였다. 이 때는 <code>switch-case</code>문을 사용하였다.</p>
</li>
</ol>
<blockquote>
<p><strong>다음에 해야 할 것</strong>
클라이언트로부터 단어장 접속 시간을 받아왔을 때 <code>daylist</code> 테이블의 <code>latest_accessed_at</code> 칼럼을 UPDATE 해야 한다. </p>
</blockquote>
<br>

<h3 id="⭐-5월-13일-화요일">⭐ 5월 13일 화요일</h3>
<h4 id="오늘-한-일-2">오늘 한 일</h4>
<ol>
<li><p>클라이언트로부터 단어장 접속 시간을 받아와 <code>daylist</code> 테이블의 <code>latest_accessed_at</code> 칼럼을     UPDATE 후 <code>daylist</code> 테이블 목록을 조회한다. 컨트롤러에서 RestAPI 메서드를 <code>GET</code> 대신 <code>PATCH</code>로 변경하였다. <code>daylist</code> 객체 전체를 수정하는게 아니라, 필드 하나만 수정하기 때문에 <code>PATCH</code>를 사용하였다. 엔드포인트에서 주소를 통해 전달받는 방식이 아니기 때문에 JSON 형태로 받아온다.</p>
</li>
<li><p>AI 서버의 이미지 생성 파트를 진행하려 했다. Stable Diffusion WebUI를 로컬에 설치하던 중 로컬 API 키를 발급받아도 클라우드 환경과 마찬가지로 WebUI 실행 시에만 API 키가 활성화된다는 사실을 뒤늦게 알아버렸다. 멘탈이 쿠쿠다스가 되던 중 ... DALLE3 API는 아직도 사용 못하는지 찾아봤다. 구글에 검색해보기로는 DALLE3 API를 사용해서 이미지 생성하는 코드가 분명히 나와있음에도 챗 GPT는 아직 DALLE2 API만 사용 가능하다고 했다. 한 달 전에도 모델명만 <code>dall-e-3</code>으로 변경하여 시도해보았지만 계속되는 실패에  2를 사용해 프로젝트를 진행하였다. </p>
</li>
<li><p>유튜브에 검색해보니 DALLE3 API를 통한 이미지 생성 영상들이 있었다. 하나 들어가서 속는 셈 치고 한 번 더 시도해보았다. 아니 근데 웬걸 !!!! 이번엔  생성이 됐다 !!! 이미지 크기 때문이었는지 뭔지 (내 실수였겠지만) 아무튼 DALLE3을 쓸 수 있다니 감격스러웠다. 왜냐믄 DALLE2는 이미지 생성을 그지 같이 하거덩요 .. 개못쌩기게 !! </p>
</li>
<li><p>아무튼 내일은 DALLE2 + SD 흐름으로 흘러가던 AI 서버 쪽을 DALLE3만을 사용해서 최종 이미지를 저장하도록 수정해야겠다. </p>
</li>
</ol>
<br>

<h3 id="⭐-5월-14일-수요일">⭐ 5월 14일 수요일</h3>
<ol>
<li>오늘은 DALLE3 API를 사용한 AI 서버 수정 작업을 진행하였다. 생성된 이미지를 보완하기 위해 스타일 프롬프트와 네거티브 프롬프트를 살짝 수정하였다. 샘플 데이터 50개를 완성해보려했지만, 프롬프팅 시 검열되는 예문들이 있어서 실패했다. 내일은 예문이 검열에 걸릴 경우 일반 영어 예문으로 프롬프팅 되도록 코드를 추가해봐야겠다.</li>
</ol>
<br>

<h3 id="⭐-5월-15일-목요일">⭐ 5월 15일 목요일</h3>
<ol>
<li><p>샘플 데이터 마저 완성했다. 이미지 생성도 다 돈이라서 200장만 만들었다. 코딩보다는 샘플 데이터 만드는 데 시간을 많이 썼다. </p>
</li>
<li><p>엉덩 언니랑 잡담하다가 문득 생각난 사업 아이템에 대해 떠들었다. 실현 가능성은 없지만 ㅋㅋㅎ 공부 중에 나누는 뻘소리 잡담은 재미있었다. 일명 DTA라고 Digital-To-Analog의 준말이다. STT(Speech-to-Text) &amp; TTS(Text-to-Speech)에서 따왔다. 자세한건 다 언니 머리에서 나온거라 기억이 안 난다. 아무튼 언젠가 실현할 수 있기를 . . . . .</p>
</li>
</ol>
<br> 

<h3 id="⭐-5월-20일-화요일">⭐ 5월 20일 화요일</h3>
<ol>
<li><p>영단어장의 발음 교정 기능에 활용할 MS Azure STT를 사용해보았다.  엘사 스픽처럼 사용자의 발음을 운소 단위로 잘라 어느 부분이 부족한지, 어떻게 발음하면 더 좋은지 피드백을 출력하고 싶지만 MS Azure STT에는 운소 단위의 발음 점수는 제공해도 피드백까지는 제공하지 않았다.</p>
</li>
<li><p>STT 사용 시 반환 받는 데이터는 아래와 같다.  </p>
</li>
</ol>
<pre><code>{
  &quot;word&quot;: &quot;danger&quot;,
  &quot;answerPhonetic&quot;: &quot;/ˈdeɪndʒər/&quot;,
  &quot;userPhonetic&quot;: &quot;Danger.&quot;,
  &quot;overallScore&quot;: {
    &quot;accuracy&quot;: 93.0,
    &quot;fluency&quot;: 100.0,
    &quot;completeness&quot;: 100.0,
    &quot;total&quot;: 95.8
  },
  &quot;phonemes&quot;: [
    {
      &quot;symbol&quot;: &quot;d&quot;,
      &quot;score&quot;: 87.0,
      &quot;feedback&quot;: &quot;Good&quot;
    },
    {
      &quot;symbol&quot;: &quot;ey&quot;,
      &quot;score&quot;: 100.0,
      &quot;feedback&quot;: &quot;Excellent&quot;
    },
    {
      &quot;symbol&quot;: &quot;n&quot;,
      &quot;score&quot;: 100.0,
      &quot;feedback&quot;: &quot;Excellent&quot;
    },
    {
      &quot;symbol&quot;: &quot;jh&quot;,
      &quot;score&quot;: 100.0,
      &quot;feedback&quot;: &quot;Excellent&quot;
    },
    {
      &quot;symbol&quot;: &quot;ax&quot;,
      &quot;score&quot;: 100.0,
      &quot;feedback&quot;: &quot;Excellent&quot;
    },
    {
      &quot;symbol&quot;: &quot;r&quot;,
      &quot;score&quot;: 77.0,
      &quot;feedback&quot;: &quot;혀를 말아올려 부드럽게 굴리는 ‘으르’ 소리를 내세요.&quot;
    }
  ]
}</code></pre><ol start="3">
<li>일단 위와 같이 데이터를 반환하고, 피드백 부분을 80점 미만일 때 <code>Retry.. 발음이 아쉽네용;</code> 같은 메시지를 띄우도록 하고, <code>{사용자 발음} 대신 {정답 발음}으로 시도해보세요</code> 로 출력되도록 .. 해봐야겠당</li>
</ol>
<br> 

<h3 id="⭐-5월-21일-수요일">⭐ 5월 21일 수요일</h3>
<ol>
<li><p>클라이언트랑 주고 받는 데이터가 달라서 메인 서버의 기본 제공 단어장 부분 API를 수정했다. 기존에는 daylist_id를 통해 daylist를 조회하고 마지막 접근 시간을 UPDATE 하는 방식으로 한번에 갔다면, basic_id를 통해 해당 daylist에 접근하여 daylist 목록을 조회한 후, 클라이언트에서 목록에 접근했을 때 <code>latestAccessedAt</code>를 업데이트 하도록 구현하였다. 
⇒ 그러니까 기존 구조에서 <code>/{basic_id}/daylist/</code> 단순히 daylist 목록을 조회하는 API가 빠져있었다. </p>
</li>
<li><p>클라이언트에서 List로 dayword_id를 전달하면 해당 아이디에 속하는 단어 상세 데이터를 불러와야 하는데, 기존 구조에서는 daylist_id에 해당하는 단어 목록의 전체 정보를 반환하도록 구현되어 있었다. 이 부분 또한 RequestBody를 추가하여 <code>GET</code> 이 아니라 <code>POST</code> 하도록 매핑 방식을 바꾸었다.</p>
</li>
</ol>
<br> 

<h3 id="⭐-5월-22일-목요일">⭐ 5월 22일 목요일</h3>
<ol>
<li><p>STT 기능을 AI 서버에 구현하였다. 클라이언트로부터 발음할 영단어와 평가할 발음의 국가를 받아오기 위해 요청 DTO 클래스를 <code>PronunciationRequest</code> 설정하였다. </p>
</li>
<li><p><code>evaluate_pronunciation</code> 에서 MS Azure STT로 발음 평가를 진행한다. 평가를 마치면 <code>dict</code> 형태로 결과를 저장한다. 그렇게 저장된 <code>dict</code> 타입 데이터를 원하는 정보만 추출하여 반환할 수 있도록 파싱하는 작업을 <code>extract_pronunciation_data</code> 에서 진행한다.</p>
</li>
<li><p><code>extract_pronunciation_data</code> 에서는 <code>dict</code> 결과를 파싱하여 반복문을 통해 음소 개수만큼 반복을 진행하여 <code>symbol</code>에 해당하는 <code>score</code>와 <code>feedback</code>을 저장하도록 하였다. </p>
</li>
<li><p><code>score &gt;= 90</code> 일 때는 <code>Excellent</code>를, <code>80 &lt;= score &lt; 90</code> 일 때는 <code>Good</code>을, 70 미만일 때는 미리 정의해둔 <code>PHONEME_FEEDBACK</code>을 출력하도록 하였다.</p>
</li>
<li><p>응답 DTO 클래스 <code>PronunciationResponse</code> 를 구현하였다. 반환 형태는 <code>전체 점수(정확도, 유창성, 완성도, 종합 점수)</code>와 <code>음소 단위 발음 점수(symbol, score, feedback)</code> 로 구성되어 있다. 이 두 개에 대한 클래스를 구현하여 반환할 때 가독성 있고 효율적이도록 처리하였다. </p>
</li>
<li><p>사용자가 원하는 국가명을 보냈을 때 STT가 처리할 수 있는 국가 코드로 파싱하였다. 아래와 같이 <code>if-else</code>를 사용하려 했는데,</p>
<pre><code>if request.country == &quot;en&quot;:
 country = &quot;en-US&quot;
elif request.country == &quot;uk&quot;:
 country = &quot;en-GB&quot;
elif request.country == &quot;aus&quot;:
 country = &quot;en-AU&quot;</code></pre></li>
<li><p>국가 코드가 늘어났을 때 유지보수를 위해서는 <code>dict</code> 방식이 낫다는 조언을 받아 아래와 같이 수정하였다.  </p>
<pre><code>country_map = {
 &quot;us&quot;: &quot;en-US&quot;,
 &quot;uk&quot;: &quot;en-GB&quot;,
 &quot;aus&quot;: &quot;en-AU&quot;
}
</code></pre></li>
</ol>
<p>country = country_map.get(request.country)</p>
<p>if country is None:
    raise HTTPException(status_code=400, detail=&quot;지원하지 않는 국가 코드입니다.&quot;)</p>
<pre><code>
8. 여기까지 녹음된 `.wav` 파일로 진행하였다. 하지만 이 프로젝트에서는 로컬에 저장된 녹음 파일로 발음 평가를 진행하는 것이 아닌, 앱에서 사용자가 녹음 버튼을 통해 발음 테스트를 진행한 후, 해당 녹음 파일을 받아야 한다. 그러기 위해서 라우터 함수의 매개변수로 필요한 세 개의 데이터를 입력하였다.</code></pre><p>async def fetch_word_pronunciation(
    word: str = Form(...),
    country: str = Form(...),
    audio: UploadFile = File(...)):</p>
<pre><code>
10. 포스트맨에서도 동일하게 타입에 맞게 `발음할 단어`와 `평가할 발음 국가`, `사용자 발음 파일(.wav)`를 전달하여 평가를 진행하도록 하였다. 사용자가 앱을 통해 녹음을 수행하면 결과가 서버로 `.wav` 형태로 오기 때문에 이렇게 처리한다고 한다... 스트림 형식을 받을 수 없는지 다시 한번 더 조사해봐야겠다.
![](https://velog.velcdn.com/images/jsuhyeon_/post/fc0c2d27-2d52-4f9b-8cb1-cd9b6b6c46d9/image.png)


&lt;br&gt;

### ⭐ 5월 28일 수요일
1. 메인 서버의 기본 단어장 즐겨찾기 기능을 구현하였다. 크게 ① 목록 조회 ② 즐겨찾기 등록 ③ 즐겨찾기 해제 세개로 구성되어 있다. 

2. 사용자의 id를 통해 이루어지기 때문에 회원가입과 로그인을 통해 토큰을 받아야 했다. 
![](https://velog.velcdn.com/images/jsuhyeon_/post/6e0429f6-275a-4a73-a4eb-20979aefe91e/image.png)
가입에 필요한 회원 정보를 입력하고 
![](https://velog.velcdn.com/images/jsuhyeon_/post/55e24510-fd4a-43f3-bffc-2ac02d0d9784/image.png)
가입한 정보를 통해 로그인을 진행하였다. 로그인을 하면 `accessToken`과 `refreshToken`을 받게 되는데 이 토큰이 필요한 이유는 인증된 사용자만이 어플 내의 API를 사용할 수 있기 때문이다. 

3. 이렇게 발급받은 `accessToken`을 통해 즐겨찾기 API를 사용할 수 있게 되는데, 포스트맨에서 헤더에 인증키를 넣어 `GET`(즐겨찾기 목록 조회), `POST`(즐겨찾기 등록), `DELETE`(즐겨찾기 해제)를 진행하면 된다. 

4. 즐겨찾기 목록 조회, 등록, 해제는 클라이언트로부터 받은 `basicWordId`를 통해서 `favorite_word` 테이블에서 `SELECT` &amp; `INSERT` &amp; `DELETE`를 진행할 수 있다. 

5. 그리고 하나 알게 된 것 .. Repository에서 클래스에서 선언한 id 변수명과 상관 없이 `@Id` 어노테이션을 사용했으면 `findById()` 메서드를 통해 기본키를 조회할 수 있었다.. 나는 그동안 웨 귀찮게 ... 함수명을 변수 이름에 맞추었는가 ...

</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[HCI] 9주차 대리자와 이벤트]]></title>
            <link>https://velog.io/@jsuhyeon_/HCI-9%EC%A3%BC%EC%B0%A8-%EB%8C%80%EB%A6%AC%EC%9E%90%EC%99%80-%EC%9D%B4%EB%B2%A4%ED%8A%B8</link>
            <guid>https://velog.io/@jsuhyeon_/HCI-9%EC%A3%BC%EC%B0%A8-%EB%8C%80%EB%A6%AC%EC%9E%90%EC%99%80-%EC%9D%B4%EB%B2%A4%ED%8A%B8</guid>
            <pubDate>Sun, 04 May 2025 08:49:18 GMT</pubDate>
            <description><![CDATA[<p><code>2025-05-04</code></p>
<br>

<h2 id="구성">구성</h2>
<h3 id="📍-대리자-delegate">📍 대리자 (Delegate)</h3>
<h4 id="delegate-생성">Delegate 생성</h4>
<h4 id="delegate---operator"><code>Delegate +/-</code> Operator</h4>
<h3 id="📍-이벤트-event">📍 이벤트 (Event)</h3>
<h4 id="events-동작-원리">Events 동작 원리</h4>
<h4 id="events-구성-요소">Events 구성 요소</h4>
<ol>
<li>이벤트 핸들러 대리자 (Delegate)</li>
<li>이벤트를 발생시키는 객체 (Publisher)</li>
<li>이벤트에 응답하는 객체 (Subscriber)</li>
<li>이벤트 매개변수 (Event Argument)</li>
</ol>
<p><br><hr></p>
<h1 id="대리자">대리자</h1>
<h2 id="📍-대리자-특징">📍 대리자 특징</h2>
<ul>
<li><p>함수를 변수처럼 다뤄서, 변수명으로 함수를 실행할 수 있다. </p>
</li>
<li><p>함수가 어딨는지 주소값을 저장한다.</p>
</li>
<li><p>함수 매개변수, 반환값이 대리자 선언과 일치해야 사용할 수 있다. </p>
</li>
<li><p>대리자에 여러 다른 함수들을 할당할 수 있다. (하지만 함수 모양은 같아야 한다.)</p>
</li>
<li><p>대리자는 코드 작성 시가 아니라, 프로그램 돌아갈 때 메모리에 만들어진다. </p>
</li>
<li><p>대리자를 통해 메서드를 매개변수로 전달할 수 있다.
→ 대리자를 통해 함수 자체를 값처럼 전달할 수 있다. </p>
</li>
<li><p>대리자를 사용하여 콜백 메서드를 정의할 수 있다.
→ 작업 끝나고 자동으로 실행될 함수를 미리 지정할 수 있다. </p>
</li>
<li><p>여러 대리자를 연결할 수 있다.
→ 하나의 대리자에 여러 함수를 연결할 수 있다. </p>
</li>
<li><p>단일 이벤트에 대해 여러 메서드를 호출할 수 있다.
→ 이벤트 하나에 함수 여러 개 연결할 수 있다. </p>
</li>
<li><p>람다 식은 인라인 코드 블럭을 작성하는 더욱 간단한 방법이다. 
→ <code>Action act = () =&gt; Console.WriteLine(&quot;Hello&quot;)</code></p>
</li>
</ul>
<h4 id="쉽게-말해">쉽게 말해</h4>
<ul>
<li>어떤 함수가 실행될지 나중에 결정하고 싶을 때</li>
<li>함수를 변수처럼 전달하고 싶을 때 사용한다.<pre><code>void Hello() =&gt; Console.WriteLine(&quot;안녕!&quot;);
void Bye() =&gt; Console.WriteLine(&quot;잘가!&quot;);
</code></pre></li>
</ul>
<p>void Execute(MyDelegate d) =&gt; d(); // 대리자를 매개변수로 받아 실행</p>
<p>Execute(Hello);  // &quot;안녕!&quot;
Execute(Bye);    // &quot;잘가!&quot;</p>
<pre><code>- 대리자 = 함수 실행권을 가진 &#39;전화번호&#39;
- 전화번호만 저장해두고, 나중에 그 번호롤 전화한다. ⇒ 함수 홓출
- 전화번호부에 여러 개 저장해서 한 번에 여러 곳 전화 가능하다. (멀티캐스트)
&lt;br&gt;&lt;hr&gt;

## 📍 대리자 정의, 생성 및 사용
- `delegate` 키워드를 사용하여 정의한다.
-  `new` 키워드를 통하여 생성한다.
- 대리자 호출로 메서드를 간접 호출한다.
</code></pre><p>// 대리자 정의
public delegate void DelegateMethod(int x);
// 대리자가 지칭할 메서드 구현
publicstatic void CallbackMethod(int x) { Console.WriteLine(x); }
// 대리자 생성 및 ㅊ기화
DelegateMethod dm = new DelegateMethod(CallbackMethod);
// 대리자를 통한 메서드 간접 호출
dm(100); // →CallbackMethod(100)이 호출된다.</p>
<pre><code>
#### 🍀 대리자 사용 예시 1
</code></pre><p>// 대리자 정의
public delegate void Del(string msg); 
public class DelegateTest
{
    // 대리자가 가리킬 함수
    public static void DelegateMethod(string msg)
    { System.Console.WriteLine(msg); }</p>
<pre><code>// &#39;대리자&#39;를 매개변수로 받는 함수
public static void MethodWithCallback(int x int y, Del callback)
{ callback(&quot;The number is &quot; + (x + y).ToString()); }

public static void Main(string[] args)
{
    // 대리자 생성 (연결)
    Del handler = DelegateMethod; 

    // DelegateMethod(&quot;Hello&quot;) 호출
    handler(&quot;Hello&quot;); 

    // DelegateMethod(&quot;The number is &quot; + ( 1 + 2));
    MethodWithCallback(1, 2, handler);
    var mc = new MethodClass();
    Del d1 = mc.Method1;
    Del d2 = mc.Method2;
    Del d3 = d1 + d2;
}</code></pre><p>}</p>
<pre><code>#### 🍀 대리자 사용 예시 2</code></pre><p>public class Click
{
    public void MouseClick(string what)
    { System.Console.WriteLine(&quot;마우스의 {0} 버튼이 클릭됐습니다.&quot;, what); }</p>
<pre><code>public void KeyBoardClick(string what)
{ System.Console.WriteLine(&quot;키보드의 {0} 버튼이 클릭됐습니다.&quot;, what);</code></pre><p>}</p>
<p>public class DelegateTest
{
    // 대리자 정의
    public delegate void OnClick(string what);</p>
<pre><code>public static void Main(string[] args)
{
    Click  c = new Click();
    // 대리자 생성 (연결)
    OnClick dm = new OnClick(c.MouseClick); 
    dm(&quot;왼쪽&quot;);
    dm = new OnClick(c.KeyBoardClick);
    dm(&quot;스페이스&quot;)
}</code></pre><p>}</p>
<pre><code>
&lt;br&gt;&lt;hr&gt;

## 📍 대리자 Operator
1. Combine (+) : 대리자를 결합하는 연산자
2. Remove (-) : 대리자를 제거하는 연산자
</code></pre><p>static void Main()
{
    NumClass c = new NumClass();</p>
<pre><code>// 대리자 Combine
Handler h = new Handler(c.Plus) + new Handler(c.Minus)

// 대리자 Remove
h = h- new Handler(c.Minus);</code></pre><p>}</p>
<pre><code>
&lt;br&gt;&lt;hr&gt;


# 이벤트
## 📍 이벤트 핸들러 대리자
- 이벤트에 응답하는 메소드를 가리키는 대리자
`public delegate void EventNameEventHandler(Object sender, EventArgs e)`

- `Object sender` 이벤트 발생 객체
- `EventArgs e` 이벤트 발생 시 넘겨줄 추가 정보

&lt;br&gt;&lt;hr&gt;

## 📍 이벤트 송신기 (Publisher)
- 이벤트를 발생시키는 객체
- 이벤트를 선언한 클래스에서만 이벤트 호출(발생)
- 하위 클래스에서 재정의하거나 호출 가능하도록 `virtual`로 정의한다. </code></pre><p>public class EventPub
{
    // ① 이벤트 선언
    public event MyEventHandler MyEvent;</p>
<pre><code>protected virtual void OnMyEvent(EventArgs e)
{
    // ② 이벤트 호출
    if(MyEvent != null) MyEvent(this, e);
}</code></pre><p>}</p>
<pre><code>
&lt;br&gt;&lt;hr&gt;

## 📍 이벤트 수신기 (Subscriber)
- 이벤트에 응답하는 ㅡ객체
- 이벤트 처리 메서드를 이벤트 대리자에 연결한다. (등록)</code></pre><p>class EventSub
{
    // 이벤트 핸들러
    virtual void MyEventH(Object sender, EventArgs e) { ... }
}</p>
<p>EventPub p = new EventPub(); // 이벤트 송신기 객체
EventSub s = new Eventsub(); // 이벤트 수신기 객체
p.MyEvent += new MyEventEventHandler(s.MyEventH); // 이벤트 핸들러 등록</p>
<p>p.OnMyEvent(args); // 이벤트 요청
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HCI] 7주차 LINQ]]></title>
            <link>https://velog.io/@jsuhyeon_/HCI-7%EC%A3%BC%EC%B0%A8-LINQ</link>
            <guid>https://velog.io/@jsuhyeon_/HCI-7%EC%A3%BC%EC%B0%A8-LINQ</guid>
            <pubDate>Wed, 30 Apr 2025 10:57:38 GMT</pubDate>
            <description><![CDATA[<p><code>2025-04-30</code></p>
<h2 id="📍-linq-쿼리">📍 LINQ 쿼리</h2>
<ul>
<li>Language Intergrated Query</li>
<li><strong>쿼리</strong> : 데이터 소스에서 데이터를 검색하는 식</li>
<li>배열/데이터베이스에서 조건에 맞는 자료만 뽑는 기능을 제공한다.</li>
</ul>
<h4 id="🍀-예시">🍀 예시</h4>
<p>numbers 배열에서 짝수만 선택한다.</p>
<pre><code>int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };

var numQuery = from num in numbers where (num % 2) == 0 select num;</code></pre><p><br><hr></p>
<h2 id="📍-데이터-소스-datasource">📍 데이터 소스 (DataSource)</h2>
<ul>
<li>데이터소스가 쿼리 가능한 형식으로 존재하지 않는 경우, LINQ 공급자가 소스를 나타내야 한다.<pre><code>// Create a data source from an XML document using System.Xml.Linq
XElement contacts = XElement.Load(@”C:\myContactList.xml”);
</code></pre></li>
</ul>
<p>// LINQ to SQL
Northwnd db = new Northwnd(@”C:\northwnd.mdf”);
IQueryable<Customer> custQuery =
        from cust in db.Customers
        where cust.City == “London” select cust;</p>
<pre><code>
&lt;br&gt;&lt;hr&gt;

## 📍 필터링
- `boolean` 형식으로 필터를 적용할 수 있다.
- 결과는 `WHERE` 절을 사용하여 생성한다.
- `&amp;&amp;` 및 `||` 연산자를 사용하여 `WHERE` 절에 필요한 만큼의 필터 식을 적용할 수 있다.

&lt;br&gt;&lt;hr&gt;

## 📍 정렬
- `orderby` 절은 반환된 시퀀스의 요소가 정렬하고 있는 형식의 기본 비교자에 따라 정렬 된다.</code></pre><p>var query = 
    FROM cust in db.Customers
    WHERE cust.City == &quot;London&quot; &amp;&amp; cust.name == &quot;Dave&quot;
    ORDERBY cust.name ascending
    SELECT cust;</p>
<pre><code>
&lt;br&gt;&lt;hr&gt;

## 📍 그룹화
- `GROUP` 절을 사용하면 지정한 키에 따라 결과를 그룹화할 수 있다.
- `INTO` 키워드를 사용하여 계속 쿼리할 수 있는 식별자를 만들어야 한다.

&lt;br&gt;&lt;hr&gt;

## 📍 조인
- 데이터 소스에서 명시적으로 모델링된 시퀀스 간의 연결을 생성한다.
-`JOIN` 절은 DB 테이블에 직접 작업하는 대신 개체 컬렉션에 대해 작업한다.

&lt;br&gt;&lt;hr&gt;

## 📍 표준 쿼리 연산자 확장 메서드
- LINQ의 선언적 쿼리 구문은 컴파일할 때 CLR에 대한 메서드 호출로 변환한다.
- `WHERE`, `SELECT`, `GROUPT BY`, `JOIN`, `MAX`, `AVERAGE` 메서드 구문을 직접 사용할 수 있다. 
#### 🍀 예시</code></pre><p>int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };</p>
<p>// 1) 쿼리 구문
var numQuery1 = from num in numbers
where (num % 2) == 0 orderby num select num;</p>
<p>// 2) 메서드 구문
var numQuery2 = numbers.Where(num =&gt; num%2 != 0).OrderBy(n=&gt;n);</p>
<p>```</p>
<ul>
<li><strong>쿼리 구문</strong> : SQL처럼 보이는 선언적 방식</li>
<li><strong>메서드 구문</strong> : 확장 메서드를 체이닝</li>
</ul>
<p>⇒ 쿼리 방식을 쓰든, 메서드 방식을 쓰든 동일한 기능을한다. 선호도에 따라 골라서 사용할 수 있다.  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[HCI] 7주차 제네릭]]></title>
            <link>https://velog.io/@jsuhyeon_/HCI-7%EC%A3%BC%EC%B0%A8-%EC%A0%9C%EB%84%A4%EB%A6%AD</link>
            <guid>https://velog.io/@jsuhyeon_/HCI-7%EC%A3%BC%EC%B0%A8-%EC%A0%9C%EB%84%A4%EB%A6%AD</guid>
            <pubDate>Wed, 30 Apr 2025 10:44:02 GMT</pubDate>
            <description><![CDATA[<p><code>2025-04-25</code></p>
<br>

<h2 id="📍-제네릭">📍 제네릭</h2>
<ul>
<li>특정 타입에 의존하지 않고 어떤 타입이든 들어올 수 있도록 설계하는 방법 (타입을 나중에 넣는 방식)
<code>&lt;T&gt;</code> <code>&lt;K&gt;</code> <code>&lt;V&gt;</code> : 타입 매개 변수 요소 타입을 일반화한 타입</li>
</ul>
<h4 id="🍀-제네릭-클래스-사례">🍀 제네릭 클래스 사례</h4>
<ul>
<li>제네릭 리스트 : <code>List&lt;T&gt;</code></li>
<li><strong>T에 특정 타입으로 구체화</strong></li>
<li><strong>정수</strong>만 다루는 리스트 : <code>List&lt;int&gt;</code></li>
<li><strong>문자열</strong>만 다루는 리스트 : <code>List&lt;string&gt;</code></li>
<li><code>Person</code>만 다루는 리스트 : <code>List&lt;Person&gt;</code></li>
</ul>
<p>⇒ 다양한 자료혀엥 적용될 수 있는 일반화된 타입 매개변수로 클래스나 메소드를 작성하는 기법이다.</p>
<h4 id="🍀-제네릭-예제">🍀 제네릭 예제</h4>
<blockquote>
<p>💡 제네릭은 사용할 자료형을 <strong>매개변수로 전달</strong>받는다.</p>
</blockquote>
<pre><code>public class IntStack
{
    int[] items;
    int count;
    public void Push(int item) { ... }
    public int Pop() { ... }    
}

public class StringStack
{
    string[] items;
    int count;
    public void Push(string item) { ... }
    public string Pop() { ... }
}</code></pre><p>⬇</p>
<pre><code>public class Stack&lt;T&gt;
{
    T[] items;
    int count;
    public void Push(T item) { ... }
    public T Pop() { ... }
}

...

Stack&lt;int&gt; istack = new Stack&lt;int&gt;();
istack.Push(3);
int x = istack.Pop();

Stack&lt;string&gt; sstack = new Stack&lt;string&gt;();
sstack.Push(&quot;C#&quot;);
string s = sstack.Pop();</code></pre><p><br><hr></p>
<h2 id="📍-제네릭-클래스">📍 제네릭 클래스</h2>
<p>클래스나 인터페이스 선언부에 일반화된 타입을 추가한다.</p>
<pre><code>public class MyClass&lt;T&gt;
{
    T val;

    void Set(T a)
    {
        val = a;
    }

    T Get()
    {
        return val;
    }
}</code></pre><ul>
<li>제네릭 클래스 MyClass를 선언, 매개변수 <code>T</code></li>
<li><code>val</code>의 타입은 <code>T</code></li>
<li><code>T</code> 타입의 값 <code>a</code>를 <code>val</code>에 지정</li>
<li><code>T</code> 타입의 값 <code>val</code> 리턴</li>
</ul>
<p><br><hr></p>
<h2 id="📍-구체화">📍 구체화</h2>
<p>제네릭 타입의 클래스에 구체적인 타입을 대입하여 객체를 생성한다.</p>
<h4 id="🍀-제네릭-타입-t에-string-지정">🍀 제네릭 타입 T에 string 지정</h4>
<pre><code>MyClass&lt;string&gt; s = new MyClass&lt;string&gt;();
s.Set(&quot;Hello&quot;);
Console.WriteLine(s.Get()); // Hello</code></pre><h4 id="🍀-제네릭-타입-t에-int-지정">🍀 제네릭 타입 T에 int 지정</h4>
<pre><code>MyClass&lt;int&gt; n = new MyClass&lt;int&gt;();
n.Set(5);
Console.WriteLine(n.Get()); // 5</code></pre><h4 id="🍀-구체화된-myclassstring-소스-코드">🍀 구체화된 <code>MyClass&lt;string&gt;</code> 소스 코드</h4>
<pre><code>public class MyClass&lt;T&gt;
{
    T val;
    void Set(T a) { val = a; }
    T Get()       { return val; }
}</code></pre><pre><code>public class MyClass&lt;string&gt;
{
    string val;
    void Set(string a) { val = a; }
    string Get()        { return val; }
}</code></pre><ul>
<li>변수 <code>val</code> 타입 : <code>string</code></li>
<li><code>string</code> 타입의 값 <code>a</code>를 <code>val</code>에 지정</li>
<li><code>string</code> 타입의 값 <code>val</code>을 리턴</li>
</ul>
<blockquote>
<p>💡 <em><strong>제네릭 타입을 선언할 때, 인자 타입을 지정할 수 있다.</strong></em></p>
</blockquote>
<ul>
<li>값 형식인지 참조 형식인지 or 어떤 특정 부모 클래스로부터 파생된 타입인지 or 어떤 인터페이스를 구현한 타입인지</li>
<li><code>where T</code> : 제약 조건</li>
</ul>
<h4 id="🍀-인자-타입-지정-예제">🍀 인자 타입 지정 예제</h4>
<ul>
<li>값 형식 <code>T</code>
<code>class MyClass&lt;T&gt; where T : struct</code></li>
<li>참조 형식 <code>T</code>
<code>class MyClass&lt;T&gt; where T : class</code></li>
<li>default constructor
<code>class MyClass&lt;T&gt; where T : new()</code></li>
<li><strong>MyBaseClass의 파생 클래스</strong>이어야 한다.
<code>class MyClass&lt;T&gt; where T : MyBaseClass</code></li>
<li><strong>IComparable 인터페이스를 가져야</strong> 한다.
<code>class MyClass&lt;T&gt; where T : IComparable</code></li>
<li>복수 타입 파라미터 제약
<code>class MyClass&lt;T&gt; where T : struct where V : class</code></li>
<li>여러 개 제약
<code>class MyClass&lt;T&gt; where T : Person, I Comparable&lt;T&gt;, new()</code></li>
</ul>
<p><br><hr></p>
<h2 id="📍-불변성과-가변성">📍 불변성과 가변성</h2>
<ul>
<li><strong>불변성</strong> : 타입이 정확히 일치해야만 한다.ㅌ<pre><code>List&lt;Dog&gt; odgs = new List&lt;Dog&gt;();
List&lt;Animal&gt; animals = dogs; // 에러</code></pre></li>
<li><strong>가변성</strong> : 타입이 정확히 일치하지 않아도, 레퍼런스를 변환할 수 있다.
→ 공변성 or 반공변성으로 나뉜다.</li>
<li><strong>공변성</strong> : 하위 타입 → 상위 타입으로 레퍼런스 변환할 수 있는 것
⇒ 자식 타입을 부모 타입처럼 쓸 수 있다. (<code>out</code> 키워드)<pre><code>IEnumerable&lt;Dog&gt; dogs = new List&lt;Dog&gt;();
IEnumerable&lt;Animal&gt; animals = dogs;</code></pre></li>
<li><strong>반공변성</strong> : 상위 타입 → 하위 타입으로 레퍼런스를 변환할 수 있는 것
⇒ 부모 타입을 자식 타입처럼 쓸 수 있다. (<code>in</code> 키워드)<pre><code>IComparer&lt;Animal&gt; animalComparer = new AnimalComparer();
IComparer&lt;Dog&gt; dogComparer = animalComparer; </code></pre></li>
</ul>
<p><br><hr></p>
<h2 id="📍-공변성과-반공변성">📍 공변성과 반공변성</h2>
<h3 id="공변성을-지원하는-제네릭-인터페이스">공변성을 지원하는 제네릭 인터페이스</h3>
<ul>
<li><code>IEnumerable&lt;out T&gt;</code></li>
<li><code>IEnumerator&lt;out T&gt;</code></li>
<li><code>IQueryable&lt;out T&gt;</code></li>
<li><code>IGrouping&lt;out TKey, out TValue&gt;</code></li>
</ul>
<h3 id="반공변성을-지원하는-제네릭-인터페이스">반공변성을 지원하는 제네릭 인터페이스</h3>
<ul>
<li><code>IComparer&lt;in T&gt;</code></li>
<li><code>IComparable&lt;in T&gt;</code></li>
<li><code>IComparer&lt;in T&gt;</code></li>
</ul>
<p><br><hr></p>
<h2 id="📍-listt-메서드">📍 <code>List&lt;T&gt;</code> 메서드</h2>
<ul>
<li><p>리스트 객체의 각 원소를 T 형으로 변환하여 리스트로 반환할 수 있다.</p>
</li>
<li><p><code>public bool Exists(Predicate&lt;T&gt; match)</code>
리스트에 있는 모든 원소 중 match 조건을 만족하는 원소가 있는지 여부를 반환한다.</p>
</li>
<li><p><code>Find()</code> : List에서 조건을 만족하는 첫번째 원소를 반환한다.
<code>FindAll()</code> : List에서 조건을 만족하는 모든 원소를 리스트로 반환한다.
<code>FindIndex()</code> : List에서 조건을 만족하는 첫번째 인덱스를 반환한다.
<code>FindLastIndex()</code> : List에서 조건을 만족하는 마지막 원소의 인덱스를 반환한다.
<code>ForEach()</code> 
<code>TrueForAll()</code> : 리스트의 모든 원소가 조건을 만족하는지 여부를 반환한다.
<code>Remove()</code> </p>
</li>
</ul>
<p><br><hr></p>
<h2 id="📍-hashsett">📍 <code>HashSet&lt;T&gt;</code></h2>
<p>중복 요소를 포함하지 않는 고유한 요소의 정렬되지 않은 컬렉션</p>
<p><br><hr></p>
<h2 id="📍-yeild-키워드">📍 <code>yeild</code> 키워드</h2>
<p>컬렉션 데이터를 하나씩 리턴할 때 사용한다.</p>
<h4 id="🍀-yield-키워드-예제">🍀 <code>yield</code> 키워드 예제</h4>
<ul>
<li><code>yield return</code> 은 컬렉션 데이터를 하나씩 리턴할 때 사용한다.</li>
<li><code>yield break</code> 는 리턴을 중지하고 iteration 루프를 빠져나올 때 사용한다.<pre><code>public class yieldTest
{
  static IEnumerable&lt;int&gt; GetNumber()
  {
      yield return 10; // 첫번째 루프에서 리턴되는 값
      yield return 20; // 두번째 루프에서 리턴되는 값
      yield return 30; // 세번째 루프에서 리턴되는 값
  }
}
</code></pre></li>
</ul>
<p>public static void Main(string [] args)
{
    foreach(int num in GetNumber())
    {
        Console.WriteLine(num);
    }
}</p>
<pre><code>출력 결과 : 10 20 30

&lt;br&gt;&lt;hr&gt;

## 📍 IComparable 인터페이스
개인적으로 만든 클래스에 대해서 컬렉션에 추가하고, Sort를 이용해 정렬할 때 인터페이스를 구현해야 한다. 
이때 구현해야할 메서드는 CompareTo 메서드이다.

### CompareTo 메서드
- 현 객체가 인자보다 **작으면** → `음수`
- 현 객체가 인자와 **동일하면** → `0`
- 현 객체가 인자보다 **크면** → `양수`

#### 🍀 CompareTo 메서드 예제 </code></pre><p>public int CompareTo(A a) 
{
    if (this.str.CompareTo(a.str) == 0) 
    {
        if (this.num &gt; a.num)
            return 1;
        else if (this.num &lt; a.num)
            return -1;
        else
            return 0;
    }
    else 
    {
        return this.str.CompareTo(a.str);
    }
}</p>
<pre><code>
### Equals 메서드
- 두 객체의 내용이 같은지 동등성을 비교한다.
- 객체의 내용이 **동일하면** → `true`
- 객체의 내용이 **동일하지 않으면** → `false`
- Equals를 사용할 때는 **연산자 오버라이딩**을 해야 한다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[HCI] 7주차 컬렉션]]></title>
            <link>https://velog.io/@jsuhyeon_/HCI-7%EC%A3%BC%EC%B0%A8-%EC%BB%AC%EB%A0%89%EC%85%98%EA%B3%BC-%EC%A0%9C%EB%84%A4%EB%A6%AD-LINQ</link>
            <guid>https://velog.io/@jsuhyeon_/HCI-7%EC%A3%BC%EC%B0%A8-%EC%BB%AC%EB%A0%89%EC%85%98%EA%B3%BC-%EC%A0%9C%EB%84%A4%EB%A6%AD-LINQ</guid>
            <pubDate>Fri, 25 Apr 2025 08:11:08 GMT</pubDate>
            <description><![CDATA[<p><code>2025-04-25</code></p>
<br>

<h2 id="📍-자료구조">📍 자료구조</h2>
<ul>
<li>자료들이 저장되고 사용되는 방식</li>
<li>정해진 방식에 따라 자료를 저장하고, 자료간 관계를 저장하고, 이를 효과적으로 사용할 수 있는 방법을 정한다.</li>
<li>자료가 많을 때 관리가 용이하다.</li>
</ul>
<h4 id="🍀-자료-저장-방식">🍀 자료 저장 방식</h4>
<ol>
<li><p><strong>자료를 순서대로 저장</strong>
→ 배열(Array) | 리스트(List)
자료를 순서에 따라 연결하는 구조
인덱스로 자료를 읽을 수 있다.
<code>ArrayList</code> <code>Stack</code> <code>Queue</code> <code>LinkedList&lt;T&gt;</code> <code>List&lt;T&gt;</code> <code>Stack&lt;T&gt;</code> <code>Queue&lt;T&gt;</code></p>
</li>
<li><p><strong>키(Key) - 값(Value)</strong>
→ 맵(Map) | 딕셔너리(Dictionary)
고유 Key에 대응하는 자료가 연동되는 구조
키가 서로 중복되지 않아야 한다.
키를 인덱스로 삼아 자료를 읽는다.
<code>Dictionary&lt;T,V&gt;</code> <code>SortedList&lt;T,V&gt;</code> <code>KeyValuePair&lt;T,V&gt;</code></p>
</li>
<li><p><strong>집합구조</strong>
→ 집합(Set)
자료를 순서 없이 저장한다.
동일한 자료는 한 개만 존재하고, 중복 허용 하지 않는다.
수학의 집합과 유사하다.
<code>HashSet&lt;T&gt;</code> <code>SortedSet&lt;T&gt;</code></p>
</li>
<li><p><strong>계층형</strong>
→ 트리(Tree) | 그래프(Graph)
자료가 하위자료를 갖는 계층적 구조
연결 경로에 따라 자료를 저장하고 이동하여 읽을 수 있다.</p>
</li>
</ol>
<br>

<h3 id="자료구조의-필요성">자료구조의 필요성</h3>
<ul>
<li>관련 자료들을 <strong>동일한 변수 이름</strong>으로 체계적으로 저장하고 사용할 수 있다.</li>
<li>배열을 이용해서 같은 변수 이름으로 <strong>여러 개의 자료를 저장</strong>하고 <strong>인덱스를 통해 각 자료에 접근</strong>할 수 있다.
⇒ 여러 개의 변수 대신 배열과 반복문을 이용하면 코드를 줄일 수 있다.<h4 id="🍀-자료구조-예시-1">🍀 자료구조 예시 1</h4>
<pre><code>int x0 = 1;
int x1 = 2;
int x2 = 3;
int x3 = 4;
int x4 = 5;</code></pre>⬇<pre><code>int[] x = new int[5];
</code></pre></li>
</ul>
<p>for(int i=0; i&lt;5; i++)
{
    x[i] = i + 1;
}</p>
<pre><code>#### 🍀 자료구조 예시 2</code></pre><p>int SBS = 6;
int KBS2 = 7;
int KBS1 = 9;
int EBS1 = 10;
int MBC = 11;</p>
<pre><code>
- 방송국 이름과 채널 번호를 나타내는 변수를 프로그래머가 연관시켜야 한다.
- 방송국 중 한 개를 이름과 채널 번호로 출력한다.
`Console.WriteLine(&quot;television networks: {0}, channel number: {1}&quot;, &quot;SBS&quot;, SBS);`

⬇

표를 프로그램에 저장해놓고, 방송국의 이름을 이용해 채널 번호를 찾울 수 있도록 구현한다.

방송국 이름을 인덱스처럼 사용할 수 있는 배열 `channels[]` </code></pre><p>channels[&quot;SBS&quot;] = 6;
Console.WriteLine(&quot;television networks: {0}, channel number: {1}&quot;, &quot;SBS&quot;, channels[&quot;SBS&quot;]);</p>
<pre><code>
&lt;br&gt;

### 자료구조를 이용할 때의 장점
- 자료가 서로 연관되어 있을 때, 동일한 변수 이름을 사용할 수 있다.
⇒ 변수를 여러 개 사용할 때보다 가독성이 높고 코드 이해의 용이성
- 자료 항목 각각에 연산 또는 비슷한 연산을 처리하는 경우, 반복문을 사용해 코드를 줄이기 쉽다.

&lt;br&gt;

## 📍 컬렉션
- 객체를 쉽게 다룰 수 있도록 여러가지 클래스와 인터페이스를 미리 정의한 자료 구조
- `ArrayList` `SortedList` `Hashtable` `Stack` `Queue` `NameValueCollection`

### 컬렉션 특징
- **데이터를 보관**할 수 있고, **수정, 삭제, 삽입, 검색** 등의 기능을 갖는다.
- 클래스마다 구현되어지는 알고리즘이 다를 뿐, 같은 부류다.
- 동적으로 **메모리 확장이 가능**하다.

### 다양한 컬렉션
- `.NET`은 다양한 컬렉션을 제공한다.
1. `System.Collections` 클래스
2. `System.Collections.Generic` 클래스
3. `System.Collections.Concurrent` 클래스

### Object 객체 컬렉션 클래스
1. `ArrayList` : 필요에 따라 크기가 동적으로 증가하는 개체 배열
2. `Hashtable` : 키의 해시코드에 따라 구성된 키값 쌍의 컬렉션
3. `Queue` : FIFO 방식의 개체 컬렉션 (선입선출)
4. `Stack` : LIFO 방식의 개체 컬렉션 (선입후출)
5. `SortedList` : 키에 따라 정렬된 키/값 쌍의 컬렉션

### 재네릭 컬렉션 클래스
1. `Dictionary` : 키에 따라 구성도니 키/값 쌍의 컬렉션
2. `HashSet` : 값 집합
3. `Linkedlist&lt;T&gt;` : 이중 연결 목록
4. `List&lt;T&gt;` : 순서가 있어서 인덱스로 액세스할 수 있는 개체 목록
5. `Queue&lt;T&gt;` : 선입선출 (FIFO)
6. `SortedList&lt;TKey,TValue&gt;` : 연관된 `IComparer&lt;T&gt;` 구현을 기반으로 키에 따라 정렬된 키ㅣ/값 쌍의 컬렉션
7. `Stack&lt;T&gt;` : 선입후출(LIFO)

&lt;br&gt;&lt;hr&gt;

## 📍 SortedList

### SortedList : Hashtable + ArrayList
- 내부의 데이터는 키와 값으로 이루어져 있으며, 키를 기준으로 정렬되고 키와 인덱스로 접근할 수 있다.
- 내부적으로 정렬된 컬렉션을 유지하고 있는 특징을 갖는다.
`pubilc class SoretdList: IDictionary, ICollection, IEnumerable, IConeable`

### SortedList 특징
- 키의 목록 또는 값의 목록만 반환하는 메서드를 제공한다.
- 내부적으로 두 개의 배열, 즉 키에 대한 배열과 값에 대한 배열을 유지하여 요소를 목록에 저장한다.
- 각 요소에 대해 키, 값 또는 인덱스의 세가지 방법으로 접근할 수 있다.
- 요소가 삽입되면, 지정된 키가 이미 존재하는 검사한다. (중복키 허용)

### SortedList 메서드
- Add() : 키와 값으로 데이터를 삽입한다.
- Clear() : 모든 요소를 제거한다.
- Contains() : 특정 키가 들어 있는지 여부를 확인한다.
- ContainsKey() : 특정 키가 들어 있는지 여부를 확인한다.
- GetByIndex() GetKey() : 지정한 인덱스에서 값/키를 가져온다.
- GetKeyList() : 키 리스트를 가져온다.
- Remove() RemoveAt() : 지정한 키/인덱스로 요소를 제거한다.
- GetEnumerator() : IDictionaryEnumerator를 반환한다.

&lt;br&gt;&lt;hr&gt;

## 📍 Queue 
### Queue - FIFO 컬렉션
- 선입선출 구조
### Queue 메서드
- Enqueue() : 큐의 첫 위치에 요소를 삽입한다.
- Dequeue() : 큐의 마지막 위치의 요소를 반환하고 삭제한다.
- Peek() : 마지막 위치의 요소를 제거하지 않고 반환한다. (반환되는 데이터형은 object형)

#### 🍀 Queue 예제</code></pre><p>Queue queue = new Queue(new object[] {10, 20, 30});
queue.Enqueue(1);
queue.Enqueue(&quot;abc&quot;);
queue.Enqueue(3.4);</p>
<p>foreach(object obj in queue)
    Console.WriteLine(obj);</p>
<p>while(queue.Count &gt; 0)
{
    Console.WriteLine(&quot;Dequeue: {0} Count: {1}&quot;,
        queue.Dequeue(), queue.Count);
}</p>
<pre><code>출력 결과 : 10 20 30 1 abc 3.4
&lt;br&gt;&lt;hr&gt;

## 📍 Stack
### Stack - LIFO 컬렉션
- 선입후출 구조
### Stack 메서드
- Push() : 스택의 맨 위에 요소를 삽입한다.
- Pop() : 스택의 맨 위에 있는 요소를 삭제하고 데이터를 반환한다. (반환 타입 : object)
- Peek() : 스택의 맨 위에 있는 요소를 제거하지 않고 반환한다. (반환 타입 : object)
#### 🍀 Stack 예제</code></pre><p>Stack stack = new Stack(new object[] {10, 20, 30});
stack.Push(1);
stack.Push(&quot;abc&quot;);
stack.Push(3.4);</p>
<p>foreach(object obj in stack)
    Console.WriteLine(obj);</p>
<p>while(stack.Count &gt; 0)
{
    Console.WriteLine(&quot;Pop: {0} Count: {1}&quot;, 
        stack.Pop(), stack.Count);
}</p>
<pre><code>출력 결과 : 3.4 abc 1 30 20 10

## 📍 ArrayList
### ArrayList는 ILIST를 구현한 대표적인 클래스
- 데이터를 삽입했을 때 순서대로 삽입되며, 중간 삽입이나 제거가 가능하다.

### ArrayList 메서드 
- Add(), AddRange() : 데이터/데이터 리스트를 삽입한다.
- Insert() : 중간에 데이터를 삽입한다.
- Remove(), RemoveAt(), RemoveRange() : 해당 요소를 제거 or 인덱스로 요소 제거 or 범위만큼 요소 제거
- Sort() : 요소 정렬
- GetEnumerator() : IEnumerator를 반환한다.

#### 🍀 ArrayList 예제</code></pre><p>ArrayList list = new ArrayList();
list.Add(10);
list.Add(20);
list.Add(30);</p>
<p>foreach(object obj in list)
    Console.WriteLine(obj);</p>
<p>list.RemoveAt(1); // 1번 요소 제거</p>
<p>foreach(object obj in list)
    Console.WriteLine(obj);</p>
<p>list.Insert(1, 25); // 1번 요소에 25 추가</p>
<p>foreach(object obj in list)
    Console.WriteLine(obj);</p>
<pre><code>⇒ 출력 결과 : 10 20 30 | 10 30 | 10 25 30

&lt;br&gt;&lt;hr&gt;

## 📍 Hashtable
### Hashtable은 IDictionary를 구현한 클래스
- 내부의 데이터는 키(Key)와 값(Value)을 이용한다.

### Hashtable 메서드
- Add() : (키, 변수)로 된 데이터를 삽입한다.
- Clear() : 모든 요소를 제거한다.
- Remove() : 키를 확인하여 요소를 삭제한다.
- ContainsKey() ContainsValue() : 특정 키/값을 포함하는지 확인
- CoptyTo() : 해쉬테이블에 있는 원소를 1차원 배열로 복사한다.
- Keys, Values : ICollection으로 반환한다.
- GetEnumerator() : IDictionaryEnumerator를 반환한다.

#### 🍀 Hashtable 예제</code></pre><p>Hashtable ht = new Hashtable();
ht.Add(1, &quot;Dooly&quot;);
ht.Add(3, &quot;Heedong&quot;);
ht.Add(2, &quot;Gildong&quot;);
ht[4] = &quot;Tochi&quot;;</p>
<p>if(!ht.ContainsKey(5))
    ht.Add(5, &quot;Douner&quot;);</p>
<p>foreach(DictionaryEntry de in ht)
{
    Console.WriteLine(&quot;Key={0} Value={1}&quot;, 
            de.Key, de.Value.ToString());
}</p>
<p>Console.WriteLine(&quot;After Remove Dooley);</p>
<p>ht.Remove(1); // 둘리 제거
foreach(DictionaryEntry de in ht)
{
    Console.WriteLine(&quot;Key={0} Value={1}&quot;,
            de.Key, de.Value.ToString());
}</p>
<pre><code>⇒ 출력 결과 : 5, 4, 3, 2, 1 | 5, 4, 3, 2 
정렬된 컬렉션이 아니기 때문에 넣은 순서대로 출력되지 않는다.
해시값 순서로 순회되기 때문에 넣은 순서와 출력순서가 다를 수 있다. 

&lt;br&gt;&lt;hr&gt;

## 📍 IEnumerable 인터페이스
- GetEnumerator() : IEnumerator 개체를 반환한다.
- 내부에서 IEnumerable을 사용하여 데이터 검색 기능을 제공한다.
- Current 속성 : 컬렉션에서 현재 객체에 대한 참조를 반환한다.
- MoveNext() : 다음 요소로 이동한다.
- Reset() : Current 포인터를 컬렉션의 처음 앞으로 설정한다.

&lt;br&gt;&lt;hr&gt;

## 📍 ICollection 인터페이스
- Count 속성 : 컬렉션의 객체 수를 반환한다.
- IsSynchronized 속성 : 다중 스레드된 액세스를 위해 컬렉션에 대한 액세스를 동기화한 경우 `true`를 반환한다.
- SyncRoot 속성 : 하나 이상의 코드 문장이 동시에 한 스레드에만 실행되는 것을 확실하게 하기 위해 잠그거나 해제한다.
- CoptyTo() : 지정한 배열 위치부터 컬렉션 요소를 배열로 복사한다.

&lt;br&gt;&lt;hr&gt;

## 📍 IList 인터페이스
- ICollection 인터페이스에서 파생된 것으로 IEnumerable과 ICollection 기능을 모두 포함한다.


- IsFixedSize 속성 : 리스트가 고정 길이 리스트인지 확인
- IsReadOnly 속성 : 리스트가 읽기 전용인지 확인
- Add() : 리스트 끝에 데이터를 추가한다.
- Clear() : 리스트 내의 모든 데이터를 제거한다.
- Contains() : 어떤 데이터가 리스트 내에 존재하는지 여부를 확인한다.
- IndexOf() : 리스트 내의 특정 데이터의 위치를 반환한다.
- Insert() : 리스트 내의 특정 위치에 데이터를 삽입한다.
- Remove() : 매개변수로 입력된 객체를 리스트 내에서 제거한다.
- RemoveAt() : 지정한 인덱스의 데이터를 제거한다.

&lt;br&gt;&lt;hr&gt;

## 📍 IDictionary 인터페이스
- 순서에 의존하는 IList와 달리 키와 값으로 대응시켜 데이터를 추출한다.

- IsFixedSize 속성 : 컬렉션의 크기가 정해져 있는지 검사한다.
- IsReadOnly 속성 : 컬렉션이 읽기 전용인지 확인한다.
- Keys 속성 : 컬렉션 내의 모든 키를 나열한다.
- Values 속성 : 컬렉션 내의 모든 값을 나열한다.
- Add() : 키와 값을 전달하여 데이터를 컬렉션에 추가한다.
- Clear() : 컬렉션의 모든 데이터를 제거한다.
- Contains() : 특정 키가 데이터와 연관되어 있는지 검사한다.
- GetEnumerator() : IDictionaryEnumberator를 반환한다.
- Remove() : 삭제할 값의 키를 전달하여 데이터를 컬렉션에서 제거한다.

&lt;br&gt;&lt;hr&gt;

## 📍 IDictionaryEnumerator 인터페이스
- DictionaryEntry 속성 : 열거 요소 내의 키와 값을 가져온다. 
- Key 속성 : 열거 요소 내의 키를 가져온다.
- Value 속성 : 열거 요소 내의 값을 가져온다.

&lt;br&gt;&lt;hr&gt;

## 📍 컬렉션 클래스의 데이터 입출력
- 컬렉션 클래스에 데이터 입출력이 있으면 그때마다 **자동으로 boxing과 unboxing**이 계속 발생한다.
- 컬렉션 클래스는 전부 object 타입으로 저장하기 때문에 데ㅣ터에 접근할 때마다 본래 타이븡로의 형식 변화가 일어나기 때문이다.
- 데이터가 많아질수록 컴퓨터 성능에 상당한 부하가 발생한다.
- 성능 상의 이슈가 문제가 된다면, Collections 클래스보다 Generic Collections를 사용해야 한다.
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 섹션7 배포]]></title>
            <link>https://velog.io/@jsuhyeon_/Spring-Boot-%EC%84%B9%EC%85%987-%EB%B0%B0%ED%8F%AC</link>
            <guid>https://velog.io/@jsuhyeon_/Spring-Boot-%EC%84%B9%EC%85%987-%EB%B0%B0%ED%8F%AC</guid>
            <pubDate>Sun, 20 Apr 2025 12:54:27 GMT</pubDate>
            <description><![CDATA[<p><code>2025-04-20</code></p>
<h3 id="섹션7-학습목표">섹션7 학습목표</h3>
<ol>
<li>배포가 무엇인지 이해하고, 배포를 하기 위해 어떤 준비를 해야 하는지 알아본다.</li>
<li>스프링 서버를 실행할 때 DB와 같은 설정들을 코드 변경 없이 제어하는 방법을 알아본다.</li>
<li>git과 github의 차이를 이해하고, git에 대한 기초적인 사용법을 알아본다.</li>
<li>AWS의 EC2가 무엇인지 이해하고, AWS를 통해 클라우드 컴퓨터를 빌려본다.</li>
</ol>
<p><br><hr></p>
<h1 id="37강-배포란-무엇인가">37강 배포란 무엇인가</h1>
<h3 id="배포">배포</h3>
<p> 최종 사용자에게 소프트웨어를 전달하는 과정 (위키백과)
 ⇒ <em>*<em>전용 컴퓨터에 우리의 서버를 옮겨 실행시키는 것 *</em></em></p>
<h3 id="다른-사용자가-내가-만든-서버를-쓸-수-있는-방법">다른 사용자가 내가 만든 서버를 쓸 수 있는 방법</h3>
<p> 전용 컴퓨터에 내 <strong>코드를 옮기고</strong> 스프링, MySQL 등을 설치해 친구가 접속하게 한다. 
 ⇒ 내 컴퓨터에서 전용 컴퓨터로 코드를 옮길 수 있는 환경을 준비하고 실제 코드로 옮기는 행위 = 배포</p>
<h3 id="aws-amazon-web-service">AWS (Amazon Web Service)</h3>
<p> 아마존 쇼핑몰에서 운영하는 웹 서비스 
→ AWS에서 무료로 &#39;전용 컴퓨터&#39;를 빌릴 수 있다. </p>
<h3 id="aws-사용-시-주의할-점">AWS 사용 시 주의할 점</h3>
<p>우리는 컴퓨터를 살 때 <strong>운영체제(OS)</strong>도 같이 선택한다.
서버용 컴퓨터에서는 보통 <strong>리눅스</strong>를 사용한다.</p>
<p><br><hr></p>
<h1 id="38강-profile과-h2-db">38강 profile과 H2 DB</h1>
<p>똑같은 서버 코드를 실행시키지만, 내 컴퓨터에서 실행할 때는 로컬 MySQL을,
전용 컴퓨터에서 시랭할 때는 전용 컴퓨터의 MySQL을 실행해야 한다.</p>
<h3 id="profile-개념">profile 개념</h3>
<p>똑같은 서버 코드를 실행시키만, 실행될 때 <strong>설정을 다르게</strong> 하고 싶다! (각기 다른 자원)</p>
<p><code>No active profile set, falling back to 1 default profile: &quot;default&quot;</code>
→ 활성화 되어 있는 profile은 없기 때문에 기본값인 default profile을 사용하겠다.</p>
<h3 id="profile-적용하기">profile 적용하기</h3>
<p>똑같은 서버 코드를 실행시키지만, <code>local</code>이라는 profile을 입력하면, H2 DB를 사용하게 되고,
<code>dev</code>라는 profile을 입력하면 MySQL DB를 사용하도록 바꾸어보자.</p>
<h3 id="h2-db란">H2 DB란</h3>
<p>경량 데이터베이스로, 개발 단계에서 많이 사용되며 디스크가 아닌 메모리에 데이터를 저장할 수 있다.
⇒ 데이터가 휘발 된다 (개발 단계에서만 사용함)</p>
<h2 id="applicationyml-파일-코드"><code>application.yml</code> 파일 코드</h2>
<p><a href="https://jojoldu.tistory.com/547">🔗 IntelliJ Community 버전에서 Active profile 설정하기</a></p>
<p><code>application.yml</code></p>
<pre><code>spring:
  config:
    activate:
      on-profile: local

  datasource:
    url: &quot;jdbc:h2:mem:library;MODE=MYSQL; NO_KEYWORDS=USER&quot; # MySQL을 기본적으로 실행하게 되면 user라는 건 키워드가 아님을 알려줌
    username: &quot;root&quot;
    password: &quot;1234&quot;
    driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      ddl-auto: create
    properties:
      show_sql: true
      format_sql: true
      dialect: org.hibernate.dialect.H2Dialect

    h2:
      console:
        enabled: true
        path: /h2-console


--- # 구분선

spring:
  config:
    activate:
      on-profile: dev # 아래 옵션들이 모두 dev profile을 갖고 실행될 때만 적용된다.

  datasource:
    url: &quot;jdbc:mysql://localhost:3306/library&quot;
    username: &quot;root&quot;
    password: &quot;1234&quot;
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        format_sql: true
        show_sql: true
        dialect: org.hibernate.dialect.MySQL8Dialect
</code></pre><ul>
<li>구분선을 기준으로 위의 코드는 <code>local</code> 환경, 아래는 <code>dev</code> 환경이다.</li>
<li><code>local</code>로 설정을 하면 <code>The following 1 profile is active: &quot;local&quot;</code> 이 콘솔창에 출려되는 것을 확인할 수 있다.</li>
<li><code>local</code> 설정의</li>
</ul>
<pre><code>    h2:
      console:
        enabled: true
        path: /h2-console</code></pre><p>↪ <code>/h2-console</code>을 주소와 함께 주소창에 입력하면 h2에 접속할 수 있는 창을 볼 수 있다.
이 페이지를 통해서 데이터베이스에 접근할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jsuhyeon_/post/bca6c952-936f-4b2c-8434-338e180a5967/image.png" alt=""></p>
<p><br><hr></p>
<h1 id="39강-git과-github란">39강 git과 github란</h1>
<h3 id="git">git</h3>
<p>코드를 쉽게 관리할 수 있도록 해주는 버전 관리 프로그램
다양한 버전으로 개발된 프로그램들을 쉽게 합칠 수 있게 해주고, 다양한 버전 자체를 관리해준다.</p>
<h4 id="예시">예시</h4>
<p>기능1, 기능2, 기능3, 기능4, 기능5를 개발해야하는 프로젝트가 있다고 가정해보자.
개발자A는 기능1(버전A)을, 개발자B는 기능4(버전B)를 맡기로 하였다.
개발 완료 후, 코드를 합칠 때 서로 다른 버전의 A+B를 합칠 수 있게 해준다.</p>
<h3 id="github">github</h3>
<p>git으로 관리되는 프로젝트의 코드가 저장되는 저장소</p>
<h3 id="왜-github에-코드를-저장하는가">왜 github에 코드를 저장하는가</h3>
<p>내 컴퓨터에 있는 코드는 소실되는 것을 대비해 코드를 원격으로 보관할 수 있다.
뿐만 아니라 배포할 때 용이하다.</p>
<p><br><hr></p>
<h1 id="40강-git-기초-사용법">40강 git 기초 사용법</h1>
<h3 id="0️⃣-초기-명령어">0️⃣ 초기 명령어</h3>
<ul>
<li><p><code>git init</code> 
→ 이 프로젝트를 git이 관리하겠다.</p>
</li>
<li><p><code>git rmote add origin [주소: https://github.com/jeong-su-hyeon/library-app.git]</code> 
→ 내 프로젝트의 git 저장소를 주어진 주소로 하겠다.</p>
</li>
</ul>
<h3 id="1️⃣-코드를-택배-상자에-담기">1️⃣ 코드를 택배 상자에 담기</h3>
<ol>
<li><p><code>git add .</code> 
→ 모든 파일을 택배 상자에 담아라.
<code>.</code>은 모든 파일을 의미한다.
(특정 파일만 담고 싶다면 <code>git add 파일명</code>)</p>
</li>
<li><p><code>git status</code> 
→ 택배 상자에 잘 담겼는지 확인</p>
</li>
<li><p><code>.gitignore</code> 
→ <code>gitignore</code> 파일에 담긴 내용들은 택배 상자에서 빼준다.</p>
</li>
</ol>
<blockquote>
<ul>
<li><code>git reset</code> : 현재 택배 상자에 담겨있는 파일들을 빼주는 명령어</li>
</ul>
</blockquote>
<ul>
<li>다시 <code>git status</code> : 담기지 않은 파일에 대한 내용이 나온다.</li>
<li>다시 <code>git add .</code> 다시 상자에 담기 </li>
</ul>
<h3 id="2️⃣-택배-상자에-송장-붙이기">2️⃣ 택배 상자에 송장 붙이기</h3>
<ul>
<li><code>git commit -m &quot;첫번째 commit&quot;</code></li>
<li>처음 commit 했을 땐 다음과 같이 이메일과 이름을 입력한다. (내 깃허브 메일, 이름)<pre><code>git config -- global user.email &quot;studyingdeveloper@gmail.com&quot;
git config --global uuser.name &quot;studying-developer&quot;</code></pre></li>
</ul>
<h3 id="3️⃣-택배-상자를-github에-보내기">3️⃣ 택배 상자를 github에 보내기</h3>
<ul>
<li><code>gitpush</code></li>
<li><code>git push --setupstream origin master</code></li>
</ul>
<h3 id="40강-정리">40강 정리</h3>
<ol>
<li>코드를 택배 상자에 담기 → <code>git add .</code></li>
<li>택배에 송장 붙이기 → <code>git commit -m &quot;적고 싶은 메시지&quot;</code></li>
<li>택배 상자 github에 보내기 → <code>git push</code></li>
</ol>
<h1 id="41강-aws의-ec2-사용하기">41강 AWS의 EC2 사용하기</h1>
<p>나중에 ...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 섹션6 연관관계]]></title>
            <link>https://velog.io/@jsuhyeon_/Spring-Boot-%EC%84%B9%EC%85%986</link>
            <guid>https://velog.io/@jsuhyeon_/Spring-Boot-%EC%84%B9%EC%85%986</guid>
            <pubDate>Sun, 20 Apr 2025 11:30:17 GMT</pubDate>
            <description><![CDATA[<h1 id="33강-객체지향적-개발">33강 객체지향적 개발</h1>
<blockquote>
<h3 id="현재-코드">현재 코드</h3>
</blockquote>
<h4 id="①-대출-기능">① 대출 기능</h4>
<p><code>Book</code> 책 정보를 가져온다. 
<code>UserLoanHistoryRepository</code> 검증한다. 
<code>User</code> 사용자 정보를 가져온다.
→ <code>BookService</code> 
→ <code>UserLoanHistory</code> 에 기록을 만들어 저장한다.</p>
<h4 id="②-반납-기능">② 반납 기능</h4>
<p><code>User</code> 사용자 정보를 가져온다.
<code>UserLoanHistory</code> 대출 기록을 가져와 반납으로 처리한다.
→ <code>BookService</code></p>
<h2 id="n--1-관계">N : 1 관계</h2>
<h3 id="객체-지향적-개발">객체 지향적 개발</h3>
<ul>
<li><p><code>User</code> ⇔ <code>UserLoanHistory</code> 사용자 정보와 대출 기록을 가져와 바로 대출 및 반납을 처리한다.
⇒ <code>BookService</code></p>
</li>
<li><p>*<em>선행 조건 *</em>: <code>User</code> 와 <code>UserLoanHistory</code> 가 서로를 알아야 한다.</p>
</li>
</ul>
<h3 id="①-manytoone-어노테이션">① <code>@ManyToOne</code> 어노테이션</h3>
<ul>
<li>내가 N이고, 너가 1이다.</li>
<li><em>예시) 학생 여러명이 교실에 들어갈 수 있다. → 학생 N : 교실 1</em><pre><code>@Entity public class UserLoanHistory
{
  @ManyToOne
  private User user;
}</code></pre>↪ <code>User</code> 객체를 <code>UserLoanHistory</code> 객체의 멤버 필드로 사용할 수 있다. </li>
</ul>
<h3 id="②-onetomany-어노테이션">② <code>@OneToMany</code> 어노테이션</h3>
<ul>
<li>1 : N 관계</li>
<li>내가 1이고, 너가 N</li>
</ul>
<pre><code>@Entity
public class User
{
    @OneToMany
    private List&lt;UserLoanHistory&gt; userLoanHistoryList = new ArrayList&lt;&gt;();
}</code></pre><p>↪ <code>UserLoanHistory</code> 객체 타입을 <code>List</code> 로 사용할 수 있다.</p>
<h3 id="③-연관관계의-주인">③ 연관관계의 주인</h3>
<p>연결되어 있는 Table을 보았을 때 누가 관계의 주도권을 갖고 있는가</p>
<pre><code>// user 테이블
CREATE TABLE user(
    user_id BIGINT AUTO_INCREMENT NOT NULL,
    user_name VARCHAR(25) NOT NULL,
    user_age INT NOT NULL,
    PRIMARY KEY(user_id)
);

// user_loan_history 테이블
CREATE TABLE user_loan_history(
    load_id BIGINT AUTO_INCREMENT,
    user_id BIGINT,
    book_name VARCHAR(255),
    is_return TINYINT(1),
    PRIMARY KEY(load_id)
);</code></pre><p>↪ <code>user_loan_history</code> 테이블이 주도권을 갖고 있다.</p>
<h4 id="mappedby-옵션"><code>mappedBy</code> 옵션</h4>
<p>↪ 연관관계의 주인이 아닌 쪽에 <code>mappedBy</code> 옵션을 달아줘야 한다.
<code>@OneToMany(mappedBy = &quot;user&quot;)</code></p>
<pre><code>@Entity
public class User
{
    @OneToMany(mappedBy = &quot;user&quot;)
    private List&lt;UserLoanHistory&gt; userLoanHistoryList = new ArrayList&lt;&gt;();
}</code></pre><p>↪ 연관관계의 주인의 값이 설정되어야만 진정한 데이터가 저장된다.</p>
<p><br><hr></p>
<h1 id="34강-jpa-연관관계">34강 JPA 연관관계</h1>
<h2 id="11-연관관계">1:1 연관관계</h2>
<h4 id="테이블-생성">테이블 생성</h4>
<pre><code>CREATE TABLE person (
    person_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    person_name VARCHAR(255),
    address_id BIGINT
);

CREATE TABLE address(
    address_id BIGINT AUTO_INCREMENT PRIMARY KEY,
    city VARCHAR(255),
    street VARCHAR(255)
);</code></pre><p>↪ <code>person</code>이 주도권을 가지고 있다. <code>person</code>이 연관관계의 주인</p>
<h4 id="엔티티-클래스-구현">엔티티 클래스 구현</h4>
<p><code>Person</code></p>
<pre><code>@Entity
public class Person
{
    // [멤버 필드]
    @OneToOne
    private Address address;
}</code></pre><p><code>Address</code></p>
<pre><code>@Entity
public class Address
{
    // [멤버 필드]
    @OneToOne(mappedBy = &quot;address&quot;)
    private Person person;
}    </code></pre><h3 id="연관관계의-주인-효과">연관관계의 주인 효과</h3>
<ul>
<li>객체가 연결되는 기준이 된다.</li>
<li>연관관계 주인이 가르치는 객체의 <code>Setter</code> 가 호출되어야만 
객체가 데이터베이스에서 두 테이블을 연결시킬 수 있다.<pre><code>@Transactioinal
public void savePerson()
{
  Person person = personRepository.save(new Person());
  Address address = addressRepository.save(new Address());
  person.setAddress(address); // 정상 반영
     // address.setPerson(person); -&gt; null
}</code></pre></li>
</ul>
<ol>
<li>상대 테이블을 참조하고 있으면 연관관계의 주인이다.</li>
<li>연관관계의 주인이 아니면 <code>mappedBy</code> 옵션을 사용한다.</li>
<li>연관관계의 주인의 <code>Setter</code> 가 호출되어야만 테이블을 연결할 수 있다. </li>
</ol>
<h3 id="연관관계-사용-시-주의해야-할-점">연관관계 사용 시 주의해야 할 점</h3>
<pre><code>@Transactioinal
public void savePerson()
{
    Person person = personRepository.save(new Person());
    Address address = addressRepository.save(new Address());
    person.setAddress(address); // 정상 반영

    System.out.println(address.getPerson()); // null
}</code></pre><ul>
<li>트랜잭션이 끝나지 않았을 때, 한쪽만 연결해두면 반대쪽은 알 수 없다. 
↪ 해결책 : <code>Setter</code> 한 번에 둘을 같이 이어준다.!</li>
</ul>
<h4 id="해결-방법">해결 방법</h4>
<p><code>Person</code></p>
<pre><code>@Entity
public class Person
{    
    // [Setter] 
    public void setAddress(Address address)
    {
        this.address = address;
        this.address.setPerson(this); // -&gt; Setter끼리 연결 
    }
}</code></pre><p><code>Address</code></p>
<pre><code>@Entity
public class Address
{    
    // [Setter]
    public void setPerson(Person person) 
    {
        this.person = person;
    }

    // [Getter]
    public Person getPerson()
    {
        return this.person;
    }
}    </code></pre><p><code>PersonService</code></p>
<pre><code>@Transactoinal void savePerson()
{
    Person person = personRepository.save(new Person());
    Address address = addressRepository.save(new Address());
    person.setAddress(address); // 테이블 간 연결
    address.getPerson(); // 객체끼리 연결 
}</code></pre><h3 id="정리">정리</h3>
<ul>
<li>N:1 관계 - <code>@ManyToOne</code>과 <code>@OneToMany</code>
↪ 연관관계의 주인인 쪽이 N이다.</li>
<li><code>@ManyToOne</code>을 단방향으로 사용할 수 있다.
↪ <code>User</code> 클래스에서 <code>private List&lt;UserLoanHistory&gt; userLoanHistoryList</code>를 생략할 수 있다.</li>
</ul>
<h2 id="nm-관계-manytomany">N:M 관계 <code>@ManyToMany</code></h2>
<ul>
<li>구조가 복잡하고, 테이블이 직관적으로 매핑되지 않아 사용하지 않는 것을 추천 ,,</li>
<li>예시) 동아리 : 학생 관계</li>
</ul>
<blockquote>
<p><strong>기타 옵션</strong>
<code>JoinColumn</code> 어노테이션
<code>cascade</code> 옵션
<code>orphanRemoval</code> 옵션</p>
</blockquote>
<h3 id="joincolumn"><code>@JoinColumn</code></h3>
<ul>
<li>연관관계의 주인이 활용할 수 있는 어노테이션</li>
<li>필드의 이름이나 null 여부, 유일성 여부, 업데이트 여부 등을 지정한다.
↪ <code>@Column</code> 어노테이션과 역할은 유사하나, <code>@JoinColumn</code>은 연관관계의 주인에게 활용할 수 있는 어노테이션이다.</li>
</ul>
<h3 id="cascade-옵션"><code>cascade</code> 옵션</h3>
<ul>
<li>cascade : 폭포처럼 흐르다.</li>
<li>한 객체가 저장되거나 삭제될 때, 그 변경이 폭포처럼 흘러 <strong>연결되어 있는 객체도 함께 저장되거나 삭제</strong>되는 기능</li>
</ul>
<p>예시) <code>User</code>가 책 1과 책 2를 빌렸을 때, <code>User</code> 객체를 삭제하면 → <code>User</code>만 삭제되고 <code>UserLoanHistory</code>는 남아있게 된다.</p>
<pre><code>@OneToMany(mappedBy = &quot;user&quot;, cacade = CascadeType.ALL)
private List&lt;UserLoanHistory&gt; userLoanHistoryList = new ArrayList&lt;&gt;();</code></pre><h3 id="orphanremoval-옵션"><code>orphanRemoval</code> 옵션</h3>
<h4 id="userloanhistory에서-user가-빌린-책을-한권만-삭제하고-싶을-때">UserLoanHistory에서 User가 빌린 책을 한권만 삭제하고 싶을 때</h4>
<pre><code>@Transactional
public void deleteUserHistory() 
{
    User user = userRepository.findByName(&quot;정수현&quot;)
        .orElseThrow(IllegalArgumentException::new);

    user.removeOneHistory();
}

public void removeOneHistory()
{
    userLoanHistoryList.removeIf(history -&gt; &quot;책1&quot;.equals(history.getBookName()));
}</code></pre><p>↪ 데이터베이스 상에 아무런 변화 없다. </p>
<h4 id="orphanremoval-옵션을-통한-해결-방법">orphanRemoval 옵션을 통한 해결 방법</h4>
<pre><code>@OneToMany(mappedBy = &quot;user&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
private List&lt;UserLoanHistory&gt; userLoanHistoryList = new ArrayList&lt;&gt;();</code></pre><ul>
<li>객체 간의 관계가 끊어진 데이터를 자동으로 제거하는 옵션</li>
<li>관계가 끊어진 데이터 = orphan (고아) / 제거 = removal</li>
</ul>
<h3 id="34강-정리">34강 정리</h3>
<ol>
<li><p>상대 테이블을 가리키는 테이블이 연관관계의 주인이다. 
연관관계의 주인이 아닌 객체는 mappedBy를 통해 주인에게 매여 있음을 표시해 주어야 한다.</p>
</li>
<li><p>양쪽 모두 연관관계를 갖고 있을 때는 양쪽 모두 한 번에 맺어주는 게 좋다. 
예시) 한쪽의 <code>Setter</code>를 부를 때 양쪽이 연결되게끔 양쪽 객체 클래스에 모두 <code>Setter</code>를 이어주도록 구현하는게 중요하다. </p>
</li>
<li><p><code>cascade</code> 옵션을 활용하면 저장이나 삭제를 할 때 연관관계에 놓인 테이블까지 함께 연관관계에 놓인 테이블까지 함께 저장 또는 삭제가 이루어진다.</p>
</li>
<li><p><code>orphanRemoval</code> 옵션을 활용하면, 연관관계가 끊어진 데이터를 자동으로 제거해준다.</p>
</li>
</ol>
<p><br><hr></p>
<h1 id="35강-리팩토링과-지연-로딩">35강 리팩토링과 지연 로딩</h1>
<p><code>User</code> 클래스에서 일반 메서드로 <code>loanBook</code> 과 <code>returnBook</code> 기능 함수를 구현하였다. </p>
<p><code>User</code></p>
<pre><code>@Entity
public class User
{
    // [일반 메서드]
    // 대출 기능
    public void loanBook(String bookName)
    {
        this.userLoanHistoryList.add(new UserLoanHistory(this, bookName, false));
    }

    // 반납 기능
    public void returnBook(String bookName)
    {
        // 1) 대출 기록을 읽어들여 bookName에 일치하는 기록을 찾는다.
        UserLoanHistory targetHistory = this.userLoanHistoryList.stream() // 리스트를 한줄씩 읽어들인다.
                .filter(history -&gt; history.getBookName().equals(bookName)) // 책이름과 일치하는 기록을 가져온다.
                .findFirst() // 첫번째에 해당하는 기록을 반환한다.
                .orElseThrow(IllegalArgumentException::new);

        // 해당 기록을 찾았으면 반납처리한다.
        targetHistory.doReturn();
    }
}</code></pre><p><code>BookService</code></p>
<pre><code>@Transactional
public void selectReturn(BookReturnRequest request)
{
    // 1) User 정보를 가져온다.
    User user = userRepository.findByUserName(request.getUserName())
        .orElseThrow(IllegalArgumentException::new);

    // 2) UserLoanHistory에서 반납을 처리한다.
    user.returnBook(request.getBookName());
}</code></pre><p>⇒ JPA의 연관관계 옵션을 활용해서 최대한 도메인들끼리 직접 협력할 수 있도록 코드를 수정하였다. </p>
<h3 id="영속성-컨텍스트-네번째-기능">영속성 컨텍스트 네번째 기능</h3>
<h3 id="지연-로딩">지연 로딩</h3>
<ul>
<li>지연 로딩 (Lazy Loading)
: 서버가 시작하자마자 <code>User</code>와 <code>UserLoanHistory</code> 클래스를 호출하는게 아닌,
처음에 <code>User</code>만 가져왔다가 <code>UserLoanHistory</code>가 필요한 순간에 호출한다. 
⇒ <em><strong>연결되어 있는 객체를 꼭 필요한 순간에만 가져온다.</strong></em></li>
</ul>
<ul>
<li><code>@OneToMany</code>의 <code>fetch</code> 옵션 </li>
<li>트랜잭션 환경에서만 가능하다.</li>
</ul>
<h3 id="연관관계의-장점">연관관계의 장점</h3>
<ol>
<li>각자의 역할에 집중하게 된다. (= 응집성)</li>
<li>새로운 개발자가 코드를 읽을 때 이해하기 쉬워진다.</li>
<li>테스트 코드 작성이 쉬워진다. </li>
</ol>
<h3 id="연관관계를-사용하는-것이-항상-좋을까">연관관계를 사용하는 것이 항상 좋을까?</h3>
<ul>
<li>지나치게 사용하면, 성능상의 문제가 생길 수도 있고,
도메인간의 복잡한 연결로 인해 시스템을 파악하기 어려워질 수도 있다. </li>
<li>비즈니스 요구사항, 기술적 요구사항, 도메인 아키텍처 등 여러 부분을 고민해서 연관관계 사용을 선택해야 한다. </li>
</ul>
<p><br><hr></p>
<h1 id="36강-요약">36강 요약</h1>
<ol>
<li>책 생성, 대출 반납 API를 온전히 객발하며 지금까지 다루었떤 모든 개념을 실습해본다.</li>
<li>객체지향적으로 설계하기 위해 연관관계를 이해하고, 연관관계의 다양한 옵션에 대해 이해한다.</li>
<li>JPA에서 연관관계를 매핑하기 위한 방법을 이해하고,
연관관계를 사용해 개발할 떄와 사용하지 않고 개발할 때의 차이점을 이해한다. </li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 섹션5 JPA]]></title>
            <link>https://velog.io/@jsuhyeon_/Spring-Boot-%EC%84%B9%EC%85%985-JPA</link>
            <guid>https://velog.io/@jsuhyeon_/Spring-Boot-%EC%84%B9%EC%85%985-JPA</guid>
            <pubDate>Tue, 15 Apr 2025 06:54:54 GMT</pubDate>
            <description><![CDATA[<p><code>2025-04-14</code></p>
<h3 id="학습-목표">학습 목표</h3>
<ol>
<li>문자열 SQL을 직접 사용하는 것의 한계를 이해하고, 해결책인 JPA, Hibernate, Spring Data JPA가 무엇인인지 이해한다.</li>
<li>Spring Data JPA를 이용해 데이터를 생성 ,조회, 수정, 삭제할 수 있다. </li>
<li>트랜잭션이 왜 필요한지 이해하고 스프링에서 트랜잭션을 제어하는 방법을 익힌다.</li>
<li>영속성 컨텍스트와 트랜잭션의 관계를 이해하고, 영속성 컨텍스트의 특징을 알아본다.</li>
</ol>
<p><br><hr></p>
<h2 id="23강-문자열-sql의-한계">23강 문자열 SQL의 한계</h2>
<h4 id="기존-방식">기존 방식</h4>
<ul>
<li><strong>Postman</strong> : 외부에서 API 호출
→ <strong>Spring Boot</strong> : 스프링 컨테이너가 관리 (<code>Controller</code>, <code>Service</code>, <code>Repository</code>)
→ <strong>MySQL</strong></li>
<li><code>Repository</code>에서 SQL을 직접 작성하였다.</li>
</ul>
<h4 id="sql-직접-작성의-아쉬운-점">SQL 직접 작성의 아쉬운 점</h4>
<ol>
<li>문자열을 작성하기 때문에 실수할 수 있고, 실수를 인지하는 시점이 느리다.
↪ 컴파일 시 발견되지 않고, 런타임 시점에 발견되기 때문</li>
<li>특정 데이터베이스에 종속적이게 된다.</li>
<li>반복 작업이 많아진다. 테이블을 하나 만들 때마다 CRUD 쿼리가 항상 필요하다.</li>
<li>데이터베이스의 테이블과 객체는 패러다임이 다르다. (단방향 구성이거나, 클래스 상속일 때)</li>
</ol>
<h4 id="개선-방법">개선 방법</h4>
<p>JPA (Java <strong>Persistence</strong> API)</p>
<ul>
<li>Persistence : 영속성 
⇒ 서버가 재시작 되어도 <strong>데이터는 영구적으로 저장</strong>되는 속성</li>
<li>API : 정해진 규칙</li>
</ul>
<p>자바 진영의 ORM 
(Object-Relation Mapping)</p>
<ul>
<li>Object : 객체</li>
<li>Relation : 관계형 DB의 테이블</li>
<li>Mapping : 객체의 정보와 테이블의 정보를 짝짓는다.</li>
</ul>
<h3 id="jpa">JPA</h3>
<ul>
<li>객체와 관계형 DB 테이블을 짝지어,</li>
<li>데이터를 영구적으로 보관하기 위해 Java 진영에서 정해진 <span style="color:green"><strong>규칙</strong></span></li>
</ul>
<h3 id="hibernate">Hibernate</h3>
<ul>
<li>JPA를 구현해서 코드로 작성한 것</li>
<li>구현체</li>
</ul>
<h3 id="정리">정리</h3>
<p>JPA - 규칙
Hibernate - 내부에 JDBC를 사용하고 있다.</p>
<p><br><hr></p>
<h2 id="24강-유저-테이블에-대응되는-entity-class-생성">24강 유저 테이블에 대응되는 Entity Class 생성</h2>
<h3 id="java-객체와-mysql-매핑">Java 객체와 MySQL 매핑</h3>
<ul>
<li><code>@Entity</code> 
⑴ 스프링이 User 객체와 User 테이블을 같은 것으로 바라본다.
⑵ 저장되고, 관리되어야 하는 데이터</li>
</ul>
<pre><code>@Entity
public class User 
{
    private long id;
    private String name;
    private Integer age;

    ...
}</code></pre><h3 id="id-generatedvalue"><code>@Id</code>, <code>@GeneratedValue</code></h3>
<ul>
<li><p><code>@Id</code> : 이 필드를 <code>PRIMARY KEY</code>로 간주한다.</p>
</li>
<li><p><code>@GeneratedValue</code> : <code>PRIMARY KEY</code>는 자동 생성되는 값이다. (<code>AUTO_INCREMENT</code>)</p>
<pre><code>@Entity
public class User
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private long id;

  protected User()
  {
      // 기본 생성자
  }
}</code></pre></li>
<li><p>JPA 를 사용하기 위해서는 기본 생성자가 필요하다.</p>
</li>
<li><p>이때 기본 생성자는 <code>protected</code>로 매개 변수 없이 생성할 수 있다.</p>
</li>
</ul>
<h3 id="column"><code>@Column</code></h3>
<ul>
<li><p><code>@Column</code> : 객체의 필드와 Table의 필드를 매핑한다.</p>
</li>
<li><p>nullable 속성이나, length, 테이블 변수명 등을 일치시킨다.</p>
</li>
<li><p>테이블 칼럼과 코드가 동일할 경우에는 <code>@Column</code>  어노테이션 생략 가능하다.</p>
<pre><code>@Entity
public class User {    
  @Column(nullable = false, length = 20, name = &quot;user_name&quot;)
  private String name;

  private Integer age; // @Column 생략 가능 
}</code></pre></li>
</ul>
<h3 id="applicationyaml">application.yaml</h3>
<pre><code>  jpa:
    hibernate:
      ddl-auto: none 
    properties:
      hibernate:
        format_sql: true
        show_sql: true
        dialect: org.hibernate.dialect.MySQL8Dialect</code></pre><h4 id="ddl-auto"><code>ddl-auto</code></h4>
<p>스프링이 시작할 때 DB에 있는 테이블과 객체의 필드가 다를 경우 어떻게 처리할지에 대한 것</p>
<ul>
<li><code>create</code> : 기존 테이블이 있다면 삭제 후 다시 생성</li>
<li><code>create-drop</code> : 스프링이 종료될 때 테이블을 모두 제거</li>
<li><code>update</code> : 객체와 테이블이 다른 부분만 변경</li>
<li><code>validate</code> : 객체와 테이블이 동일한지 확인</li>
<li><code>none</code> : 별다른 조치를 하지 않는다.</li>
</ul>
<h4 id="format_sql"><code>format_sql</code></h4>
<ul>
<li>SQL을 보여줄 때 예쁘게(?) 포맷팅할 것인가에 대한 것</li>
</ul>
<h4 id="show_sql"><code>show_sql</code></h4>
<ul>
<li>JPA를 사용해 DB에 SQL을 날릴 때 SQL을 보여줄 지에 대한 것</li>
</ul>
<h4 id="dialect"><code>dialect</code></h4>
<ul>
<li>현재 옵션으로 DB를 특정하면 설정한 기준에 맞춰서 SQL을 수정해준다. (지금은 MySQL 사용)</li>
</ul>
<p><br><hr></p>
<h2 id="25강-spring-data-jpa를-이용해-자동으로-쿼리-날리기">25강 Spring Data JPA를 이용해 자동으로 쿼리 날리기</h2>
<h4 id="학습-목표-1">학습 목표</h4>
<ul>
<li>SQL을 작성하지 않고, 기존에 만들었던 User INSERT / SELECT/ UPDATE 기능을 리팩토링 한다.</li>
</ul>
<br>

<h3 id="구현">구현</h3>
<ul>
<li><code>UserJdbcRepository</code> 클래스명 변경</li>
<li><code>UserRepository</code> 인터페이스 생성
→ <code>JpaRepository</code> 상속<pre><code>public interface UserRepository extends JpaRepository&lt;User, Long&gt;
{
</code></pre></li>
</ul>
<p>}</p>
<pre><code>- `JpaRepository`를 상속받기 때문에 `@Repository` 어노테이션을 생략 해도 된다.

### JpaRepository
- 기본 CRUD 기능이 다 들어있는 인터페이스
- `&lt;User&gt;` : 이 Repository가 관리할 엔티티 클래스
- `&lt;Long&gt;` : User의 PK 타입 (보통 @Id가 붙은 필드 타입) 
→ 고유한 데이터를 구분하기 위함

### ① INSERT 기능
`Service`</code></pre><p>// [INSERT]
public void insertUser(UserCreateRequest request)
{
        User user
            = userRepository.save(new User(request.getName(), request.getAge()));
}</p>
<pre><code>- `userRepository` (`JpaRepository` 상속) 를 통해 데이터를 INSERT 할 수 있다.

### ② SELECT 기능
`Service`</code></pre><p>private List<UserResponse> selectUser()
{
    List<User> userList = userRepository.findAll();</p>
<pre><code>return userList.stream()
    .map(user -&gt; new UserResponse(user.getId(), user.getName(), user.getAge()))
    .collect(Collectors.toList());</code></pre><p>}</p>
<pre><code>- **함수 반환 타입**
여기서 함수 반환 타입이 `List&lt;User&gt;` 가 아닌 `&lt;UserResponse&gt;` 인 이유는 `&lt;User&gt;`는 DB와 연결된 객체이고, 사용자에게 보여지기 위해서는 DB에 직접 연결된 `&lt;User&gt;` 보다는 `&lt;UserResponse&gt;`를 통해 한번 감싸서 보여지는 것이 보안상 더 안전하기 때문이다.

- `findAll()` 
: 자동으로 SQL을 날려서 해당 테이블에 있는 모든 데이터를 가져온다. (`SELECT * FROM user`)
↪ 그렇게 가져온 정보는 List가 된다. 여기서 List는 DB에 접근한 객체이기 때문에 `&lt;User&gt;` 타입이다.

- **RETURN**
⑴ 그렇게 만들어진 `userList`를 `stream()`을 통해서 모든 데이터에 접근한다.
⑵ `map()`을 통해 List의 `&lt;User&gt;` 타입을 함수 반환 타입인 `&lt;UserResponse&gt;` 타입으로 변환한다.
⑶ `User` 객체의 `id`, `name`, `age` 값을 꺼내서(get 메서드) `UserResponse` 객체를 생성한다. (= DTO 로 변환한다.)

&gt; - `stream()` : 리스트 같은 데이터를 한줄씩 꺼내면서, 편하게 가공할 수 있는 파이프 라인 (반복문을 더 깔끔하게 만들었다고 보면 됨)
- `map()` : 각 User 객체를 DTO로 변환한다. 즉, 스트림 내부의 데이터를 다른 타입으로 변환한다.
- `collect()` : 스트림을 다시 묶어서 반환
- `Collectors.toList()` : 리스트로 묶는다.

#### 더 간단하게 쓰기</code></pre><p>// [UserResponse 생성자]
public UserResponse(User user)
{
    this.id = user.getId();
    this.name = user.getName();
    this.age = user.getAge();
}</p>
<pre><code></code></pre><p>// [Service]
return userList.stream()
    .map(UserResponse::new)
    .collect(Collectors.toList());</p>
<pre><code>- `UserResponse::new` : 생성자 참조
⇒ `user -&gt; new UserResponse(user)`

### ③ UPDATE 기능
`Service`</code></pre><p>public void updateUser(UserUpdateRequest request)
{
    // 1) id 존재 여부 판단
    User user = userRepository.findById(request.getId())
        .orElseThrow(IllegalArgumentException::new);</p>
<pre><code>// 2) uddate
user.updateName(request.getName()); 
userRepository.save(user); // save() 메서드를 호출한다. -&gt; 자동으로 UPDATE SQL이 실행됨</code></pre><p>}</p>
<pre><code>
1. id 존재 여부 판단
`findById()` : `Id` 에 해당하는 `user`를 조회한다. (`SELECT * FROM user WEHRE user_id = ?`)
`Id`에 해당하는 `user`가 있다면 `user` 객체에 저장
`Id`에 해당하는 `user`가 없다면 예외 발생

2. update
`save()` → 자동으로 UPDATE SQL이 실행됨


### 주요 기능 정리
① `save()` : 주어지는 객체를 **저장**하거나 **업데이트** 시켜준다.
② `findAll()` : 주어지는 객체가 매핑된 테이블의 **모든 데이터**를 가져온다. (`SELECT * FROM user`)
③ `findById()` : `Id`를 기준으로 **특정한 1개의 데이터**를 가져온다. (`SELECT * FROM user WEHRE user_id = ?`)

#### SQL을 작성하지 않아도 동작하는 이유
- **Spring Data JPA**
복잡한 JPA 코드를 스프링과 함께 쉽게 사용할 수 있도록 도와주는 라이브러리
`save()` | `findAll()` | `findById()` 

- `Spring Data JPA &gt; JPA &gt; Hibernate(JPA 구현체) &gt; JDBC`
⇒ Spring Data JPA는 JDBC를 사용하기 때문에 SQL을 작성하지 않아도 동작할 수 있다.

&lt;br&gt;&lt;hr&gt;

## 26강 Spring Data JPA를 이용해 다양한 쿼리 작성하기
### ④ DELETE 기능
`UserRepository`</code></pre><p>public interface UserRepository extends JpaRepository&lt;User, Long&gt; 
{
    User findByName(String name);
}</p>
<pre><code>- `find`만 작성하면, 1개의 데이터만 가져온다.
- `By` 뒤에 붙는 필드 이름으로 `SELECT` 쿼리의 `WHERE` 문이 작성된다.
</code></pre><p>public void deleteUser(String name)
{
    // SELECT * FROM user WHERE name = ?
    // name을 찾아서 해당 user가 존재하면 user 객체 생성
    // 해당 user가 없으면 null 반환
    User user = userRepository.findByName(name);</p>
<pre><code>if (user == null)
    throw new IllegalArgumentException();

userRepository.delete(user);</code></pre><p>}</p>
<pre><code>- `userRepository.delete()` : 주어지는 데이터를 DB에서 제거한다. (`DELETE SQL`)

### By 앞에 들어갈 수 있는 구절
- `find` : 1건을 가져온다.
반환 타입 - 객체 or `Optional&lt;T&gt;`
예시) `User user = findByName(String name);`
→ `Name`에 해당하는 user 객체를 찾는다.

- `findAll` : 쿼리의 결과물이 N개인 경우에 사용한다. 
반환 타입 - `List&lt;T&gt;`
예시) `List&lt;User&gt; userList = userRepository.findAll();`
→ user 테이블의 모든 정보를 반환하여 리스트에 저장
- `exists` : 쿼리 결과가 존재하는지 확인한다. 
반환 타입 - `boolean` 
예시) `existsByAge()` 
→ Age에 해당하는 회원의 존재 여부를 반환한다.
- `count` : SQL의 결과 개수를 센다. 
반환 타입 - `long`
예시) `countByAge(Integer age)`
→ 해당 Age를 가진 user의 수를 반환한다.

### By 뒤에 들어갈 수 있는 기능
1. `AND` or `OR`로 조합할 수 있다.
`List&lt;User&gt; userList = findAllByNameAndAge(String name, Integer age);`
↪ `SELECT * FROM user WHERE user_name= ? AND age = ?`

2. `GreaterThan` : 초과
`GreaterThanEqual` : 이상
`LessThan` : 미만
`LessThanEqual` : 이하

3. `Between` : 사이에 
예시) `List&lt;User userList = findAllByBetween(int startAge, int endAge);`
↪ `SELECT * FROM user WHERE user_age BETWEEN ? AND ?;`


4. `StartWith` : ~로 시작하는
`EndsWith` : ~로 끝나는

&lt;br&gt;&lt;hr&gt;

## 27강 트랜잭션 이론편
### 트랜잭션
- 쪼갤 수 없는 업무의 최소 단위
- 모든 SQL을 성공시키거나, 하나라도 실패하면 모두 실패시킨다.

### 트랜잭션 명령어
- 트랜잭션 시작하기 : `START TRANSACTION;`
- 트랜잭션 정상 종료하기 : `COMMIT;`
- 트랜잭션 실패 처리하기 : `ROLLBACK;`

#### `START TRANSACTION;`
- 서로 다른 단말기에서는 SQL의 결과물이 보이지 않는다.
⇒ A 단말기에서 `INSERT`한 결과가 B 단말기에서 보이지 않는다.

#### `COMMMIT;`
- A 단말기에서 `COMMIT;`을 실행해야 B 단말기에서도 결과를 볼 수 있다.

#### `ROLLBACK;` 
- A 단말기에서 `ROLLBACK;`을 실행하면 A, B 단말기 모두 결과를 볼 수 없다.

&lt;br&gt;&lt;hr&gt;

## 28강 트랜잭션 적용과 영속성 컨텍스트
#### 우리가 구현할 것
1. 서비스 메서드가 시작할 때 트랜잭션이 시작된다. → `START TRANSACTION;`
2. 서비스 메서드 로직이 모두 정상적으로 성공하면 → `COMMIT;`
3. 서비스 메서드 로직 실행 도중 문제가 생기면 → `ROLLBACK;`

### `@Transactional` 
#### 동작 원리 
1. `@Transactional` 어노테이션의 아래에 있는 함수가 시작될 때 → `START TRANSACTION;`
2. 함수가 예외 없이 잘 끝났다면 → `COMMIT;`
3. 함수에 문제가 있다면 → `ROLLBACK;`</code></pre><p>// [INSERT]
@Transactional
public void insertUser(UserCreateRequest request)
{
    User user = userRepository.save(new User(request.getName(), request.getAge()));
}</p>
<p>// [SELECT]
@Transactional(readOnly = true)
public List<UserResponse> selectUsers()
{
    List<User> userList = userRepository.findAll();</p>
<pre><code>return userList.stream() // 현재 List는 stream 형태이므로 collect()를 통해 리스트로 변환한다.
    .map(user -&gt; new UserResponse(user.getId(), user.getName(), user.getAge()))
    .collect(Collectors.toList());</code></pre><p>}</p>
<pre><code>- `@Transactional(readOnly = true)` 
: `SELECT `쿼리만 사용한다면, `readOnly` 옵션을 사용할 수 있다.
↪ 데이터 변경을 위한 불필요한 기능이 빠지기 때문에 성능적 이점이 있다.


- `IOException`과 같은 `Checked Exception`은 예외가 발생해도 `ROLLBACK;`이 일어나지 않는다.

### 영속성 컨텍스트
- 테이블과 매핑된 `Entity` 객체를 관리/보관하는 역할
- 스프링에서는 트랜잭션을 사용하면 영속성 컨텍스트가 생겨나고,
트랜잭션이 종료되면 영속성 컨텍스트가 종료된다.

#### 영속성 컨텍스트의 기능
1. 변경 감지 (Dirty Check)
→ 영속성 컨텍스트 안에서 불러와진 `Entity`는 명시적으로 `save`하지 않더라도, **변경을 감지해** 자동으로 저장된다.

2. 쓰기 지연 
→ `INSERT`, `UPDATE`, `DELETE` 시, 객체를 세 개 저장할 때, DB와 세 번 통신하는 것이 아니라, 영속성 컨텍스트가 세 개의 객체를 기억해뒀다가 한 번에 DB에 저장하여 총 한 번 통신하도록 한다.

3. 1차 캐싱
→ ID를 기준으로 `Entity`를 기억한다. 
→ `SELECT` 시 동일한 데이터를 여러번 조회할 경우, 한 번 조회했던 데이터를 계속 사용할 수 있다.
→ 캐싱된 객체(영속성 컨텍스트가 잠깐 저장한 객체)는 **완전히 동일**하다. (주소까지)
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[HCI] 6주차 인터페이스]]></title>
            <link>https://velog.io/@jsuhyeon_/HCI-6%EC%A3%BC%EC%B0%A8-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@jsuhyeon_/HCI-6%EC%A3%BC%EC%B0%A8-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Mon, 14 Apr 2025 07:30:21 GMT</pubDate>
            <description><![CDATA[<h1 id="인터페이스-interface">인터페이스 (Interface)</h1>
<h3 id="다중-상속">다중 상속</h3>
<ul>
<li><em>** 하나의 클래스가 여러 개의 클래스로부터 상속 받는 것**</em></li>
<li>C#에서는 하나의 클래스가 동시에 두 개 이상의 클래스에서 상속 받을 수 없다. 
= 두 개 이상의 부모 클래스를 가질 수 없다.
→ 인터페이스를 통해 다중 상속할 수 있다.</li>
</ul>
<br>

<h2 id="📍-인터페이스">📍 인터페이스</h2>
<ul>
<li><strong>행동적인 특성만을 정의</strong> (메서드, 속성, 인덱서, 이벤트)</li>
<li>자식 클래스에 구현되어야 하는 기능을 선언할 시,  일반적으로 이질적인 클래스들이 공통으로 제공해야 할 메소드들을 선언할 때 사용한다.</li>
</ul>
<ul>
<li>다중 상속을 구현하기 위해 사용한다.</li>
<li><code>class</code> 키워드 대신 <code>interface</code> 키워드를 사용하여 선언한다.<pre><code>interface IPrintable
{
  void Print();
}
</code></pre></li>
</ul>
<p>class Document : IPrintable
{
    public void Print() { Console.WriteLine(&quot;Print document&quot;); }
}</p>
<p>class Photo : IPrintable
{
    public void Print() { Console.WriteLine(&quot;Print photo&quot;); }
}</p>
<pre><code>- 여기서 `Document` 클래스와 `Photo` 클래스가 서로 이질적인 관계이다. 

&lt;br&gt;

## 📍 클래스 &amp; 인터페이스 관계
- `class` &amp; `class` ⇒ 상속 관계
- `class` &amp; `interface` ⇒ 구현 관계
- `interface` &amp; `interface` ⇒ 상속 관계

### 🍀 클래스 &amp; 인터페이스 사용 예시 1</code></pre><p>interface IMyInterface: IBase1, IBase2
{
    void MethodA();
    void MethodB();
}</p>
<pre><code>- 여러 개의 인터페이스를 상속 받은 인터페이스는 상위 인터페이스의 메서드를 모두 구현해야 한다.
</code></pre><p>class ClassA: IFace1, IFace2
{
    // class members, implementing interface
} </p>
<p>class ClassB: BaseClass, IFace1, IFace2
{
    // class members, implementing interface
}</p>
<pre><code>- `ClassB` : 클래스와 인터페이스를 함께 상속 받는 경우, 기본 클래스가 처음에 위치한다.
`BaseClass` ⇒ 클래스
`IFace1` &amp; `IFace2` ⇒ 인터페이스

### 🍀 클래스 &amp; 인터페이스 사용 예시 2
</code></pre><p>interface IScalable 
{ 
    void ScaleX(float factor); 
    void ScaleY(float factor); 
} </p>
<p>public abstract class DrawObject 
{ 
    public DrawObject() {} 
    public abstract void Print(); 
} </p>
<p>public class TextObject: DrawObject, IScalable 
{ 
    private string text; </p>
<pre><code>public TextObject(string text) 
{ 
    this.text = text; 
}

public void ScaleX(float factor) 
{ 
    Console.WriteLine(&quot;ScaleX: {0} {1}&quot;, text, factor); 
} 

public void ScaleY(float factor) 
{ 
    Console.WriteLine(&quot;ScaleY: {0} {1}&quot;, text, factor); 
} 

public override void Print() 
{ 
    Console.WriteLine(“TextObject: {0}&quot;, text); 
} </code></pre><p>}</p>
<pre><code>- 클래스와 인터페이스를 동시에 상속 받을 땐 클래스가 더 앞에 온다.
- 추상 클래스(부모)는 `abstract` 메서드로 선언되고, 반드시 자식 클래스에서 `override` 메서드로 구현한다.
- 인터페이스는 키워드 없이 반드시 자식에서 구현해야 한다.

&lt;br&gt;

## 📍 IComparable 인터페이스
- **객체 간 비교**를 가능하게 해주는 인터페이스
- **정렬**을 하기 위해 사용된다.
- `ComparableTo` 메서드를 제공한다.

### `int CompareTo (Object obj)`
- `obj` 인수 : 인터페이스를 구현하는 클래스와 같은 형식
- 리턴 값은 현 객체가 obj보다 작으면 → 음수 / 같은면 → 0 / obj보다 크면 → 양수를 리턴한다.</code></pre><p>public clas Age: IComparable
{</p>
<pre><code>protected int m_value;

public Age(int value)
{
    m_value = value;
}

public int CompareTo(object obj)
{
    // 실제 int 비교
    if(obj is Age temp)
        return m_value.CompareTo(temp.m_value);

    throw new ArgumentException(&quot;Object is not Age&quot;);
}</code></pre><p>}</p>
<pre><code>
### 🍀 `Sort()` 활용 예시</code></pre><p>List<Age> list = new List<Age>
{
    new Age(25), new Age(20), new Age(30)
};</p>
<p>list.Sort(); </p>
<pre><code>- `list.Sort()`  : `CompareTo` 에 따라 정렬
- `List&lt;T&gt;.Sort()` 메서드는 내부에서 `CompareTo()`를 호출하여 정렬한다.


&lt;br&gt;

## 📍 IEquatable 인터페이스
- 객체가 다른 객체와 **같은지 비교**할 수 있도록 해주는 인터페이스 

### `bool Equals (Object obj)`
- `obj` 인수 : 인터페이스를 구현하는 클래스와 같은 형식
- 리턴값 &lt;span style=&quot;color:red&quot;&gt;==&lt;/span&gt; `obj` → `true` 를 리턴
리턴값 &lt;span style=&quot;color:red&quot;&gt;!=&lt;/span&gt; `obj` → `false` 를 리턴

### 🍀 IEquatable 인터페이스 예시
#### Person 클래스가 같은 사람인지 비교</code></pre><p>public class Person: IEquatable<Person>
{
    public string name;
    public int age;</p>
<pre><code>public bool Equals (Person p)
{
    if(p is Person)
        return name.Equals(p.name &amp;&amp; age.Equals(p.age);

    throw new ArgumentException(&quot;Object is not Person&quot;);
}</code></pre><p>}</p>
<pre><code>#### 사용 예시</code></pre><p>Person p1 = new Person { name = &quot;Suhyeon&quot;, age = 22 };
Person p2 = new Person { nae = &quot;Suhyeon&quot;, age = 22 } ;</p>
<p>Conole.WriteLine(p1.Equals(p2)); // -&gt; true</p>
<pre><code>

&lt;br&gt;

## 📍 IEnumerable 인터페이스
- **컬렉션을 반복**할 수 있도록 해주는 인터페이스
- 반복을 위한 &quot;열거자(IEnumerator)&quot;를 반환한다.
- `IEnumerator GetEnumerator()`
↪ `foreach` 루프가 동작하려면 이 메서드를 사용해야 한다.

### 🍀 IEnumerable foreach문 예시
</code></pre><p>foreach -&gt; IEnumerable.GetEnumerator() -&gt; IEnumerator 사용 (MoveNext + Current)</p>
<pre><code>
&lt;br&gt;

## 📍 IEnumerator 인터페이스
- **반복하면서 현재 위치를 추적**할 수 있도록 해주는 인터페이스 
- 아래와 같은 메서드로 구성되어 있다.
① `object Current` : 컬렉션의 현재 요소를 가져오는 속성
② `bool MoveNext()` : 컬렉션의 다음 요소로 이동하는 메서드
③ `void Reset()` : 컬렉션의 첫번째 요소 앞의 초기 위치로 설정하는 메서드

### 🍀 `IEnumerable interface` 예시</code></pre><p>public class Tokens: IEumerable
{
    private string[] elements;</p>
<pre><code>public Tokens(string source, char[] delimiters)
{
    elements = source.Split(delimiters);
}

// implementaion of GetEnumerator()
public IEnumerator GetEnumerator()
{
    return new TokenEnumerator(this);
}

// implementation of TokenEnumerator
private class TokenEnumerator 
private class TokenEnumerator: IEnumerator
{
    private int positioin = -1;
    private Tokens t;
    public TokenEnumerator(Tokens t)
    {
        this.t = t;
    }

    public void Reset()
    {
        position = -1;
    }

    public bool MoveNext()
    {
        if(position &lt; t.elements.Length-1)
        {
            position+++;
            return true;
        }
        else
            return false;
    }

    public object Current
    {
        get { return t.elements[position] };
    }        
}</code></pre><p>}</p>
<pre><code>
#### Token 테스트</code></pre><p>class EnumeratorTest
{
    pubilc static void Main()
    {
        Tokens f = new Tokens(&quot;This is a sample test.&quot;, new char[] {&#39; &#39;, &#39;-&#39;});
        foreach (string item in f)
        {
            Console.WriteLine(item);
        }
    }
}</p>
<pre><code>

&lt;br&gt;

### 💡 추상 클래스와 인터페이스의 공통점과 차이점
- **모두 추상의 의미이다.**
- `new` 키워드를 사용하여 객체를 생성하는 것이 불가능하다.
- 자식 클래스에서 모든 메서드를 구현하였을 경우에만 기능을 발휘할 수 있다.
- 클래스와 메서드가 `abstract`로 선언되어 있다면 **인터페이스로 변환할 수 있다**.

### 🍀 추상 클래스 → 인터페이스 변환 예시
#### 추상 클래스</code></pre><p>abstract public class StarPlayer
{
    public abstract void GoodPlay();
    public abstract void Handsome();
}</p>
<pre><code>
#### 인터페이스</code></pre><p>interface IStarPlayer
{
    void GoodPlay();
    void Handsome();
}</p>
<pre><code>- 모든 추상 클래스가 인터페이스로 변환될 수 없다.
- 추상 메서드는 `override` 키워드를 사용한다.
- 추상 클래스는 다중 상속을 지원하지 않는다.

&lt;br&gt;

## 📍 is 연산자
- 데이터의 형 변환이 가능하면 true를 반환한다.
- **실패 시 결과** : `false`
- **사용 용도 ** : 타입의 확인을 위해 사용한다. **(확인 후 수동 변환)**</code></pre><p>Bird b;
if (a is Bird)
    b = (Bird)a; // 안전한 형 변환
else
    Console.WriteLine(&quot;Not a Bird.);</p>
<pre><code>
&lt;br&gt;

## 📍 as 연산자
- 객체 사이의 형 변환 연산자
- 오류 발생 시, exception 발생 없이 null을 반환한다.
- **실패 시 결과** : `null`
- 사용 용도 : 타입을 바꾸고 싶을 때 사용한다. **(안전하게 형 변환)**</code></pre><p>Bird b = a as Bird; // 형 변환
if (b == null)
    Console.WriteLine(&quot;Not a Bird.&quot;)</p>
<pre><code>
&lt;br&gt;

## 📍 Boxing
- 값형식 → 참조형식으로 바꾸는 것
- 암시적 변환</code></pre><p>int a = 127;
object o1 = a;
object o2 = o1;</p>
<pre><code>
&lt;br&gt;

## 📍 UnBoxing
- 참조형식 → 값형식으로 바꾸는 것 
- 명시적 변환
- `int b = (int)o2;`

&lt;br&gt;

## 📍 메서드 오버로딩
- 한 클래스 내에서 두 개 이상의 이름이 같은 메서드를 작성한다.
✔ 메서드 이름이 동일해야 한다.
✔ 매개 변수의 개수가 서로 다르거나, 타입이 서로 달라야 한다.
✔ 리턴 타입은 오버로딩과 관련 없다.
</code></pre><p>class Method overloading</p>
<pre><code>

&lt;br&gt;

## 📍 메서드 오버라이딩 
- 부모 클래스의 메서드를 자식 클래스에서 재정의한다.
- 동적 바인딩이 발생한다.
✔ 자식 클래스에서 오버라이딩된 메서드가 무조건 실행되도록 동적 바인딩 된다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[논문] 간단한 온라인 및 실시간 추적 (SORT)]]></title>
            <link>https://velog.io/@jsuhyeon_/%EB%85%BC%EB%AC%B8-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%98%A8%EB%9D%BC%EC%9D%B8-%EB%B0%8F-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B6%94%EC%A0%81-SORT</link>
            <guid>https://velog.io/@jsuhyeon_/%EB%85%BC%EB%AC%B8-%EA%B0%84%EB%8B%A8%ED%95%9C-%EC%98%A8%EB%9D%BC%EC%9D%B8-%EB%B0%8F-%EC%8B%A4%EC%8B%9C%EA%B0%84-%EC%B6%94%EC%A0%81-SORT</guid>
            <pubDate>Mon, 14 Apr 2025 07:29:42 GMT</pubDate>
            <description><![CDATA[<h2 id="초록">초록</h2>
<ul>
<li><strong>SORT</strong> : 단순 온라인 및 실시간 객체 추적 알고리즘</li>
</ul>
<h4 id="연구-목적">연구 목적</h4>
<ul>
<li>SORT 성능을 개선하고, 객체의 <strong>외형 정보</strong>를 통합한다.</li>
<li>객체가 가려진 상태에서 <strong>더 긴 시간동안 추적</strong>을 유지할 수 있다.</li>
<li>식별 오류의 수를 효과적으로 줄인다.</li>
<li>외형 정보를 학습하는 과정을 오프라인 단계로 분리하여, 실시간 어플에서 가볍게 작동하도록 설계한다.</li>
</ul>
<h4 id="연구-결과">연구 결과</h4>
<ul>
<li>식별 오류 45% 감소, 높은 프레임 속도에서 전반적으로 경쟁력 있는 성능을 달성하였다.</li>
</ul>
<h2 id="1-서론">1. 서론</h2>
<h3 id="사용-기술">사용 기술</h3>
<h4 id="1-다중-객체-추적-mot">1) <strong>다중 객체 추적</strong> (MOT)</h4>
<ul>
<li>Multiple Object Tracking</li>
<li>주어진 프레임 시퀀스에서 객체를 탐지하고, 각 객체의 궤적을 생성한다. (자율 주행, 영상 감시, 스포츠 분석 등)</li>
</ul>
<ul>
<li><p>** 주기능 : 추적-측정 간의 연관 **
:  이전 프레임에서 추적된 객체들 &amp; 현재 프레임에서 새롭게 탐지된 객체들 간의 관계를 설정한다.</p>
</li>
<li><p>** MOT의 문제점 **
: 센서 잡음, 객체 간의 차단, 배경 혼란 등 다양한 문제점으로 인해 복잡해질 수 있다.</p>
</li>
</ul>
<h4 id="2-단순-온라인-및-실시간-추적-sort">2) <strong>단순 온라인 및 실시간 추적</strong> (SORT)</h4>
<ul>
<li>Simplt Online and Real-time Tracking</li>
<li>칼만 필터(Kalman Filter) : 단일 객체의 상태를 예측한다.</li>
<li>헝가리 알고리즘(Hungarian Algorithm) : 흐리게 처리된 측정값과 연관 짓는다.</li>
</ul>
<ul>
<li>** SORT의 한계 **
⑴ 객체 간의 외형 특징을 무시한다. 
⑵ 객체 차단이 발생하면 추적 기능이 급격히 저하된다.
⑶ 식별 오류가 자주 발생한다.</li>
</ul>
<h4 id="3-연구-목적">3) 연구 목적</h4>
<ul>
<li>SORT의 한계를 보완한다.</li>
<li>외형 정보를 통합하여 식별 오류를 줄인다.</li>
<li>장기 추적 능력을 향상시킨다.</li>
<li>외형 정보를 학습하는 오프라인 단계와 실시간 추적에 활용하는 방식을 통해, SORT의 간단함과 효율성을 유지한다.</li>
<li>식별 오류를 45% 줄이고, 높은 프레임 속도를 유지하도록 하였다.
<img src="https://velog.velcdn.com/images/jsuhyeon_/post/05c295c2-9c9f-40f1-a9e7-8fa58d51a43e/image.png" alt="">
MOT 데이터셋에서 흔히 발생하는 <strong>빈번한 차단</strong> 상황에서 일반적인 추적 상황을 나타낸다.</li>
</ul>
<h2 id="2-심층-연관-지표를-활용한-sort">2. 심층 연관 지표를 활용한 SORT</h2>
<ul>
<li>단일 가설 추적 방식을 채택하였다. </li>
<li>한 번에 하나의 가설(하나의 추적 궤적)만 고려하는 방식이다.</li>
</ul>
<h3 id="21-트랙-처리-및-상태-추정">2.1 트랙 처리 및 상태 추정</h3>
<ul>
<li>단일 가설 추적 방식 : 객체의 궤적을 처리하고 상태를 추정한다.</li>
<li>칼만 필터 : <strong>각 객체의 상태</strong>를 재귀적으로 예측하고 갱신한다.</li>
<li>객체의 상태 : 위치, 속도, 크기</li>
</ul>
<ol>
<li>위치 : 객체의 현재 중심 위치</li>
<li>속도 : 객체의 움직임 속도</li>
<li>객체의 폭(width)과 높이(height)</li>
</ol>
<ul>
<li>객체의 상태는 프레임 간 변화를 설명하기 위해 설계된 동적 모델을 사용하여 업데이트 된다.</li>
<li>선형 모델을 기반으로 한다.</li>
<li>센서 잡음 및 예측 오차를 처리하기 위해 사용된다. </li>
</ul>
<h4 id="트랙-생성-및-삭제">트랙 생성 및 삭제</h4>
<ul>
<li><strong>트랙 생성</strong> : 새롭게 감지된 객체는 기존의 추적 궤적과 연결되지 않을 경우 새로운 궤적으로 초기화 된다.</li>
<li><strong>트랙 삭제</strong> : 여러 프레임에 걸쳐 연속적으로 탐지가 이루어지지 않으면, 해당 트랙은 삭제된다.</li>
</ul>
<h3 id="22-할당-문제">2.2 할당 문제</h3>
<h4 id="추적-측정-간의-연관성">추적-측정 간의 연관성</h4>
<ul>
<li><strong>할당 문제</strong> : 각 프레임에서 새롭게 탐지된 객체와 이전 프레임에서 추적된 객체를 연결하는 작업</li>
<li><strong>비용 행렬(cost matrix)</strong> : 추적기와 탐지기 사이의 연관성을 나타낸다.
↪ 비용 행렬을 계산하여 추적-측정 간의 연관성을 해결한다.</li>
</ul>
<h4 id="비용-행렬-구성">비용 행렬 구성</h4>
<ol>
<li><p>위치 정보
: 칼만 필터를 사용하여 추적기의 예상 위치와 현재 프레임에서 탐지된 객체 간의 거리를 계산한다.</p>
</li>
<li><p>외형 정보
: 추적기와 탐지기의 외형 특징 간의 유사성을 측정하여, 시각적 유사성을 고려한다.</p>
</li>
</ol>
<h4 id="위치-정보의-통합">위치 정보의 통합</h4>
<ul>
<li>탐지값이 평균 트랙 위치에서 얼</li>
</ul>
<h4 id="헝가리-알고리즘">헝가리 알고리즘</h4>
<ul>
<li>헝가리 알고리즘(Hungarian Algorithm) : 프레임 간 객체를 매칭한다.</li>
<li>비용 행렬이 계산되면, 추적기와 탐지기 간의 최적 매칭을 수행한다.</li>
</ul>
<h4 id="sort-개선-방법">SORT 개선 방법</h4>
<ul>
<li>외형 정보를 SORT에 통합하는 방식을 제안한다.</li>
<li>객체 탐지기의 출력 결과를 활용하여 외형 특징을 계산하여, 데이터 연관 문제를 더 정확히 해결한다.</li>
</ul>
<h3 id="23-매칭-캐스케이드">2.3 매칭 캐스케이드</h3>
<ul>
<li>오래된 트랙(older tracks)에 대해 새로운 탐지값과 매칭될 기회를 제공한다.</li>
<li>차단이 빈번한 상황에서 객체 추적의 지속성을 보장한다.</li>
</ul>
<h4 id="매칭-캐스케이드의-원리">매칭 캐스케이드의 원리</h4>
<p>1) 새로운 트랙 우선 처리</p>
<ul>
<li>최근 프레임에서 활성화된 &#39;젊은 트랙(young tracks)&#39;이 새로운 탐지값과 먼저 매칭된다.</li>
<li>최근 데이터가 더 신뢰할 수 있음을 가정한다.</li>
</ul>
<p>2) 오래된 트랙 처리 </p>
<ul>
<li>젊은 트랙과 매칭되지 않은 탐지값은 이후 &#39;오래된 트랙&#39;과 매칭된다. </li>
<li>오래된 트랙은 차단 또는 가림으로 인해 일정 기간 동안 탐지값과 매칭되지 않았을 가능성이 있다.</li>
</ul>
<p>↪ 오래된 트랙에 대한 새로운 탐지값과 매칭될 기회를 제공하여, 차단이 빈번한 상황에서 객체 추적의 지속성을 보장한다.
↪ 연관의 우선순위를 합리적으로 배분할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jsuhyeon_/post/7c0438f0-bbc8-452b-901f-52885ee27185/image.png" alt="">
CNN 아키텍처. 배치 정규화와 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[YOLO 논문 정리]]></title>
            <link>https://velog.io/@jsuhyeon_/YOLO-%EB%85%BC%EB%AC%B8-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@jsuhyeon_/YOLO-%EB%85%BC%EB%AC%B8-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 14 Apr 2025 07:29:22 GMT</pubDate>
            <description><![CDATA[<p>DPM : 이미지 전체를 전역적으로 분석한다. 슬라이딩 윈도우 접근 방식을 사용한다.</p>
<h2 id="1-introduction">1. Introduction</h2>
<ul>
<li>YOLO는 객체 탐지를 단일 회귀 문제로 재정의한 혁신적인 시스템이다. </li>
<li>이미지를 한 번만 보고 (You Only Look Once) 객체의 종류와 위치를 &#39;동시에&#39; 예측한다.</li>
<li>기존의 복잡한 탐지 파이프 라인과 달리 단일 신경망으로 처리된다.<ul>
<li>파이프 라인
⑴ 각 단계가 서로 독립적이고, 개별적으로 학습된다. 
⑵ 지역 제안, CNN 실행, 분류 등 여러 단계를 거치므로 연산량이 많고 속도가 느리다.
⑶ 각 단계가 따로따로 동작하기 때문에 전체 시스템을 통합적으로 최적화하기 어렵다.</li>
<li>단일 신경망
⑴ YOLO의 입력 이미지를 처리해 경계 상자와 클래스 확률을 한 번에 예측한다.
⑵ 이미지 → 단일 신경망 → 최종 출력 (객체 탐지 결과)
⑶ 지역 제안이나 분리된 특징 추출 단계가 없어서 기존 파이프라인 방식보다 훨씬 빠르다. 
⑷ 하나의 신경망으로 모든 작업을 처리하므로 실시간 처리가 가능하다.
⑸ 전체 모델을 통합적으로 학습할 수 있다.</li>
</ul>
</li>
</ul>
<h4 id="yolo의-장점">YOLO의 장점</h4>
<ol>
<li>속도가 빠르다. </li>
<li>실시간으로 비디오를 처리할 수 있다.</li>
<li>다른 실시간 탐지 시스템보다 두 배 이상의 평균 정밀도를 달성한다.</li>
<li>슬라이딩 윈도우, 지역 제안 기반 방식과 달리 훈련 및 테스트 시 이미지 전체를 분석한다.</li>
<li>DPM이나 R-CNN보다 새로운 데이터나 예상치 못한 입력에서 더 안정적으로 동작한다.</li>
</ol>
<h4 id="yolo의-단점">YOLO의 단점</h4>
<ol>
<li>정확도 면에서 뒤쳐진다.</li>
<li>작은 객체의 정밀한 위치(localization) 탐지에서 어려움이 있다.</li>
</ol>
<h4 id="객체-탐지란">객체 탐지란?</h4>
<ul>
<li>Localiszation(바운딩 박스 처리) + Classification(이미지 분류)</li>
<li>이미지로부터 특정 객체를 찾아주는 기술</li>
<li>이미지, 영상으로부터 객체를 찾아서 바운딩 박스를 표시해준다.</li>
</ul>
<p><br><hr></p>
<h2 id="2-unified-detection">2. Unified Detection</h2>
<ul>
<li>개별적 구성 요소들을 하나의 신경망으로 통합했다.</li>
<li>전체 이미지의 특징을 사용하여 각 바운딩 박스를 예측한다.</li>
<li>이미지에 대한 모든 클래스의 모든 바운딩 박스를 동시에 예측한다.</li>
<li>이미지 전체와 이미지 내의 모든 객체에 대해 전역적으로 분석한다.</li>
<li>탐지 성능을 유지하면서 실시간 속도와 최적의 평균 정밀도를 가능하게 한다.</li>
</ul>
<h4 id="그리드-구조">그리드 구조</h4>
<p><img src="https://velog.velcdn.com/images/jsuhyeon_/post/96170dad-9d27-41bb-81ae-d55f6930f012/image.png" alt=""></p>
<ol>
<li>이미지를 S x S 그리드로 나눈다. (1 x 1 비율)</li>
<li>그리드 셀을 기준으로 바운딩 박스를 예측하고, 해당 바운딩 박스에 객체가 잇을 확률도 예측한다.</li>
<li>그리드 셀에 객체가 있을 경우에 클래스 별 확률도 예측한다.</li>
<li>두 과정을 조합하여 최종 결과를 만들어 낸다.</li>
</ol>
<h4 id="신뢰도-점수-confidence-score">신뢰도 점수 (Confidence Score)</h4>
<ul>
<li><code>Confidence Score</code> = <code>Pr</code> * <code>IoU</code> 
<code>Pr</code> : 객체가 해당 상자에 존재할 확률
<code>IoU</code>(Truth Pred) : 실제 객체와 예측 객체의 교집합과 합집합을 통해 예측이 얼마나 정확한 지를 나타내는 값 (수치가 1에 가까울수록 좋다)<blockquote>
<ul>
<li>(바운딩 박스 안에 객체가 확실히 없으면) <code>0</code> * <code>IoU</code> = <code>0</code></li>
<li>(바운딩 박스 안에 객체가 확실히 있으면) <code>1</code> * <code>IoU</code> = <code>IoU</code></li>
</ul>
</blockquote>
</li>
<li>바운딩 박스 안에 객체가 있다면, 신뢰도 점수는 예측된 실제 상자 간의 <code>IoU</code>와 동일해야 한다.</li>
</ul>
<h4 id="바운딩-박스-예측">바운딩 박스 예측</h4>
<ol>
<li><code>x</code>, <code>y</code> : 상자의 중심 좌표 (그리드 셀 기준 상대 좌표)</li>
<li><code>w</code>, <code>h</code> : 상자의 너비와 높이 (이미지 전체 기준 상대 크기)</li>
<li>Confidence : 예측된 상자와 실제 상자 간의 IoU</li>
</ol>
<h4 id="클래스-확률-예측">클래스 확률 예측</h4>
<ul>
<li>각 그리드 셀은 조건부 클래스 확률을 예측한다.</li>
<li>하나의 그리드 셀은 하나의 클래스 확률 세트만 예측하며, 예측된 상자 B의 개수와는 관계가 없다.</li>
</ul>
<h4 id="최종-클래스별-신뢰도-점수-계산">최종 클래스별 신뢰도 점수 계산</h4>
<p><img src="https://velog.velcdn.com/images/jsuhyeon_/post/89e4ff12-8fce-40e3-b43a-f29e5f0805b2/image.png" alt=""></p>
<ul>
<li>조건부 클래스 확률로 각 상자의 신뢰도 점수를 곱하여 계산한다.</li>
</ul>
<h4 id="출력-텐서의-구조">출력 텐서의 구조</h4>
<ul>
<li>입력 이미지를 S x S 그리드로 나누고, 각 그리드 셀에서 정보를 예측한다.</li>
<li><code>B</code> : 경계 상자의 수</li>
<li><code>5</code> : 각 상자의 좌표와 신뢰도 (<code>x</code>, <code>y</code>, <code>w</code>, <code>h</code>, <code>confidence</code>)</li>
<li><code>C</code> : 클래스 확률 (데이터셋의 클래스 개수)
=&gt; <code>S x S ( B x 5 + C)</code></li>
</ul>
<h3 id="21-network-design">2.1 Network Design</h3>
<p><img src="https://velog.velcdn.com/images/jsuhyeon_/post/c562888b-73a3-4bee-a25c-773db01620ca/image.png" alt=""></p>
<ol>
<li><code>448 * 448</code> 크기의 이미지를 입력받는다.</li>
<li><code>7 * 7</code> 필터를 통해 이미지의 간단한 특징을 추출한다.</li>
<li>스트라이드 값을 통해 이미지의 크기를 줄여나간다.</li>
<li>2, 3번 과정을 반복하며 최종 결과에서 입력받은 이미지의 객체(클래스)를 결정한다.</li>
</ol>
<h4 id="일단-내가-이해한거">일단 내가 이해한거</h4>
<ul>
<li><strong>합성곱 계층</strong> : 이미지에서 중요한 저수준 특징(가장자리, 간단한 패턴)을 추출한다.</li>
<li><strong>필터</strong> : 입력 이미지에서 특징 패턴을 감지하는 역할을 하는 작은 행렬</li>
<li><strong>스트라이드</strong> : 합송곱 계층에서 필터가 입력 데이터를 가로지르며 이동하는 간격 (입력 데이터에서 샘플링의 밀도와 출력 크기에 영향을 미친다.)<ul>
<li>작은 스트라이드 : 더 세밀한 특징을 유지한다.</li>
<li>큰 스트라이드 : 크기를 빠르게 줄이고, 연산량을 감소시킨다.</li>
</ul>
</li>
<li>만약 스트라이드가 2라면, 이미지의 크기는 <code>⁒2</code>만큼 줄어든다. (<code>112 * 112</code> → <code>56 * 56</code> ..)</li>
</ul>
<ol>
<li>입력 이미지<pre><code>1 2 0 1 2
4 5 1 3 1
1 0 2 4 1
3 1 0 1 2
1 1 3 0 2</code></pre></li>
<li>필터링 (3 * 3)
```</li>
</ol>
<p>-1 0 1
-1 0 1
-1 0 1</p>
<pre><code>
3. 계산 결과 (특징 맵)</code></pre><p>-3 1 0
-5 2 0
3 -2 4</p>
<pre><code>1. 이런식으로 필터 크기와 스트라이드를 조절함으로써 이미지의 크기를 줄여나간다.
2. 필터링하면서 이미지의 크기를 줄여나가지만, 채널의 개수(직육면체의 가로)가 늘어나면서 더 많은 패턴을 학습한다.
3. 초기 계층에서는 간단한 모양을, 중간 계층에서는 구조적 특징을 감지한다. 최종 계층에서는 감지한 특징들을 통해 최종 클래스를 결정하게 된다.
4. 초기 계층과 중간 계층의 과정은 동일하나, 각 계층에서 감지하는 특징에 차이가 있다. (위 설명과 동일)
5. 위 이미지에서는 최종적으로 `7 * 7 * 30` 텐서를 출력한다.

### 2.2 학습 (Training)
- 합성곱 계층을 1000개의 클래스 데이터셋에서 미리 학습시켰다.
- 미리 학습시킨 모델에 **합성곱 계층**(convolutional layer)과 `average pooling layer`, `fully connected layer`을 사용하였다.
- 약 1주일 간 학습한 결과, 단일 크롭 기준 상위 5개의 정확도가 88%에 도달했으며, 이는 GoogleNet 모델과 비슷한 수준이다.
- 그 후, 이 모델을 탐지 작업에 맞게 수정했다. 사전 학습된 네트워크에 합성곱 계층과 완전 연결 계층을 추가하면 성능이 향상된다고 주장하였다. 



#### Loss Function

![](https://velog.velcdn.com/images/jsuhyeon_/post/a425cd46-04d7-4286-82f2-365eeddfbd32/image.png)

[ 1 ] : `i`번째 그리드 셀의 `j`번째 바운딩 박스가 객체 하나를 책임지고 있는지에 대한 여부
[ 2 ] : Localization의 비중을 늘리기 위한 값 
[ 3 ] : 바운딩 박스의 크기에 따른 오차 차별을 없애기 위해 루트(√)를 씌운다.

- `λcoord` : 위치 손실의 중요도를 강화하기 위해 사용됨
- `λnoobj` : 객체가 없는 배경 셀의 손실을 줄이기 위해 사용됨


`i` : 그리그 셀 (7 x 7의 경우 49개의 그리드 셀이 된다.)
`j` : 바운딩 박스 
(그리드 셀마다 갖고 있는 바운딩 박스의 수??)
지시 함수 : `i`번째 그리드 셀에 `j`번째 바운딩 박스가 객체 하나를 책임지고 있는지에 대한 여부이다.
-&gt; 책임을 지고 있다면 1, 아니면 0

- YOLO에서는 여러 개의 그리드 셀, 여러 개의 바운딩 박스에 속하는 것이 아니라, 한 개의 그리드 셀, 한 개의 바운딩 박스에 속하게 된다. ⇒ &quot;그리드 셀을 책임진다&quot; 
- 바운딩 박스의 중심점이 위치한 그리드 셀만이 강아지 객체를 갖게 된다. → 나머지 그리드 셀은 객체가 없는 그리드 셀이 된다.
- 객체가 있는 바운딩 박스의 `x` 좌표와, `y` 좌표, 높이와 너비를 학습시킨다.
- 너비와 높이의 루트(√) : 바운딩 박스의 크기에 따라 로스율 차별을 없애기 위함 
예시) 실제 10, 예측 5 → 차이 5 / 실제 100, 예측 50 → 차이 50 ⇒ 바운딩 박스가 크면 객체의 크기가 크면 더 큰 오류로 인식한다. 그렇기 때문에 루트(√)를 씌움으로써 차이를 줄인다.

![](https://velog.velcdn.com/images/jsuhyeon_/post/f7f4717c-d78c-4f24-b883-4fc623a33d79/image.png)

- `x`, `y`, `w`, `h` ⇒ Localization 값 
- Localization 값에 비중을 주기 위해서 &#39;이 값&#39;을 준다고 ...?
- 논문의 경우 Localization 값에 5를 주었다. ⇒ 로스율이 커진다.
- 로스율이 커지면 비중이 커진다.

- confidence score는 객체가 없으면 0이라는 답을 줘야 한다.
그렇기 때문에 객체가 없는 경우도 학습을 해야 한다.
보통은 이미지에서 객체가 없는 그리드 셀이 훨씬 많기 때문에 학습 비중을 맞춰주기 위해 비중을 줄여준다. -&gt; 1보다 작은 값을 줘야 함 (논문에서는 0.5)

![](https://velog.velcdn.com/images/jsuhyeon_/post/f68dda73-0173-4ecf-a4ff-4ed75a404de5/image.png)
- 각 그리드 셀 별로 객체가 있을 경우에 클래스의 확률 
- 그리드 셀 별 값이니까 지시 함수가 그리드 셀까지만 있다 ..?
- 객체가 없으면 0이 된다 -&gt; 객체가 있는 경우에만 클래스 별로 전부 예측을 한다.

![](https://velog.velcdn.com/images/jsuhyeon_/post/6f44255b-467d-4943-84db-07fc45f6d107/image.png)

- 이미지가 입력되면 7 x 7 x 30의 텐서가 만들어진다.
- 49개의 그리드 셀
- 셀마다 30개의 값
- 5개의 바운딩 박스 두 개
- 객체가 존재할 경우 클래스별 확률 (20개의 클래스) 

객체가 존재할 경우에
클래스 별 confidence score 값 
그리드 셀마다 바운딩 박스가 두 개씩 존재한다. 
객체가 존재할 경우 클래스 별 확률은 20개
뭘 어떻게 다 곱하면 총 40개가 만들어짐

그리드가 49개이므로, 바운딩 박스는 그 두 배의 값인 98개가 된다.
클래스 별로 객체 탐지 과정을 수행한다.
예시 : 강아지 클래스 - 5번
클래스 별 confidence score가 특정 값 이하인 경우를 다 제거한다.
임계값이 2라고 했을 때, confidence score가 0.2 이하인 놈들은 다 제거된다. ⇒ 강아지 주변에만 바운딩 박스가 남는다.

#### NMS 알고리즘 
- 바운딩 박스들의 서로 IoU 값을 비교한다.
- IoU 값이 서로 0이면 같은 클래스지만, 다른 객체로 본다.
- 예시 : IoU 값이 0이면 같은 강아지 클래스지만, 서로 다른 강아지로 인식된다. 
- 만약 IoU 값이 0보다 크면, 바운딩 박스가 겹친다는 의미 
같은 객체가 아닌데도 겹친다 -&gt; 임계값을 지정해준다
임계값이 0.5면 절반만 겹쳐도 같은 객체로 본다고 ..?
겹치는 객체 중에 confidence score 값이 높은 객체만 남겨둔다.


### ~~2.3 Interference~~


### ~~2.4 Limitations of YOLO~~
1. 강한 공간적 제약 : 각 그리드 셀이 두 개의 경계 상자만 예측할 수 있고, 하나의 클래스만 가질 수 있다.
2. 새롭거나 독특한 구성을 다루기 어렵다.
3. 바운딩 박스 예측 시 사용되는 특징이 비교적 조잡하다.
4. 손실 함수의 한계 : 작은 바운딩 박스와 큰 바운딩 박스의 오류를 동일하게 취급한다.
5. 모델이 정확한 위치를 찾는 데 어려움이 있다.


## 3. ~~다른 감지 시스템과의 비교~~
1. DPM (Deformable Ports Model)
DPM은 정적 특징(이미지, 신호 등) 추출, classify region. 바운딩 박스 예측 등의 작업이 분리된 파이프라인을 가집니다.
하지만, YOLO는 feature extraction, 바운딩 박스 예측, non-maximal suppression, contextual reasoning 모두 동시에 가능하여 빠르고 정확합니다.
2. R-CNN
R-CNN에서 진행하는 파이프라인은 상당히 복잡합니다. DPM에서 사용한 슬라이딩 윈도우 대신에 region proposal 을 사용합니다.
세부적으로는, selective search 로 potential 바운딩 박스들을 찾고, convolutional network 가 특징을 추출하고, SVM이 박스들을 scoring 하고, linear model 이 바운딩 박스를 조정하고, non-maximal suppression 이 duplicate detection 을 제거합니다. 이러한 과정들은 각각 개별적으로 tuning 되어야 하고 학습이 느립니다.
하지만, YOLO는 이러한 individual components 를 하나의 모델로 optimize 할 수 있어서 더 빠릅니다.
3. Other Fast Detectors
Fast R-CNN, Faster R-CNN 은 sharing computation 과 selective search 대신 neural network 를 사용해 R-CNN 의 속도를 개선합니다. 하지만 그럼에도 real-time performance 부족했습니다. 같은 맥락으로 많은 연구가 DPM pipeline 을 speed-up 하는데 초점을 맞추었지만 30Hz 까지만 지원하는 등 부족한 점이 존재했습니다.
YOLO 는 그 방법론에 대한 디자인 자체가 빠르기 때문에 real-time performance 에서도 좋은 성능을 보입니다.
4. Deep MultiBox
R-CNN 과 달리 multibox 는 selective search 대신 convolutional neural network 를 사용했습니다. 하지만 single object detection 은 가능하지만 multiple object detection 을 구현하지는 못했습니다.
YOLO 는 classification probabilities 를 두어 multiple object detection 이 가능합니다.
5. OverFeat
OverFeat 은 앞서 등장했던 R-CNN 과 DPM 과 마찬가지로 disjoint 한 system 을 가지고 있는 문제를 포함해서 prediction 을 내릴 때 local information 만을 보아 prediction 에 global context 반영하기 어려웠습니다. 또한 일관된 결과를 내려면 significant 한 post-processing 이 필요한 점도 단점입니다.
YOLO 는 local 한 patch 에 대한 classifying 이 아닌 한 번의 모델 학습으로 이루어지기 때문에 global context 를 충분히 반영할 수 있습니다.
6. MultiGrasp
YOLO 의 grid approach 의 기원인 MultiGrasp 는 하나의 object 를 포함한 이미지에서 graspable region 을 예측해내는 비교적 간단한 작업에 사용됩니다.
YOLI 는 이런 MultiGrasp 에 기반하여 이미지 속 다중 라벨의 다중 물체의 bounding box 와 classification probabilities 를 찾아낸다는 점에서 더욱 복잡한 문제를 해결했습니다.






## 5. Real-Time Detection in the Wild
- YOLO는 빠르고 정확하게 물체를 감지한다.
- 웹캠을 사용하여 카메라에서 이미지를 가져오고, 표시하느 시간을 포함하여 실시간 성능을 유지하는지 확인할 수 있다.
- 결과적으로 YOLO는 상호작용적이고, 동시 이미지에 부착된 경우 개별적으로 이미지를 처리한다. 
- 웹캠은 추적 시스템처럼 작동하며, 물체가 이동하는 동안 물체를 감지하고 외관을 변화시킨다.
- 시스템 및 소스 코드 데모 : http://pjreddie.com/yolo/

## 6. Conclusion
- YOLO는 객체 감지를 위한 통합 모델이다.
- YOLO는 구성이 간단하고 학습이 가능하다.
- 전체 이미지에 직접 표시된다.
- 분류 기반 접근 방식과 달리, YOLO는 직접적으로 대응하는 손실 함수에 대해 훈련된다.
- 실시간 성능을 감지하고 전체 모델이 학습된다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 섹션4 Clean Code]]></title>
            <link>https://velog.io/@jsuhyeon_/Spring-Boot-%EC%84%B9%EC%85%984</link>
            <guid>https://velog.io/@jsuhyeon_/Spring-Boot-%EC%84%B9%EC%85%984</guid>
            <pubDate>Mon, 14 Apr 2025 07:26:13 GMT</pubDate>
            <description><![CDATA[<h2 id="17강-clean-code는-왜-중요한가">17강 Clean Code는 왜 중요한가</h2>
<h3 id=""></h3>
<ul>
<li>코드 : 요구사항을 표현하는 언어</li>
<li>개발자는 요구사항을 구현하기 위해 코드를 읽고 작성한다.</li>
<li>유지 보수, 가독성을 위해 Clean Code는 중요하다.</li>
</ul>
<h3 id="우리가-작성한-controller">우리가 작성한 Controller</h3>
<ol>
<li>API 진입 지점으로써 HTTP Body 객체로 변환한다.</li>
<li>현재 유저가 있는지 없는지 확인하고 예외 처리를 한다.</li>
<li>SQL을 사용해 실제 Database와의 통신을 담당한다.</li>
</ol>
<p><br><hr></p>
<h2 id="18강-controller-3단-분리하기">18강 Controller 3단 분리하기</h2>
<h3 id="--service와-repository">- Service와 Repository</h3>
<h3 id="controller-함수-1개가-하고-있던-역할">Controller 함수 1개가 하고 있던 역할</h3>
<ol>
<li>API 진입 지점으로써 HTTP Body 객체로 변환한다.
→ <strong>Controller</strong>에 유지 (API, HTTP 담당)</li>
<li>현재 유저가 있는지 없는지 확인하고 예외 처리를 한다.
→ <strong>Service</strong>의 역할 (분기 처리, 로직 담당)</li>
<li>SQL을 사용해 실제 Database와의 통신을 담당한다.
→ <strong>Repository</strong>의 역할 (DB와의 접근 담당)</li>
</ol>
<p>위 세 개의 역할을 각각 나눌 것이다.</p>
<h3 id="layered-architecture">Layered Architecture</h3>
<p>Repository → Service → Controller</p>
<p><code>jdbcTemplate.update()</code> : 데이터를 바꾸는 쿼리 (<code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>)
<code>jdbcTemplate.query()</code> : 데이터를 읽는 쿼리 (<code>SELECT</code>)</p>
<p><br><hr></p>
<h2 id="19강-usercontroller와-스프링-컨테이너">19강 <code>UserController</code>와 스프링 컨테이너</h2>
<h3 id="usercontroller의-의아한-점"><code>UserController</code>의 의아한 점</h3>
<ol>
<li><code>static</code> 이 아닌 코드를 사용하려면 <strong>인스턴스화</strong>가 필요하다.
→ 어디서 UserController를 <strong>인스턴스화</strong>하고 있는 것인가 (<code>new UserController();</code></li>
<li>UserController는 JdbcTemplate이 필요하다.
→ UserController는 JdbcTemplate에 <strong>의존</strong>한다.
⇒ UserController는 JdbcTemplate이 없으면 동작하지 않는다!</li>
</ol>
<h3 id="①-usercontroller-자동-인스턴스화">① <code>UserController</code> 자동 인스턴스화</h3>
<ul>
<li><code>@RestController</code> 
↪ API의 진입 지점을 생성함
↪ 💡 <code>UserController</code> 클래스를 <strong>스프링 빈</strong>으로 등록시킨다. 💡</li>
</ul>
<h4 id="스프링-빈">스프링 빈</h4>
<ul>
<li>서버가 시작되면, 스프링 서버 내부에 <strong>(스프링) 컨테이너</strong>가 생긴다.</li>
<li>컨테이너 안에는 <strong>클래스</strong>가 들어가게 된다.</li>
<li><strong>다양한 정보</strong>(이름 - <code>userController</code>, 타입 - <code>UserController</code>)와 함께 <strong>인스턴스화</strong>(<code>UserController userController = new UserController();</code>)도 이루어진다.</li>
</ul>
<ul>
<li><code>UserController</code>를 인스턴스화 하려면 <code>JdbcTemplate</code>이 필요하다. </li>
<li>이 또한 <strong>스프링 빈</strong>으로 등록되어 있다.</li>
<li>Spring Initializr 에서 프로젝트 생성 전에 의존성(Dependence) 란에서 등록함
<code>ctrl + shift + N</code> →<code>build.gradle</code> → <code>dependencies</code>에서 확인 가능</li>
</ul>
<h4 id="스프링-컨테이너">스프링 컨테이너</h4>
<ul>
<li>서로 필요한 관계에 있는 스프링 빈끼리 연결해주는 역할
⇒ 필요한 클래스끼리 연결해준다.</li>
</ul>
<h3 id="정리">정리</h3>
<ol>
<li>서버가 시작되면</li>
<li>스프링 컨테이너가 생성된다.</li>
<li>스프링 컨테이너 안에는 많은 스프링 빈(클래스)들이 등록된다.
ex) <code>JdbcTemplate</code>, <code>DataSource</code>, <code>Environment</code> 등 ...
<code>UserController</code> 우리가 설정해준 스프링 빈도 등록된다.</li>
<li>이때 필요한 <strong>의존성</strong>이 자동으로 설정된다.</li>
</ol>
<h3 id="②-userrepository는-jdbctemplate을-가져오지-못하는-이유">② <code>UserRepository</code>는 <code>JdbcTemplate</code>을 가져오지 못하는 이유</h3>
<blockquote>
<p>💡 <code>JdbcTemplate</code>을 가져오려면 <code>UserRepository</code>가 스프링 빈이어야 하는데 <code>UserRepository</code>는 스프링이 아니다.</p>
</blockquote>
<p>↪ <code>UserRepository</code>를 스프링 빈으로 등록하면 <code>JdbcTemplate</code>을 가져올 수 있다.</p>
<h3 id="코드-작성-예시">코드 작성 예시</h3>
<h4 id="수정-전">수정 전</h4>
<p><code>UserController</code></p>
<pre><code>@RestController
public class UserController {
    private final UserService userService = new UserService();

    public UserController(JdbcTemplate jdbcTemplate)
    {
        this.userService = new UserService(jdbcTemplate);
    }
}</code></pre><p><code>UserSerivce</code></p>
<pre><code>public class UserService {
    private final UserRepository userRepository;

    public UserService(JdbcTemplate jdbcTemplate)
    {
        userRepository = new UserRepository(jdbcTemplate);
    }
}</code></pre><p><code>UserRepository</code></p>
<pre><code>public class UserRepository {
    private final JdbcTemplate jdbcTemplate;

    public UserRepository(JdbcTemplate jdbcTemplate)
    {
        this.jdbcTemplate = jdbcTemplate;
    }
}</code></pre><h4 id="수정-후">수정 후</h4>
<p><code>UserController</code></p>
<pre><code>@RestController
public class UserController {
    private final UserService userService;

    public UserController(UserService userService)
    {
        this.userService = userService;
    }
}</code></pre><p><code>UserSerivce</code></p>
<pre><code>@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository)
    {
        this.userRepository = userRepository;
    }
}</code></pre><p><code>UserRepository</code></p>
<pre><code>@Repository
public class UserRepository {
    private final JdbcTemplate jdbcTemplate;

    public UserRepository(JdbcTemplate jdbcTemplate)
    {
        this.jdbcTemplate = jdbcTemplate;
    }
}</code></pre><ul>
<li><code>UserRepository</code>는 <code>JdbcTemplate</code>을 가져오고</li>
<li><code>UserService</code>는 <code>UserRepository</code>를 가져오고</li>
<li><code>UserController</code>는 <code>UserService</code>를 가져온다.</li>
</ul>
<p><br><hr></p>
<h2 id="20강-스프링-컨테이너를-왜-사용할까">20강 스프링 컨테이너를 왜 사용할까</h2>
<p><code>BookController</code> → <code>BookService</code> → <span style="color:red"><code>BookRepository</code></span> → <code>BookMemoryRepository</code> or <code>BookMySqlRepository</code></p>
<pre><code>@Repository
@Service
@RestController</code></pre><ol>
<li>위와 같은 어노테이션을 통해 스프링 컨테이너를 생성한다.</li>
<li>스프링 컨테이너를 통해 스프링 빈이 만들어진다. (필요한 클래스들끼리 연결)</li>
<li>스프링 컨테이너를 생성하면 자동으로 인스턴스화 해주기 때문에 <code>private final BookRepository bookRepository = new BookRepository();</code> 이런 과정을 거치지 않아도 된다.</li>
<li>또한 스프링 컨테이너를 사용하면 <strong><code>BookService</code>에서 <code>BookMemoryRepository</code>를 사용할지, <code>BookMySqlRepository</code>를 사용할지 선택한다.</strong></li>
</ol>
<p>위와 같은 방식을 <strong>제어의 역전</strong>이라고 한다. </p>
<h3 id="제어의-역전">제어의 역전</h3>
<blockquote>
<p>💡 스프링 컨테이너가 어떤 <code>Repository</code>를 사용할지 대신 결정해주는 방식을 <strong>제어의 역전(IoC, Inversion of Control)</strong>이라고 한다.
<br>
💡 컨테이너가 <code>Repository</code>를 선택해 <code>BookService</code>에 넣어주는 과정을 <strong>의존성 주입 (DI, Dependency Injection)</strong>이라고 한다.</p>
</blockquote>
<h4 id="의존성-주입">의존성 주입</h4>
<p><code>@Primary</code> </p>
<ul>
<li>어떤 <code>Repository</code>를 사용할지 결정하도록 <code>Repository</code>에 우선권을 부여한다.<pre><code>@Primary
@Repository
public class BookMemoryRepository implements BookRepository
{
</code></pre></li>
</ul>
<p>}</p>
<pre><code>
&lt;br&gt;&lt;hr&gt;

## 21강 스프링 컨테이너를 다루는 방법
### 스프링 빈을 등록하는 방법
`@Configuration`
- **클래스**에 붙이는 어노테이션
- `@Bean`을 사용할 때 함께 사용한다.

`@Bean`
- **메소드**에 붙이는 어노테이션
- 메소드에서 반환되는 객체를 **스프링 빈**에 등록한다.
</code></pre><p>@Configuration
public class UserConfiguration {</p>
<pre><code>@Bean
public UserRepository userRepository(JdbcTemplate jdbcTemplate)
{
    return new UserRepository(jdbcTemplate);    
}    </code></pre><p>}</p>
<pre><code>
#### `@Service`, `@Repository` 
- 개발자가 **직접 만든 클래스**를 스프링 빈으로 등록할 때 사용한다.

#### `@Configuration` + `@Bean` 
- 외부 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 

#### `@Component`
- 주어진 클래스를 &#39;컴포넌트&#39;로 간주한다.
- 이 클래스들은 스프링 서버가 뜰 때 자동으로 감지된다.
- 우리가 사용하는 어노테이션들을 자동 감지한다.
- 스프링 빈 등록 방법 중 하나이다.
1. `Controller`, `Service`, `Repository`가 모두 아니고
2. 개발자가 직접 작성한 클래스를 스프링 빈으로 등록할 때 사용되기도 한다.

### 스프링 빈 등록 방법
1. (가장 권장) 생성자를 이용해 주입 받는다.
→ `@Autowired` 생략 가능

2. `setter` &amp; `@Autowired` 사용
→ 오작동의 원인이 될 수 있음</code></pre><p>public class UserController
{
    private UserService userService;</p>
<pre><code>@Autowired
public void setUserService(UserService userService) 
{
    this.userService = userService;
}</code></pre><p>}</p>
<pre><code>
3. 필드에 직접 `@Autowired`를 적는다.
→ 테스트를 어렵게 만드는 요인</code></pre><p>@Autowired
private JdbcTemplate jdbcTemplate;</p>
<pre><code>
### `@Qualifier`
- 여러개의 후보 클래스들이 있을 때, 그 중 하나를 특정해서 가져오고 싶은 경우에 사용한다.
- 스프링 빈을 사용하는 쪽, 등록하는 쪽 모두 `@Qualifier` 를 사용할 수 있다.
- 방법 1 : 스프링 빈을 사용하는 쪽에서만 쓰면, 빈의 이름을 적어주어야 한다.
- 방법 2 : 양쪽 모두 사용하면, `@Qualifier` 끼리 연결된다.

#### 스프링 빈 등록 예시 1
`UserController` → `FruitService` → `AppleService` | `BananaService` | `OrnageService`</code></pre><p>private final FruitService fruitService;</p>
<p>public UserController(@Qualifier(&quot;appleService&quot;) FruitService fruitService)
{
    this.fruitService = fruitService;
}</p>
<pre><code>
#### 스프링 빈 등록 예시 2</code></pre><p>@RestController
public class UserController {</p>
<pre><code>private final FruitService fruitService;

public UserController(UserService userService, @Qualifier(&quot;main&quot;) FruitService fruitService)
{
    this.fruitService = fruitService;
}</code></pre><p>}</p>
<hr>
<p>@Service
@Qualifier(&quot;main&quot;)
public class BananaService implements FruitService
{</p>
<p>}</p>
<pre><code>
### `@Primary` vs `@Qualifier`
- 사용자가 직접 적어준`@Qualifier`가 더 우선이다.


## 정리 : 배운 내용
1. 좋은 코드가 왜 중요한지 이해하고, 원래 있떤 `Controller` 코드를 보다 좋은 코드로 리팩토링한다.
2. 스프링 컨테이너와 스프링 빈이 무엇인지 이해한다.
3. 스프링 컨테이너가 왜 필요한지, 좋은 코드와 어떻게 연관이 있는지 이해한다.
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[HCI] 5주차 상속성]]></title>
            <link>https://velog.io/@jsuhyeon_/HCI-5%EC%A3%BC%EC%B0%A8-%EC%83%81%EC%86%8D%EC%84%B1</link>
            <guid>https://velog.io/@jsuhyeon_/HCI-5%EC%A3%BC%EC%B0%A8-%EC%83%81%EC%86%8D%EC%84%B1</guid>
            <pubDate>Fri, 11 Apr 2025 09:00:11 GMT</pubDate>
            <description><![CDATA[<h1 id="상속inheritance">상속(Inheritance)</h1>
<ul>
<li>상위 클래스의 기능과 속성을 하위 클래스에게 그대로 물려주는 것</li>
</ul>
<h3 id="💡-용어-정리">💡 용어 정리</h3>
<h4 id="상위-클래스">상위 클래스</h4>
<ul>
<li>부모/기반 클래스 (base class)</li>
<li>한 개 이상의 자식 클래스와 관계를 맺는 계층 구조로 표현 가능</li>
<li>공통 부분을 부모 클래스에 구현</li>
</ul>
<h4 id="하위-클래스">하위 클래스</h4>
<ul>
<li>자식/파생 클래스 (derived class)</li>
<li>상속을 받으면 상위 클래스의 모든 멤버필드와 메서드를 사용할 수 있음</li>
<li>서로 다른 부분은 자식 클래스에 구현 가능 </li>
</ul>
<h4 id="클래스의-계층-구조-생성">클래스의 계층 구조 생성</h4>
<ul>
<li>업캐스팅 : 상속 받은 인터페이스 사용 보장 </li>
</ul>
<h4 id="재사용성-및-확장성">재사용성 및 확장성</h4>
<ul>
<li>이미 존재하는 클래스를 구체화하여 새로운 클래스 생성</li>
<li>동일한 특성을 재정의할 필요가 없어 서브 클래스가 간결해짐</li>
</ul>
<p><br><hr></p>
<h2 id="📍-상속inheritance">📍 상속(Inheritance)</h2>
<ul>
<li><code>base</code> 연산자로 상위 객체에 접근한다.</li>
<li>클래스를 정의할 때 : (콜론)을 사용하여 상속 관계를 표시한다.</li>
</ul>
<pre><code>class Person
{
    ...
}
class Student: Person 
{
    ...
}
class StudentWorker: Studnet 
{
    ...
}</code></pre><ul>
<li><code>Student</code> 클래스 : <code>Person</code>을 상속 받는다.</li>
<li><code>StudentWorker</code> 클래스 : <code>Student</code>를 상속 받는다.</li>
</ul>
<br>

<h3 id="상속-문법">상속 문법</h3>
<h4 id="①-상위-클래스에-공통-멤버를-정의한다">① 상위 클래스에 공통 멤버를 정의한다.</h4>
<pre><code>class Person
{
    string name;
    int age;
    public int GetAge() { return age; }
}</code></pre><ul>
<li>상위 클래스 <code>Person</code>에 공통 부분을 정의하였다.</li>
</ul>
<h4 id="②-하위-클래스에-필요한-멤버를-추가한다">② 하위 클래스에 필요한 멤버를 추가한다.</h4>
<pre><code>class Student: Person
{
    int id;
}

class Researcher: Person
{
    string research;
}</code></pre><ul>
<li>하위 클래스 <code>Student</code>와 <code>Researcher</code>에 필요한 멤버를 추가하였다.</li>
</ul>
<br>

<h3 id="상속-시-메모리-할당">상속 시 메모리 할당</h3>
<ul>
<li>하위 객체 생성시 상위 객체도 생성되어 데이터의 메모리를 할당한다. <h4 id="부모-클래스">부모 클래스</h4>
<pre><code>class Person
{
  string name;
  int age;
}</code></pre><h4 id="자식-클래스">자식 클래스</h4>
<pre><code>class Student: Person
{
  int id;
  ...
}</code></pre><h4 id="메모리-생성">메모리 생성</h4>
</li>
<li><code>Student s = new Student();</code></li>
<li><code>s</code> ⇒ <code>name</code> | <code>age</code> | <code>id</code></li>
</ul>
<br>

<h3 id="🍀-상속-예제">🍀 상속 예제</h3>
<pre><code>public class Point 
{
    private int x, y;
    public void Set(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
    public void ShowPoint()
    {
        Console.WriteLine(&quot;x + y&quot;);
    }
}

public class ColorPoint: Point
{
    private string color;
    public void ShowColorPoint()
    {
        Console.WriteLine(color);
        ShowPoint();
    }
}</code></pre><ul>
<li><code>Point</code>를 상속받은 <code>ColorPoint</code> 선언</li>
<li><code>ShowColorPoint()</code> : 컬러 점의 좌표 출력</li>
<li><code>ShowPoint()</code> : <code>Point</code> 클래스의 <code>ShowPoint()</code> 호출</li>
</ul>
<pre><code>public class ColorPointApp
{
    static void Main(string[] args)
    {
        ColorPoint cp = new ColorPoint();
        cp.Set(3,4);
        cp.SetColor(&quot;Red&quot;);
        cp.ShowColorPoint();    
    }
}</code></pre><ul>
<li><code>cp.Set(3,4)</code> : <code>Point</code> 클래스의 <code>Set()</code> 메서드 호출</li>
<li><code>cp.SetColor(&quot;Red&quot;)</code> : 색 지정</li>
<li><code>cp.ShowColorPoint()</code> : 컬러 점의 좌표 출력</li>
</ul>
<br>

<h2 id="📍-부모-클래스-멤버-접근">📍 부모 클래스 멤버 접근</h2>
<ul>
<li><p>부모 클래스의 <code>protected</code>와 <code>public</code>으로 지정된 멤버만을 상속</p>
</li>
<li><p>상속 받은 멤버는 부모 클래스에서 접근 지정자를 유지</p>
<h4 id="person-클래스-부모">Person 클래스 (부모)</h4>
<pre><code>class Person
{
  int age;
  protecte String name;
  public int height;
  private int weight;

  public int GetAge()
  {
      return age;
  }    

  public void SetAge(int age)
  {
      this.age = age;
  }    

  public int GetWeight()
  {
      return weight;
  }

  public void SetWeight(int weight)
  {
      this.weight = weight;
  }
}</code></pre></li>
</ul>
<h4 id="student-클래스-자식">Student 클래스 (자식)</h4>
<ul>
<li><p><code>Person</code> 클래스의 private 필드인 <code>age</code>, <code>weight</code>는 <code>Student</code> 클래스에서 접근이 불가능하여 부모 클래스인<code>Person</code>의 <code>public</code> 메소드를 통해서만 조작 가능하다.</p>
<pre><code>public class Student: Person
{
  int id;

  public int GetID()
  {
      return id;
  }

  void Set()
  {
      // age = 30; -&gt; 사용 불가
      // weight = 7-; -&gt; 사용 불가
      name = &quot;Park&quot;;
      height =175;        
      id = 1000;
  }

  static void Main(string[] args)
  {
      Student s = new Student();
      s.Set();
      int age = s.GetAge();
      int id = s.GetId();

      Console.WriteLine(&quot;학생 이름: {0}&quot;, s.name);
  }
}</code></pre></li>
<li><p><code>s.GetAge()</code> : 상속된 객체에서 부모 객체의 <code>public</code> 메서드 호출</p>
</li>
<li><p><code>s.GetId()</code> : 상속된 객체 자신의 메서드 호출</p>
</li>
</ul>
<br>

<h2 id="📍-상위-클래스의-생성자-호출">📍 상위 클래스의 생성자 호출</h2>
<ul>
<li>자식 클래스에는 부모 클래스의 생성자가 상속되지 않으므로 직접 호출해야 함<pre><code>public class Car 
{
  public Car() { }
  public Car(int wheel) { this.wheel = wheel; }
  ...
}
</code></pre></li>
</ul>
<p>public class Sedan: Car
{
    Sedan() { }
    Sedan(int wheel) : base(wheel) { }
}</p>
<pre><code>
- 만약 위의 문법과 같이 명시적으로 호출하지 않으면 임시적으로 기반 클래스의 기본 생성자를 호출
- `Sedan()` : `base()`와 동일 -&gt; 암시적 부모 클래스 생성자 호출
- `Sedan(int wheel) : base(wheel)` -&gt; 명시적 부모 클래스 생성자 호출

&lt;br&gt;

## 📍 base 키워드
- 부모 클래스의 멤버를 나타냄
- 부모 클래스의 멤버를 자식 클래스에서 재정의하였을 경우, 디폴트는 override된 멤버
- 부모 클래스의 멤버를 사용할 경우 명시</code></pre><p>public class Car
{<br>    protected bool gasoline;
    protected Car() { gasoline = true; }
    protected Car(int wheel) 
    { 
        this.wheel = wheel;
        gasoline = false;
    }
}</p>
<p>public class Sedan: Car
{
    private bool gasoline;
    Sedan() 
    { 
        gasoline = false;
        // base.gasoline = false;
        // this.gasoline - false;
    }
    Sedan(int wheel) : base(wheel) { gasoline = true; }
    public void SedanMove() 
    {
        if (base.gasoline) ...
        if (this.gasoline) ...
    }
}</p>
<pre><code>
&lt;br&gt;

## 📍 base와 this
### base
- 상속 받은 부모 클래스의 생성자 호출</code></pre><p>using System;
class A
{
    public A() { Console.WriteLine(&quot;A&quot;); }
}
class B: A
{
    public B() { Console.WriteLine(&quot;B&quot;); }
    public B(int foo): this()
    {
        Console.WriteLine(&quot;B({0})&quot;, foo);
    }
}
class DefaultInitializerTest
{
    public static void Main()
    {
        A a1= new A();
        B b1 = new B();
        B b2 = new B(100);
    }
}</p>
<pre><code>
### this
- 자기 자신에게서 정의한 다른 생성자 호출

&lt;br&gt;

### 🍀 base-this 예제
### Point &amp; ColorPoint 클래스</code></pre><p>public class Point
{
    private int x, y;</p>
<pre><code>public Point(): this(0, 0) { }

public Point(int x, int y)
{
    this.x = x;
    this.y = y;
}

public void Set(int x, int y)
{
    this.x = x;
    this.y = y;
}

public void ShowPoint()
{
    Console.WriteLine(&quot;(&quot; + x + &quot;, &quot; + y +&quot;)&quot;);
}</code></pre><p>}</p>
<pre><code>
### Point &amp; ColorPoint 클래스 생성자</code></pre><p>public class ColorPoint: Point
{
    private string color;</p>
<pre><code>public ColorPoint()
{
    this.color = &quot;Green&quot;;
}

public void SetCoor(string color)
{
    this.color = color;
}

public void ShowColorPoint()
{
    Console.WriteLine(color);
    ShowPoint();
}</code></pre><p>}</p>
<p>public class ColorPointApp
{
    static void Main() string[] args)
    {
        Point p = new Point();
        p.ShowPoint();
        ColorPoint cp = new ColorPOint(5, 7, &quot;Blue&quot;);
        cp.ShowColorPoint();
        cp = new ColorPoint();
        cp.ShowColorPoint();
    }</p>
<p>}</p>
<pre><code>
&lt;br&gt;

## 📍 is-a 관계
- 상속 관계 **(부모-자식 관계)**
- ~는 ~이다.
- 자동차는 탈것이다. = Car `is a` Vehicle.
- 강아지는 동물이다. = Dog `is a` animal.
</code></pre><p>class Animal
{</p>
<p>}</p>
<p>class Dog: Aniaml 
{</p>
<p>}</p>
<pre><code>
&lt;br&gt;

## 📍 has-a 관계
- 포함, 위임 관계 (멤버 변수로 포함)
- 구성 관계 또는 집합 관계를 나타낸다.

&gt; **예시**
 도서관은 책을 가지고 있다. = Library `has a` book.
거실은 소파를 가지고 있다. = Living room `has a` sofa.

### 🍀 has-a 관계 예제
- **자동차가 라디오를 가지고 있다. = Car `has a` radio.**
- 라디오를 갖고 있고, 라디오를 켜고 끄는 경우.
- 라디오를 작동하는 세부 방법은 각 객체에 위임</code></pre><p>class Radio
{
    public void TurnOn(bool on)
    {
        if (on)
            Console.WriteLine(&quot;Radio On&quot;);
        else
            Console.WriteLine(&quot;Radio Off&quot;);
    }
}</p>
<p>public class Car
{
    private Radio music;</p>
<pre><code>public Car()
{
    music = new Radio(); // Car has-a Radio
}

public void MusicOn(bool on)
{
    music.TurnOn(on); // 자식 객체(Radio)의 기능을 부모 객체(Car)에 위임
}</code></pre><p>}</p>
<pre><code>
### 🍀 has-a 관계 예제
#### Car 클래스
- 에어컨과 라디오 클래스를 갖고 있음
- 에어컨 온도를 조절하고 라디오를 작동시킴
- 객체를 생성하면 에어컨과 라디오 객체 자동 생성
- 자식 객체의 기능은 잘 모르므로 실제 구현은 위임</code></pre><p>class Airconditioner
{
    public void Up() { temperature++; }
    public void Down() { temperature--; }
}</p>
<p>public class Car
{
    private Airconditioner aircon;
    public Car()
    {
        aircon = new Airconditioner(); // Car has-a Aircon
    }</p>
<pre><code>public void TemperatureUp { aircon.Up(); }
public void TemperatureDown() { aircon.Down(); }</code></pre><p>}</p>
<p>public class CarHasATest
{
    public static Main()
    {
        // 차를 생성할 때 동시에 라디오와 에어컨 생성
        Car c = new Car(&quot;Avante&quot;);
        // 라디오 On
        c.MusicOn(true);
        // 에어컨 온도 높인다
        c.TemperatureUp();
    }
}</p>
<pre><code>&gt; 쉽게 말해서 `Car` 클래스가 **`Radio` 클래스 객체를 생성해서 사용**하는 것을 의미한다.

&lt;br&gt;

## 📍 업캐스팅 (upcasting)
- 자식 타입 → 부모 타입 **자동 타입 변환**
- 자식 클래스의 레퍼런스 값을 부모 클래스 레퍼런스에 대입
✔ 부모 클래스가 자식 클래스 객체를 가리키게 되는 현상
✔ 객체 내에 있는 모든 멤버에 접근할 수 없고, 부모 클래스의 멤버에만 접근 가능하다.
- 장점 : 부모 타입으로 묶어서 다룰 수 있다.
- 단점 : 자식 고유의 멤버에 접근할 수 없다.</code></pre><p>class Person
{
    class Student: Person
    {</p>
<pre><code>}

Student student = new Student();
Person person = student;</code></pre><p>}</p>
<pre><code>- `person`는 `Student` 객체를 참조하고 있지만, 타입은 `Person`이라서 `Person`**에 정의된 멤버만 사용**할 수 있다.
</code></pre><p>class Person
{
    protected string name;
    protected int id;</p>
<pre><code>public Person(string name)
{
    this.name = name;
}</code></pre><p>}</p>
<p>class Student: Person
{
    string grade;
    string dept;</p>
<pre><code>public Student(string name) : base(name) { }
public static void Main(string[] args)
{
    Person person;
    Student student = new Student(&quot;Park&quot;);
    person = student; // 업캐스팅     
    Console.WriteLine(p.name); // 오류 X
}</code></pre><p>}</p>
<pre><code>- `p.grade = &quot;A&quot;` → 컴파일 오류
- `p.dept = &quot;CS&quot;` → 컴파일 오류

&lt;br&gt;

## 📍 다운캐스팅 (downcasting)
- 부모 타입 → 자식 타입 **강제 타입 변환**
- 부모 클래스 레퍼런스를 자식 클래스 레퍼런스에 대입
- 업캐스팅된 것을 다시 원래대로 되돌리는 것
- 명시적으로 타입 지정 </code></pre><p>class Person
{</p>
<p>}</p>
<p>class Student: Person
{</p>
<p>}</p>
<p>Student student = new Student();
Person person = student; // 업캐스팅, 자동타입변환 
Studnet student = (Student)person; // 다운캐스팅, 강제타입변환</p>
<pre><code></code></pre><p>public static void Main(String[] args)
{
    Person p = new Student(&quot;Park&quot;); // 업캐스팅
    Student s = (Student)p; // 다운캐스팅
    Console.WriteLine(s.name); // 오류 X</p>
<pre><code>s.grade = &quot;A&quot;; // 오류 X
s.dept = &quot;CS&quot;; // 오류 X</code></pre><p>}</p>
<pre><code>- 업캐스팅 하여 자식 멤버에 접근할 수 없을 때, 다시 **자식 타입으로 돌려서 자식 기능을 쓰고 싶을 때** 다운캐스팅 한다.

&lt;br&gt;

## 📍 오버라이딩
- 부모 클래스에서 선언된 메소드를 자식 클래스에서 재구현(Override) 할 수 있다.
- 실행 시간에 새로 정의된 Override된 멤버의 내용으로 처리한다.

&lt;br&gt;

## 📍 virtual 메서드
- 부모 클래스의 virtual 메서드를 재정의하기 위해서 `override` 키워드를 사용한다. **(선택적)**
- `static`, `private`과 함께 사용할 수 없다.
- virtual 메서드와 override 메서드는 이름, 접근 제한자, 반환 값, 매개변수 리스트가 동일해야 한다.

### virtual 메서드 정의</code></pre><p>class Shape
{
    public string Name()
    {
        ...
    }</p>
<pre><code>public virtual void Draw()
{
    Console.WriteLine(&quot;그림을 그린다.);
}</code></pre><p>}</p>
<p>class Circle: Shape
{
    public override void Draw() 
    {
        Console.WriteLine(&quot;원을 그린다.&quot;);
    }
}</p>
<pre><code>
### 🍀 virtual - override 예시 
#### 오버라이딩된 메서드가 항상 호출된다. </code></pre><p>public class MethodOverridingEx
{
    public static void Main(string[] args)
    {
        Shape shape = new Shape();
        Circle circle = new Circle();</p>
<pre><code>    Shape a = new Circle(); // 업캐스팅
    Shape b = circle;         // 업캐스팅

    shape.Draw(); // Shape Draw() 실행
    circle.Draw(); // Circle Draw() 실행

    a.Draw(); // 오버라이딩된 Circle Draw() 실행
    b.Draw(); // 오버라이딩된 Circle Draw() 실행
}</code></pre><p>}</p>
<pre><code>- `Draw()` 메서드는 `virtual`로 선언되어 있어서 
→ 런타임(실행 시간)에 **실제 객체 타입**인 `Circle`의 `Draw()`가 실행된다. 

&lt;br&gt;

## 📍 추상 클래스
- 부모 클래스에서 `abstract` 키워드를 통해 정의한 메서드를 자식 클래스에서 `override`하여 재정의한다. **(필수적)**
- 상속관계에서 가장 상위에 존재한다.</code></pre><p>abstract class Shape 
{ 
    public abstract void Draw() { }
}</p>
<p>public class Circle: Shape
{
    public override void Draw() { }
}</p>
<p>class AbstractTest
{
    static void Main()
    {
        Shape shape = new Circle();
        shape.Draw(); // Circle Draw()</p>
<pre><code>    shape = new Square();
    shape.Draw(); // Square Draw()
}</code></pre><p>}</p>
<pre><code>
### 추상 메서드 정의
- 추상 클래스만이 추상 메서드를 가질 수 있다.
- 추상 메서드는 암시적으로 virtual 메서드이다.
- `virtual` 키워드와 함께 사용할 수 없다.
- 메서드 이름 앞에 `abstract` 키워드를 사용해야 한다.
- **자식 클래스에서 반드시 재정의**해야 한다.</code></pre><p>abstract class Ticket
{
    public virtual string StartTime() { }
    public abstract int Fare(); // 강제성 메서드명만 정의
}</p>
<p>class BusTicket: Ticket
{
    public override string StartTime() { }
    public override int Fare() {} // 자식 클래스에서 선언
}</p>
<pre><code>
### 💡 추상 메서드 정리
- 자식 클래스마다 **목적에 맞게** 추상 메서드를 **다르게 구현**할 수 있다.
- **부모 클래스**에서는 **개념을 정의**하고, 각 **자식 클래스**에서 **구체적인 행위**를 구현한다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring Boot] 섹션1]]></title>
            <link>https://velog.io/@jsuhyeon_/Spring-%EC%8B%9C%EC%9E%91</link>
            <guid>https://velog.io/@jsuhyeon_/Spring-%EC%8B%9C%EC%9E%91</guid>
            <pubDate>Tue, 08 Apr 2025 08:55:55 GMT</pubDate>
            <description><![CDATA[<p><code>2025-04-07</code>
<code>2025-04-08</code></p>
<h4 id="인강-링크">인강 링크</h4>
<p><a href="https://www.youtube.com/watch?v=lqgEbqty7O4&amp;t=1163s">💻 Spring Boot로 서버 만들기 (Youtube)</a>
<a href="https://www.inflearn.com/course/%EC%9E%90%EB%B0%94-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EC%98%AC%EC%9D%B8%EC%9B%90">💻 Spring Boot로 서버 만들기 (Inflearn)</a></p>
<br>

<h2 id="1강-스프링-서버를-시작하는-방법">1강 스프링 서버를 시작하는 방법</h2>
<h3 id="intelli-j-새-프로젝트-시작하기">Intelli J 새 프로젝트 시작하기</h3>
<p><a href="https://start.spring.io/">💻 Intelli J 설정 링크</a></p>
<ul>
<li>spring initializr에서 어쩌구 저쩌구 설정</li>
<li>프로젝트를 새로 만들 땐 의존성(Dependencies)을 추가해야 한다.<h4 id="용어-정리">용어 정리</h4>
</li>
<li>라이브러리 : 프로그래밍 개발할 때 미리 만들어져있는 기능을 가져다 사용하는 것 </li>
<li>프레임워크 : 프로그래밍을 개발할 때미리 만들어져 있는 구조에 코드를 가져다 끼워넣기</li>
</ul>
<br>

<h2 id="2강-springbootapplication과-서버">2강 @SpringBootApplication과 서버</h2>
<ul>
<li><code>@SpringBootApplication</code> : Spring에 있는 다양한 설정들을 모두 자동으로 해주는 키워드</li>
<li><code>class LibraryAppAPplication</code> : 클래스 명</li>
<li><code>SprinAppApplication.run(LibraryAppApplication.class, args);</code> : <code>LibraryAppApplication</code>을 실행한다.</li>
<li><code>System.out.println()</code> : 출력문</li>
</ul>
<h4 id="서버란">서버란</h4>
<ul>
<li>어떠한 기능을 제공하는 프로그램 </li>
<li>그 프로그램을 실행시키고 있는 컴퓨터</li>
</ul>
<p>⇒ 컴퓨터에게 원하는 정보를 얻으려면 &#39;요청&#39;을 해야 한다. 요청은 &#39;인터넷&#39;, &#39;네트워크&#39;를 통해 할 수 있다.</p>
<br>

<h2 id="3강-네트워크란-무엇인가">3강 네트워크란 무엇인가</h2>
<ul>
<li>IP : 컴퓨터의 고유 주소</li>
<li>IP를 통해 데이터를 주고 받을 수 있다.
예시) IP : 244.66.51.9, PORT : 3000</li>
<li>IP의 숫자를 외우기 힘들어 Domain Name이 등장하였다. (주소의 별칭이라 생각하면 됨)
예시) 도메인 이름 : spring.com, PORT : 3000</li>
</ul>
<p>IP = Domain Name
⇒ 이러한 체계를 <strong>Domain name System(DNS)</strong>라고 한다.</p>
<br>

<h2 id="4강-http와-api란-무엇인가">4강 HTTP와 API란 무엇인가</h2>
<blockquote>
</blockquote>
<h4 id="운송장-표준-1">운송장 표준 1</h4>
<ol>
<li>운송장을 받는 사람에게 요청하는 <strong>행위</strong></li>
<li>운송장이 가는 집 (주소, IP)</li>
<li>운송장을 실제로 받는 사람 (PORT)</li>
<li>운송장을 받는 사람에게 원하는 자원 (어떤 항목을 원하는가)</li>
<li>자원의 세부 조건<h4 id="운송장-표준-2">운송장 표준 2</h4>
</li>
<li>운송장을 받는 사람에게 요청하는 행위</li>
<li>운송장을 받는 사람 </li>
<li>운송장을 받는 사람에게 원하는 자원 </li>
<li>실제 자원이 들어 있는 박스</li>
</ol>
<h3 id="실제-인터넷에서-데이터를-주고-받을-때-표준">실제 인터넷에서 데이터를 주고 받을 때 표준</h3>
<ul>
<li>HTTP (Hyper TExt Transfer Protocal)</li>
<li>Protocal : 표준, 약속</li>
</ul>
<blockquote>
<h4 id="http-예시-1">HTTP 예시 1</h4>
</blockquote>
<pre><code>GET /portion?color=red&amp;count=2
HOST: spring.com:3000</code></pre><ol>
<li><code>GET</code> : HTTP 요청을 받는 컴퓨터에게 요청하는 <strong>행위</strong> (데이터를 달라) (HTTP Method)</li>
<li><code>Host: spring.com:3000</code> (IP:PORT) : HTTP 요청을 받는 대상 (컴퓨터와 프로그램 정보)</li>
<li><code>/portion</code> : HTTP 요청 받는 컴퓨터에게 원하는 자원 (path라고 함)</li>
<li><code>?</code> : 구분 기호 | <code>color=red</code> : 자원의 세부 조건 | <code>&amp;</code> : 구분 기호 | <code>count=2</code> : 자원의 세부 조건 
⇒ 원하는 조건 (Query)</li>
</ol>
<blockquote>
<h4 id="http-예시-2">HTTP 예시 2</h4>
</blockquote>
<pre><code>POST /oak/leather
Host: spring.com:3000 
오크가죽정보</code></pre><ol>
<li><code>POST</code> : HTTP 요청을 받는 컴퓨터에게 요청하는 행위 (저장하라) (HTTP Method)</li>
<li><code>Host: spring.com:3000</code> : HTTP 요청을 받는 대상 (컴퓨터와 프로그램 정보)</li>
<li><code>/oak/leather</code> : HTTP 요청을 받는 컴퓨터에게 원하는 자원</li>
<li><code>오크가죽정보</code> : 실제 저장할 오크 가죽 정보 
⇒ 전달할 데이터 (Body)</li>
</ol>
<h4 id="①-쿼리-query">① 쿼리 Query</h4>
<pre><code>GET /portion?color=red&amp;count=2
HOST: spring.com:3000 </code></pre><h4 id="②-바디-body">② 바디 Body</h4>
<pre><code>POST /oak/leather
Host: spring.com:3000

오크가죽정보</code></pre><h3 id="http-method">HTTP Method</h3>
<ul>
<li>GET : 데이터를 달라 | Query</li>
<li>POST : 데이터를 저장하라 | Body</li>
<li>PUT : 데이터를 수정하라 | Body</li>
<li>DELETE : 데이터를 삭제하라 | Query</li>
</ul>
<h3 id="api-appllication-programming-interface">API (Appllication Programming Interface)</h3>
<ul>
<li>Interface : 정해진 규칙, 약속</li>
<li>정해진 약속을 하여, 특정 기능을 수행하는 것</li>
</ul>
<h3 id="http-요청">HTTP 요청</h3>
<ul>
<li>첫째줄 (메서드 패스 쿼리)</li>
<li>헤더 (여러줄 가능)</li>
<li>(한 줄 띄기)</li>
<li>바디 (여러줄 가능)</li>
</ul>
<pre><code>POST /oak/leather &lt;method query&gt;
Host: spring.com:3000 &lt;header&gt;
&lt;-&gt;
오크가죽정보 &lt;body&gt;</code></pre><h4 id="url-uniform-resource-locator">URL (Uniform Resource Locator)</h4>
<ul>
<li><code>http://spring.com:3000/portion?color=red&amp;count=2</code></li>
<li><code>http</code> : 사용하고 있는 프로토콜 (HTTP)</li>
<li><code>://</code> : 구분 기호</li>
<li><code>spring.com:3000</code> : 도메인이름:포트, 도메인 이름은 IP로 대체 가능</li>
<li><code>/</code> : 구분 기호</li>
<li><code>portion</code> : 자원 경로 (path)</li>
<li><code>?</code> : 구분 기호</li>
<li><code>color=red&amp;count=2</code> : 추가 정보</li>
</ul>
<h3 id="http-응답">HTTP 응답</h3>
<ul>
<li><strong>서버(Server)</strong> : 요청에 대한 응답을 제공(serve)한 컴퓨터</li>
<li><strong>클라이언트(Client)</strong> : 요청을 한 컴퓨터</li>
</ul>
<h4 id="응답에-들어가는-숫자-상태-코드">응답에 들어가는 숫자 (상태 코드)</h4>
<ul>
<li>매우 다양함
<code>200 OK</code> | <code>300 Moved Permanently</code> | <code>404 NotFound</code> | <code>500 Internal Server Error</code><h4 id="응답에는-추가-정보바디를-담을-수-있다">응답에는 추가 정보(바디)를 담을 수 있다</h4>
</li>
<li>첫째줄 (메서드 패스 쿼리)</li>
<li>헤더 (여러줄 가능)</li>
<li>(한 줄 띄기)</li>
<li>바디 (여러줄 가능)</li>
</ul>
<pre><code>HTTP/1.1 200 OK 
Content-Type: appication/json

{
    &quot;name&quot; : &quot;A&quot;,
    &quot;age&quot; : null
}</code></pre><h2 id="총정리">총정리</h2>
<blockquote>
<ol>
<li>(웹을 통한) 컴퓨터 간의 통신은 HTTP 라는 표준화도니 방식ㅇ ㅣ있다.</li>
<li>HTTP 요청은 HTTP Method (GET, POST)와 Path(<code>/portion/</code>)가 핵심이다.</li>
<li>요청에서 데이터를 전달하기 위한 2가지 방법은 쿼리와 바디이다.</li>
<li>HTTP 응답은 <strong>상태 코드</strong>가 핵심이다.</li>
<li>클라이언트와 서버는 HTTP를 주고 받으며 기능을 동작하는 이때 정해진 규칙을 API라고 한다.</li>
</ol>
</blockquote>
<p><br><hr></p>
<h2 id="5강-get-api-개발하고-테스트하기">5강 GET API 개발하고 테스트하기</h2>
<pre><code>GET /portion?color=red&amp;count=2
Host: spring.com:3000</code></pre><ol>
<li>HTTP Method</li>
<li>HTTP Path</li>
<li>쿼리 (key와 value)</li>
<li>*<em>API 반환 결과 *</em></li>
</ol>
<pre><code>@RestController 
public class CalcualtorController {
    @GetMapping(&quot;/add&quot;); // GET /add
    public int addTwoNumbers(@RequestParam int number1, @RequestParam int number2)
    {
        return number1 + number2;
    }
}</code></pre><ul>
<li><code>@RestController</code> : 주어진 Calss를 Controller로 등록한다.</li>
<li>Controller : API의 입구</li>
</ul>
<ul>
<li><code>@GetMapping(&quot;/add&quot;)</code> : 아래 함수를 HTTP Method가 <code>GET</code>이고 HTTP Path가 <code>/add</code>인 API로 지정한다.</li>
<li><code>@RequestParam</code> : 주어지는 쿼리를 함수 파라미터에 넣는다.</li>
</ul>
<p>⇒ <code>GET /add?number1=10&amp;number2=20</code></p>
<h3 id="dto-data-transfer-object">DTO (Data Transfer Object)</h3>
<ul>
<li>데이터를 전달하는 객체</li>
<li>&#39;쿼리&#39; 라는 데이터를 오이붕에서 서버 안 Controller로 전달하는 역할</li>
<li>이런 객체를 Data Transfer Object라고 한다.</li>
</ul>
<br>

<h2 id="6강-post-api-개발하고-테스트하기">6강 POST API 개발하고 테스트하기</h2>
<h3 id="json">JSON</h3>
<ul>
<li>Body는 JSON 형식으로 데이터를 받는다.</li>
<li>key-vaule 형태로 값을 저장한다.<pre><code>{
   &quot;name&quot; : &quot;정수현&quot;,
   &quot;age&quot; : 23
}</code></pre></li>
</ul>
<h3 id="post">POST</h3>
<p><code>[CaculatorController.java]</code></p>
<pre><code>@PostMapping(&quot;/multiply&quot;)
public int multiplyTwoNumbers(@RequestBody CalculatorMultiplyRequest request)
{
    return request.getNumber1() * request.getNumber2();
}</code></pre><ul>
<li><code>@RequestBody</code> : HTTP Body로 들어오는 JSON을 <code>CalculatorMultiplyRequest</code>로 바꾸어준다</li>
<li><code>CalculatorMulltiplyRequest</code> : HTTP Body를 객체로 바꾸는 <code>@RequestBody</code>를 사용하는 경우, 생성자를 만들지 않아도 괜찮다.</li>
</ul>
<p><code>[CalculateMultiplyRequest]</code></p>
<pre><code>public class CalculatorMultiplyRequest {

    private final int number1;
    private final int number2;

    public CalculatorMultiplyRequest(int number1, int number2)
    {
        this.number1 = number1;
        this.number2 = number2;
    }

    public int getNumber1()
    {
        return number1;
    }

    public int getNumber2()
    {
        return number2;
    }
}</code></pre><ul>
<li>HTTP Body는 <code>CalculatorMultiplyRequest</code>에 매핑된다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] Functions]]></title>
            <link>https://velog.io/@jsuhyeon_/Dart-Functions</link>
            <guid>https://velog.io/@jsuhyeon_/Dart-Functions</guid>
            <pubDate>Tue, 01 Apr 2025 09:22:43 GMT</pubDate>
            <description><![CDATA[<h3 id="0️⃣-defining-a-function">0️⃣ Defining a Function</h3>
<ul>
<li><code>void</code> : 반환 타입이 없는 함수</li>
<li><code>String</code> : 반환 타입이 <code>String</code>인 함수</li>
<li>함수 내의 문장이 한문장일 경우 <em><strong>fat arrow syntax</strong></em> 를 활용할 수 있다. (곧바로 return 하는 거와 같은 의미)<pre><code>void main()
{
  print(sayHello(&#39;수현&#39;));
  print(sayBye(&#39;수현&#39;));
}
</code></pre></li>
</ul>
<p>String sayHello(String name)
{
    return &quot;Hello $name, Nice to meet you!&quot;;
}</p>
<p>String sayBye(String name) =&gt; &quot;Bye $name&quot;;</p>
<pre><code>
### 1️⃣ Named Parameters
#### 1) Positional Parameter 
- 함수 호출을 통해 매개변수로 값을 전달할 때, 함수에 선언되어진 매개변수 순서대로 값을 입력해야 한다.
- 사용할 때 매개변수의 순서를 기억해야 한다는 번거로움이 있음
#### 2) Named Parameter
- 매개변수를 순서대로 입력하여 값을 전달하는 것이 아닌, &lt;키, 밸류&gt; 형태로 항목을 명시하여 값을 전달할 수 있다.

#### null이 되지 않도록 
1. `default value` : 선언된 함수 파라미터에서 값을 미리 정의한다.</code></pre><p>String sayHello({String name=&#39;수현&#39;, int age=23, String country})
{
    return &quot;Hello $name, your age is $age, and you come from $country.&quot;;
}</p>
<p>void main()
{
    print(sayHello(
        country: &quot;korea&quot;
        ));
}</p>
<pre><code>- 출력 ⇒ Hello 수현, your age is 23, and you come from korea.

2. `required` : 변수 선언 시 함께 명시하면 null 값을 가질 수 없게 됨</code></pre><p>String sayHello({required String name, required int age, required String country})
{
    return &quot;Hello $name, your age is $age, and you come from $country.&quot;;
}</p>
<pre><code>- `sayHello` 메서드는 반드시 `name`, `age`, `country` 값을 가져야 한다.


### 2️⃣ Optional Positional Parameters
- `sayHello` 메서드에서 한 매개변수가 `null` 값일 경우에 디폴트 값을 갖도록 설정할 수 있다.</code></pre><p>String sayHello(String name, int age, [String? country = &#39;germany&#39;])
{
  return &quot;Hello $name, you are $age years old from $country.&quot;;
}</p>
<p>void main()
{
  var result = sayHello(&#39;수현&#39;, 23);
  print(result);
}</p>
<pre><code>- 출력 ⇒ Hello 수현, you are 23 years old from germany.

### 3️⃣ QQ Operator
- `??` : 좌항 ?? 우항 =&gt; 값이 null이 아니면 좌항을, null이면 우항을 반환한다.</code></pre><p>String capitalizeName(String? name)
    =&gt; name?.toUpperCase() ?? &#39;ANON&#39;;</p>
<pre><code>- `??=` : 변수가 `null`이라면 초기화한 값을 할당해달라는 의미</code></pre><p>void main()
{
    String? name;
    name ??= &#39;nico&#39;;
}</p>
<pre><code>
### 4️⃣ Typedef 
- `typedef` : 자료형 키워드를 정할 수 있다.
- 좀 더 간단한 타입명을 만들 때 사용한다.</code></pre><p>typedef ListOfInts = List<int>;</p>
<p>ListOfInts reverseListOfNumbers(ListOfInts list)
{
  var reversed = list.reversed;
  return reversed.toList();
}</p>
<p>void main()
{
  print(reverseListOfNumbers([1,2,3]));
}</p>
<pre><code></code></pre><p>typedef UserInfo = Map&lt;String, String&gt;;</p>
<p>String sayHi(UserInfo userinfo)
{
    return &quot;Hi ${userInfo[&#39;name&#39;]}&quot;;
}</p>
<p>void main()
{
    sayHi({&quot;name&quot; : &quot;수현&quot;});
}</p>
<pre><code></code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] Data Types]]></title>
            <link>https://velog.io/@jsuhyeon_/Dart-Data-Types</link>
            <guid>https://velog.io/@jsuhyeon_/Dart-Data-Types</guid>
            <pubDate>Tue, 01 Apr 2025 08:11:14 GMT</pubDate>
            <description><![CDATA[<p><code>2025-04-01</code></p>
<h3 id="0️⃣-basic-data-types">0️⃣ Basic Data Types</h3>
<ul>
<li><p><code>num</code> : <code>int</code>와 <code>double</code>은 <code>num</code>에서 유래했기 때문에, <code>num</code> 명령어를 사용하여 변수를 선언하면 <code>int</code> 타입과, <code>double</code> 타입 두 개 다 허용한다.</p>
<pre><code>void main()
{
  String name = &#39;수현&#39;;
  bool avlive = true;
  int age = 23;
  double money = 55.66;

  // -----------
  num x = 12;
  x = 1.1;
}</code></pre></li>
</ul>
<h3 id="1️⃣-lists">1️⃣ Lists</h3>
<pre><code>void main() 
{
    var numbers = [ 1, 2, 3, 4 ];
    numbers.add(1);
    numbers.first; // 리스트의 첫번째 요소에 접근
    numbers.last; // 리스트의 마지막 요소에 접근   
}</code></pre><ul>
<li><code>collection if</code> : 리스트 안에 <code>if</code>문을 작성할 수 있다.<pre><code>void main()
{
  giveMeFive = true;
  var numbers = [ 1, 2, 3, 4, if(giveMeFive) 5 ];
}</code></pre></li>
</ul>
<h3 id="2️⃣-string-interpolation">2️⃣ String Interpolation</h3>
<ul>
<li>문자열 안에 원하는 변수를 넣고 싶으면 변수명 앞에 <code>$</code> 기호를 입력한다.</li>
<li>{ } 안에 계산할 값을 넣어도 된다.</li>
<li>작은 따옴표 기호를 너고 싶으면 <code>\&#39;</code>와 같이 쓴다.<pre><code>void main()
{
  var name = &#39;수현&#39;;
  var age = 23;
  var greeting = &#39;Hello, I\&#39;m $name and I\&#39;m ${age+1}, nice to meet you!&#39;;
  print(greeting);
}</code></pre></li>
<li>출력 ⇒ Hello, I&#39;m 수현 and I&#39;m 24, nice to meet you!</li>
</ul>
<h3 id="3️⃣-collection-for">3️⃣ Collection For</h3>
<ul>
<li><code>collection for</code> : 컬렉션(리스트) 안에서 For문을 사용할 수 있다.<pre><code>void main()
{
  var oldFriends = [&#39;dog&#39;, &#39;cat&#39;];
  var newFriends = [
  &#39;tiger&#39;,
  &#39;lion&#39;,
  &#39;dolphin&#39;,
  for(var friend in oldFriends) &quot;🧡$friend&quot;;
   ];    
  print(newFriends);
}</code></pre></li>
<li>출력 ⇒ [tiger. lion, dolphin, 🧡dog, 🧡cat]</li>
</ul>
<h3 id="4️⃣-maps">4️⃣ Maps</h3>
<ul>
<li><code>Map</code> : Python의 dictionary 같은 거</li>
<li>key와 value로 이루어져있다.</li>
<li><code>object</code> : 기본적으로 어떤 형태의 자료형이든 될 수 있다.</li>
<li>Map&lt;String, Object&gt; : key 자리에는 String, value 자리에는 어떤 형태의 자료형이든 올 수 있다. <pre><code>void main()
{
  var player = {
      &#39;name&#39; : &#39;수현&#39;,
      &#39;xp&#39; : 19.99;
      &#39;superpower&#39; : false,
  };
}
</code></pre></li>
</ul>
<pre><code>
### 5️⃣ Set
- 요소가 하나만 있어야할 때 사용한다.
- 요소가 여러개 쓰여도 될 땐 List를 사용한다.
</code></pre><p>void main()
{
    Set<int> numbers = {1, 2, 3, 4};
    numbers.add(1);
    numbers.add(1);
    print(numbers);
}</p>
<p>```</p>
<ul>
<li>출력 ⇒ 1, 2, 3, 4</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Dart] Variables]]></title>
            <link>https://velog.io/@jsuhyeon_/Dart-Variables</link>
            <guid>https://velog.io/@jsuhyeon_/Dart-Variables</guid>
            <pubDate>Tue, 01 Apr 2025 07:08:28 GMT</pubDate>
            <description><![CDATA[<p><code>2025-04-01</code></p>
<h3 id="1️⃣-the-var-keyword">1️⃣ The Var Keyword</h3>
<ul>
<li><code>var</code> : 컴파일러가 변수 타입을 추론한다.</li>
<li>처음 초기화한 타입으로 변수의 타입을 정한다..</li>
<li>Dart에서는 <code>var</code>을 가능한 많이(?) 사용하는 것을 추천함<pre><code>void main()
{
  var name = &#39;수현&#39;;
  name = 12; // → 에러
}</code></pre></li>
</ul>
<h3 id="2️⃣-dynamic-type">2️⃣ Dynamic Type</h3>
<ul>
<li><code>dynamic</code> : 변수의 타입을 컴파일러가 알아서 판단한다. 여러 타입의 값을 저장할 수 있다.<pre><code>void main()
{
  dynamic name = &#39;수현&#39;;
  name = 12;
  name = true;
}</code></pre></li>
</ul>
<h3 id="3️⃣-nullable-variables">3️⃣ Nullable Variables</h3>
<ul>
<li><code>?</code> : 타입 뒤에 <code>?</code>를 써서 <code>null</code> 형태가 될 수 있음을 명시할 수 있다. (ex : <code>String?</code> : 자료형이 <code>String</code>형일 수도 있고, <code>null</code>일 수도 있다.)<pre><code>void main()
{
  String? name = &#39;수현&#39;;
  name = null;
  if (name is String)
  {
      name.isNotEmpty;
  }
}</code></pre></li>
</ul>
<h3 id="4️⃣-final-variables">4️⃣ Final Variables</h3>
<ul>
<li><code>final</code> : JS의 <code>const</code>와 같이 수정할 수 없는 변수(상수)를 선언할 때 사용하는 명령어</li>
<li>재할당하지 못하는 변수를 선언할 때 사용한다.</li>
<li>API 키를 받아와야 할 때 사용 (컴파일할 때 모르는 값이어도 됨)</li>
<li>어떤 데이터가 들어올지 모른다는 뜻.<pre><code>void main()
{
  final API = fetchApi();
}</code></pre></li>
</ul>
<h3 id="5️⃣-late-variables">5️⃣ Late Variables</h3>
<ul>
<li><code>late</code> : <code>late</code> 변수를 만들고 API에 요청을 보낸 다음, API에서 값을 보내주면 해당 값을 <code>late</code> 변수에 저장한다. (API 작업할 때 많이 사용)</li>
<li>변수를 먼저 선언하고, 데이터는 나중에 넣는 방식<pre><code>void main ()
{
  late final String name;
  name = &#39;수현&#39;;
  print(name);
}</code></pre></li>
</ul>
<h3 id="6️⃣-constant-variables">6️⃣ Constant Variables</h3>
<ul>
<li><code>const</code> : 코드를 컴파일 하기 전에 알고 있어야 하는 값에 사용한다.<pre><code>void main ()
{
  const name = &#39;수현&#39;;
}</code></pre></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>