선호하는 구성은 다형성에 관한 것이 아닙니다. 그것이 그것의 일부이지만, 사람들이 실제로 의미하는 바는 "구성과 인터페이스 구현의 조합을 선호합니다"라는 것이 맞습니다. 그러나 (많은 상황에서) 구성을 선호하는 이유는 심오합니다.
다형성 은 여러 가지 방식으로 동작하는 것입니다. 따라서 제네릭 / 템플릿은 단일 코드 조각이 유형에 따라 동작을 변화시킬 수있는 "다형성"기능입니다. 실제로,이 유형의 다형성은 실제로 가장 잘 작동하며 일반적으로 변수가 매개 변수에 의해 정의되기 때문에 파라 메트릭 다형성 이라고합니다.
많은 언어는 "오버로딩"또는 임시 다형성이라는 형태를 제공합니다. 여기서 동일한 이름을 가진 여러 프로 시저가 임시 방식으로 정의되고 언어에 따라 선택됩니다 (가장 구체적으로 표시됨). 개발 된 규칙을 제외하고는 두 절차의 동작을 연결하는 것이 없기 때문에 이것은 가장 잘 동작하지 않는 다형성입니다.
세 번째 종류의 다형성은 아형 다형성 입니다. 여기에서 주어진 유형에 정의 된 절차는 해당 유형의 전체 "하위 유형"패밀리에서 작동 할 수 있습니다. 인터페이스를 구현하거나 클래스를 확장 할 때 일반적으로 하위 유형을 만들려는 의도를 선언합니다. 진정한 하위 유형은 Liskov의 대체 원칙 에 의해 관리됩니다즉, 수퍼 타입의 모든 객체에 대해 무언가를 증명할 수 있으면 서브 타입의 모든 인스턴스에 대해 증명할 수 있습니다. 그러나 C ++ 및 Java와 같은 언어에서 사람들은 일반적으로 하위 클래스에 대해 사실 일 수도 있고 그렇지 않을 수도있는 클래스에 대해 시행되지 않고 문서화되지 않은 가정을 가지고 있기 때문에 위험합니다. 즉, 코드는 실제보다 더 증명 가능한 것처럼 작성되므로 부주의하게 하위 유형을 지정하면 전체 문제가 발생합니다.
상속 은 실제로 다형성과 무관합니다. 자체에 대한 참조가있는 "T"가있는 경우 "T"에서 "S"로의 참조를 "S"에 대한 참조로 대체하여 "T"에서 새 "S"를 작성할 때 상속이 발생합니다. 상속은 많은 상황에서 발생할 수 있기 때문에 의도적으로 모호합니다.하지만 가장 일반적인 것은 this
가상 함수에 의해 호출되는 this
포인터를 하위 유형에 대한 포인터로 대체하는 효과가있는 객체를 서브 클래 싱하는 것입니다 .
상속은 상속이 혼란을 야기 할 수있는 모든 강력한 것들과 마찬가지로 위험 합니다. 예를 들어, 어떤 클래스에서 상속 할 때 메서드를 재정의한다고 가정 해 봅시다. 클래스의 다른 메서드가 상속 한 메서드가 특정 방식으로 동작한다고 가정 할 때까지는 모두 훌륭합니다. 결국 원래 클래스의 작성자가 설계 한 방식입니다 . 재정의 되도록 설계된 경우가 아니면 다른 방법으로 호출 된 모든 메서드를 비공개 또는 비가 상 (최종) 선언하여이를 부분적으로 보호 할 수 있습니다 . 그럼에도 불구하고 항상 충분하지는 않습니다. 때로는 다음과 같은 것을 볼 수도 있습니다 (의사 Java에서는 C ++ 및 C # 사용자가 읽을 수 있음)
interface UsefulThingsInterface {
void doThings();
void doMoreThings();
}
...
class WayOfDoingUsefulThings implements UsefulThingsInterface{
private foo stuff;
public final int getStuff();
void doThings(){
//modifies stuff, such that ...
...
}
...
void doMoreThings(){
//ignores stuff
...
}
}
여러분은 이것이 사랑스럽고 자신 만의 "일"을하는 방식을 가지고 있지만 상속을 사용하여 "moreThings"를 할 수있는 능력을 얻습니다.
class MyUsefulThings extends WayOfDoingUsefulThings{
void doThings {
//my way
}
}
그리고 모든 것이 좋고 좋습니다. WayOfDoingUsefulThings
하나의 방법을 대체해도 다른 방법의 의미는 변경되지 않도록 설계되었습니다. 기다리십시오. 그것은 그대로있는 것처럼 보이지만 doThings
중요한 변경 가능한 상태로 변경되었습니다. 따라서 재정의 가능한 함수를 호출하지는 않았지만
void dealWithStuff(WayOfDoingUsefulThings bar){
bar.doThings()
use(bar.getStuff());
}
이제 당신이 그것을 전달할 때 예상했던 것과 다른 것을 수행합니다 MyUsefulThings
. 더 나쁜 것은, 당신 WayOfDoingUsefulThings
은 그러한 약속을 했는지 알지 못할 수도 있습니다 . 아마 dealWithStuff
같은 라이브러리에서 제공 WayOfDoingUsefulThings
하고 getStuff()
(생각조차 라이브러리에서 내보낼 수 없습니다 친구 클래스 C ++로). 더 나쁜 것은, 당신이 그것을 실현하지 않고 언어의 정적 검사를 물리 치고 : dealWithStuff
했다 WayOfDoingUsefulThings
단지는 것이다 있는지 확인하기 위해 getStuff()
특정한 방식으로 행동 기능.
구성 사용
class MyUsefulThings implements UsefulThingsInterface{
private way = new WayOfDoingUsefulThings()
void doThings() {
//my way
}
void doMoreThings() {
this.way.doMoreThings();
}
}
정적 타입 안전을 되 찾습니다. 일반적으로 구성은 하위 유형 지정을 구현할 때 상속보다 사용하기 쉽고 안전합니다. 또한 최종 메소드를 무시할 수 있습니다. 즉 , 대부분의 경우 인터페이스를 제외하고 모든 것을 최종 / 비 가상적 으로 선언 할 수 있습니다.
더 나은 언어에서는 delegation
키워드를 사용 하여 상용구를 자동으로 삽입합니다 . 대부분은 그렇지 않으므로 단점은 더 큰 클래스입니다. 그럼에도 불구하고 IDE가 위임 인스턴스를 작성하도록 할 수 있습니다.
이제 인생은 다형성에 관한 것이 아닙니다. 항상 하위 유형을 지정할 필요는 없습니다. 다형성의 목표는 일반적으로 코드 재사용 이지만 그 목표를 달성하는 유일한 방법은 아닙니다. 종종 기능 관리 방법으로 하위 유형 다형성없이 구성을 사용하는 것이 합리적입니다.
또한 행동 상속에는 그 용도가 있습니다. 컴퓨터 과학에서 가장 강력한 아이디어 중 하나입니다. 단지 대부분의 경우 인터페이스 상속 및 구성 만 사용하여 좋은 OOP 응용 프로그램을 작성할 수 있습니다. 두 가지 원칙
- 상속 또는 디자인 금지
- 구성 선호
위의 이유에 대한 좋은 가이드이며 실질적인 비용이 발생하지 않습니다.