여러 클래스가 일련의 규칙과 구현을 준수하는 인터페이스의 요점이 아닌가?
여러 클래스가 일련의 규칙과 구현을 준수하는 인터페이스의 요점이 아닌가?
답변:
엄밀히 말하면, YAGNI 는 적용 되지 않습니다 . 즉, 인터페이스를 작성하는 데 소요되는 시간은 최소화됩니다. 특히 대부분의 작업을 수행하는 편리한 코드 생성 도구가있는 경우에 특히 그렇습니다. 인터페이스가 필요한지 확실하지 않은 경우 인터페이스 정의를 지원하는쪽으로 잘못하는 것이 좋습니다.
또한 단일 클래스에서도 인터페이스를 사용하면 프로덕션이 아닌 단위 테스트를위한 또 다른 모의 구현을 제공 할 수 있습니다. Avner Shahar-Kashtan의 대답 은이 시점에서 확장됩니다.
인터페이스가 필요한지 여부는 인터페이스 를 구현할 클래스 수에 따라 달라 지지 않습니다 . 인터페이스는 응용 프로그램의 여러 하위 시스템 간의 계약 을 정의하기위한 도구입니다 . 실제로 중요한 것은 응용 프로그램을 하위 시스템으로 나누는 방법입니다. 클래스를 구현하는 클래스 수에 상관없이 캡슐화 된 서브 시스템에 대한 프론트 엔드 인터페이스가 있어야합니다.
매우 유용한 경험 법칙은 다음과 같습니다.
Foo
직접 참조 BarImpl
하게 Foo
할 때마다 변경할 때마다 변경하도록 강요 합니다 BarImpl
. 기본적으로 두 클래스로 나뉘어 진 하나의 코드 단위로 처리합니다.Foo
참고하여주십시오 인터페이스 Bar
, 당신은 대신에 자신을 투입하고 피 에 대한 변경 사항 Foo
을 변경할 때를 BarImpl
.애플리케이션의 주요 지점에서 인터페이스를 정의하는 경우, 지원해야하는 메소드와 그렇지 않아야하는 메소드를 신중하게 고려하고 구현이 어떻게 작동해야하는지 (및 어떻게 수행되지 않아야하는지) 응용 프로그램이 이러한 주석 인터페이스는 일종의 제공하기 때문에 이해하기가 훨씬 더 쉬울 것이다 사양 이 어떻게의 응용 프로그램에 대한 설명의 의도 된 행동을. 이렇게하면 코드를 훨씬 쉽게 읽을 수 있습니다 ( "이 코드의 기능은 무엇입니까?"라고 묻지 않고 "이 코드는 어떻게해야합니까?").
이 모든 것 외에도 실제로 인터페이스는 별도의 컴파일을 촉진합니다. 인터페이스는 컴파일하기가 쉽지 않고 구현보다 종속성이 적기 때문에 인터페이스 Foo
를 사용하기 위해 클래스 를 작성 Bar
하면 일반적으로 다시 컴파일 BarImpl
하지 않고도 다시 컴파일 할 수 있습니다 Foo
. 대규모 응용 프로그램에서는 시간을 많이 절약 할 수 있습니다.
Foo
인터페이스에 따라 Bar
다음 BarImpl
다시 컴파일 할 필요없이 변경 될 수 있습니다 Foo
. 4. 공개 / 개인 오퍼보다 세분화 된 액세스 제어 (다른 인터페이스를 통해 동일한 클래스를 두 클라이언트에 노출).
If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImpl
변경할 때 Foo에서 피할 수있는 변경 사항은 무엇 interface Bar
입니까? BarImpl에서 메소드의 서명 및 기능이 변경되지 않는 한 Foo는 인터페이스 (인터페이스의 전체 목적)가 없어도 변경이 필요하지 않습니다. 나는 하나의 클래스, 즉 BarImpl
Bar를 구현 하는 시나리오에 대해 이야기 하고 있습니다. 멀티 클래스 시나리오의 경우 Dependency Inversion Principle과 인터페이스가 어떻게 도움이되는지 이해합니다.
인터페이스는 동작, 즉 함수 / 방법의 프로토 타입 집합을 정의하도록 지정됩니다. 인터페이스를 구현하는 형식은 해당 동작을 구현하므로 이러한 형식을 처리 할 때 해당 동작이 무엇인지 (부분적으로) 알고 있습니다.
인터페이스가 정의한 동작이 한 번만 사용된다는 것을 알고 있으면 인터페이스를 정의 할 필요가 없습니다. 키스 (간단하고 바보처럼 유지)
이론적으로 인터페이스를 위해 인터페이스를 가져서는 안되지만 Yannis Rizos의 답변 은 다음과 같은 복잡한 문제를 암시합니다.
단위 테스트를 작성하고 Moq 또는 FakeItEasy와 같은 모의 프레임 워크를 사용하면 (내가 사용한 가장 최근의 두 가지 이름을 지정하기 위해) 인터페이스를 구현하는 다른 클래스를 암시 적으로 만듭니다. 코드 또는 정적 분석을 검색하면 구현이 하나 뿐이지 만 실제로 내부 모의 구현이 있다고 주장 할 수 있습니다. 모의 작성을 시작할 때마다 인터페이스 추출이 의미가 있음을 알게 될 것입니다.
그러나 더 많은 것이 있습니다. 암시 적 인터페이스 구현이있는 시나리오가 더 있습니다. 예를 들어 .NET의 WCF 통신 스택을 사용하면 원격 서비스에 대한 프록시가 생성되어 인터페이스가 다시 구현됩니다.
깨끗한 코드 환경에서 나는 나머지 답변에 동의합니다. 그러나 인터페이스를 사용할 수있는 프레임 워크, 패턴 또는 종속성에주의하십시오.
아니요, 필요하지 않으며 모든 클래스 참조에 대해 자동으로 인터페이스를 만드는 것이 안티 패턴이라고 생각합니다.
모든 것을 위해 Foo / FooImpl을 만드는 데 실제 비용이 있습니다. IDE는 인터페이스 / 구현을 무료로 만들 수 있지만, 코드를 탐색 할 때는 F3 / F12에서 foo.doSomething()
인터페이스 구현으로 가고자하는 실제 구현이 아닌 인지적인 추가로드가 있습니다. 또한 모든 것을 위해 하나가 아닌 두 개의 파일이 복잡합니다.
따라서 실제로 무언가를 위해 필요할 때만해야합니다.
이제 반론을 다루고 있습니다.
의존성 주입 프레임 워크에 대한 인터페이스가 필요합니다
프레임 워크를 지원하는 인터페이스는 레거시입니다. Java에서 인터페이스는 CGLIB 이전의 동적 프록시에 대한 요구 사항이었습니다. 오늘날에는 일반적으로 필요하지 않습니다. EJB3, Spring 등에서 더 이상 필요하지 않은 진보와 개발자 생산성의 이점으로 간주됩니다.
단위 테스트를위한 모의가 필요합니다
자신의 모형을 작성하고 실제 구현이 두 개인 경우 인터페이스가 적합합니다. 코드베이스에 FooImpl과 TestFoo가 모두 있으면이 토론이 시작되지 않았을 것입니다.
그러나 Moq, EasyMock 또는 Mockito와 같은 조롱 프레임 워크를 사용하는 경우 클래스를 조롱 할 수 있으며 인터페이스가 필요하지 않습니다. foo.method = mockImplementation
메소드를 지정할 수있는 동적 언어 로 설정 하는 것과 유사합니다 .
의존성 역전 원리 (DIP)를 준수하는 인터페이스가 필요합니다
DIP는 구현이 아닌 계약 (인터페이스)에 의존하여 빌드한다고 말합니다. 그러나 클래스는 이미 계약과 추상화입니다. 이것이 공개 / 비공개 키워드의 목적입니다. 대학에서 표준 예제는 매트릭스 또는 다항식 클래스와 같습니다. 소비자는 매트릭스를 생성하고 추가하는 등 공용 API를 가지고 있지만 매트릭스가 희박하거나 밀도가 높은 형태로 구현되는지는 신경 쓰지 않습니다. 그 점을 증명하기 위해 IMatrix 또는 MatrixImpl이 필요하지 않았습니다.
또한 DIP는 주요 모듈 경계뿐만 아니라 모든 클래스 / 메소드 호출 수준에서 과도하게 적용되는 경우가 많습니다. DIP를 과도하게 적용한다는 신호는 인터페이스와 구현이 잠금 단계에서 변경되어 하나의 파일 대신 변경하기 위해 두 개의 파일을 터치해야한다는 것입니다. DIP를 적절하게 적용하면 인터페이스를 자주 변경할 필요가 없습니다. 또한 인터페이스에 하나의 실제 소비자 (자체 응용 프로그램) 만 있음을 나타냅니다. 다양한 앱에서 사용할 클래스 라이브러리를 구축하는 경우 다른 이야기.
이것은 조롱에 대한 Bob Martin 아저씨의 견해입니다. 주요 건축 경계에서만 조롱하면됩니다. 웹앱에서 HTTP 및 DB 액세스는 주요 경계입니다. 사이에있는 모든 클래스 / 메소드 호출은 그렇지 않습니다. DIP도 마찬가지입니다.
또한보십시오:
new Mockup<YourClass>() {}
있으며 인터페이스를 비롯하여 추상 클래스 또는 구체적 클래스이든 생성자를 포함하여 전체 클래스가 조롱됩니다. 원하는 경우 생성자 동작을 "재정의"할 수도 있습니다. Mockito 또는 Powermock에 동등한 방법이 있다고 가정합니다.
울타리의 양쪽에 대한 답변을 다음과 같이 요약 할 수 있습니다.
잘 설계하고 인터페이스가 필요한 곳에 인터페이스를 배치하십시오.
Yanni의 답변에 대한 답변 에서 언급했듯이 인터페이스에 대해 단단하고 빠른 규칙을 가질 수 있다고 생각하지 않습니다. 규칙은 정의상 유연해야합니다. 인터페이스에 대한 나의 규칙은 인터페이스를 API를 생성 할 때마다 사용해야한다는 것입니다. 그리고 한 책임 영역에서 다른 영역으로 경계를 넘어가는 곳이라면 어디에서나 API를 만들어야합니다.
(끔찍하게 고안된) 예를 들어 Car
수업을 만들고 있다고 가정 해 봅시다 . 수업에는 반드시 UI 레이어가 필요합니다. 이 특정 예에서,는의 형태를 취하고 IginitionSwitch
, SteeringWheel
, GearShift
, GasPedal
, 및 BrakePedal
. 이 자동차에는가 포함되어 있으므로을 AutomaticTransmission
(를) 필요로하지 않습니다 ClutchPedal
. (그리고 이것은 끔찍한 차이 기 때문에 A / C, 라디오 또는 좌석이 없습니다. 사실, 마루판도 없습니다. 스티어링 휠에 매달아 서 최선을 다해야합니다!)
이러한 클래스 중 어느 인터페이스가 필요한가요? 대답은 디자인에 따라 전부이거나 아예 없을 수 있습니다.
다음과 같은 인터페이스를 가질 수 있습니다.
Interface ICabin
Event IgnitionSwitchTurnedOn()
Event IgnitionSwitchTurnedOff()
Event BrakePedalPositionChanged(int percent)
Event GasPedalPositionChanged(int percent)
Event GearShiftGearChanged(int gearNum)
Event SteeringWheelTurned(float degree)
End Interface
이 시점에서 해당 클래스의 동작은 ICabin 인터페이스 / API의 일부가됩니다. 이 예제에서 클래스 (일부가있는 경우)는 몇 가지 속성과 함수 또는 두 개로 단순 할 수 있습니다. 그리고 설계에 내재적으로 언급 한 것은이 클래스가 ICabin의 구체적인 구현을 지원하기 위해서만 존재하며 자체적으로 존재할 수 없거나 ICabin 컨텍스트 외부에서 의미가 없다는 것입니다.
개인 멤버를 단위 테스트하지 않는 것과 같은 이유로 공개 API를 지원하기 위해서만 존재 하므로 API를 테스트하여 동작을 테스트해야합니다.
따라서 클래스가 다른 클래스를 지원하기 위해 존재하고 개념적으로 실제로 자신의 도메인이 없는 것으로 보는 경우 인터페이스를 건너 뛰는 것이 좋습니다. 그러나 클래스가 자신의 도메인을 가질만큼 성장했다고 생각할만큼 충분히 중요하다면 인터페이스를 제공하십시오.
편집하다:
자주 (이 답변에 포함) 프로그램을 시작할 때 당신에게 의미가없는 '도메인', '종속성'(종종 '주입'과 결합 됨)과 같은 내용을 읽습니다 (확실하지 않았습니다. 나에게 무엇이든 의미). 도메인의 경우 정확히 다음과 같은 소리를 의미합니다.
지배권이나 권력이 행사되는 지역; 주권 또는 연방 등의 재산. 비 유적으로도 사용됩니다. [WordNet sense 2] [1913 웹스터]
내 예제의 관점에서-를 고려해 봅시다 IgnitionSwitch
. 미트 스페이스 차량에서 점화 스위치는 다음을 담당합니다.
이러한 속성은 구성하는 도메인 의를 IgnitionSwitch
, 또는 다른 말로, 무엇에 대해 알고하는 책임이있다.
에 대한 IgnitionSwitch
책임은 없습니다 GasPedal
. 점화 스위치는 모든면에서 가스 페달을 완전히 무시합니다. 그것들은 서로 완전히 독립적으로 작동합니다 (두 자동차가 없으면 차가 상당히 가치가 없지만).
원래 언급했듯이 디자인에 따라 다릅니다. IgnitionSwitch
켜짐 ( True
)과 꺼짐 ( False
)의 두 가지 값을 가진을 설계 할 수 있습니다. 또는 제공된 키와 기타 여러 가지 작업을 인증하도록 설계 할 수 있습니다. 개발자가 모래에 선을 그릴 위치를 결정하는 것은 어려운 일입니다. 솔직히 대부분의 시간은 완전히 상대적입니다. 모래의 라인은 중요하지만 API가있는 곳이므로 인터페이스가 있어야합니다.
에서 MSDN :
인터페이스는 응용 프로그램이 특정 기능을 제공하기 위해 관련이없는 많은 개체 유형을 요구하는 상황에 더 적합합니다.
여러 인터페이스를 구현할 수있는 단일 구현을 정의 할 수 있으므로 인터페이스는 기본 클래스보다 융통성이 있습니다.
기본 클래스에서 구현을 상속 할 필요가없는 상황에서는 인터페이스가 더 좋습니다.
인터페이스는 클래스 상속을 사용할 수없는 경우에 유용합니다. 예를 들어 구조체는 클래스에서 상속 할 수 없지만 인터페이스를 구현할 수 있습니다.
일반적으로 단일 클래스의 경우 인터페이스를 구현할 필요는 없지만 프로젝트의 미래를 고려할 때 필요한 클래스 동작을 공식적으로 정의하는 것이 유용 할 수 있습니다.
질문에 대답하려면 그 이상이 있습니다.
인터페이스의 중요한 측면 중 하나는 의도입니다.
인터페이스는 "데이터를 포함하지 않지만 동작을 노출시키는 추상 유형"입니다.- 인터페이스 (계산) 따라서 인터페이스가 올바른 패턴보다 클래스가 지원하는 동작 또는 동작 집합 인 경우입니다. 그러나 행동 (들)이 클래스에 의해 구체화 된 개념에 내재되어 있다면, 당신은 전혀 인터페이스를 원하지 않을 것입니다.
물어볼 첫 번째 질문은 당신이 표현하려고하는 것 또는 과정의 본질이 무엇인가입니다. 그런 다음 주어진 방식으로 자연을 구현하는 실질적인 이유를 따르십시오.
이 질문을 했으므로 인터페이스가 여러 구현을 숨기는 데 관심이 있다는 것을 이미 알고 있다고 가정합니다. 이것은 의존성 역전 원리에 의해 나타날 수 있습니다.
그러나 인터페이스를 가질 필요가 있는지 여부는 구현 횟수에 의존하지 않습니다. 인터페이스의 실제 역할은 구현 방법 대신 어떤 서비스를 제공해야하는지에 대한 계약을 정의하는 것입니다.
계약이 정의되면 둘 이상의 팀이 독립적으로 작업 할 수 있습니다. 모듈 A에서 작업 중이고 모듈 B에 의존한다고 가정 해보십시오 .B에 인터페이스를 만들면 인터페이스에 의해 모든 세부 사항이 숨겨져 있기 때문에 B의 구현에 대해 걱정하지 않고도 작업을 계속할 수 있습니다. 따라서 분산 프로그래밍이 가능해집니다.
모듈 B의 인터페이스 구현은 하나만 있지만 인터페이스는 여전히 필요합니다.
결론적으로 인터페이스는 사용자로부터 구현 세부 사항을 숨 깁니다. 인터페이스 프로그래밍은 계약을 정의하고 더 많은 모듈 식 소프트웨어를 작성하며 단위 테스트를 촉진하고 개발 속도를 높이기 때문에 더 많은 문서를 작성하는 데 도움이됩니다.
여기에 모든 대답이 매우 좋습니다. 실제로 대부분의 경우 다른 인터페이스를 구현할 필요 가 없습니다 . 그러나 어쨌든 그것을하고 싶을 수도 있습니다 . 내가하는 경우가 있습니다.
이 클래스는
타사 코드를 연결하는 어댑터 클래스를 사용하여 자주 노출하고 싶지 않은 다른 인터페이스를 구현 합니다.
interface NameChangeListener { // Implemented by a lot of people
void nameChanged(String name);
}
interface NameChangeCount { // Only implemented by my class
int getCount();
}
class NameChangeCounter implements NameChangeListener, NameChangeCount {
...
}
class SomeUserInterface {
private NameChangeCount currentCount; // Will never know that you can change the counter
}
이 클래스는
대부분의 외부 라이브러리와 상호 작용할 때 유출되지 않아야하는 특정 기술을 사용합니다 . 구현이 하나만 있더라도 외부 라이브러리와의 불필요한 연결을 도입하지 않도록 인터페이스를 사용합니다.
interface SomeRepository { // Guarantee that the external library details won't leak trough
...
}
class OracleSomeRepository implements SomeRepository {
... // Oracle prefix allow us to quickly know what is going on in this class
}
계층 간 통신
하나의 UI 클래스 만 도메인 클래스 중 하나를 구현하더라도 해당 계층간에 더 나은 분리가 가능하며 가장 중요한 것은 순환 종속성을 피하는 것입니다.
package project.domain;
interface UserRequestSource {
public UserRequest getLastRequest();
}
class UserBehaviorAnalyser {
private UserRequestSource requestSource;
}
package project.ui;
class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
// UI concern, no need for my domain object to know about this method.
public void displayLabelInErrorMode();
// They most certainly need to know about *that* though
public UserRequest getLastRequest();
}
대부분의 객체에 메소드의 하위 세트 만 사용할 수 있어야합니다
. 구체적인 클래스에 구성 메소드가있을 때 주로 발생합니다.
interface Sender {
void sendMessage(Message message)
}
class PacketSender implements Sender {
void sendMessage(Message message);
void setPacketSize(int sizeInByte);
}
class Throttler { // This class need to have full access to the object
private PacketSender sender;
public useLowNetworkUsageMode() {
sender.setPacketSize(LOW_PACKET_SIZE);
sender.sendMessage(new NotifyLowNetworkUsageMessage());
... // Other details
}
}
class MailOrder { // Not this one though
private Sender sender;
}
그래서 결국 개인 필드를 사용하는 것과 같은 이유로 인터페이스를 사용합니다. 다른 객체는 액세스해서는 안되는 물건에 액세스 할 수 없어야합니다. 나는 그런 경우가있는 경우에만 경우에도, 나는 인터페이스를 도입 한 클래스를 구현합니다.
인터페이스는 정말 중요하지만 가지고있는 인터페이스의 수를 제어하십시오.
거의 모든 것에 대한 인터페이스를 만드는 길을 갔다가 '잘게 썬 스파게티'코드로 끝나기가 쉽습니다. 나는이 주제에 대해 매우 현명한 말을 게시 한 Ayende Rahien의 더 큰 지혜를 미루고있다.
http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application
이것은 전체 시리즈의 첫 번째 게시물이므로 계속 읽으십시오!
이 경우 여전히 인터페이스를 도입하고 싶은 한 가지 이유는 Dependency Inversion Principle 을 따르는 것 입니다. 즉, 클래스를 사용하는 모듈은 구체적인 구현에 의존하지 않고 추상화 (즉, 인터페이스)에 의존합니다. 상위 레벨 구성 요소를 하위 레벨 구성 요소에서 분리합니다.
항상 클래스에 대한 인터페이스를 정의 할 필요는 없습니다.
가치 객체와 같은 간단한 객체에는 여러 구현이 없습니다. 그들은 조롱 할 필요도 없습니다. 구현은 자체적으로 테스트 할 수 있으며 다른 클래스가 테스트 될 때 실제 값 개체를 사용할 수 있습니다.
인터페이스를 만드는 데 비용이 듭니다. 구현과 함께 업데이트해야하며 추가 파일이 필요하며 일부 IDE는 인터페이스가 아니라 구현을 확대 / 축소하는 데 문제가 있습니다.
따라서 구현에서 추상화하려는 고급 클래스에만 인터페이스를 정의합니다.
클래스를 사용하면 인터페이스가 무료로 제공됩니다. 구현 외에도 클래스는 공용 메소드 세트에서 인터페이스를 정의합니다. 이 인터페이스는 모든 파생 클래스에 의해 구현됩니다. 인터페이스를 엄격하게 말하지는 않지만 정확히 같은 방식으로 사용할 수 있습니다. 따라서 클래스 이름 아래에 이미 존재하는 인터페이스를 다시 만들 필요가 없다고 생각합니다.