-
객체 지향과 디자인 패턴 - 다형성과 추상 타입언어/객체지향 2023. 8. 13. 15:33
- 객체 지향의 장점은 구현 변경의 유연함
- 캡슐화를 통해 내부 구현의 유연함을 얻을 수 있었음
- 또 다른 유연함은 추상화를 통해 얻을 수 있다.
* 객체 지향의 사실과 오해 -> 추상화의 방법 1. 공통점(객체 구분) 2. 일반/서브타입 나누기 (부모/자식)
1. 상속
- 상속은 기존 클래스의 기능을 그대로 사용하면서 확장할 수 있게 한다.
- 오버라이드
2. 다형성과 상속
- 다형성이란 한 객체가 여러 모습을 가진다 -> 여러 기능(책임)을 수행할 수 있다.
- 자바와 같은 정적 타입 언어는 상속을 통해서 다형성을 구현한다.
3. 인터페이스 상속 및 구현
- 인터페이스 상속과 구현 상속으로 구분 할 수 있다.
- 인터페이스 상속: 타입(개념상 인터페이스) 정의만 상속받는 것 (추상클래스, 인터페이스 등)
구현 상속: 이미 구현된 클래스 상속 -> 기능 재사용 + 다형성 함께 제공
4. 추상 타입과 유연함
- 추상화는 데이터나 프로세스등 의미가 비슷한 개념이나 표현으로 정의하는 과정 -> 단순화
* 추상화가 반드시 추상타입만을 의미하는 것은 아님
ex) FTP에서 파일다운로드, 소켓으로 데이터 읽기, DB 테이블의 데이터 조회
-> 추상화 -> 로그수집
이러한 추상화를 통해 인터페이스를 도출할 수 있다.
추상화된 타입은 오퍼레이션 시그니처만 제공할 뿐 실제 구현은 없다 (인터페이스 타입)
4.1 추상타입과 실제 구현 연결
- 상속을 통해서 연결한다 즉, 구현 클래스가 추상 타입을 상속 받는 방법으로 연결한다.
- 실제 구현을 제공하는 클래스를 콘크리트 클래스라고 부른다.
4.2 추상 타입을 이용한 구현 교체의 유연함
public class FlowController{ private boolean useFile; public FlowController (boolean useFile){ this.useFile= useFile; } public void process(){ byte[] data = null; if(useFile){ FileDataReader filereader = new FileReader(); data = fileReader.read(); }else{ SocketDataReader soketreader = new SoketDataReader(); data = soketreader.read(); } // 읽어온 데이터 암호화 코드..쓰는 코드 } }
- FlowController 본연의 책임(흐름제어)와 상관 없이 데이터를 읽는 구현의 변경(요구사항의 변경)으로 FlowController도 함께 바뀌었다.
- 소켓을 통해 읽던, 파일에서 읽던 바이트 데이터를 읽는다는 공통점이 있다. -> 추상화
public interface ByteSource{ public byte[] read(); } public class FileDataReader implements ByteSource{//.. } public class SoketDataReader implements ByteSource{//.. } // 코드 변경 ByteSoruce source =null if(useFile) source = new FiledataReader(); else source = new SocketDataReader();
- 추상화를 통해 ByteSource 인터페이스 도출 -> ByteSource 도출 -> 기존 코드 단순화
- 아직 if-else 문에 있으니 ByteSource 구현 객체 생성하는 부분 별도처리
의존 객체 변경되더라도, 기존 객체 변경x의 방법
-> 객체 생성하는 기능을 별도 객체 분리, 그 객체로 ByteSource 생성
-> 생성자(또는 다른 메서드)를 이용해서 ByteSoruce 전달받기
public class FlowController{ public void process(){ ByteSource source = ByteSourceFactory.getInstance().create(); byte[] data = source.read(); // 팩토리 메서드 패턴을 통해 위와 같이 변경 } }
- FlowController에서 ByteSourceFactory로 분리할 수 있었다.
- 푸상화로 공통 개념 도출 + 많은 책임을 가진 객체 책임 분리
* FlowContrller가 그 안의 다른 객체 dataReader등 (의존하는 객체)들 보다 더 큰 수준에서 재사용이 이루어진다.
재사용의 중요성으로 봤을 때 하위수준 상세구현보다 변하지 않는 상위 수준의 로직을 재사용 가능하게 설계하는 것이 중요하다.
4.3 변화되는 부분을 추상화하기
- 추상화는 어렵다 -> 쉽게 추상화하는 기준 -> 변화되는 부분을 추상화하자!
- 변화가 자주 발생하는 곳을 추상 타입으로 교체하면 향후 변경에 유연하게 대처할 가능성이 높아진다.
- 변화 부분에 추상화가 잘 적용되어 있지 않으면, if-else 중첩구조 발생한다.
4.4 인터페이스에 대고 프로그래밍 하기
- 오퍼레이션을 정의한 개념상의 인터페이스를 의미한다.
- 콘크리트 클래스가 아닌, 기능을 정의한 인터페이스를 사용해서 프로그래밍 하라는 것이다.
- 하지만, 인터페이스는 요구사항의 변화와 함께 점진적으로 도출됨 -> 변화 가능성이 높아 보이는 곳에 사용하자
4.5 인터페이스는 사용자 입자에서 만들자
- 인터페이스를 작성할 때는 그 인터페이스를 사용하는 코드 입장에서 작성하자
- 사용의 혼란을 야기할 수 있다.
4.6 인터페이스와 테스트
- 객체의 책임이 인터페이스를 사용하여 추상화되어 있다면, 테스트가 보다 쉬워진다.
public void testProcess(){ FlowController fc = new FlowController(); fc.process(); // 만약 process() 내부에 아직 데이터 읽는 ByteSoruce가 완성 전이라면 테스트 할 수 없음 } //1 .테스트를 위해 생성자를 추가하자 (FlowController에 ) public class FlowController { private ByteSource byteSource; public FlowController(ByteSource bytesource){ this.byteSource = bytesource; } public void process(){ byte[] data = byteSource.read(); } } // 2. ByteSource를 구현한 임의의 콘크리트 클래스를 재정의한 뒤에 이를 주입 public void testProcess(){ ByteSoruce mocksource = new ByteSource{ @Override public byte[] read(){ //..구현 } }; FlowController fc = new FlowController(mocksource); fc.process(); }
- 위와 같이 추상화된 부분은 테스트하기 더 쉽다. (임의 구현으로 구현을 교체해서 테스트하면 됨)
- 임의 객체 사용할 대상을 인터페이스로 추상화하면 더 쉽게 Mock객체와 같은 것을 만들 수 있다.
'언어 > 객체지향' 카테고리의 다른 글
입출금 내역 분석기(2) (0) 2024.05.26 입출금 내역 분석기 (1) - 응집도와 결합도에 따른 클래스 분리 (0) 2024.05.24 객체지향과 디자인패턴 2 (0) 2023.08.13 객체지향과 디자인패턴 1 (0) 2023.08.11 객체지향의 사실과 오해 3 (0) 2023.08.02