IDictionary VS Dictionary 차이점이 뭔가요?

안녕하세요~ 먼저 제글에 관심을 보여주셔서 감사합니다

접두사 "I"가 붙으면 인터페이스라는 것은 알고 있습니다
그리고 인터페이스는 (제가 이해하는 바) 인터페이스를 상속받은 클래스는
인터페이스에서 정의한 멤버가 꼭 들어가야하는 것으로 알고 있습니다
클래스가 설계도라면 인터페이스는 설계도에 꼭 들어가야하는 것들을 명시(?) 하는 정도로 알고 있는데요

질문입니다

책을 보다가
사전 클래스 인스턴스 생성을 하는 문장을 봤는데요 (아래)

IDictionary<string, string> data = new Dictionary<string, string>(); 
// 클래스의 인스턴스 생성 : IDictionary 인터페이스로 받기

왜 Dictionary 로 안만들고 IDictionary 인터페이스로 만들까요?
차이점은 뭐고 어떠한 장점이 있는지 질문드립니다…

또한…

IDictionary<string, string> 대신 var를 쓰다면 var는 Dictionary를 뜻하나요?

감사합니다!!

좋아요 2

이후 부분이 IDictionary 인지 아닌지만 중요하면 그렇게 하는게 장단기적으로 나중에 편할 거라고 생각하기 때문입니다.

좋아요 3

다형성을 위해 인터페이스 객체로 사용하는 것이 유지보수 차원에서 장기적으로 더 좋습니다.

인터페이스로 받아 처리 되면 추후에 Dictionary 구조자료가 아닌 다른 구조자료로 변경해서 처리 할때 더 유연하게 처리 할 수 있습니다.

위 답변은 네, Dictionary<string, string>() 타입으로 표현됩니다.

좋아요 6
  1. 인터페이스를 그냥 사용 설명서 정도로 이해하면 좋습니다.
    즉, Dictionary를 직접 쓴 코드보다 IDictionary를 쓴 코드가 이해하기 쉽습니다.
  2. 결합도를 낮춥니다.
    이 글이 짧지만 잘 정리해주셨네요.
  3. @aroooong 님 말씀처럼 다형성 특성을 사용할 수 있고 IDictionary 인터페이스를 준수하는 다른 클래스 인스턴스로 쉽게 변경 가능합니다.
    예) 여러 개의 스레드에서 동시에 액세스할 수 있는 스레드 안전 사전 - ConcurrentDictionary<TKey,TValue> : IDictionary<TKey,TValue>
  4. 인스턴스를 인터페이스로 사용하는 다른 경우는 IReadOnlyDictionary입니다. 사전을 읽기만 하도록 사용 설명서에서 관련 기능을 뺀 경우입니다. (인스턴스는 동일하다는게 핵심입니다)
    • Dictionary<TKey,TValue> : IReadOnlyDictionary<TKey,TValue>
좋아요 6

이게 c 계열 언어의 상속과 비슷한 문법을 쓰기 때문에
처음 배울 때 오해를 많이 하게 되는데,

인터페이스는 상속하고는 관련이 없어요. 상속은 클래스에서만 사용합니다.

대신 인터페이스는 일종의 계약서 라고 보시면 됩니다.
타입을 사용하는 구조에서 인터페이스는,
‘반드시 여기 정의된 것을 구현하라!’ 라는 의미를 지닌 계약서라고 보면 됩니다.

이 계약은 클래스 뿐만 아니라 다른 구조체에도 적용될 수 있습니다.

따라서 이 계약서를 구현하는 타입은
모두 해당 인터페이스 타입으로 간주하겠다. 라는 의미와도 같다고 보면 됩니다.

이런 예시를 드셨는데,
이걸 이렇게 쪼개서 보자면

IDictionary<string, string> data;
data = new Dictionary<string, string>();

이건 당연히 가능하겠죠? 왜냐하면 Dictionary<, >IDictionary<,> 를 구현하고 있으니까요.

여기에 만약 CustomDictionary<,> 라는 녀석이 있다 치고
이 녀석이 IDictionary<,> 를 구현하고 있다면

