<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발하는 피자 양목장</title>
    <link>https://pizzasheepsdev.tistory.com/</link>
    <description>게임 개발 관련 글들을 포스팅합니다.</description>
    <language>ko</language>
    <pubDate>Mon, 11 May 2026 03:17:21 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>피자 먹는 양</managingEditor>
    <image>
      <title>개발하는 피자 양목장</title>
      <url>https://tistory1.daumcdn.net/tistory/3831251/attach/6c4d5e5591684775bf46235a78ad5954</url>
      <link>https://pizzasheepsdev.tistory.com</link>
    </image>
    <item>
      <title>Addressable Asset System에 대한 자잘한 특징??</title>
      <link>https://pizzasheepsdev.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2019.3 버전에 들어서면서 Addressable Asset System이 정식으로 릴리즈 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Addressable Asset System은 유니티의 새로운 에셋 관리 시스템인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 Resources, Remote AssetBundle의 통합하는 새로운 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 사용만 한다면, 기존에 에셋 로딩 방식이 가지던 단점들을 보완하는 꽤 좋은 시스템이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 앞으로 게임 개발 시엔 이 Addressable Asset System이 기본이 될 것이며 유니티도 이 시스템을 밀어줄 것이라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(따지고 보면, 기존의 에셋 번들 시스템을 개선한 버전이기는 하다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시대에 뒤처지지 않기 위해, 나도 이것저것 테스트해보는 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트해보면서 알게 된 몇 가지 특징들이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잊지 않기 위해서 여기에 적어두려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 부분은 어느 정도 Addressable Asset System에 대해 이해가 있어야지 이해가 쉬울 것이다.)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Addressable에서 에셋을 분류하는 단위인 Group은 기존의 번들 개념과 유사하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 에셋에 번들 이름을 부여해서, 나중에 번들 이름 단위로 데이터를 생성하듯이, 기본적으로 어드레서블은 그룹 단위로 에셋 번들을 만든다.&lt;/li&gt;
&lt;li&gt;그래서 기존의 에셋번들 시스템에서 어드레서블로 컨버팅을 하면 에셋 번들 이름이 그룹으로 처리되면서, 그룹 별로 에셋들이 분류된다.&lt;/li&gt;
&lt;li&gt;옵션 변경해서 그룹 단위가 아닌 개별 에셋, 혹은 레이블 단위로 에셋 번들을 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Addressable에서 LoadAsset과 DownloadDependencies는 Key 또는 Label 값을 파라미터로 받는다.&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 부분이 아직 나에게 낯선 개념인데, 에셋을 그룹 단위로 로드할 수 없다. 에셋의 Key값이나, Label을 통해서만 로드할 수 있다. 여기서 레이블이란, 일종의 Tag개념으로 에셋마다 여러 개 레이블을 가질 수 있다. 그래서 레이블로 로딩 시, 해당 레이블을 가진 모든 데이터를 로드한다.&lt;/li&gt;
&lt;li&gt;이유는 위에서 설명한 옵션 때문인데, 에셋 번들이 무조건 그룹 단위로 만들어지는 것이 아니기 때문에, 그룹 단위로 처리하는 함수는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LoadAsset, Instantiate은 에셋이 리모트 에셋일 경우, 다운로드를 한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;굳이 다운로드 함수를 호출하지 않아도, 로컬에 캐싱된 에셋 번들이 없다면 자동으로 다운로드를 해서 캐싱을 한다.&lt;/li&gt;
&lt;li&gt;로컬 캐싱관련 옵션을 끄면 매번 로드할 때마다 다운로드를 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LoadAsset, Instantiate, DownloadDependencies를 이용해 에셋을 로드하면, 관련 의존된 에셋 또한 알아서 다운로드하고 로드해준다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;에셋 하나를 로드하는데 의존 에셋이 있다면 같이 다운로드한다. 그게 설령 의존에 의존에 의존 에셋이라도.&lt;/li&gt;
&lt;li&gt;예를 들어, 리모트 번들 A에 메쉬 Prefab이 있고, 리모트 번들 B에 메쉬의 Material이 있고, 리모트 번들 C에 머테리얼의 Texture가 있으면, Prefab 에셋을 로드할 때, 자동으로 번들 B와 C까지 알아서 같이 로드한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그룹 단위의 처리 방식이 아니기 때문에 새로운 방식의 에셋 데이터 관리 방향을 제시할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 게임들은 게임 시작 시에 필요한 데이터를 사전에 전부 다운로드하도록 하지만, 위에 설명한 어드레서블의 특징 덕분에 그룹 단위로 에셋 데이터를 관리하지 않아도 되기 때문에, 유저가 게임을 플레이를 하면서 필요한 데이터만 다운로드하게 할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LoadAsset로 불러온 에셋은 사용 후 Release를 호출해줘야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 변수를 Null로 처리하는 것 또한 잊으면 안 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Instantiate으로 생성한 오브젝트는 ReleaseInstance를 호출해줘야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다만, 호출하면, 오브젝트가 Destroy 되므로 풀링을 했다면, 더 이상 필요 없어진 시점에 해야 할 것이다.&lt;/li&gt;
&lt;li&gt;Release를 호출해도 똑같이 작동한다.&lt;/li&gt;
&lt;li&gt;반대로 LoadAsset으로 불러 온 Prefab 또한 ReleaseInstance로 처리해도 동일하게 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;LoadAsset으로 불러온 Prefab으로 생성한 인스턴스는 Release 대상이 아니다.&lt;/li&gt;
&lt;li&gt;AsyncOperationHandle로 Release를 하면 로드/생성된 에셋 또한 릴리즈 된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;때문에 Instantiate의 AsyncOperationHandle을 이용해 해제하면, 인스턴스를 Release 한 것처럼 오브젝트가 Destroy 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;에셋끼리 동일한 키값을 가질 수 있다. 해당 키로 에셋을 로드할 때, LoadAsset&lt;b&gt;s&lt;/b&gt;로 에셋을 불러오면, 배열로 로드할 수 있다.&lt;/li&gt;
&lt;li&gt;LoadAssets으로 한 번에 여러 에셋을 불러올 때 특정 데이터 타입으로 하면, 같은 값(키 또는 레이블)을 가진 에셋 중에 해당 타입을 가진 에셋만 불러온다.&lt;/li&gt;
&lt;li&gt;LoadAssets으로 한 번에 여러 에셋을 불러올 때 object로 타입을 정하면, 같은 값(키 또는 레이블)을 가진 모든 에셋을 불러온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기능을 더 확장하고 활용하려면, Provider 같은 인터페이스들을 상속하고 커스터마이즈 하는 법까지&lt;span&gt;&amp;nbsp;&lt;/span&gt;알아야 할 텐데.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 부분은 나에게 너무 어렵다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시스템을 이용해서 어떻게 번들 데이터들의 버전 업데이트 시스템을 만들 수 있을지는 아직 고민해보지 않아서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 차차 업데이트할 예정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unity</category>
      <category>Addressable</category>
      <category>assetbundle</category>
      <category>Unity</category>
      <category>어드레서블</category>
      <category>에셋번들</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/13</guid>
      <comments>https://pizzasheepsdev.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 19 May 2020 21:42:01 +0900</pubDate>
    </item>
    <item>
      <title>왜 Unity의 GC(Garbage Collector)는 무식할까</title>
      <link>https://pizzasheepsdev.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;성능에 조금이라도 관심 있는 사람은 GC에 대하여 들어본적이 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 유니티의 GC에 대해 공부하다보면, 발견할 수 있는 점이 .NET의 GC와 Unity의 GC가 다르다는 점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 유니티의 GC를 언뜻 듣고, .NET의 GC를 공부하고 나서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 유니티도 C#을 쓰니 같은 GC겠거니 했는데, 그게 아니었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에선 왜 그런지를 알아보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;.NET Framework Garbage Collector&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 각 GC가 어떻게 동작하는지 심플하게 설명해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;.NET Framework의 GC는 세월이 흐르면서 여러 방법으로 업그레이드 되어 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 .NET의 GC는 세대(Generation) 방식을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;할당된 메모리별로 0~2세대까지 세대를 부여하여,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;통계적으로 &lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(자기네들이 생각하기에)&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt; 최적의 타이밍에 필요한 메모리를 해제할 수 있도록 관리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 힙영역은 SOH(Small Object Heap)과 LOH(Large Object Heap)으로 구분하여,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 메모리를 할당하고 해제할 때의 부담을 줄도록 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOH에는 작은 메모리들이 할당되고, GC가 일어날 때, 정리된 세대의 메모리의 재정렬이 일어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LOH에는 큰 메모리들이 할당되고(기준은 85,000바이트라고 한다), GC가 일어날 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능 저하를 피하기 위해서 메모리 재정렬은 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 GC시에는 GC 작업으로 인한 부하를 더 줄이려고, 전용 쓰레드를 쓰고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 거기에 로직을 넣고 적절한 시기에 쓰레드를 돌리는 등 여러가지가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Unity Garbage Collector&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티의 GC는 꽤나 단순무식하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그말인즉, 성능은 상대적으로 좋지 않다는 뜻이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세대 구분? 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOH와 LOH? 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 재정렬? 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 하나의 힙 메모리 영역만 있으며 메모리가 할당되면 죄다 여기에 할당한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해제 시 재정렬을 하지 않아, 할당과 해제를 자주하면 메모리에 구멍이 송송 나있어서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공간은 많은데 메모리 할당을 못하는 경우도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 내부적으로 메모리가 부족하면 힙 영역을 늘리지만, 결국 근본적인 문제는 해결되지 않고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임은 메모리 먹는 하마가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;게다가 GC시엔 그냥 앱의 다른 동작을 정지시키기 때문에 렉도 심하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이정도면 너무하지 않나 싶을 정도이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;왜?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;왜 유니티는 이런 구린 GC를 쓰는걸까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;궁금해서 구글링을 해봤다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그리고 StackOverflow에서 흥미로운 코멘트를 읽었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;(검증은 안했으므로 카더라 일수도 있는 점 양해바랍니다.)&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;It was in early 2008 that Unity and Mono announced their collaboration, and at that time Unity licensed the Mono runtime (GPL covered for open source usage) so as to embed it. And the Boehm GC was the primary GC in Mono then.&lt;br /&gt;Time passed and Mono 4.x/5.x by default uses SGen GC with generational/compacting features. However, Unity did not want to pay the licensing again. Thus, you see the documentation remains it was.&lt;br /&gt;&lt;br /&gt;Microsoft acquired Xamarin in 2016, and hence gained control of Mono core assets. It republished the code base under MIT, so solving the licensing issue for ever. Unity joined .NET Foundation and started to work with Microsoft/Xamarin to incorporate the latest Mono runtime into the game engine.&lt;br /&gt;&lt;br /&gt;That effort is still undergoing and should soon reach maturity (currently an experimental feature).&lt;br /&gt;&lt;br /&gt;BTW, Unity cannot use the standard .NET GC yet. Microsoft does not open source its GC in .NET Framework, but a version in .NET Core. That GC is different from Mono's, and would require more efforts to be embedded into Unity. I guess that's why Mono 5 was chosen to be integrated right now. Maybe in the future Unity would migrate to the .NET Core GC.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/46574407/unitys-garbage-collector-why-non-generational-and-non-compacting&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;https://stackoverflow.com/questions/46574407/unitys-garbage-collector-why-non-generational-and-non-compacting&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Unity's garbage collector - Why non-generational and non-compacting?&quot; data-og-description=&quot;I've just read in Unity's docs that Unity&amp;rsquo;s garbage collection &amp;ndash; which uses the Boehm GC algorithm &amp;ndash; is non-generational and non-compacting. &amp;ldquo;Non-generational&amp;rdquo; means that the GC must sweep throu...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/46574407/unitys-garbage-collector-why-non-generational-and-non-compacting&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGlkfN/hyFMwcJU6R/iEwgHx7JHzl0mUajuYkJM1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot; data-og-url=&quot;https://stackoverflow.com/questions/46574407/unitys-garbage-collector-why-non-generational-and-non-compacting&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/46574407/unitys-garbage-collector-why-non-generational-and-non-compacting&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/46574407/unitys-garbage-collector-why-non-generational-and-non-compacting&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGlkfN/hyFMwcJU6R/iEwgHx7JHzl0mUajuYkJM1/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Unity's garbage collector - Why non-generational and non-compacting?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I've just read in Unity's docs that Unity&amp;rsquo;s garbage collection &amp;ndash; which uses the Boehm GC algorithm &amp;ndash; is non-generational and non-compacting. &amp;ldquo;Non-generational&amp;rdquo; means that the GC must sweep throu...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 요약하자면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'과거에 유니티는 가난해서 최신 Mono의 GC를 적용할 돈이 없었어서 초장기 계약한 구식 GC를 계속 사용 중이었다. &lt;/b&gt;&lt;b&gt;(구식 C# 버전과 함께)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이제는 Xamarin(Mono개발사)이 마이크로소프트에 인수되어, Mono가 MIT 라이센스로 풀렸기 때문에, 유니티는 최신 버전을 적용할 수 있게 되었고, 열심히 작업 중이다'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국, 유니티는 가난했고, 마소의 자마린 인수 후 오픈소스 정책으로 이제야 적용하는 중이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 찾아보면 현재 유니티가 점진적 가비지 콜렉션을 조금씩 시범 적용 중인 것을 알 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 기능이 적용된 이유가 아마 이것과 관련이 있지 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마소를 찬양하라...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마 마소가 자마린 인수 안했으면, MIT로 전환하지 않았으면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 아직도 구닥다리 GC와 C#을 사용하고 있었을지도 모른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unity</category>
      <category>garbage collection</category>
      <category>garbage collector</category>
      <category>GC</category>
      <category>Unity</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/12</guid>
      <comments>https://pizzasheepsdev.tistory.com/12#entry12comment</comments>
      <pubDate>Fri, 24 Apr 2020 00:14:16 +0900</pubDate>
    </item>
    <item>
      <title>SOLID : 의존 역전 원칙</title>
      <link>https://pizzasheepsdev.tistory.com/11</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존 역전 원칙 (DIP : Dependency Inversion Principle)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOLID 원칙의 마지막 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원칙을 지키기 위해 해야할 행동은 꽤나 심플하기 때문에 그리 어렵지 않은 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선, 사전적으로 이 원칙은 다음과 같은 내용을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 상위 모듈은 하위 모듈에 의존해서는 안된다. 상위 모듈과 하위 모듈 모두 추상화에 의존해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 추상화는 세부 사항에 의존해서는 안된다. 세부사항이 추상화에 의존해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 사전적 의미가 싫다. 쉽게 직설적인 의미로 설명하지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위 모듈과 하위 모듈이 뭘까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 정확히 정의하기 위해 구글링을 엄청나게 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감이 잘 오지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어디는 상위 모듈이 유저 인터페이스와 가까운 레이어에 있는 모듈이라고 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른데는 하위 모듈이 유저 인터페이스와 가까운 레이어라고 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;또 다른 곳은 의미 있는 단일 기능을 구현한게 상위 모듈이라 하고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바와 관련해서 설명하는 곳은 변화가 쉬운 것에 의존하지 않는게 DIP라고 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람 더 헷갈리게 만드는 설명들이 따로 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누가 추상화를 장려하는 원칙 아니랄까봐 그런지 설명들도 참 추상적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 곳은 상위 모듈이 추상화, 인터페이스를 뜻하고, 하위 모듈은 구현 클래스를 뜻한다고 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 너무 결과만 놓고 본 설명이다,.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영문 사이트도 뒤져보고 이것저것 정보를 추합해서 이해한 결과.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 직설적으로 게임 개발쪽과 연관지어서 설명하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;상위 모듈: UI 같이 게임을 개발할 때 유저에게 노출되는, 최종 구현 클래스가 되는 부분&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;하위 모듈: Service, Manager 클래스 혹은 라이브러리 같이 게임 뒷단에 있는 코어가 되는 부분&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(왜 이렇게 확 와닿게 설명하는 곳이 없는건지 모르겠다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정의를 토대로 의존 역전 원칙의 성립 조건을 다시 설명하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'최종 구현 클래스에서 매니저 클래스 자체를 참조하지 말고, 인터페이스를 통해 추상화한 타입으로 참조하라'&lt;/b&gt;라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정의를 한번 더 가공하면 이렇게 설명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'인터페이스 써라'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존 역전을 위한 의존성 주입 (Dependency Injection)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존 역전을 실천하기 위해서 의존성 주입이라는 패턴에 대해 알아야할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름은 무시무시한데, 정말 이론 자체는 정말 간단하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'내부 구현 클래스에서 참조&lt;b&gt;(또는 생성)&lt;/b&gt;하던 외부 클래스를 인터페이스를 통해 외부로 부터 주입되게 하는 것'&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 게임 내에 광고 기능을 추가한다고 생각해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회의를 통해 우리는 A 회사에서 제공하는 광고 플러그인을 사용하기로 하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A사의 플러그인 API에 맞춰서 매니저 클래스와, 광고 시청 버튼을 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 유저가 광고 버튼을 누르면 광고 매니저 클래스를 통해, 광고를 재생시킬 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1586847482914&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AdvertisementButton
{
    public void OnClick()
    {
    	AdvertisementManager.Instance.PlayAd();
    }
}

