인터페이스를 사용하는 기준

저는 이렇게 이해하고 있습니다.

여러 곳에서 사용되지만 컴파일 타임에 대상이 결정될 때 : 제네릭을 통해 구현(제네릭 제약조건에서 인터페이스를 사용)

컴포지트 패턴, 커맨드 패턴 등 런타임에 임의의 대상이 결정될 때 : 인터페이스 사용

제가 오해하고 있는 부분이 있을까요? 애매해서 여쭤봅니다.

5개의 좋아요

애매하다고 하셨는데, 부연 설명을 조금 더 드리면 좋을 것 같아 글을 남깁니다!

인터페이스를 사용하는 경우는 여러 클래스에 걸쳐서 "공통된 기능"을 정의해야 할 때입니다. 이 때 인터페이스를 구현하는 클래스들은 서로 직접적인 관련성이 크게 떨어질 수도 있지만, "공통된 기능"으로 묶일 수 있는 것이 특징입니다.

예를 들어, IDisposable 인터페이스 같은 경우, 이 인터페이스를 구현하고 있다는 사실을 통해 개체가 가비지 컬렉터가 메모리 해지를 할 때까지 기다리지 않고 먼저 자원 사용을 해제할 수 있다는 것을 확언할 수가 있습니다. 그리고 비슷하게 ISerializable, ICloneable 등의 인터페이스도 각각 의미가 있죠. 그런데 이들 인터페이스를 구현하는 클래스들 사이에는 서로 큰 상관이 없습니다. 그리고 한 클래스가 지금 이야기한 인터페이스 여럿을 동시에 구현하기도 하고요.

제네릭을 사용하는 경우는 특정 타입 하나만을 받지 않고 여러 타입을 일반화해서 받고 싶을 때 사용합니다. 이걸 System.Object로 처리하면, 딱 System.Object로 접근할 수 있는 멤버에 국한해서만 접근이 가능하고, 나머지 구체적인 타입을 알고 싶을 땐 매번 타입 캐스팅을 해야 하죠. 그런데 이 때 정확히 원하는 타입의 개체가 들어왔는지를 정확히 검사하지 않으면 문제가 생길 수 있습니다.

이런 비효율을 컴파일 시점에서부터 미리 가려내고, 굳이 형변환을 하지 않아도 되게끔 제네릭을 쓴다고 보시면 되겠습니다. 더 나아가서, 제네릭 인자로 인터페이스를 지정하면, 세부 클래스에 대한 정보를 몰라도 인터페이스 구현 여부만을 따져 확정성있게 클래스를 구현할 수 있습니다. 제네릭이 주로 잘 쓰이는 곳은 컬렉션, 대리자 같은 부분에서 잘 쓰입니다.

9개의 좋아요

저같은 경우는 자동화 유지 보수의 편의성을 위해 사용합니다.
문서화 작업시 클래스에 모든 메소드를 주석을 다는것도 힘들고 분석도 힘들지만
대표 메소드만 interface 선언을 해두면 후임자가 파악하기도 좋고
그리고 형변환시 interface로 변환해야 재사용성에서 필수적이라고 봅니다.
그리고 무엇보다 Tdd시 거의 필수적입니다.
그냥 습관적으로 선택이 아니라 필수로 왠만한 class는 interface 를 선언해야 한다 봅니다.
개발자 관점에서 class를 본다는 개념이 아니라 interface를 보는 개념으로 봐야 한다 생각합니다

5개의 좋아요

인터페이스는 'A와 [X]를 이어주는 다리’라고 이해하면 좋을 듯 싶어요.
A는 [X]의 정체를 굳이 몰라도됩니다.
A는 [X]가 어떠한 행위를 할 수 있는지, 어떤 속성을 가지고 있는지만 알기만 하면 됩니다.
A가 [X]의 정체를 알아버리면 정이 생겨서 일을 효율적으로, 유연하게 처리 할 수 없고, 그 둘은 종속관계가 형성 되어 버립니다. 또한 중복 코드도 생기게 되죠.

