Java 언어에서 추상 클래스와 인터페이스는 추상 클래스 정의를 지원하는 두 가지 메커니즘입니다. Java에 강력한 객체 지향 기능이 부여되는 것은 바로 이 두 가지 메커니즘의 존재 때문입니다. 추상 클래스 정의에 대한 지원 측면에서 추상 클래스와 인터페이스 사이에는 큰 유사점이 있으며 서로 대체될 수도 있습니다. 따라서 많은 개발자는 추상 클래스를 정의할 때 추상 클래스와 인터페이스를 선택하는 데 좀 더 무관심한 것 같습니다. 실제로 둘 사이에는 여전히 큰 차이가 있습니다. 심지어 문제 영역의 성격에 대한 이해와 설계 의도에 대한 이해가 정확하고 합리적인지 여부도 반영됩니다. 이 기사에서는 둘 사이의 차이점을 분석하고 개발자에게 둘 중 하나를 선택할 수 있는 근거를 제공하려고 합니다.
추상 클래스 이해
추상 클래스와 인터페이스는 모두 Java 언어에서 추상 클래스를 정의하는 데 사용됩니다. (이 기사의 추상 클래스는 추상 클래스를 번역한 것이 아니라 추상 본문을 나타내며 추상 클래스는 Java 언어에서 추상 클래스를 정의하는 데 사용됩니다.) 메소드의 차이점에 주의하세요) 그렇다면 추상 클래스는 무엇이며 추상 클래스를 사용하면 어떤 이점을 얻을 수 있습니까?
객체지향 개념에서는 모든 객체가 클래스로 표현된다는 것을 알고 있지만 그 반대는 그렇지 않습니다. 모든 클래스가 객체를 설명하는 데 사용되는 것은 아닙니다. 클래스에 특정 객체를 설명하는 데 필요한 정보가 충분하지 않은 경우 해당 클래스는 추상 클래스입니다. 추상 클래스는 문제 영역의 분석 및 설계에서 파생된 추상 개념을 나타내는 데 자주 사용됩니다. 이는 다르게 보이지만 본질적으로 동일한 일련의 특정 개념을 추상화한 것입니다. 예를 들어, 그래픽 편집 소프트웨어를 개발하면 문제 영역에 원과 삼각형과 같은 특정 개념이 있지만 모두 모양의 개념에 속한다는 것을 알게 됩니다. 문제 영역에 존재한다면 그것은 추상적인 개념입니다. 이는 추상 개념이 문제 영역에 상응하는 구체적인 개념을 갖고 있지 않기 때문에 추상 개념을 표현하는 데 사용되는 추상 클래스를 인스턴스화할 수 없기 때문입니다.
객체지향 분야에서는 유형 숨기기를 위해 추상 클래스가 주로 사용됩니다. 고정된 동작 집합에 대한 추상적인 설명을 구성할 수 있지만 이 동작 집합에는 가능한 구체적인 구현이 얼마든지 있을 수 있습니다. 이 추상 설명은 추상 클래스이며, 가능한 구체적인 구현 집합은 가능한 모든 파생 클래스로 표현됩니다. 모듈은 추상체에서 작동할 수 있습니다. 모듈은 고정된 추상화에 의존하기 때문에 동시에 수정할 수 없으며 이 추상화에서 파생되어 이 모듈의 동작을 확장할 수도 있습니다. OCP에 익숙한 독자라면 객체지향 설계의 핵심 원리 중 하나인 OCP(Open-Closed 원리)를 구현하기 위해서는 추상 클래스가 핵심이라는 사실을 알아야 한다.
문법적 정의 수준에서 추상 클래스와 인터페이스 살펴보기
문법 수준에서 Java 언어는 추상 클래스와 인터페이스에 대해 서로 다른 정의 방법을 제공합니다. 다음은 이러한 차이점을 설명하기 위해 Demo라는 추상 클래스를 정의하는 예입니다.
추상 클래스를 사용하여 Demo 추상 클래스를 정의하는 방법은 다음과 같습니다.
추상 클래스 데모{
추상 무효 메소드1();
추상 무효 메소드2();
…
}
인터페이스를 사용하여 Demo 추상 클래스를 정의하는 방법은 다음과 같습니다.
인터페이스 데모{
무효 메소드1();
무효 메소드2();
…
}
추상 클래스 메소드에서 Demo는 자체 데이터 멤버 또는 비추상 멤버 메소드를 가질 수 있습니다. 인터페이스 메소드에서 Demo는 수정할 수 없는 정적 데이터 멤버만 가질 수 있지만 데이터는 static final이어야 합니다. 멤버는 일반적으로 인터페이스에 정의되지 않습니다. 모든 멤버 메서드는 추상입니다. 어떤 의미에서 인터페이스는 추상 클래스의 특별한 형태입니다.
프로그래밍 관점에서 보면 추상 클래스와 인터페이스를 모두 사용하여 "계약에 의한 설계" 아이디어를 구현할 수 있습니다. 그러나 구체적인 사용법에는 여전히 약간의 차이가 있습니다.
우선, 추상 클래스는 자바 언어에서 상속 관계를 나타내며, 클래스는 상속 관계를 한 번만 사용할 수 있습니다(자바는 다중 상속을 지원하지 않기 때문에 - 전송 참고). 그러나 클래스는 여러 인터페이스를 구현할 수 있습니다. 아마도 이는 Java의 다중 상속 지원을 고려할 때 Java 언어 설계자가 타협적으로 고려한 사항일 것입니다.
둘째, 추상 클래스 정의에서 메소드의 기본 동작을 할당할 수 있습니다. 그러나 인터페이스 정의에서 메서드는 기본 동작을 가질 수 없습니다. 이러한 제한을 우회하려면 대리자를 사용해야 하지만 이로 인해 약간의 복잡성이 추가되고 때로는 많은 문제가 발생할 수 있습니다.
추상 클래스에서 기본 동작을 정의할 수 없다는 데에는 또 다른 심각한 문제가 있는데, 이는 유지 관리 문제를 일으킬 수 있다는 것입니다. 나중에 새로운 상황에 적응하기 위해 클래스의 인터페이스(일반적으로 추상 클래스 또는 인터페이스로 표시됨)를 수정하려는 경우(예: 새 메소드를 추가하거나 이미 사용된 메소드에 새 매개변수를 추가하려는 경우) 매우 번거로울 수 있기 때문입니다. 시간이 많이 걸릴 수 있습니다(특히 파생 클래스가 많은 경우). 그러나 인터페이스가 추상 클래스를 통해 구현되는 경우에는 추상 클래스에 정의된 기본 동작만 수정하면 됩니다.
마찬가지로 기본 동작을 추상 클래스에서 정의할 수 없는 경우 동일한 메서드 구현이 추상 클래스의 모든 파생 클래스에 나타나며 "하나의 규칙, 한 장소" 원칙을 위반하여 코드 중복이 발생합니다. 미래. 따라서 추상 클래스와 인터페이스 중에서 선택할 때는 매우 주의해야 합니다.
디자인 컨셉 레벨에서 추상 클래스와 인터페이스를 살펴봅니다.
위에서는 문법적 정의와 프로그래밍의 관점에서 추상 클래스와 인터페이스의 차이점을 주로 논의했습니다. 이러한 수준의 차이점은 상대적으로 낮은 수준이며 중요하지 않습니다. 이 섹션에서는 다른 수준의 추상 클래스와 인터페이스 간의 차이점, 즉 두 가지에 반영된 디자인 개념을 분석합니다. 저자는 이 수준에서 분석해야만 두 개념의 본질을 이해할 수 있다고 믿는다.
앞서 언급했듯이 추상 클래스는 자바 언어에서 상속 관계를 구현하는데, 상속 관계를 합리적으로 만들기 위해서는 부모 클래스와 파생 클래스, 즉 부모 클래스와 파생 클래스 사이에 "is-a" 관계가 있어야 합니다. 파생 클래스는 기본적으로 동일해야 합니다. 인터페이스의 경우에는 그렇지 않습니다. 인터페이스 구현자와 인터페이스 정의는 개념적으로 일관성이 필요하지 않으며 인터페이스에서 정의한 계약만 구현하면 됩니다. 논의를 더 쉽게 이해할 수 있도록 아래에 간단한 예를 들어 설명하겠습니다.
예를 들어, 문제 영역에 Door에 대한 추상 개념이 있다고 가정하면 Door에는 open과 close라는 두 가지 동작이 있습니다. 이때 추상 클래스 또는 인터페이스를 통해 추상 개념을 나타내는 유형을 정의할 수 있습니다. 방법은 다음과 같습니다:
추상 클래스를 사용하여 Door를 정의합니다.
추상 클래스 Door{
추상 무효 open();
추상 무효 닫기();
}
인터페이스 방법을 사용하여 문을 정의합니다.
인터페이스 도어{
무효 열기();
무효 닫기();
}
다른 특정 Door 유형은 추상 클래스 메소드를 사용하여 정의된 Door를 확장하거나 인터페이스 메소드를 사용하여 정의된 Door를 구현할 수 있습니다. 추상 클래스를 사용하는 것과 인터페이스를 사용하는 것에는 큰 차이가 없는 것 같습니다.
이제 도어에 알람 기능이 필요한 경우. 이 예제의 클래스 구조를 어떻게 디자인해야 합니까?(이 예제에서는 주로 추상 클래스와 인터페이스 간의 디자인 개념 차이를 보여주기 위한 것이며 기타 관련 없는 문제는 단순화되거나 무시되었습니다.) 가능한 솔루션은 아래에 나열되어 있으며 이러한 다양한 옵션은 설계 개념 수준에서 분석됩니다.
해결 방법 1:
다음과 같이 Door 정의에 알람 방법을 추가하기만 하면 됩니다.
추상 클래스 Door{
추상 무효 open();
추상 무효 닫기();
추상 무효 경보();
}
또는
인터페이스 도어{
무효 열기();
무효 닫기();
무효 경보();
}
그런 다음 알람 기능이 있는 AlarmDoor는 다음과 같이 정의됩니다.
AlarmDoor 클래스는 Door를 확장합니다.
무효 개방(){...}
무효 닫기(){...}
무효 경보(){...}
}
또는
AlarmDoor 클래스는 Door를 구현합니다.
무효 개방(){...}
무효 닫기(){...}
무효 경보(){...}
}
이 방식은 객체지향 디자인의 핵심원리인 ISP(Interface Segregation 원리)에 위배된다. Door의 정의에는 Door 개념 자체에 내재된 행위 방식과 또 다른 개념인 'alarm'의 행위 방식이 혼합되어 있다. 이로 인해 발생하는 한 가지 문제는 Door 개념에만 의존하는 모듈이 "경보" 개념 변경(예: 경보 방법의 매개변수 수정)으로 인해 변경되고 그 반대의 경우도 마찬가지라는 것입니다.
해결 방법 2:
Open, Close, Alarm은 서로 다른 두 개념에 속하므로 ISP 원칙에 따라 이 두 개념을 나타내는 추상 클래스에서 정의해야 합니다. 정의 방법은 다음과 같습니다. 두 개념 모두 추상 클래스 방법을 사용하여 정의됩니다. 두 개념 모두 인터페이스 방법을 사용하여 정의하고, 한 개념은 추상 클래스 방법을 사용하여 정의하고, 다른 개념은 인터페이스 방법을 사용하여 정의합니다.
분명히 Java 언어는 다중 상속을 지원하지 않기 때문에 추상 클래스를 사용하여 두 개념을 모두 정의하는 것은 불가능합니다. 후자의 두 가지 방법은 모두 가능하지만, 그 선택은 문제 영역에서 개념의 본질에 대한 이해와 설계 의도의 반영이 정확하고 합리적인지 여부를 반영합니다. 하나씩 분석하고 설명해보자.
인터페이스 방법을 사용하여 두 개념을 모두 정의하면 다음과 같은 두 가지 문제가 반영됩니다. 1. 문제 영역을 명확하게 이해하지 못할 수 있습니다. AlarmDoor는 본질적으로 문입니까 아니면 경보기입니까? 2. 문제 영역에 대한 이해에 문제가 없다면, 예를 들어 문제 영역 분석을 통해 AlarmDoor가 Door와 개념적으로 일치한다는 것을 알게 되면 구현 시 설계 의도를 올바르게 밝힐 수 없습니다. 왜냐하면 이 두 개념(둘 다 인터페이스 메소드를 사용하여 정의됨)의 정의는 위의 의미를 반영하지 않기 때문입니다.
문제 영역에 대한 이해가 다음과 같다면: AlarmDoor는 개념적으로 본질적으로 문이며 경보 기능도 가지고 있습니다. 우리의 의미를 명확하게 반영하려면 어떻게 설계하고 구현해야 할까요? 앞에서 언급했듯이 추상 클래스는 Java 언어에서 상속 관계를 나타내며 상속 관계는 본질적으로 "is-a" 관계입니다. 따라서 Door의 개념에 대해서는 추상 클래스 메소드를 사용하여 정의해야 합니다. 또한 AlarmDoor에는 알람 기능이 있는데, 이는 알람 개념에 정의된 동작을 완료할 수 있다는 의미이므로 인터페이스를 통해 알람 개념을 정의할 수 있습니다. 아래와 같이:
추상 클래스 Door{
추상 무효 open();
추상 무효 닫기();
}
인터페이스알람{
무효 경보();
}
클래스 알람 도어 확장 도어 구현 알람{
무효 개방(){...}
무효 닫기(){...}
무효 경보(){...}
}
이 구현 방법은 기본적으로 문제 영역에 대한 우리의 이해를 명확하게 반영하고 우리의 설계 의도를 정확하게 드러낼 수 있습니다. 실제로 추상 클래스는 "is-a" 관계를 나타내고 인터페이스는 "like-a" 관계를 나타냅니다. 물론 이는 문제 도메인에 대한 이해를 바탕으로 합니다. 예: AlarmDoor가 기본적으로 알람 개념이고 Door의 기능을 갖고 있는 경우 위의 정의를 바꿔야 합니다.
요약
1.추상 클래스는 Java 언어에서 상속 관계를 나타내며, 클래스는 상속 관계를 한 번만 사용할 수 있습니다. 그러나 클래스는 여러 인터페이스를 구현할 수 있습니다.
2. 추상 클래스에서는 자신만의 데이터 멤버를 가질 수 있고 추상이 아닌 멤버 메서드도 가질 수 있지만 인터페이스에서는 수정할 수 없는 정적 데이터 멤버만 가질 수 있습니다(즉, 정적 최종이어야 합니다. 그러나 인터페이스 데이터 멤버는 일반적으로 정의되지 않습니다. 모든 멤버 메서드는 추상입니다.
3. 추상 클래스와 인터페이스는 다양한 디자인 개념을 반영합니다. 실제로 추상 클래스는 "is-a" 관계를 나타내고, 인터페이스는 "like-a" 관계를 나타냅니다.
4. 추상 클래스와 인터페이스를 구현하는 클래스는 해당 클래스의 모든 메서드를 구현해야 합니다. 추상 클래스에는 추상이 아닌 메서드가 있을 수 있습니다. 인터페이스에는 구현 메소드가 있을 수 없습니다.
5. 인터페이스에 정의된 변수는 기본적으로 public static final이며, 초기값을 주어야 하기 때문에 구현 클래스에서 재정의하거나 값을 변경할 수 없습니다.
6. 추상 클래스의 변수는 기본적으로 친숙하며 해당 값은 하위 클래스에서 재정의되거나 재할당될 수 있습니다.
7. 인터페이스의 메소드는 기본적으로 공개 및 추상 유형입니다.
결론적으로
추상 클래스와 인터페이스는 Java 언어에서 추상 클래스를 정의하는 두 가지 방법이며 매우 유사합니다. 그러나 그들의 선택은 종종 문제 영역에서 개념의 본질에 대한 이해와 디자인 의도의 반영이 정확하고 합리적인지 여부를 반영합니다. 왜냐하면 그들은 개념 간의 서로 다른 관계를 표현하기 때문입니다(모두 필요한 기능을 달성할 수 있음에도 불구하고). 이것은 실제로 일종의 관용적 언어 사용입니다. 독자들이 주의 깊게 이해해 주기를 바랍니다.