public class AdvertisementManager
{
    public static AdvertisementManager Instance;

    public void PlayAd()
    {
    	// A사의 광고 플러그인 API 호출
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AdvertisementManager는 많은 개발자가 사랑하는 싱글턴 클래스로 되어 있다. (디테일한 구현은 넘어가자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 AdvertisementButton클래스는 그 매니저 클래스를 직접 접근해서 호출하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우가 바로 의존 역전 관계를 지키지 않는 경우이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, 추가한 광고 플러그인이 맘에 안들거나 문제가 있어서, 다른 회사의 광고 플러그인을 넣는다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 그 회사의 플러그인의 API가 기존에 구현한 인터페이스와 다르다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(그리고 또 결정이 바뀌어서 다시 기존의 광고 플러그인을 다시 사용하기로 한다면???)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우린 &lt;span style=&quot;color: #333333;&quot;&gt;AdvertisementManager를&amp;nbsp;&lt;/span&gt;사용하는 모든 클래스를 수정해야할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해서 사용되는 것이 바로 의존성 주입 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 의존성 주입을 하기 위해선 기존 클래스들을 인터페이스로 추상화 시켜야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586848166340&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IAdvertisementManager
{
    void PlayAd();
}

public class AdvertisementManager : IAdvertisementManager
{
    public void PlayAd()
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 광고 플러그인이던 가장 중요한 광고를 재생하는 함수는 존재할 것이다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 광고 재생 함수를 인터페이스로 추상화 시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 인터페이스를 광고 버튼 클래스에 넣어주면 되는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 사용하는게 아니고 외부로부터 인터페이스의 참조를 주입시키는 통로를 만들어줘 값을 대입시키게 만들어야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586848719350&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AdvertisementButton
{
    private IAdvertisementManager _advertisementManager;
    
    public void Initialize(IAdvertisementManager advertisementManager)
    {
    	_advertisementManager = advertisementManager;
    }

    public void OnClick()
    {
    	_advertisementManager.PlayAd();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드에선 함수를 통해 참조값을 주입시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면, 어떤 광고 플러그인을 쓰던 버튼 클래스는 인터페이스를 통해 광고를 재생시키기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전혀 코드를 수정할 필요가 없다. 그저 외부에서 버튼에게 실제 참조 클래스를 주입시키는 코드만 바꿔주면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 바로 의존성 주입이다. 이름 그대로 '외부에서 의존성을 주입시켜준다'해서 붙여진 이름이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 얘기한대로, 의존성 주입은 다양한 통로로 넣어줄 수 있다. 속성이던 함수이던 생성자이던 상관없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 클래스를 내부에서 직접 생성하거나 참조만 하지 않으면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 의존성 주입 패턴을 사용하면, 의존 역전 원칙을 지킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;외부 어디에서 의존성을 붙여주는가?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입 패턴을 사용하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 어딘가에는 인터페이스의 참조값을 넣어주는 클래스가 존재해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이걸 어디에서 호출해줘야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 각자 정의하기 나름이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보편적으로는 프로그램이 정상적으로 작동하기 위해서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 시작할 때 맨 처음 호출 되는 부분에 인터페이스들의 구현 클래스들을 생성하는 코드들 만들어주곤 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1586849806193&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SomeClass
{
    public AdvertisementButton _adButton;

    public void OnStartUp()
    {
        AdvertisementManager adManager = new AdvertisementManager();
        _adButton.Initialize(adManager);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(어딘가에는 이런 코드가 존재한다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;싱글톤과 서비스 로케이션을 버려야한다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤과 서비스 로케이션 패턴은 편리하면서도 보편적으로 쓰이는 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 의존 역전 관계에서 이들은 안티 패턴(지양해야할 패턴)들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1586850626495&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AdvertisementButton
{
    private IAdvertisementManager _advertisementManager;
    
    public void Initialize()
    {
    	_advertisementManager = Locator.GetInstance&amp;lt;IAdvertisementManager&amp;gt;();
    }

    public void OnClick()
    {
    	_advertisementManager.PlayAd();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 로케이터의 전역 함수를 통해, 인터페이스 타입을 가져오지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AdvertisementButton은 다시 Locator에 의존성을 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Locator은 모든 코드에 전반적으로 쓰이는 코드인데, 만약에 이 클래스의 함수들이 바뀐다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AdvertisementManager에서 발생한 문제와 똑같은 문제를 일으킬 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 뭔가 괜찮은 방법을 넣긴했으나, 의존성의 문제점은 여전히 가지고 있는 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static 인스턴스 사용은 SOLID 원칙에서는 지양해야할 코드 습관 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 '의존 역전 원칙'일까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 쉽고 정확하게 딱 이해되진 않지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로는 기존(Button에서 Manager에 의존하는) 의존 관계의 개념이 뒤집어졌다는 뜻에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역전이라는 의미를 뜻한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 곳에서는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;----&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;darr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;----&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;----&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 &lt;span&gt;&amp;larr;&lt;/span&gt; 상위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;uarr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하위&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;----&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념으로 의존 방향이 역전 되어서 역전이라고 의존 역전이라고 설명하는 곳도 있긴하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 개념은 나도 모르겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어차피 이 원칙에서 제일 중요한건 클래스들 간에 의존성을 가지지 않도록 해야한다는거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 주입하는 방법은 다양하게 있다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 방법은 메소드, 생성자 주입 같은 것을 말하는게 아니라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 &lt;span style=&quot;color: #333333;&quot;&gt;OnStartUp() &lt;/span&gt;예시처럼 인스턴스를 초기화 하고 의존성을 주입 것에 대한 방법을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시처럼 시작 단계에서 초기화해서 수동적으로 주입하는 방법도 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC 프레임워크에서 자동으로 주입하는 방법도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;제어의 역전(역행)(IoC : Inversion of Control)과 DI Framework&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전적인 의미부터 설명하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 프로그래밍처럼, 프로그래머가 외부 라이브러리를 호출하는게 아닌,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 라이브러리가 프로그래머가 짠 코드를 호출하여, 제어의 흐름이 역전되었다는 뜻을 가진 개념(혹은 패턴)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임워크가 바로 이런 개념을 가진 대표적인 예이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(프레임워크: 특정한 구조와 규약을 가져서, 개발자가 그 위에서 작업을 하면, 자동으로 코드를 관리하고 제어하도록 만들어진 구조.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입 개념에 프레임워크 개념을 이용하여, 프로그래머가 수동으로 의존성을 주입하는게 아닌,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;인스턴스를 생성하고 관리하는 &lt;/span&gt;컨테이너라고 불리는 제어자를 통하여 자동으로 의존성이 주입되도록 하는 DI Framework가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 개념들에 대해 찾다보면, IoC, IoC Container, DI, DI Framework 용어들에 대한 시간적인 순서가 조금 헷갈린데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아본바에 의하면, IoC -&amp;gt; DIP -&amp;gt; IoC Container -&amp;gt; DI, DI Framework 인거 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추측하기로는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DIP 개념으로부터 인스턴스들의 의존성을 자동으로 관리해주는 프레임워크 개념인 IoC Container가 나왔고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC Container의 IoC 단어는 우리가 아는 의존성 주입의 개념으로써 사용되고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 IoC의 개념이 일반적인 IoC의 개념과 충돌하여 혼동의 여지가 있기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IoC를 DI라고 명명하여 사용하기 시작했고 DI Container(Framework)란 단어가 생긴 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 주입을 위한 프레임워크 쓰자!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의존성 주입을 간편하게 사용하기 위한 다양한 프레임워크 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비주얼 스튜디오에서도 존재하고 자바에선 스프링이란 프레임 워크도 존재하고 유니티에서도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필자는 유니티에서 Zenject라는 플러그인을 사용한적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 강력한 프레임워크 플러그인으로 이걸 사용 하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각종 시스템 클래스들을 직관적이게 초기화 할 수 있고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현 클래스에서 싱글톤이나 로케이터 패턴보다 훨씬 편하고 깔끔하게 접근할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/modesttree/Zenject&quot;&gt;https://github.com/modesttree/Zenject&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(에셋 스토어에도 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Refactoring &amp;amp; Clean Code</category>
      <category>Dependency Injection</category>
      <category>Dependency Inversion Principle</category>
      <category>Di</category>
      <category>DIP</category>
      <category>IOC</category>
      <category>Solid</category>
      <category>의존 역전 원칙</category>
      <category>의존성 역전 원칙</category>
      <category>의존성 주입</category>
      <category>제어의 역전</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/11</guid>
      <comments>https://pizzasheepsdev.tistory.com/11#entry11comment</comments>
      <pubDate>Tue, 14 Apr 2020 18:07:07 +0900</pubDate>
    </item>
    <item>
      <title>SOLID : 인터페이스 분리 원칙</title>
      <link>https://pizzasheepsdev.tistory.com/10</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;인터페이스 분리 원칙 (ISP : Interface Segregation Principle)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 어려운 원칙이 지나갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 행복해질 시간이다. 이 원칙은 상대적으로 쉽고, 단일 책임 원칙(SRP)을 알고 있다면, 더 이해하기 쉬운 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원칙의 사전적 정의는 &lt;b&gt;'클라이언트는 자신이 사용하지 않는 인터페이스 멤버에 대하여 의존적이면 안된다'&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트라고 하니 딱딱하다. 흔히 이 정의는 이렇게로도 많이 알려져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'하나 인터페이스가 하나의 동작만을 하도록 인터페이스가 분리되어야 한다.'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 좋아하는 예를 들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시에서 사용하지 않는 인터페이스 멤버에 대해 의존성을 가진다는 게 어떤 건지, 이로 인한 문제가 뭔지 살펴볼 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1586693579343&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IDataManager
{
    void Load();
    
    void Prepare();
    
    void Save();
}

public class DataLoader : IDataManager
{
    public void Load()
    {
    }

    public void Prepare()
    {
    }
    
    public void Save()
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataLoader클래스는 인터페이스로 IDataManager를 상속하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 DataLoader는 Load 함수만 필요한 클래스인데, 원치않게&amp;nbsp;Prepare와 Save까지 구현을 해야 되는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 설상가상으로, &lt;span style=&quot;color: #333333;&quot;&gt;Save&lt;/span&gt;의 시그니처(파라미터, 리턴, 함수명에 대한 정의)가 변경되어, 리턴 값으로 bool을 갖게 된다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;DataLoader&lt;/span&gt;도 똑같이 수정을 해야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 바로 불필요한 인터페이스 멤버에 대한 의존성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 문제를 해결하기 위해서 우리는 인터페이스를 잘게 쪼갤 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 원칙을 따라야 하는 또 다른 이유가 하나가 더 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스를 잘게 쪼게면. 인터페이스를 사용하는 개발자가 잘못된 방법으로 인터페이스를 사용하도록 예방하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 동작만을 수행할 수 있게 유도할 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드를 예를 들어, &lt;span style=&quot;color: #333333;&quot;&gt;IDataManager&lt;/span&gt; 파라미터를 이용한 함수를 구현하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;IDataManager&lt;/span&gt;로 &lt;span style=&quot;color: #333333;&quot;&gt;DataLoader&lt;/span&gt;가 전달되는 상황이라고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586694797050&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AnotherClass
{
    public void LoadData(IDataManager dataLoader)
    {
        dataLoader.Prepare();
        dataLoader.Load();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현된 함수를 보면, 이 함수를 개발한 개발자가 &lt;span style=&quot;color: #333333;&quot;&gt;IDataManager&lt;/span&gt;의 인터페이스만 보고, Prepare를 사전에 호출해야 하는 함수로 착각해,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출한 것을 알 수 있다. 인터페이스를 잘게 쪼갠다면, 이런 문제를 사전에 방지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인터페이스 분리하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원칙을 적용하는 법은 간단하다. 하나의 인터페이스를 여러 개의 인터페이스로 분리하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이 원칙은 단일 책임 원칙 마냥 적절히 쪼개는 것에 그치지 않고, 아예, &lt;b&gt;인터페이스 하나 당, 하나의 멤버&lt;/b&gt;만을 갖도록 정의하고 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586695778425&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IDataLoader
{
    void Load();
}

public interface IDataPreparer
{
    void Prepare();
}

public interface IDataSaver
{
    void Save();
}

public class DataLoader : IDataLoader
{
    public void Load()
    {
    }
}

public class DataPreparer : IDataPreparer
{
    public void Prepare()
    {
    }
}

public class DataSaver : IDataSaver
{
    public void Save()
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이렇게, 각자 쪼갠 인터페이스 별로 구현을 하거나,&lt;/p&gt;
&lt;pre id=&quot;code_1586695861082&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IDataLoader
{
    void Load();
}

public interface IDataPreparer
{
    void Prepare();
}

public interface IDataSaver
{
    void Save();
}

public class DataManager : IDataLoader, IDataPreparer, IDataSaver
{
    public void Load()
    {
    }
    
    public void Prepare()
    {
    }
    
    public void Save()
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;혹은 이렇게&amp;nbsp;&lt;/span&gt;다중 상속을 이용해서 하나의 클래스에 구현할 수 있다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1586696081361&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AnotherClass
{
    public void LoadData(IDataLoader dataLoader)
    {
        dataLoader.Load();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그래서 인터페이스를 잘 분리하면, 다른 클래스에서 인터페이스를 사용할 때, 다른 개발자가 쓸데없는 멤버를 사용하는 것을 막을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다만, 이 원칙에서 주의할 점은 분리한 인터페이스를 다시 하나의 인터페이스로 병합하는 경우인데,&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1586696781262&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IDataLoader
{
    void Load();
}

public interface IDataPreparer
{
    void Prepare();
}

public interface IDataSaver
{
    void Save();
}

public interface IDataManager : IDataLoader, IDataPreparer, IDataSaver
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이렇게 기껏 분리한 인터페이스를 다시 병합하지 않게 조심해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(이런 경우를 '인터페이스 수프(Soup)'라고 부른다.)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단일 책임 원칙과의 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇몇 구글에 퍼진 얘기로 인터페이스 분리 원칙과 단일 책임 원칙은 같은 문제에 대한 두 가지의 다른 해결책이다라는 말이 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 볼 때 이건 잘못된 얘기다. 누군가 잘못 이해하고 생산한 걸 퍼 나르고 퍼 나르다 보니 사실인 것처럼 퍼져 있는 거 같은데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 책임 원칙은 클래스와 메서드가 하나의 책임만을 갖도록 하는 원칙이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 분리 원칙은 인터페이스가 하나의 멤버만을 갖도록 분리하는 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;걍 쉽게 예를 들어, 위에 있는 예시 코드를 보면 답이 나온다.&lt;/p&gt;
&lt;pre id=&quot;code_1586696581649&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IDataLoader
{
    void Load();
}

public interface IDataPreparer
{
    void Prepare();
}

public interface IDataSaver
{
    void Save();
}

public class DataManager : IDataLoader, IDataPreparer, IDataSaver
{
    public void Load()
    {
    }
    
    public void Prepare()
    {
    }
    
    public void Save()
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시는 인터페이스들이 하나의 목적만을 정의하고, DataManager는 Data 관리에 대한 단일 책임을 가지고 있으므로,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 책임 원칙과, 인터페이스 분리 원칙 둘 다 적용된 사례이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 상식적으로 생각해봐도 '객체지향 설계의 5대 원칙'인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 흔히 #대 원칙을 말할 때, 'A 원칙과 B 원칙은 양립할 수 없다.'라고 얘기하진 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생각해볼 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원칙에 가장 걸리는 부분은 아무래도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터페이스 하나 당, 하나의 멤버&lt;/b&gt;라는 점일 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상식적으로 인터페이스에 하나의 멤버만 넣는 것은 불가능이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장 자기가 만든 프로젝트만 생각해도, 그렇게 적용했다간 프로젝트 내에 인터페이스 파일이 수백 개가 될 것이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스 관리에 헬게이트가 열릴 것이 분명하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 사람들도 이 부분에 같은 생각인지, 잘게 쪼개는 건 동의하지만, 적정선까지만 쪼개는 게 더 나을 것이라고 얘기하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 읽는 사람도 이 부분에 대해선 어떻게 실전에 적용하는 게 좋을지 생각해볼 필요가 있다.&lt;/p&gt;</description>
      <category>Refactoring &amp;amp; Clean Code</category>
      <category>Interface Segregation Principle</category>
      <category>ISP</category>
      <category>Solid</category>
      <category>인터페이스 분리 원칙</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/10</guid>
      <comments>https://pizzasheepsdev.tistory.com/10#entry10comment</comments>
      <pubDate>Sun, 12 Apr 2020 22:31:02 +0900</pubDate>
    </item>
    <item>
      <title>SOLID : 리스코프 치환 원칙</title>
      <link>https://pizzasheepsdev.tistory.com/9</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;리스코프 치환 원칙 (LSP: Liskov Substitution Principle)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스코프 치환 원칙은 SOLID 원칙 중에서도 가장 애매하고 오해하기 쉬운 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전과 마찬가지로 이 원칙의 이론적 정의를 설명하자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'B가 A의 자식 타입이면 부모 타입인 A 객체는 자식 타입인 B로 치환해도, 작동에 문제가 없어야 한다'&lt;/b&gt;.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본적인 뜻&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 원칙은 자식 클래스를 구현하는 개발자가 기존 프로그램이 문제없이 안정적으로 작동할 수 있도록&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;가이드라인을 알려주는 원칙이라고 볼 수 있을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 정의로는 A - B의 부모 자식에 대한 정의가 논리적으로 제대로된 상속이어야.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 프로그램이 자식 클래스인 B로 치환해도 문제 없이 작동해야한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 많이 드는 예시가 바로 직사각형을 상속한 정사각형 클래스의 예시인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정사각형 클래스가 직사각형 클래스를 상속해버리면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정사각형의 특징인 '네 변의 길이는 동일하다'와 그렇지 않은 직사각형의 차이로 인해,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직사각형을 정사각형 클래스로 치환해서 사용할 때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네 변의 길이에 대한 두 클래스의 특징 차이 때문에 기존 프로그램이 오작동 할 수 있다는 얘기이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;포괄적인 뜻&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 뜻은 그렇지만, 이 원칙은 &lt;span style=&quot;color: #333333;&quot;&gt;자식 클래스의 잘못된 상속 구현으로 인한 문제에&lt;/span&gt; 대해 좀 더 포괄적인 의미를 내포하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 관점으로 더 자세히 얘기하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'부모 클래스 타입인 A를 사용하는 기존의 프로그램 코드가 자식 클래스 B로 대입 시켰을 때도 문제 없이 작동하도록 하기 위해서,&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자식 클래스는 부모 클래스가 따르던 계약 사항을 자식도 따라야한다&lt;/b&gt;&lt;b&gt;.'&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 계약이란, 클래스의 멤버가 어떻게 작동하는지에 대한 구현 조건 사항 같은 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 쉬운 이해를 위해 예를 들어보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586609052551&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void Main()
{
    Parent parent = new Parent();
    Content content = parent.GetContent();
    content.WriteLog();
}

public class Parent
{
    public virtual Content GetContent()
    {
    	Content content = new Content();
    	return content;
    }
}

public class Child : Parent
{
    public override object GetContent()
    {
    	return null;
    }
}

public class Content
{
    public void WriteLog()
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A라는 개발자가 Parent라는 클래스를 만들고, Content 클래스를 리턴하는 GetContent 함수를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GetContent는 무슨 일이 있어도 최소한 디폴트 값을 가진 null이 아닌 값을 리턴하도록 기획이 되어,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A는 GetContent가 디폴트 값을 리턴하도록 구현해놓았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 나중에 GetContent에 대한 변경 사항이 생겨서 함수의 내부 로직을 바꿔야하는 상황이 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 B라는 다른 개발자는 개방 폐쇄 원칙에 의거해, Parent 클래스를 수정하는 게 아닌, 새로운 Child 클래스를 구현해,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GetContent를 오버라이드 하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 B 개발자는 디폴트 값으로 null을 리턴하도록 작성했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 Parent를 Child로 치환한 Main 함수에서는 NullReferenceException에 의해 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 리스코프 치환 원칙의 중요 포인트다. 자식 클래스로 부모 클래스의 내용을 상속하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드에서 보장하던 조건을 수정하거나 적용시키지 않아서, 또는 엉뚱한 자식 클래스를 구현해서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 부모 클래스를 사용하는 코드에서 예상하지 않은 오류를 발생시킨 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전에 약속한 기획대로 구현하고, 상속 시, 부모에서 구현한 원칙을 따라야 한다가 이 원칙의 핵심이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;좀 더 자세하게 (이론적으로)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;... 솔직히 여기서 끝내려고 했는데, 구글링으로 이론에 대해 제대로 이해하고 보충하려고 찾다가 보니,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세하게 이 이론에 대해 설명하는 곳이 없어서 좀 더 이론적인 부분에 대해 적어보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 여기서 자식 클래스가 부모 클래스의 계약을 제대로 수행한다는게 어떤 의미를 거지고 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;갓갓 위키피디아에서는 이 원칙을 충족시키기 위한 조건을 제대로 설명을 하고 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스코프의 원칙은 새로운 객체 지향 프로그래밍 언어에 채용된&lt;span&gt;&amp;nbsp;시그니처에&lt;/span&gt;&amp;nbsp;관한 몇 가지 표준적인 요구사항을 강제한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위형에서 메서드 인수의 반공변성&amp;nbsp;&lt;/li&gt;
&lt;li&gt;하위형에서 반환형의&lt;span&gt;&amp;nbsp;공변성&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;하위형에서 메서드는 상위형 메서드에서 던져진 예외의 하위형을 제외하고 새로운 예외를 던지면 안 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기에 더하여 하위형이 만족해야 하는 행동 조건 몇 가지가 있다. 이것은 계약이&lt;span&gt;&amp;nbsp;상속&lt;/span&gt;에 대해 어떻게 상호작용 하는지에 대한 제약조건을 유도하는 계약에 의한 설계&lt;span&gt;&amp;nbsp;&lt;/span&gt;방법론과 유사한 용어로 자세히 설명되어있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위형에서&lt;span&gt; 선행 조건&lt;/span&gt;은 강화될 수 없다.&lt;/li&gt;
&lt;li&gt;하위형에서&lt;span&gt; 후행 조건은&lt;/span&gt; 약화될 수 없다.&lt;/li&gt;
&lt;li&gt;하위형에서 상위형의 불변 조건은 반드시 유지되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84_%EC%B9%98%ED%99%98_%EC%9B%90%EC%B9%99&quot;&gt;https://ko.wikipedia.org/wiki/%EB%A6%AC%EC%8A%A4%EC%BD%94%ED%94%84_%EC%B9%98%ED%99%98_%EC%9B%90%EC%B9%99&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;(위키피디아 답게 사람이 이해할 수 있는 언어로 설명하고 있지 않고 있다.)&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 위에 첫째 문단은 좀 더 나중에 설명하고 아래 문단부터 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 문단은 앞에서 설명했던 것을 더 자세하게 설명한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하위형에서&lt;span&gt; 선행 조건&lt;/span&gt;은 강화될 수 없다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 하위형은 자식 클래스를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선행 조건은 사전 조건이라고도 하는데, 사전적 의미로 함수가 오류 없이 실행되기 위한 모든 조건을 정의한 것이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 함수를 처리할 때, 전달된 파라미터 값이 옳지 그른지 체크하는 것을 말한다.&lt;/p&gt;
&lt;pre id=&quot;code_1586616870513&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void Method(int data)
{
    // 값이 음수여선 안된다.
    if (data &amp;lt; 0)
    {
    	throw new ArgumentOutOfRangeException(&quot;data&quot;, &quot;데이터 값은 음수이면 안됩니다.&quot;);
    }
    
    // 코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같이 data 파라미터가 제대로 된 값이 들어오는지 확인하는 것을 선행 조건이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강화된 조건은 선행 조건을 추가하는 것을 말한다. 조건을 더 까다롭게 만든다라고 생각할 수도 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586617136848&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void Method(int data)
{
    // 값이 0이거나 음수여선 안된다.
    if (data &amp;lt;= 0)
    {
    	throw new ArgumentOutOfRangeException(&quot;data&quot;, &quot;데이터 값은 0보다 커야합니다.&quot;);
    }
    
    // 코드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면 Data 값이 0이면 안된다라는 조건을 추가시켰다. 리스코프 원칙은 이렇게 짜선 안된다는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 클래스와 동일한 수준의 선행 조건을 기대하고 사용하는 프로그램 코드에서 예상치 못한 문제를 일으킬 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(그럼 완화는 괜찮냐에 대해선 이미 프로그램에서 함수 호출 시에 선행 조건을 충족시켜서 값을 전달하기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완화된 조건이 들어오는 일은 일어날 수 없기 때문에 괜찮다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하위형에서&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;후행 조건은&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;약화될 수 없다.&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후행 조건은 사후 조건이라고도 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사전적 의미로 함수가 호출된 후에 객체(데이터)가 유효한 상태로 존재하는지 여부를 검사하는 것이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 함수 내에서, 처리 후에 리턴 값이나 참조 파라미터로 전달된 값이 제대로 정의된 값인지 검사하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1586618784268&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public int Method_A(int data)
{
    int result;
    
    // 실행 코드
    
    if (result &amp;lt; 0)
    {
    	result = 0;
    }
    
    return result;
}

public void Method_B(Money money, int data)
{
    // 실행 코드
    
    if (money.Amount &amp;lt; 0)
    {
         throw new ArgumentOutOfRangeException(&quot;money&quot;, &quot;money.Amount 결과값이 음수 입니다.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 함수가 바로 후행 조건의 예시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 함수 종료 시점에 전달될 객체 값이 유효한 값인지 검사하는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 약화된 다는 뜻은 무엇일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약화된다는 뜻은 후행 조건이 느슨해지는(완화되는) 것을 말한다. (위키피디아의 단어 선택은 정말 대단하다.)&lt;/p&gt;
&lt;pre id=&quot;code_1586619011034&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public int Method_A(int data)
{
    int result;
    
    // 실행 코드
    
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서 음수 조건을 제거해서, 후행 조건을 완화시켰다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하겠지만, 이 행동은 음수 조건을 리턴함으로써 Method_A를 호출하는 코드가 오작동을 일으키도록 만들 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후행 조건을 완화시키는 건 선행 조건을 추가하는 것과 같이 프로그램 코드에서 문제를 일으키게 만들어서 해서는 안 되는 행동이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하위형에서 상위형의 불변 조건은 반드시 유지되어야 한다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불변 조건이라는 말보단 불변 데이터라는 말이 더 와 닿을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모 클래스에 있는 데이터에 정의한 값의 조건은 하위형에서도 계속 유지되어야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1586619849811&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Parent
{
    public int Data;
    {
    	get
        {
            return _data;
        }
        set
        {
            if (value &amp;lt; 0)
            {
                _data = 0;
                return;
            }
            
            _data = value;
        }
    }
    
    protected int _data;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 _data 값이 항상 0이나 양수를 갖도록 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 자식 클래스에서 다른 함수를 만들어 _data 값을 바꾼다고 해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586619952974&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Child : Parent
{
    public void Method(int data)
    {
         _data = data;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 클래스에서 _data를 아무런 조건 처리 없이 그대로 덮어쓰기를 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 부모 클래스에서 외부로부터 유지하던 불변성이 깨져버려 프로그램에 문제를 일으키게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 부모 클래스를 구현할 시에 개방 폐쇄 원칙을 잘 지키거나, 자식 클래스를 구현하는 사람이 불변성을 깨뜨리지 않도록 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하위형에서 메서드는 상위형 메서드에서 던져진 예외의 하위형을 제외하고 새로운 예외를 던지면 안 된다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공변성과 반공변성을 들어가기에 앞서, 그나마 무난한 부분부터 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조건은 꽤나 직관적이라 이해하기 쉽다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 클래스의 함수에서 부모 클래스의 함수에서 던지는 예외를 제외하고 다른 예외를 던지지 말라는 얘기인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해, 사전에 약속하지 않은 예외를 던지면, 기존 프로그램 코드는 해당 예외를 캐치할 수 없어서 (혹은 적절한 처리를 할 수가 없어서) 문제를 일으킬 수 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공변성과 반공변성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 죽음의 데스가 찾아왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤나 생소한 단어인데, 뜻도 생소하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공변성: B가 A를 상속하는 상속관계 B -&amp;gt; A가 존재할 때, C&amp;lt;T&amp;gt;가 C&amp;lt;B&amp;gt; -&amp;gt; C&amp;lt;A&amp;gt;를 만족하면 이를 공변성을 띈다고 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;반공변성: B가 A를 상속하는 상속관계 B -&amp;gt; A가 존재할 때, C&amp;lt;T&amp;gt;가 C&amp;lt;A&amp;gt; -&amp;gt; C&amp;lt;B&amp;gt;를 만족하면 이를 반공변성을 띈다고 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 공변성과 반공변성은 제네릭과 관련되어 있는 정의이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무난한 공변성부터 설명하자면, 우리가 평소에 쓰던 Array가 바로 공변성의 한 예이다.&lt;/p&gt;
&lt;pre id=&quot;code_1586621009816&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void Main()
{
    Parent[] array = new Child[];
}

public class Parent { }

public class Child : Parent { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 다형성에 의해서, Parent를 상속하는 Child의 Array는 Parent[]로 치환해서 사용할 수 있다는 것을 너무나도 잘 알고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Child[]를 Parent[]로 치환할 수 있는 것, 이게 공변성이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 &lt;span style=&quot;color: #333333;&quot;&gt;IEnumerable가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;IEnumerable&amp;lt;Child&amp;gt;가 IEnumerable&amp;lt;Parent&amp;gt;로 캐스팅될 수 있는 이유는 IEnumerable&amp;lt;T&amp;gt;가 공변성을 띄고 있기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반공변성은 이와 반대되는 개념이다. 대표적인 예로 delegate&amp;lt;T&amp;gt;(Action&amp;lt;T&amp;gt;)&lt;span style=&quot;color: #333333;&quot;&gt;가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1586621882212&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void Main()
{
    Action&amp;lt;Parent&amp;gt; parentAction = (parent) =&amp;gt; { };
    Action&amp;lt;Child&amp;gt; childAction = parentAction;
    
    childAction(new Child());
}

public class Parent { }

public class Child : Parent { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드는 전혀 문제가 없는 코드이다. Child는 Parent를 상속하기 때문에, Action&amp;lt;Parent&amp;gt;를 Action&amp;lt;Child&amp;gt;로 치환해서 호출해도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Action&amp;lt;Parent&amp;gt;에 있는 람다식 함수는 작동에 전혀 문제가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;Action&amp;lt;Parent&amp;gt;&lt;/span&gt;를 &lt;span style=&quot;color: #333333;&quot;&gt;Action&amp;lt;Child&amp;gt;&lt;/span&gt;로 치환할 수 있는 것, 이게 반공변성이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다른 반공변성의 예로는 IComparable&amp;lt;T&amp;gt;이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IComparable&amp;lt;Parent&amp;gt;를 IComparable&amp;lt;Child&amp;gt;로 치환해도 문제없이 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;in / out 키워드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#에선 제네릭 타입의 interface와 delegate에 대해, 형 변환을 지원할 수 있게, 공변성과 반공변성을 위한 키워드를 제공하고 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 in (반공변성)과 out (공변성)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 예시로 언급한 IEnumerable&amp;lt;T&amp;gt;과 IComparable&amp;lt;T&amp;gt;의 정의를 MSDN에서 보면, 이 키워드들이 적용되어있는 것을 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586624055164&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IEnumerable&amp;lt;out T&amp;gt; : System.Collections.IEnumerable&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1586624089971&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IComparable&amp;lt;in T&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하위형에서 메서드 인수의 반공변성 / 반환형의&lt;span&gt;&amp;nbsp;공변성&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;공변성과 반공변성은 각각 리턴 값과 파라미터와 관련이 깊다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;리턴값과 파라미터가 공변성과 반공변성을 반드시 지켜줘야 작동에 문제가 없기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;공변성과 리턴 값에 대한 예시부터 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1586624506287&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void Main()
{
    Func&amp;lt;Parent&amp;gt; method1 = () =&amp;gt; new Child();
    
    Parent parent = method1();
}

public class Parent { }

public class Child : Parent { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예시는 리턴 값의 공변성을 지원하는 Func&amp;lt;T&amp;gt;에 대한 예시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Child에서 Parent로 형 변환이 가능해서, Func가 호출되어도, 문제가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아래와 같이 리턴 값에 반공변성을 지원해주면 코드는 문제가 된다,&lt;/p&gt;
&lt;pre id=&quot;code_1586624642390&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void Main()
{
    Func&amp;lt;Parent&amp;gt; method1 = () =&amp;gt; new Child_B();
    Func&amp;lt;Child_A&amp;gt; method2 = method1;
    
    Child_A child = method2();
}

public class Parent { }

public class Child_A : Parent { }

public class Child_B : Parent { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 C#에서 Func&amp;lt;T&amp;gt;에 공변성(out 키워드)을 적용시켰기 때문에 컴파일이 되지 않는 코드지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Func&amp;lt;T&amp;gt;가 &lt;span style=&quot;color: #333333;&quot;&gt;Func&amp;lt;Parent&amp;gt;에서 Func&amp;lt;Child_A&amp;gt;로의&lt;/span&gt;&amp;nbsp;반공변성까지 지원한다면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출 시, 리턴 값에 문제가 발생할 수 있다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 반공변성과 파리미터에 대하여 알아보자.&lt;br /&gt;앞서 보여준 Action&amp;lt;T&amp;gt; 예시를 다시 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1586624913954&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void Main()
{
    Action&amp;lt;Parent&amp;gt; parentAction = (parent) =&amp;gt; { };
    Action&amp;lt;Child&amp;gt; childAction = parentAction;
    
    childAction(new Child());
}

public class Parent { }

public class Child : Parent { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 예시는 파라미터의 반공변성을 지원하는 Func&amp;lt;T&amp;gt;에 대한 예시이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;파라미터 타입이 Parent에서 Child으로 바뀌어도 상관없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;어차피 실제로 호출되면 Parent로 형 변환되어 파라미터 값이 들어올 것이기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 아래와 같이 파라미터에 공변성을 지원해주면 코드는 문제가 된다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1586624812424&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void Main()
{
    Action&amp;lt;Child_A&amp;gt; childAction = (child) =&amp;gt; { };
    Action&amp;lt;Parent&amp;gt; parentAction = childAction;
    
    parentAction(new Child_B());
}

public class Parent { }

public class Child_A : Parent { }

public class Child_B : Parent { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것도 마찬가지로 &lt;span style=&quot;color: #333333;&quot;&gt;C#에서 Action&amp;lt;T&amp;gt;에 공변성(in 키워드)을 적용시켰기 때문에&lt;/span&gt; 컴파일이 되지 않는 코드지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Action&amp;lt;T&amp;gt;가&amp;nbsp;&lt;span style=&quot;color: #333333;&quot;&gt;Action&amp;lt;Child_A&amp;gt;에서 Func&amp;lt;Parent&amp;gt;로의&lt;/span&gt;&amp;nbsp;반공변성까지 지원한다면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출 시, 파라미터를 전달할 때 문제가 발생할 수 있다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자식 클래스를 구현하는데, 함수의 공변성과 반공변성을 왜 따지지?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 치환 원칙에서 어려운 공변성과 반공변성에 대한 조건을 요구하는 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는 부모 클래스에서 자식 클래스로 치환했을 때, 작동에 문제가 없으려면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 코드에 노출되어 호출되는 함수 또한 공변성과 반공 변성 또한 보장되어야 하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, C#에서는 제네릭 타입을 지원하기 때문에, 이에 대한 개념이 필요한 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다행히 C#에는 in / out 키워드가 있기 때문에, 공변성과 반공변성 적용에 머리를 끙끙거리며 고민할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 원칙은 참 어려운 원칙이다. 정의 자체가 확 와 닿지도 않고, 이해하기 어려운 공변성, 반공변성 정의까지 들어가 있는 데다,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 이 원칙이 알게 된다고 앞으로의 내 개발 환경이 막 드라마틱하게 좋아지거나 변하지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 요번에 포스팅을 위해 제대로 설명하려고, 구글에서 엄청 찾아봤는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 대충 대충 겉핥기식으로 설명하고 넘어가서 제대로 설명하는 곳은 거의 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 공변성/반공변성 언급하는 곳은 거의 손에 꼽을 정도이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설사 설명해도 그냥 사전에 나와있는 정도로만 설명할 뿐, 제대로 이해할 수 있도록 설명하는 블로그는 거의 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 잘못 이해해서 잘못 설명하는 블로그도 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 '상속 관계를 제대로 만들어야한다는게 이 원칙의 정의이다' 라고 소개하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에 언급한 위키피디아의 치환 관계가 제대로 성립되기 위한 조건들을 살펴보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 상속관계만 신경쓴다고 해서 지켜지는 원칙이 아닌 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말로... 이 원칙에 대해 알아보느라 몇 시간 동안 진땀을 뺐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 포스팅 작성하는데, 6시간 가까이 걸렸다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요점만 놓고 보면 그리 대단한 원칙도 아닌데 배우기만 힘든 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Refactoring &amp;amp; Clean Code</category>
      <category>Liskov Substitution Principle</category>
      <category>LSP</category>
      <category>Solid</category>
      <category>리스코프 치환 법칙</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/9</guid>
      <comments>https://pizzasheepsdev.tistory.com/9#entry9comment</comments>
      <pubDate>Sun, 12 Apr 2020 02:52:20 +0900</pubDate>
    </item>
    <item>
      <title>SOLID : 개방 폐쇄 원칙</title>
      <link>https://pizzasheepsdev.tistory.com/8</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;개방 폐쇄 원칙 (OCP: Open-Closed Principle)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 책임 원칙보다는 조금 애매하고 추상적인 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 책임 원칙은 '클래스가 한 동작만 하게 쪼개라!'라는 명확한 방향성이 있지만, 개방 폐쇄 원칙은 조금 다르기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 어떤 원칙이냐에 대해 설명하자면, 이론적으로는 &lt;b&gt;'확장에 대해서는 개방적이되, 수정에는 폐쇄적이여야 한다'&lt;/b&gt;이다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 원칙을 돌직구로 해석하면, 이렇다.&amp;nbsp;&lt;b&gt;'&lt;b&gt;확장(상속)할 수 있는 구조를 잡아서, 기존 코드를 &lt;/b&gt;수정할 일이 없도록 만들어라.'&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 알아서 나중까지 생각해서 구조와 코딩을 잘 해라이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참 꼰대 같은 원칙이 아닐 수 없다. 마치, 실력 없는 개발자가 아는 게 별로 없을 때, 애매하게 하는 말 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 행동 지침도 없다. 어떻게, 어느 정도로, 잘하냐는 온전히 개발자의 몫이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#을 기준으로 이 원칙에 대해 좀 더 포인트를 넣어 하나씩 설명하자면 이렇다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 코드는 되도록 수정이 되어선 안된다.&lt;/li&gt;
&lt;li&gt;코드를 수정하면 안 되므로 항상 코드는 확장을 있는 여지를 남길 수 있게 구현해야 한다.&lt;/li&gt;
&lt;li&gt;확장성을 위해, 다형성과 추상화를 이용한다. 이를 위해 클래스 상속과 인터페이스 상속 사용한다.&lt;/li&gt;
&lt;li&gt;클래스의 상속은 자식이 상위 부모들에게 의존적이게 만드는 단점이 있다. 부모가 수정되면, 자식도 수정될 가능성이 크다.&lt;br /&gt;때문에 되도록 상속 단계는 낮게 (1, 2 단계 정도만) 해야 한다.&lt;/li&gt;
&lt;li&gt;인터페이스 상속이 클래스 상속보다, 더 구조의 유연함을 가져다준다. 하지만, 인터페이스 정의 시, 모든 상속 클래스가 수정을 해야 하므로 수정이 되지 않도록 &lt;b&gt;잘&amp;nbsp;&lt;/b&gt;선언해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어떻게 상속 구조를 짤지, 어떤 걸 추상 멤버(메소드, 속성)로 할지는 개발자의 몫이다.&lt;br /&gt;미래의 변화에 대해 예측을 잘해서 추상화를 해야 하며.&lt;br /&gt;너무 과하게 예상해서 추상시켜도 안되고, 아무것도 확장할 수 없게 다 폐쇄적으로 숨기면 안 된다. 적당히 알아서 잘해야 한다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;확장할 수 있는 것과 없는 것을 명확하기 위해, 상속해서는 안 되는 클래스는 sealed 키워드를 사용하라.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;볼드체 한 부분이 문제다. abtract class, interface, virtual, sealed 키워드 같은 것을 이용해 확장의 여지를 고려해야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떻게, 어느 정도로 해야 하는지는 기준이 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분은 순전히 경험으로 극복하는 수밖에 없다. 많은 경험을 해서, 개방해야 될 것과 감춰야 할 것을 구별할 수 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링에 있어서, 조심해야 할 적은 바로 과한 수정과 예측이 아닐까 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 코드를 수정하기 전에, 해당 코드가 정말 리팩토링이 필요한지 고려를 잘해야 한다. 시간과 돈, 인력 자원은 한정적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한정된 자원으로 적절하게 시간을 투자해야 하는데, 구조적으로 투박하고 문제가 많아 보이는 코드라도 작동에 문제가 없고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;미래에 수정의 여지가 없는 코드이면, 무리하게 시간을 써가면서까지 수정을 할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링이라는게 의존성을 없애기 위해 하는 작업이기에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스와 코드가 많아지고 파일이 분산돼서 구조가 이전보다 복잡해질 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로, 너무 심하게 미래에 미래에 미래까지 예측해서, '이건 한 5년 뒤면 필요할 거야' 하면서 구현하거나&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'이 구조는 이런 게 문제고 저 구조는 이런게 문제고...' 하면서 추상화 디자인에 너무 시간을 많이 소비하는 것 경우도 있는데.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 또한 조심해야 한다. 코드 구조가 알아볼 수 없게 이전보다 복잡해지거나, 기획만 하다가 시간이 다 지날 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 개발할 때, 주어진 시간을 &lt;s&gt;적절히&lt;/s&gt; 고려해서 어느 게 최선인지를 &lt;s&gt;적절히&lt;/s&gt; 생각하며 &lt;s&gt;적절히&lt;/s&gt; 작업하는 게 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(근데, 솔직히 이런 거 잘할 줄 알면, 다 프로그래밍 초고수였을 거다.)&lt;/p&gt;</description>
      <category>Refactoring &amp;amp; Clean Code</category>
      <category>OCP</category>
      <category>Open-Closed Principle</category>
      <category>Solid</category>
      <category>개방 폐쇄 원칙</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/8</guid>
      <comments>https://pizzasheepsdev.tistory.com/8#entry8comment</comments>
      <pubDate>Fri, 10 Apr 2020 21:09:08 +0900</pubDate>
    </item>
    <item>
      <title>SOLID : 단일 책임 원칙</title>
      <link>https://pizzasheepsdev.tistory.com/7</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리팩토링과 SOLID 원칙은 &lt;span style=&quot;color: #333333;&quot;&gt;서로 간에 연관이 아주 깊다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 모두 유지보수가 쉽고, 이해하기 쉬운 코드를 짜기 만들기 위함이라는 목적이 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단일 책임 원칙 (&lt;span style=&quot;color: #333333;&quot;&gt;SRP: Single Responsibility Principle)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 &lt;b&gt;'클래스, 메서드는 하나의 책임만을 가져야 한다'&lt;/b&gt;라고 알려진 원칙이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책임이라고 하니 뜻이 되게 애매한데, 그냥 &lt;b&gt;'각각의 클래스, 메서드는 한 가지 짓만 해야 한다'&lt;/b&gt;라고 하면 이해하기 쉬울 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 유저의 계정을 관리하는 AccountManager라는 클래스가 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계정&lt;span style=&quot;color: #333333;&quot;&gt; 관련 기능을 다 AccountManager에 구현했다고 가정해보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1586504862998&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AccountManager : IAccountManager
{
    public void LogIn()
    {
    	...
    }
	
    public void LogOut()
    {
    	...
    }
    
    public AchievementData GetAchievementData()
    {
    	...
    }
    
    public void UpdateAchievementData()
    {
    	...
    }
    
    public StatisticsData GetStatisticsData()
    {
    	...
    }
    
    public void UpdateStatisticsData()
    {
    	...
    }
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 코드를 보면 단순해 보이지만, 만약 내부 함수와 실제 구현 코드를 포함하면 더 많은 코드를 포함하고 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 클래스가 다른 사람이 구현한 코드이고, 중간에 자신이 참여해서 업적 관련 기능을 수정한다고 해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 다른 구현된 기능들이 뒤섞여 있어서, 업적 관련 코드를 파악하기도 어렵고, 제대로 디버깅하기도 힘들 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결 해기 위해서 코딩을 할 때, 또는 위와 같은 코드를 리팩토링 할 때는, 단일 책임 원칙을 따를 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원칙을 지키는 법은 간단하다, 앞서 말한대로 클래스는 한 가지 짓만 해야 하므로 기능 별로 분리하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 로그인(LogInService), 업적(AchievementService), 기록(StatisticsService) 클래스로 분리할 수 있을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1586506007713&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LogInService : ILogInService
{
    private IAccountDataService accountDataService;

    public LogInService(IAccountDataService accountDataService)
    {
    	
    }

    public void LogIn()
    {
    	...
    }
    
    public void LogOut()
    {
    	...
    }
}

public class AchievementService : IAchievementService
{
    public AchievementData GetData()
    {
    	...
    }
    
    public void UpdateAchievementData()
    {
    	...
    }
}

public class StatisticsService : IStatisticsService
{
    public StatisticsData GetStatisticsData()
    {
    	...
    }
    
    public void UpdateStatisticsData()
    {
    	...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 나중에 기능 추가, 수정도 쉽고, 관련 작업자들도 기존 기능을 파악하기 쉬울 것이다.&lt;/p&gt;</description>
      <category>Refactoring &amp;amp; Clean Code</category>
      <category>Single Responsibility Principle</category>
      <category>Solid</category>
      <category>SRP</category>
      <category>단일 책임 원칙</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/7</guid>
      <comments>https://pizzasheepsdev.tistory.com/7#entry7comment</comments>
      <pubDate>Fri, 10 Apr 2020 17:56:39 +0900</pubDate>
    </item>
    <item>
      <title>리팩토링에 대한 개인적인 생각</title>
      <link>https://pizzasheepsdev.tistory.com/6</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;예전 신입이나 취준생일 때는 리팩토링이란 것을 엄청 무언가 대단한 작업이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 영어 단어니까 뽀대나고, 뭔가 내가 생각하지 못한 엄청난 기술이 들어가서 코드를 환골탈태시키는 일일 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 사실 들여다 보면 별거 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 리팩토링의 의미, 의의 자체는, &quot;기존의 코드를 개선시키는 작업&quot;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러니까, 15년차 프로그래머가 하든, 시니어가 하든, 주니어가 하든, 아마추어가 하든.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 코드가 너무 보기 어렵거나, 유지보수가 힘들다고 느껴질 때, 이를 개선하는 행위 자체가 그냥 &lt;span style=&quot;color: #333333;&quot;&gt;리팩토링&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'아 코드 너무 더럽다. 코드 좀 바꿔야 할 듯' 하고 행동에 옮기면 &lt;span style=&quot;color: #333333;&quot;&gt;리팩토링&lt;/span&gt;을 하고 있는 거다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 리팩토링이란 단어에 얽메이거나, 두려워할 필요는 없다. 코딩이란게 그렇듯, 정답은 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 리팩토링이란 것이 궁극적으로 '유지보수가 쉽고, 이해하기 쉬운 코드' 같은 게 목표이기 때문에, 리팩토링을 했다 하더라도,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 어설프게 문제점은 그대로 가져가면서, 개선은 안되거나, 오히려 코드 구조만 더 복잡해지는 문제는 피해야 하기 때문에.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느 정도 리팩토링 기법들을 알아두는 게 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만,&amp;nbsp; '아 리팩토링에 대해서 공부해야지! 이거 전부 다 배운 다음에 제대로 리팩토링 할 거임ㅎㅎ'라고 생각하기보단,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'어떻게 하면, 내 코드를 개선시킬 수 있을까'라는 느낌으로 접근해서, 하나하나 차근차근 적용해 나가면서 배우는 게 좋다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 개선하기 위한 좋은 리팩토링 기법들은 어찌 보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 코드를 만드는 법을 배우는 것이기에 일단 습관이 베게 하는 게 가장 중요할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;억지로 머릿속에 넣어봤자, 막상 코딩을 하면 또 예전 습관이 베어 나올 것이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;사실 나도 이렇게 말은 했지만, 아직 제대로 알지도 못하고 좋은 코딩 습관이 제대로 몸에 베지도 않았다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 기회에 여러 기법들을 포스팅해가면서, 배워보려고 한다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Refactoring &amp;amp; Clean Code</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/6</guid>
      <comments>https://pizzasheepsdev.tistory.com/6#entry6comment</comments>
      <pubDate>Fri, 10 Apr 2020 14:49:53 +0900</pubDate>
    </item>
    <item>
      <title>Unit Tests가 게임 개발에 필요할까?</title>
      <link>https://pizzasheepsdev.tistory.com/5</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;나는 Unit Tests(단위 테스트)를 별로 선호하는 편이 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 회사에서 한동안 단위 테스트를 해보았는데, 테스트 케이스를 작성하고 테스트 코드를 작성하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체감상 그 기능을 개발하는 시간만큼의 시간이 들어가는 느낌이었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 '솔직히 게임 클라이언트 개발에 단위 테스트는 별로 안 맞지 않나?'라는 생각을 하고 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러다가, 한번 이번에 다른 사람은 어떤 생각을 가지고 있을까 하고 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 레딧에서 꽤 괜찮은 코멘트를 하나 발견했다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;...&lt;br /&gt;&lt;br /&gt;The single biggest benefit I have personally experienced with writing tests is the impact it has on your code design, not the execution of the test itself. To make code testable you end up doing many things like having small, single-responsibility classes, separating &lt;br /&gt;presentation and logic, injecting dependencies, minimizing number of publicly exposed methods, etc. etc. I would argue that these are almost always good thing. This is why writing tests early (even if not exactly TDD) is more valuable than retrofitting them afterwards.&lt;br /&gt;&lt;br /&gt;...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/gamedev/comments/4ghpwe/unit_testing_in_game_development/d2hpozk/&quot;&gt;https://www.reddit.com/r/gamedev/comments/4ghpwe/unit_testing_in_game_development/d2hpozk/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 설명하자면, &lt;span style=&quot;color: #333333;&quot;&gt;코멘트 작성자가 느낀 개인적인 단위 테스트의 가장 큰 장점은,&lt;/span&gt; 그 테스트 자체에 있다기 보단, 그 테스트를 하기 위한 과정들이 가장 큰 장점이라는 것이다. 그 과정들 자체가 코드의 품질을 좋게 유지시킨다고 얘기하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 단위 테스트를 하기 위해선, 클래스 자체의 코드 품질이 꽤 좋아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 조건들로는 다음과 같은 게 필요한데,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 데이터를 사용하지 않고&lt;/li&gt;
&lt;li&gt;단일 책임 원칙을 지키고&lt;/li&gt;
&lt;li&gt;로직과 출력 코드를 분리시키고&lt;/li&gt;
&lt;li&gt;클래스 간의 의존성을 줄이고&lt;/li&gt;
&lt;li&gt;메소드 노출을 최소화&lt;/li&gt;
&lt;li&gt;... 등등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이와 같이 단위 테스트를 하기 위해선 클래스에 많은 노력이 들어가야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 조건들이 맞아야지만 단위 테스트를 할 수 있기 때문에, 단위 테스트를 준비하는 것 자체가 코드 품질을 좋게 유지시킨다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 코멘트 작성자는 단위 테스트는 필요는 하지만 너무 쓸데없이 모든 것을 테스트하려고 하거나 아예 안 하거나 하기보단, 딱 적절한 테스트 수준을 유지하는 게 나은 것 같다고 주장했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코멘트를 읽고 꽤 맞는 이야기라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면 예전에 단위 테스트 코드를 만들 때, 테스트를 에러 없이 통과하기 위해서 이것저것 코드를 수정하면서 노력을 많이 해야 했고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;덕분에 관련 코딩 습관을 어느 정도 베개 할 수 있었기 때문이다. (아직 완벽하진 않지만)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어찌 보면, 꽤 좋은 선생님 역할을 해준 셈이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코멘트를 읽으며, 어느 정도 단위 테스트가 필요할 수 있겠구나를 생각할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 지금 프로젝트를 진행 중인데, 높은 코드 품질을 장기적으로 유지하고, 다른 개발자분들이 유지보수에 좋은 코딩에 익숙하지 않다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트를 적용해보는 것도 팀의 실력 향상과 프로젝트의 품질에 꽤 좋은 도움을 줄 거 같다고 생각한다.&lt;/p&gt;</description>
      <category>Programming</category>
      <category>Unit Tests</category>
      <category>단위 테스트</category>
      <category>유닛 테스트</category>
      <category>유지보수</category>
      <category>코드 품질</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/5</guid>
      <comments>https://pizzasheepsdev.tistory.com/5#entry5comment</comments>
      <pubDate>Tue, 7 Apr 2020 20:09:37 +0900</pubDate>
    </item>
    <item>
      <title>명시적 인터페이스 구현의 접근자는 Public일까?</title>
      <link>https://pizzasheepsdev.tistory.com/4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;명시적 인터페이스를 꽤 최근에서야 알게 되었는데 (ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 봤을 때 되게 독특한 녀석이라고 느꼈었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;누가 봐도 평소 내가 하던 C# 코딩과는 좀 이질적으로 느껴졌었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명시적 인터페이스 구현에 대하여&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일반적으로 명시적 인터페이스 구현은 한 클래스가 여러 인터페이스를 구현할 때, &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이름이 겹쳐서 생기는 문제를 방지하기 위해 사용되는 기능이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다음과 같은 코드가 예이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1586234530414&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface IMachine
{
    string Id { get; set; }
    
    void Run();
}

public interface IRunnable
{
    int Id { get; set; }

    void Run();
}

public class Machine_A : IMachine
{
    string IRunnable.Id { get; set; }

    int IMachine.Id { get; set; }

    void IRunnable.Run()
    {
        Console.WriteLine(&quot;Run!&quot;);
    }
        
    void IMachine.Run()
    {
    	Console.WriteLine(&quot;Machine starts operation.&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IMachine과 IRunnable의 Id라는 속성과 Run이라는 메소드가 겹친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;일반적으로 이런 상황이 벌어지면 한쪽 이름을 적절하게 변경하면 되겠지만.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약, IMachine이 A사의 라이브러리에서 정의한 인터페이스고 IRunnable이 B사의 라이브러리에서 정의한 인터페이스라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 코드가 아니라 수정할 수가 없다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(함수라면 하나의 메소드로 작동해서 IRunnable이건 IMachine이건 같은 동작을 하겠지만, 상식적으로 서로 다른 라이브러리 인터페이스를 사용하는데 같은 동작을 한다면 나중에 좋은 꼴을 보지 못할 것이란 걸 짐작할 수 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결할 때 적절한 게 바로 명시적 인터페이스 구현이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이 녀석 참 특이한 게 인터페이스명을 함수명 앞에 붙이고 접근 한정자도 붙이지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참 어색하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 특이한 건 이 명시적 인터페이스 구현의 특징인데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구현된 인터페이스 요소(속성, 메소드, 이벤트)들은 절대로 해당 클래스의 참조값으로는 접근할 수 없고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 인터페이스로 형변환을 해줘야 호출할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586234699334&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MainClass
{
    public static void Main(string[] args)
    {
        Machine_A machine_A = new Machine_A();
        IMachine machine = machine_A as IMachine;

        // 인터페이스로 형변환을 해준뒤 인터페이스 참조값으로만 접근할 수 있다.
        machine.Run();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 구현된 같은 클래스 내부에서도 함부로 호출할 수가 없다. ㄷㄷ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반드시 같은 클래스라도 형변환을 해야 호출할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1586235585413&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Machine_A : IRunnable, IMachine
{
    string IRunnable.Id { get; set; }

    int IMachine.Id { get; set; }

    void IRunnable.Run()
    {
        IRunnable thisRunnable = this as IRunnable;
        Console.WriteLine(&quot;Machine_A runs! &quot; + thisRunnable.Id);
    }

    void IMachine.Run()
    {
        Console.WriteLine(&quot;Machine_A operates!&quot;);
    }

    public void WriteLog()
    {
        IRunnable thisRunnable = this as IRunnable;
        thisRunnable.Run();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명시적 인터페이스 구현의 접근 한정자는 뭘까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자, 그럼 이렇게 구현된 인터페이스 요소들은 한정자를 지정해주지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 제일 궁금한 점이 바로, 이 녀석의 접근 한정자가 뭘까이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 그들은 클래스의 참조로 접근할 수 없는데, 인터페이스를 캐스팅한 참조 변수로는&amp;nbsp;참조가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길게 가지 않고, 결과만 얘기하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그들은 private이면서 public이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상하게 들리지만 그렇다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 왜 private이냐면, 그들이 컴파일로 나왔을 때, 컴파일에 의해 지정된 접근 한정자가 private이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 Machine 클래스 코드를 컴파일하고 어셈블리를 뜯어보면 Machine.IRunnable.Run은 이렇게 되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpS5h0/btqDgx0A86M/zARzQ02oKBqG7kYS5FiBxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpS5h0/btqDgx0A86M/zARzQ02oKBqG7kYS5FiBxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpS5h0/btqDgx0A86M/zARzQ02oKBqG7kYS5FiBxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpS5h0%2FbtqDgx0A86M%2FzARzQ02oKBqG7kYS5FiBxK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1200&quot; height=&quot;510&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 필자는 IL 코드를 잘 모르기 때문에 이게 무슨 의미인지는 어렴풋이 만 추측할 뿐이지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;눈에 띄는 부분이 바로 함수명 쪽이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;private&lt;/span&gt; final hidebysig newslot virtual instance void TestLibrary.IRunnable.Run() cil managed&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 private으로 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 명시적 인터페이스로 구현된 요소는 private이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 실제로는 조금 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;interface의 기본적인 특징으로 모든 요소는 public이기 때문에, 다른 클래스에서도 인터페이스로 캐스팅만 하면 접근이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하지만, 다른 어셈블리에서도 호출이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(필자는 테스트를 해보았지만, 생략하겠다. ㅎㅎ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private인데, public처럼 캐스팅을 하면 호출이 가능하기에 이 녀석은 private이면서 public인 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이유?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 위에서 글을 마무리하려고 했는데, 몇 가지 il코드를 둘러보다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭔가 의심되는 부분을 발견했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2020-04-07 오후 2.39.40.png&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;1002&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coGa34/btqDhvBe4ri/qdmHnFpfTNloMgErsYD0y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coGa34/btqDhvBe4ri/qdmHnFpfTNloMgErsYD0y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coGa34/btqDhvBe4ri/qdmHnFpfTNloMgErsYD0y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoGa34%2FbtqDhvBe4ri%2FqdmHnFpfTNloMgErsYD0y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1192&quot; height=&quot;1002&quot; data-filename=&quot;스크린샷 2020-04-07 오후 2.39.40.png&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;1002&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 IRunnable 인터페이스의 IL 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데, 코드를 살펴보면 꽤 신기하게 생겼다.&lt;/p&gt;
&lt;pre id=&quot;code_1586238641405&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.class interface public auto ansi abstract TestLibrary.IRunnable
{
    ....
    .method public hidebysig newslot abstract virtual instance void Run () cil managed 
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;인터페이스 코드가 마치 추상 클래스처럼 되어 있고&lt;/span&gt; 메소드는 마치 추상 메소드처럼 구현되어 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헉, 너무 신기하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, IL 코드로 추측할 수 있는 의미는 인터페이스는 결국 내부적으론 추상 클래스처럼 다뤄지고 있다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;헉 사실 내가 클래스를 사용하고 있었다니.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;똑같이 class이면서 굳이 인터페이스라고 따로 둔 이유는, 아마, 인터페이스 같은 완전 추상 클래스의 이점은 가져가면서 다중 클래스 상속의 문제를 회피하기 위해서가 아닐까 추측해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 명시적 인터페이스로 구현된 private 요소를 접근할 수 있는 이유에 대해서도 납득할 수 있는 추측을 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;기본적으로 클래스에 정의 된 요소는 private이기 때문에 접근할 수 없지만, &lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;인터페이스에서는 public으로 선언되었기 때문에,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;명시적으로 구현된 인터페이스 요소들은 해당 인터페이스로 형변환한 후에만 접근 가능한 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 만약 해당 인터페이스 요소를 접근하게 되면, 이는 abstract로 선언된 것들이기 때문에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 오버라이드에 의해서 클래스 내에 구현된 요소들이 호출된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 위 Machine_A.Run의 IL코드를 다시 보면,&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1586239360618&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private final hidebysig newslot virtual
    instance void TestLibrary.IRunnable.Run() cil managed
{
    .override method instance void TestLibrary.IRunnable::Run()
    
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되어 있는데, 이 코드를 아까 서술한 이유를 떠올리면서 읽어보면 이렇게 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;'void IRunnable.Run 메소드는 가상 메소드이며, TestLibrary.IRunnable::Run()을 재정의 한다.'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;즉, Run 함수는 인터페이스의 추상 메소드를 구현하는 것이기 때문에 virtual 속성이 붙고,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;명시적 인터페이스 구현에 의해, &lt;span style=&quot;color: #333333;&quot;&gt;컴파일일 할 때,&lt;/span&gt;&amp;nbsp;&lt;/span&gt;.override method instance void TestLibrary.IRunnable::Run() 코드가 추가되어&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 함수는 IRunnable.Run()을 오버라이드 한 것임을 명시적으로 정의하고 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론은 아까 내린 것과 변함이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;명시적으로 구현된 인터페이스 요소들은 private이지만, 인터페이스에선 public으로 선언됐기 때문에 public이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;하지만, 이유를 알게 되었기 때문에 좀 더 정확하게 이것의 접근자는 이렇다!라고 얘기할 수 있게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;[참고]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;알다시피, 인터페이스는 public 외에 internal로도 지정할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;해당 어셈블리 내에서만 사용되길 원한다면, internal로 지정하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;p.s.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 이유를 빼고 끝내려고 했던 건데, 찾다 보니 처음 글 쓰던 의도와 다르게 꽤 재미난 사실을 알게 되어서 좋은 시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programming</category>
      <category>C#</category>
      <category>Explicit Interface Implementation</category>
      <category>interface</category>
      <category>명시적 인터페이스 구현</category>
      <author>피자 먹는 양</author>
      <guid isPermaLink="true">https://pizzasheepsdev.tistory.com/4</guid>
      <comments>https://pizzasheepsdev.tistory.com/4#entry4comment</comments>
      <pubDate>Tue, 7 Apr 2020 15:24:46 +0900</pubDate>
    </item>
  </channel>
</rss>