IDictionary<string, string> data;
data = new CustomDictionary<string, string>();

이것도 가능하다는 얘기입니다.
왜냐하면 IDictionary<,> 라는 인터페이스를 구현했다면 같은 타입으로 인정하게 되니까요.

만약 data 의 선언을

Dictionary<string, string> data;

이렇게 해버리면, 이 data는 죽었다 깨어나도 Dictionary<string, string> 만 할당할 수 있어요.
(물론 Dictionary<,> 에서 파생된 객체는 할당할 수 있지만서도…)

하지만 이렇게 인터페이스로 타입을 선언하면,
해당 인터페이스를 구현한 어떠한 타입이라도 할당할 수 있습니다.

상황에 따라서 여러 종류의 타입을 담을 수 있게 되는 거죠.
(당연하지만 한 번 한 개의 인스턴스만 담을 수 있어요. 오해 ㄴㄴ)

이런식으로 다형성을 지원하는 게 인터페이스의 핵심이에요.

좋아요 9

컴퓨터 하드웨어 인터페이스로 설명을 드린다면…
우리가 자주 접하는 USB, HDMI등이 모두 인터페이스에 따라 사용이 가능합니다.
사용자들은 내부에 어떤 방식으로 되어있는지는 몰라도 USB등의 사용이 가능합니다.

USB인터페이스로 되어 있는 곳에 HDMI를 이용할 수는 없습니다.
USB 2.0 인터페이스로 되어 있는 곳에는 USB 1.0을 사용할 수 있습니다.

[참조]

좋아요 4

@favdra 답변 감사합니다!!
@aroooong 다형성이 왜 필요한지 공부해보겠습니다 감사합니다!!
@dimohy 핵심을 간결하게 표현해주셔서 감사합니다!! 이해하기 쉽다는 것과 결합도를 낮춘다는 것이 무엇인지 공부해보게습니다 다시 한번 감사드립니다!!

좋아요 1

@Greg.Lee 자세한 답변 정말 감사드립니다 ㅜ
다형성을 지원할 수 있기 때문에 인터페이스를 사용하는 것이 좋다는 부분을 얼추 이해할 수 있었습니다
또한 인터페이스를 구현했다는 (상속이 아닌) 의미도 이해할 수 있었습니다
감사합니다!!

그렇다면 IDictionary VS Dictionary 에서 항상 IDictionary 로 개체를 만드는 것이 맞는걸까요?
인터페이스에 많은 장점이 있다면 굳이 Dictionnary<T,T> 로 개체를 만들 이유가 궁금합니다

좋아요 1

아주 좋은 질문이네요!

설명을 워낙 많은 분들이 잘 해주셔서 더 이상 할 이야기는 없을 것 같고 이해를 돕기 위해 몇 가지 추가 하고 싶습니다.

이 말은 실제로 인터페이스를 구현하겠다고 선언하고 구현을 하지 않으면 컴파일 에러가 나서 프로그램을 실행 할 수가 없습니다. 고로 프로그램이 컴파일 에러 없이 컴파일 되었다면 구체 클래스를 그 구체 클래스가 구현하겠다고 선언 한 인터페이스로 형 변환을 해도 안전하다고 판단할 수 있게 됩니다. 이런 계약으로 인해 안전한 타입을 추구하는 C#에서도 구체 클래스를 그 클래스가 구현하고 있는 인터페이스로 형 변환하여 그 인터페이스에 있는 함수를 호출해도 문제가 없다고 판단할 수 있습니다.

그리고 인터페이스를 이용해서 프로그래밍 하면 코드가 구체 클래스의 세세한 부분까지 신경쓰지 않고 코드의 로직이 추상적으로 코드가 흘러가 논리적으로 읽히는 코드가 됩니다.

