상속 개요
- 상속(Inheritance): 한 타입을 그대로 사용하면서 구현을 추가할 수 있도록 하는 방법
- 상속 대상이 되는 클래스 = 상위(super) 클래스, 부모(parent) 클래스
- 상속을 받는 클래스 = 하위(sub)클래스, 자식(child)클래스
- 자식 클래스는 부모 클래스에 정의된 구현을 물려 받는다.
private 접근 제어자가 명시된 메소드, 필드를 제외하고 물려받는다.
- 재정의(Overriding): 하위 클래스에서 상위 클래스에 정의된 메소드를 새로 구현하는 것
- 메소드를 오버라이딩하면, 메소드 실행 시 상위 타입 메소드가 아닌 재정의한 하위 타입 메소드가 실행된다
다형성과 상속
- 다형성(Polymorphism): 한 객체가 여러 가지 모습(타입)을 갖는 것, 한 객체가 여러 타입을 가질 수 있는 것
정적 타입 언어(e.g Java)에서는 타입 상속을 통해 다형성을 구현한다.
인터페이스 상속과 구현 상속
- 타입 상속은 인터페이스 상속과 구현 상속으로 구분된다.
- 인터페이스 상속: 타입 정의만 상속 받는 것 (자바의 인터페이스)
- 구현 상속: 클래스 상속을 통해 이뤄지며, 상위 클래스에 정의된 기능을 재사용하기 위해 사용된다.
추상 타입과 유연함
- 추상화(abstraction): 데이터나 프로세스 등을 의미가 비슷한 개념이나 표현으로 정의하는 과정
- 추상화된 타입은 오퍼레이션의 시그니처만 정의하며 실제 구현은 제공하지 못한다.
- 추상 타입은 구현을 제공하지 않는 타입(인터페이스)을 통해 추상 타입을 정의한다.파일 다운로드, 소켓 로그 읽기, DB 읽기를 로그 수집으로 추상화 할 수 있다.
추상화는 단순 구현 클래스로부터 추상 타입을 이끌어 내는 것만 의미하는 것은 아니다.
e.g) 객체 모델링, 컴퓨터의 수행 처리 과정을 개념적으로 추상화
추상 타입과 실제 구현의 연결
- 추상 타입과 구현 클래스는 상속을 통해 둘을 연결한다.
LogCollector collector = createLogCollector(); // 추상 타입을 이용해 코드 사용가능 collector.collect();
- 콘크리트 클래스: 상위 타입을 실제로 구현한 클래스
추상 타입을 이용한 구현 교체의 유연함
public class FlowController {
public void process() {
FileDataReader reader = new FileDataReader();
byte[] data = reader.read();
...
}
}
- 파일 뿐만 아니라 소켓에서도 데이터를 읽도록 요구사항이 변경되는 경우 구현체를 사용하면 코드는 다음과 같다.
public class FlowController {
private boolean useFile;
public FlowController(boolean useFile) {
this.useFile = useFile;
}
public void process() {
byte[] data = null;
if(useFile) {
FileDataReader fileReader = new FileDataReader();
data = fileReader.read();
} else {
SocketDataReader socketReader = new SocketDataReader();
data = socketReader.read();
}
}
}
- FlowController의 책임과는 상관없이 데이터 읽기 구현의 변경으로 인해 FlowController 코드 자체가 영향을 받는다.
- 요구사항이 계속해서 추가되면 계속해서 코드가 영향을 받는 문제가 발생한다.
- 각 데이터 읽기 구현체는 “데이터 읽기”라는 공통된 기능을 가지며 이를 추상화할 수 있다.
public interface ByteSource {
public byte[] read();
}
public class FileDataReader implements ByteSource {
@Override
public byte[] read() { ... }
}
public class SocketDataReader implements ByteSource {
@Override
public byte[] read() { ... }
}
- 이제 추상화된 타입을 통해서 가져올 수는 있게 되었지만, 아직 요구사항이 변경되어도 FlowController 클래스 내부에서 구현체를 선택해줘야 하므로 코드 변경이 발생한다.
ByteSource source = null;
if(useFile)
source = new FileDataReader();
else
source = new SocketDataReader();
- FlowController가 구현체가 변경되더라도 바뀌지 않게 하는 방법에는 2가지가 존재한다.
- 객체를 생성하는 코드를 팩토리로 분리
- 생성자를 통해 실제 사용할 구현체를 받기
- 객체 생성 팩토리 사용
public class ByteSourceFactory {
public ByteSource create() {
if(useFile())
return new FileDataReader();
else
return new SocketDataReader();
}
private boolean useFile() {
String useFileVal = System.getProperty("usefFile");
return useFileVal != null && Boolean.valueOf(useFileVal)l
}
// 싱글톤
private static ByteSourceFactory instance = new ByteSourceFactory();
public static ByteSourceFactory getInstance() {
return instance;
}
private ByteSourceFactory() {}
}
ByteSource source = ByteSourceFactory.getInstance().create();
byte[] data = source.read();
- 생성자를 이용해 구현체를 받는 방법
private final ByteSource byteSource;
public FlowController(ByteSource byteSource) {
this.byteSource = byteSource;
}
public void process() {
this.byteSource.read();
...
}
- 추상화를 사용함으로써 크게 2가지 유연성을 갖게되었다.
- 추상화 타입을 사용하는 객체가 구현체가 변경되었다 해서 코드 변경이 일어나지 않게 되었다.
- 추상화 타입을 사용하는 객체는 자신의 책임에 대한 부분만 코드를 변경할 수 있다.
- 추상화는 공통된 개념을 도출해 추상 타입을 정의하며, 많은 책임을 가진 객체로부터 책임을 분리할 수 있다.
인터페이스에 대고 프로그래밍하기
- “인터페이스에 대고 프로그래밍” 하자.
- 콘크리트 클래스보단 인터페이스를 사용해 프로그래밍하는 것이 바람직하다.
- 인터페이스는 추상화를 통해 유연함을 얻는 것이 목적이기 때문에 변화 가능성이 높은 경우에만 사용하자.
- 변경 가능성이 희박한 클래스에 대해 인터페이스를 만들면 프로그램의 구조만 복잡해진다.
인터페이스는 인터페이스 사용자 입장에서 만들기
- 인터페이스를 작성할 때는 인터페이스를 사용하는 코드 입장에서 작성하자.
- 인터페이스를 사용하는 FlowController 입장에서는 FileDataReaderIF가 파일로부터 데이터를 읽어와야 하는 인터페이스라고 생각할 수 있다.
인터페이스와 테스트
- 인터페이스를 사용하면 Mock 객체를 쉽게 만들어 테스트의 유연함을 가져올 수 있다.
- 콘크리트 클래스의 구현 없이도 테스트가 가능하다.
- Mock 객체를 만들어 FlowController 자체에 대한 테스트를 먼저 진행할 수 있다.
public void test() {
ByteSource mockSource = new MockByteSource();
FlowController fc = new FlowController();
fc.process();
}
테스트 주도 개발 (Test Driven Development, TDD)
- 테스트 코드를 먼저 작성하고 실제 코드를 작성하는 방법으로 개발하는 기법
- 작성한 테스트를 통과하는 코드를 점진적으로 완성해 나가며, 완성하기 어려운 구현부로 인해 테스트가 어려워지면 해당 부분을 별도의 인터페이스로 구분해 개발하는 방식이다.
반응형
'기술 서적 > 개발자가 반드시 정복해야할 객체 지향과 디자인 패턴' 카테고리의 다른 글
2장. 객체 지향 (0) | 2024.06.09 |
---|