[F-lab 모각코 챌린지 16일차] TIL

JeongheeKim·2023년 6월 15일

TIL

목록 보기
16/66
💡 여기서 말하는 상속은 구체 클래스 상속이지 인터페이스 상속이 아님!

클래스가 다른 클래스를 구현 상속할 경우 캡슐화를 깨지게 할 수 있으며, 상위클래스 변경에 따라 하위클래스에 여러 영향을 줄 수 있다.

아래 예제는 HashSet을 상속받아 add, addAll을 재정의한 예제이다. 추가된 요소들의 개수를 확인할 수 있는 메서드이다.

/**
 * @author jhkim
 * @since 2023/01/07
 *
 */
public class InstrumentHashSet<E> extends HashSet<E> {
	private int addCount = 0;

	public InstrumentHashSet() {

	}
	public InstrumentHashSet(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;
	}

	public static void main(String[] args) {
		InstrumentHashSet<String> s = new InstrumentHashSet<>();
		s.addAll(List.of("a","ab","abc"));
		System.out.println("s.getAddCount() = " + s.getAddCount());// 6
	}
}

예상 결과인 3과는 다르게 6이 출력된다. addAll메서드에 정의된 상위 클래스의 addAll 메서드안에서 add메서드는 재 구현된 add 메서드를 호출하게된다. 이는 상위 클래스에 구현된 메소드를 확인하여 하위 클래스 구현 시 반복되지 않는 상황을 고려하게 하여 캡슐화가 깨지는 상황이 발생한다.

또한 위와 같은 상황에서 상위 클래스의 변경이 하위 클래스에 영향을 줄 수 있게 된다.

이를 방지하기 위해 Composition을 책에서 제안한다.

  • Composition
    - 새로운 클래스(InstrumentedSet<E>)를 생성 한뒤, private필드로 기존 클래스의 인스턴스를 참조하게 하는 방식
    - 새로운 클래스(InstrumentedSet<E>)의 인스턴스 메서드들은 기존 클래스의 대응하는 메서드를 호출해 결과를 반환한다.

  • Forwarding

    • 새 클래스의 인스턴스 메서드들은 기존 클래스의 대응하는 메서드를 호출해 그 결과를 반환
    • ForwardingSet : 전달 메서드만을 이뤄진 재사용 가능한 전달 클래스

인터페이스

프로그램에 어떤 메서드를 제공하는지 알려주는 명세

인터페이스에서 선언한 변수는 컴파일 과정에서 상수로, 선언한 메서드는 컴파일 과정에서 추상 메서드로 변환됨

예시) jdbc 인터페이스에 서드파트사에서 인터페이스에서 정의된 규격만 보고 자바 애플리케이션과 디비와의 연결 connection 객체 생성

자바에서는 인터페이스를 통해 다중 상속을 지원함


1. 인터페이스 정의하는 방법

인터페이스 선언 시 접근 제어자와 함께 interface키워드 사용

인터페이스의 모든 필드는 public static final, 메서드는 public abstract으로 선언되어야 한다.

( 모든 인터페이스에 공통으로 적용되므로 생략 가능, 컴파일 시 컴파일러가 자동으로 추가)

→ 인터페이스는 구현한 클래스를 어떻게 조작할 것인가를 규정한다 그러므로 생략해도 public 접근 제한자가 붙는다.

접근제어자 interface interface{
  public static final 변수명;
  public abstract 메서드 명;
}

2. 인터페이스 구현하는 방법

interface Animal {
	public abstract void cry();
}

class Dog implements Animal{

	@Override
	public void cry() {
// TODO Auto-generated method stub
	}
}

클래스에서 implements 키워드를 통해 인터페이스를 상속받는다. 만약 상속받은 클래스에서 인터페이스에서 명세한 메서드를 모두 구현하지 않는다면 클래스는 추상 클래스가 되어야 한다.


3. 인터페이스 레퍼런스를 통해 구현체를 사용하는 방법

package interfaceex;

public interface Calc {
    double PI = 3.14;//프리컴파일 단계에서 public static final 제한자가 붙게됨int ERROR = -999999;

    int add (int num1, int num2);
    int sbustract (int num1, int num2);
    int times (int num1, int num2);
    int divide (int num1, int num2);

    default void description(){
        System.out.println("정수 계산기를 구현합니다.");
    }

}

계산기 기능을 명세한 Calc 인터페이스

package interfaceex;

public abstract class Calculator implements Calc {
    @Override
    public int add(int num1, int num2) {
        return 0;
    }

    @Override
    public int sbustract(int num1, int num2) {
        return 0;
    }

}

Caculator클래스에서 인터페이스 Calc를 상속받았다. Calc에서 정의된 메서드를 모두 구현하지 않았으므로 Calculator는 추상 클래스가 되어야 한다.

package interfaceex;

public class CompleteCalc extends Calculator{
    @Override
    public int times(int num1, int num2) {
        return num1 * num2;
    }

    @Override
    public int divide(int num1, int num2) {
        if(num2 == 0){
            return ERROR;
        }
        return num1 / num2;
    }

    public void showInfo(){
        System.out.println("모두 구현하였습니다.");
    }
//온전하게 구현된 클래스는 CompleteCalc 뿐이어서 인스턴스화 가능
}

인터페이스에서 정의된 메서드 중 나머지를 CompleteCalc클래스에서 구현하였다.

Calc calc = new CompleteCalc();
Calc calc = new CompleteCalc2();//예시로 Calc인터페이스를 상속받은 클래스
  • Calc 인터페이스 타입으로 상속받은 여러 클래스 타입으로 인스턴스 생성이 가능하다.

Calc 인터페이스 타입으로 인스턴스 생성 시  showInfo() 메서드는 선언이 불가하다. 만약 업 캐스팅으로 Calc 타입으로 인스턴스를 선언 시 위의 showInfo() 메서드는 Calc 인터페이스를 상속받은 하위 클래스에서 정의했으므로 선언이 불가하다.

package interfaceex;

public class CalcTest {
    public static void main(String[] args) {

//Calc calc = new CompleteCalc();
        CompleteCalc calc = new CompleteCalc();
        int n1 = 10;
        int n2 = 2;

        System.out.println(calc.add(n1,n2));
        System.out.println(calc.sbustract(n1,n2));
        System.out.println(calc.times(n1,n2));
        System.out.println(calc.divide(n1,n2));


        calc.showInfo();

    }
}

0개의 댓글