이 기사에서는 Java의 상속 및 구성 개념을 설명합니다. 첫 번째 예에서는 상속을 설명한 다음 구성을 사용하여 상속 디자인을 개선하는 방법을 보여줍니다. 마지막에 그 중에서 선택하는 방법을 요약하겠습니다.
1. 상속
Insect
클래스 (영어 곤충)가 있다고 가정해 보겠습니다. 이 클래스에는 1. move()
(영어 이동에서) 및 2. attack()
(영어 공격에서) 의 두 가지 메서드가 포함되어 있습니다.
class Insect {
private int size;
private String color;
public Insect(int size, String color) {
this.size = size;
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void move() {
System.out.println("Move");
}
public void attack() {
move(); //assuming an insect needs to move before attacking
System.out.println("Attack");
}
}
Bee
이제 유형 중 하나이지만 및 의 Insect
구현이 다른 클래스(English bee)를 정의하려고 합니다 . 이는 상속을 사용하여 수행할 수 있습니다. attack()
move()
class Bee extends Insect {
public Bee(int size, String color) {
super(size, color);
}
public void move() {
System.out.println("Fly");
}
public void attack() {
move();
super.attack();
}
}
public class InheritanceVSComposition {
public static void main(String[] args) {
Insect i = new Bee(1, "red");
i.attack();
}
}
클래스 계층 다이어그램은 매우 간단합니다. 실행 결과:
Fly
Fly
Attack
"Fly"가 두 번 입력되었으므로 메서드가 move()
두 번 호출됩니다. 하지만 한 번만 호출해야 합니다. 문제는 super.attack()
. 메소드는 클래스 메소드를 attack ()
호출합니다 . 하위 클래스가 호출할 때 재정의된 메서드도 호출합니다 . 문제를 해결하기 위해 다음을 수행할 수 있습니다. move()
Insect
super.attack ()
move()
- 하위 클래스 메서드를 제거합니다
attack()
. 이렇게 하면 하위 클래스가 상위 클래스의 메서드 구현에 종속되게 됩니다attack()
. 슈퍼클래스가 다른 이동 방법을 사용하기 시작 하면attack()attack()
서브클래스도 변경해야 합니다. 이것은 잘못된 캡슐화입니다. -
메소드를
attack()
다음과 같이 다시 작성하십시오.public void attack() { move(); System.out.println("Attack"); }
-
이는 하위 클래스가 더 이상 상위 클래스에 종속되지 않기 때문에 올바른 결과를 보장합니다. 그러나 코드는 슈퍼클래스와 중복됩니다. (이 메소드는
attack()
단순히 문자열을 출력하는 것보다 더 복잡한 작업을 수행합니다). 이는 좋은 소프트웨어 설계가 아니며 중복된 코드가 없어야 합니다.
2. 구성
상속 대신 합성을 사용할 수 있습니다. 이를 활용한 솔루션을 살펴보겠습니다. 함수는attack()
인터페이스로 추상화됩니다.
interface Attack {
public void move();
public void attack();
}
공격 인터페이스를 구현하여 다양한 유형의 공격을 정의할 수 있습니다.
class AttackImpl implements Attack {
private String move;
private String attack;
public AttackImpl(String move, String attack) {
this.move = move;
this.attack = attack;
}
@Override
public void move() {
System.out.println(move);
}
@Override
public void attack() {
move();
System.out.println(attack);
}
}
공격 기능은 외부이므로 클래스에 Insect
더 이상 포함되지 않습니다.
class Insect {
private int size;
private String color;
public Insect(int size, String color) {
this.size = size;
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
클래스 Bee
(English Bee에서), 공격 유형이 어떻게 Insect
될 수 있는지.
// This wrapper class wrap an Attack object
class Bee extends Insect implements Attack {
private Attack attack;
public Bee(int size, String color, Attack attack) {
super(size, color);
this.attack = attack;
}
public void move() {
attack.move();
}
public void attack() {
attack.attack();
}
}
클래스 다이어그램:
public class InheritanceVSComposition2 {
public static void main(String[] args) {
Bee a = new Bee(1, "black", new AttackImpl("fly", "move"));
a.attack();
// if you need another implementation of move()
// there is no need to change Insect, we can quickly use new method to attack
Bee b = new Bee(1, "black", new AttackImpl("fly", "sting"));
b.attack();
}
}
실행 결과:
fly
move
fly
sting
3. 이러한 접근 방식을 언제 사용해야 합니까?
다음 2가지 사항은 상속과 구성 사이를 결정하는 데 도움이 될 수 있습니다.- "IS" 형식의 클래스 간의 관계를 처리하고 클래스가 모든 인터페이스를 다른 클래스에 제공하려는 경우 상속이 더 좋습니다.
- 관계가 "HAS"이면 구성이 선호됩니다.
- 블로흐, 조슈아. 효과적인 자바. 피어슨 교육 인도, 2008
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one-should...
GO TO FULL VERSION