상속보다는 조합을 사용하자.

cutiepazzipozzi·2023년 4월 17일
1

지식스택

목록 보기
19/35

스레드를 공부하고 얘기해보는 시간을 갖다가, Thread는 그 클래스를 상속시켜서 구현한다는 얘기가 나오면서 상속보다는 조합을 사용하라는 말이 나왔는데, 여기서 궁금증이 생겨서 포스팅을 시작했다. 사실 조합에 대한 개념이 아예 없기도 해서,,
(내가 아는 조합은 combination 조합밖에 없는girl)

간단히 알아보는 상속

상속부모 클래스의 기능을 자식 클래스가 그대로 물려받는 것을 일컫는다. 실제 코드에서는 extends 키워드를 활용해 상속을 표시한다.

//상속의 기본적인 예시
class Student {
	String name;
    void setName(String name) {
    	this.name = name;
    }
}

class St1 extends Student {
	void greet() {
    	System.out.println("안녕하세요! 저는 "+this.name+"입니다!");
    }
    //보통은 이렇게 부모의 기능에다가 추가적인 기능을 더 넣어준다.
}

public static void main(String[] args) {
	St1 st1 = new St1();
    st1.setName("주디");
   	st1.greet();
    //안녕하세요! 저는 주디입니다!
}

Student ex1 = new St1(); 
//상속 관계에 있기 때문에(is-A)
//자식 클래스의 객체는 부모 클래스의 자료형인 것처럼 사용할 수 있다.

++추가적으로, 자바에서 만드는 모든 클래스는 Object 클래스를 상속받기 때문에, Object student = new St1();이런 식으로도 코드를 작성할 수 있다.

메서드 오버라이딩(Method Overriding)

위에서 작성한 St1 예시에서 특이하게 인사를 하는 학생이 있다고 가정해보자.

public St2 extends St1 {
	void greet() {
    	System.out.println("안녕하시와요 저는 "+this.name+"입니당");
    }
}

public static void main(String[] args) {
	St2 st2 = new St2();
    st2.setName("닉");
    st2.greet();
    //안녕하시와요 저는 닉입니당
}

위의 greet 메서드는 상속받은 St1클래스의 greet 메서드와 입출력이 동일한 메서드이며, 더 높은 우선순위를 가지게 되어 기존 클래스의 greet 메서드가 덮어씌워진다. 이를 우리는 메서드 오버라이딩이라고 부른다.

메서드 오버로딩(Method Overloading)

오버라이딩은 기존의 메서드를 자식 클래스에서 덮어쓰는 것이라면, 오버로딩은 새로운 메서드를 추가하는 것이다.

public St2 extends St1 {
	void greet() {
    	System.out.println("안녕하시와요 저는 "+this.name+"입니당");
    }
    void sleep(int time) {
    	System.out.println(this.name+"이 "+time+"분 자는 중입니다.");
    }
}

일단, 조합이 뭐야?

조합기존 클래스가 새로운 클래스의 구성요소로 쓰이는 것을 말한다.
즉 기존 클래스가 새로운 클래스의 private 필드로 참조되는 것이다.

public class Nurse {
	private int injector;
    
    public Nurse(int injector) {
    	this.injector = injector;
    }
    
    public int getInjector() {
    	return injector;
    }
}

public class Doctor {
	public void greet() {
    	System.out.println("안녕하세요~ 구체적으로 어디가 아파서 오셨나요?");
    }
}

public class Hospital {
	private Nurse nurse; //조합!!
    private Doctor doctor;
    
    public Hospital() {
    	this.nurse = new Nurse(10);
        this.doctor = new Doctor();
    }
    
    public void greet() {
    	doctor.greet();
    }
    
    public int getNurseInjectorNumber() {
    	return nurse.getInjector();
    }
}

이렇게 사용하게 되면

  • 참조한 메서드를 호출하기 때문에 캡슐화가 깨지지 않는다.
  • 상위 클래스의 변화에 안정적이다.

이는 상속의 단점인 캡슐화(외부에서 내부 속성이나 메서드에 접근하지 못하게 함) 보장 x를 보완한다.

그렇다면 상속의 이러한 단점에 대해 코드로 알아보자!

// 잘못된 상속의 예시
// 원소의 덧셈 횟수를 구하기 위해 HashSet을 상속받음
public class InstrumentedHashSet<E> extends HashSet<E> {
    // The number of attempted element insertions
    private int addCount = 0;
    public InstrumentedHashSet() {
    }
    public InstrumentedHashSet(int initCap, float loadFactor) {
        super(initCap, loadFactor);
    }
    @Override public boolean add(E e) {
        addCount++;
        return super.add(e);
    }
    @Override public boolean addAll(Collection<? extends E> c) {
        addCount += c.size();
        return super.addAll(c);
    }
    public int getAddCount() {
        return addCount;
    }
}

InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("Snap", "Crackle", "Pop"));

우리가 이 메서드에서 addAll을 활용해 위의 리스트를 넣어주면 3을 기대하지만 실제로는 6이 출력된다. 왜? addAll 메서드를 실행할 때 위에서 설명했던 오버라이딩 된 add 메서드가 호출되는데 그 안에는 addCount를 증가시켜주는 코드가 있기 때문에 3개의 요소가 또 더해지기 때문이다.

그래서 어떨 때 쓰면 좋은데?

상속도 적절하게 사용되면 개발이 간편해진다는 장점을 갖기 때문에,

  • is-a관계라면 상속을 사용 하도록 하자.
    (is-a관계 = 일반적 개념 - 구체적 개념 간의 관계)
    ex. 사람은 동물이다. 토끼는 동물이다.
  • API에 결함이 없거나 하위 클래스에 전파돼도 영향이 없을 때 사용하자.

조합은 그 외의 has-a관계에서 사용하도록 한다.
(has-a관계 = 일반적인 포함 관계)

참고

https://blogs.oracle.com/javamagazine/post/java-inheritance-composition
https://dundung.tistory.com/201
https://wikidocs.net/280

profile
노션에서 자라는 중 (●'◡'●)

0개의 댓글