public Interface ISaveService
{
   bool Save();
}
public class FileSaveService : ISaveService { // 생략 }
public class DBSaveService : ISaveService { // 생략 }

위와 같은 Save() 함수를 갖는 ISaveService 인터페이스가 있고
ISaveService를 파라미터로 받아서 Save()를 호출 하는 함수가 있다면
호출자 입장에서는 아래 주석과 같이 생각할 수 있습니다.

public bool Save(ISaveService saveService)
{
 // 어디에 어떻게 저장되는진 모르겠지만 
 // 이 함수를 호출하는 사람이 원하는 방식대로 (파라미터로 넣어 준 타입대로)
 // DB든 파일이든 메모리든 어딘가에 저장이 될거야
 // 그 저장이 올바로 완료되면 true를 반환해줄거야. 
 // 저장은 내 역할이 아니고 ISaveService를 구현하는 클래스의 역할이야
 // 난 저장이 어떻게 되는지 구체적인건 신경쓰지말고 저장 이후의 로직에 신경쓰자.
  bool result = saveService.Save();
}

너무 구체적인 사항까지 들여다 봐야 하는 코드들이 많을 수록 유지보수가 힘들고 if문이 많아지며 그만큼 코드가 복잡해집니다.

사실 인터페이스를 이해하고 잘 사용하는 것은 생각보다 쉽지 않습니다. 많이 고민해보고 사용해보세요 :grinning:

좋아요 4

인터페이스는 기능의 사양만 명시하고 있을 뿐, 그 기능의 구현은 인터페이스를 구현하는 클래스의 몫입니다.

가령 프로퍼티를 IDictionary로 노출하더라도, 그 프로퍼티를 할당하는 측에서는 결국 IDictionary를 구현하는 특정한 클래스로 초기화해줄 수밖에 없습니다. 인터페이스는 그 자체만으로는 초기화 등 값을 할당할 수 없으니까요.

그리고 만약 프로퍼티를 외부로부터 주입받는 상황이라면 더 복잡해지는데, 인터페이스로 입력받는다면 외부에서 입력받는 인터페이스의 구현체가 내가 원하는 동작을 하는지도 고려해야 될 수 있어요. IDictionary를 가지고 극단적인 예시를 들어보면, Add 메소드를 호출했는데 Dictionary에 값이 추가되지 않거나 같은 Key를 갖는 복수의 Value가 생긴다든지, TryGetValue에서 true를 반환받아 값을 확인해보니 out은 null이라든지 하는 이뭐병한 상황을 마주할 수도 있는 거죠.

Dictionary로 받는다면 내가 기대했던 동작을 했을 거에요. Dictionary 클래스의 Add나 TryGetValue는 virtual이 아니니까, Add 메소드를 호출하면 값이 추가되거나 갱신될 거고, TryGetValue 메소드를 호출하면 Key를 이용해 값이 있는지 없는지 확인한 후 bool을 반환한다는 것을 신뢰할 수 있어요. 물론 굳이 Dictionary가 아니더라도 마찬가지고, 반드시 내가 원하는 동작만을 해야 한다면 상속조차 불가능하도록 클래스를 sealed로 선언한 후 노출할 수도 있어요.

그런 면에서 보면 저는 인터페이스를 노출할 것인지, 구현체를 노출할 것인지의 여부는 다형성 지원에 더해서 아래 같은 선택 기준이 추가된다고 생각해요.

  • 외부에 최소한의 기능만을 노출할 것인지 or 이미 구현된 많은 기능을 노출할 것인지
  • 외부에서 주입하는 객체의 동작을 신뢰할 수 있는지 or 100% 내가 원하는 동작만을 하는 객체를 입력받을 것인지
좋아요 3

부가적으로 C#의 상속/구현 문법에 대해서는 @Greg.Lee 님 의견에 동의합니다. 개인적으로 상속/구현 문법은 Java 스타일이 좀 더 직관적이라고 생각해요. C#은 단순할 지언정, 입문하는 입장에서 클래스 상속/인터페이스 구현을 다른 개념으로 받아들이기에는 좀 직관적이지 못하죠.

좋아요 1

추가 질문에 대한 답변 감사합니다! 아직 잘 이해되진 않지만 이를 바탕으로 공부해보겠습니다 감사합니다!!

좋아요 1