동작이 매개 변수 유형을 전환하는 방법을 볼 때마다 해당 방법이 실제로 방법 매개 변수에 속하는지 즉시 고려합니다. 예를 들어 다음과 같은 방법 대신
public void sort(List values) {
if (values instanceof LinkedList) {
// do efficient linked list sort
} else { // ArrayList
// do efficient array list sort
}
}
나는 이것을 할 것이다 :
values.sort();
// ...
class ArrayList {
public void sort() {
// do efficient array list sort
}
}
class LinkedList {
public void sort() {
// do efficient linked list sort
}
}
우리는 행동을 언제 사용해야하는지 알고있는 곳으로 옮깁니다. 우리 는 구현의 유형이나 세부 사항을 알 필요가없는 진정한 추상화를 만듭니다 . 상황에 따라이 메소드를 원래 클래스 (내가 호출 할 O
)에서 type으로 입력 A
하고 재정의하는 것이 더 합리적 일 수 있습니다 B
. 메소드가 doIt
일부 오브젝트에서 호출 된 경우의 다른 동작으로 이동 doIt
하여 A
대체하십시오 B
. doIt
원래 호출 된 위치의 데이터 비트 가 있거나 메소드가 충분한 위치에서 사용되는 경우 원래 메소드를 그대로두고 위임 할 수 있습니다.
class O {
int x;
int y;
public void doIt(A a) {
a.doIt(this.x, this.y);
}
}
그래도 조금 더 깊이 뛰어들 수 있습니다. 부울 매개 변수를 대신 사용하라는 제안을 살펴보고 동료가 생각하는 방식에 대해 배울 수있는 내용을 살펴 보겠습니다. 그의 제안은 다음과 같습니다.
public void doIt(A a, boolean isTypeB) {
if (isTypeB) {
// do B stuff
} else {
// do A stuff
}
}
이것은 instanceof
우리가 그 검사를 외부화한다는 점을 제외하고는 첫 번째 예에서 사용한 것과 매우 끔찍 합니다. 즉, 다음 두 가지 방법 중 하나로 호출해야합니다.
o.doIt(a, a instanceof B);
또는:
o.doIt(a, true); //or false
첫 번째 방법으로, 콜 포인트는 그 유형이 무엇인지 전혀 모릅니다 A
. 따라서 부울을 완전히 아래로 전달해야합니까? 이것이 실제로 코드 기반에서 원하는 패턴입니까? 세 번째로 고려해야 할 유형이 있으면 어떻게됩니까? 이것이 메소드가 호출되는 방식이라면, 우리는 그 메소드를 타입으로 옮기고 시스템이 다형성으로 구현을 선택하도록해야합니다.
두 번째 방법으로, 콜 포인트 의 유형을 이미 알고 있어야합니다a
. 일반적으로 인스턴스를 작성하거나 해당 유형의 인스턴스를 매개 변수로 사용함을 의미합니다. 의 방법 작성 O
하는 것은 소요 B
일하는 것이 여기. 컴파일러는 어떤 방법을 선택할지 알 것입니다. 우리가 이런 변화를 겪고있을 때, 우리가 실제로 어디로 가고 있는지 알아낼 때까지 중복은 잘못된 추상화를 만드는 것보다 낫습니다 . 물론, 우리가이 시점으로 무엇을 변경했는지에 상관없이 실제로 수행되지 않았 음을 제안합니다.
우리 사이의 관계를 좀 더 자세히 살펴볼 필요 A
하고 B
. 일반적으로 상속보다는 구성을 선호 해야한다는 말이 있습니다. 이 모든 경우에 사실이 아니다, 그러나 우리가 파고 일단은 케이스의 놀라운 숫자에 해당됩니다. B
에서 상속 A
, 우리가 믿는 것을 의미 B
입니다 A
. 약간 다르게 작동한다는 점을 제외하고는 그대로 B
사용해야합니다 A
. 그러나 그 차이점은 무엇입니까? 차이점을 좀 더 구체적으로 지정할 수 있습니까? 그렇지 않은 B
입니다 A
,하지만 정말 A
이 X
될 수있는 A'
또는 B'
? 그렇게하면 코드는 어떻게 생겼습니까?
우리가 위에 방법 이동 한 경우 A
이전 제안을, 우리의 인스턴스 주입 수 X
로를 A
, 그리고 해당 방법을 위임 X
:
class A {
X x;
A(X x) {
this.x = x;
}
public void doIt(int x, int y) {
x.doIt(x, y);
}
}
구현 A'
하고 B'
제거 할 수 있습니다 B
. 보다 암묵적인 개념에 이름을 부여하여 코드를 개선했으며 런타임에 컴파일 시간 대신 해당 동작을 설정할 수있었습니다. A
실제로 덜 추상적이되었습니다. 확장 된 상속 관계 대신 위임 된 개체에서 메서드를 호출합니다. 그 객체는 추상적이지만 구현상의 차이점에만 더 중점을 둡니다.
그래도 마지막으로 볼 것이 있습니다. 동료의 제안으로 롤백합시다. 모든 콜 사이트에서 우리 A
가 보유 하고있는 유형을 명시 적으로 알고 있다면 다음과 같이 호출해야합니다.
B b = new B();
o.doIt(b, true);
즉 작성할 때 우리는 이전에 가정 A
이 X
중 하나입니다 A'
또는 B'
. 그러나 아마도이 가정조차 올바르지 않습니다. 이 유일한 장소 사이의 차이 A
와 B
문제? 그렇다면 약간 다른 접근법을 취할 수 있습니다. 우리는 여전히이 X
중 하나입니다을 A'
하거나 B'
,하지만 속하지 않는 A
. O.doIt
그것에 대해서만 관심이 있으므로 전달하십시오 O.doIt
.
class O {
int x;
int y;
public void doIt(A a, X x) {
x.doIt(a, x, y);
}
}
이제 우리의 콜 사이트는 다음과 같습니다 :
A a = new A();
o.doIt(a, new B'());
다시 한 번 B
사라지고 추상화가 더 집중적으로 이동합니다 X
. 그러나 이번에 A
는 덜 알면 훨씬 간단합니다. 훨씬 덜 추상적입니다.
코드베이스에서 중복을 줄이는 것이 중요하지만, 우선 중복이 발생하는 이유를 고려해야합니다. 복제는 나 가려고하는 더 깊은 추상화의 표시 일 수 있습니다.