예로들어서
A는 동사무소의 어떤 사람이 무슨일을 하는지 다 알필요는 없습니다.
A는 동사무소에서 등본이나 증명서… 기타 신고를 할 수 있다고만 알면 됩니다.

(그럴일은 없겠지만)A가 동사무소의 [가]직원하고 친해진 상황을 생각해봅시다. (더더더더더더욱 그럴일은 없겠지만… 불가능하지만) [가]라는 직원은 평소 A가 전화 벨만 울려도 자료들을 준비해주고 특정 신고까지 다 해줬습니다. 어느날 갑자기 [가]직원이 다른 곳으로 발령이 나고 [나]라는 사람이 왔을 때, [나]는 A의 요구하는 것들을 알 수 있을까요? A도 [가]가 아니면 어떠한 신고도 못하는 처지가 되어버립니다.

반대로 A가 동사무소에 가면 무엇무엇을 하면 어떤 신고가 가능하다는걸 안다고 해봅시다.
A는 동사무소에 가서 어떤직원을 만나든
1번 서류를 떼주시고요, 2번 서류를 떼주시고, 마지막에는 3번 신고를 해주세요. 라고 요청만 하면 됩니다.
A는 동사무소에 굳이 가지 않아도 됩니다. 정부24 플랫폼을 사용하여
1번 서류를 떼고, 2번 서류를 떼고, 마지막에는 3번 신고를 하면 되니깐요.

클래스간에 결속은 처음엔 편하지만 결국엔 독, 파국이 되어버립니다.
적절한 연결과 느슨한 관계는 프로젝트 초반부에는 할게 많고 버거울지 모르지만, 시간이 지나면 보다 빠른 개발, 효율적인 기능 구현이 가능하게 됩니다.

글을 쓰다보니 이상한 소리를 많이 했는데,
제가 인터페이스를 사용하는 기준은
‘어떠한 목적으로 만든 클래스들의 기능들을 추상화 했는데 하나로 묶을 수 있을 때’
‘A가 굳이 [X]를 몰라도 되지만 기능은 사용하고 싶을 때. 특히 프로젝트간 참조시…’
입니다. 더 많이 있겠지만 지금 생각나는 건 위에 두 가지 일 때 네요ㅎㅎ

3개의 좋아요

그냥 약속입니다.
인터페이스는 다향성을 부여하는 것이고, 그 부여된 약속을 지킴으로써 다른 부분을 신경 쓰지 않아도 된다고 할 수 있겠습니다.

그 이후 다양하게 해석하고 활용하는 것은 개발자의 몫입니다.

3개의 좋아요

저는 맥락에 관한 정보를 덜 주는 인터페이스보다는 추상클래스를 선호하는 편입니다.

예를 들어,

    interface IRepository<T> 
    {
        void Add(T data);
        T Get(T data);
        T Update(T data);
        bool Remove(T data);
    }

    abstract class DBRepository<T>
    {
        protected string _connectionString;
        public DBRepository(string connectionString)
        {
            _connectionString = connectionString;
        }
        public abstract void Add(T data);
        public abstract T Get(T data);
        public abstract T Update(T data);
        public abstract bool Remove(T data);
    }

추상 클래스나 인터페이스나 모두 구현을 위임한다는 의미에서는 같습니다.
그러나, 이들을 소비하는 입장에서, 몇 글자라도 맥락 정보가 포함된 추상 클래스가 더 편리하기 마련이죠.

그런 이유로, 닷넷 라이브러리도 맥락(컨텍스트)이 중요한 객체는 인터페이스가 아닌 클래스나 추상 클래스로 정의하고 있지 않나하는 생각이 듭니다. Socket, TCPClient, StreamWriter, WebClient, DbContext…

거꾸로 나의 구현 맥락을 감추고 싶을 때는 Interface 로 노출하는 것을 선호합니다.
그런데, 이 경우 구현의 완성 단계에서나 노출 인터페이스를 정의하지, 구현 와중에 정의하지 않습니다.
미리 해놓으면, 리펙토링 시에 항상 두 번씩 고쳐야 해서, 손이 많이 갑니다.

7개의 좋아요

답변해주신분들 모두 감사합니다!

3개의 좋아요