본문 바로가기
책 내용 정리/[책] 개발자가 반드시 정복해야 할 객체지향과 디자인 패턴

[2] 객체 지향, 다형성, 추상화, 캡슐화, 객체, 메시지, 객체 지향 설계 과정

by 문자메일 2023. 2. 25.

https://link.coupang.com/a/QoNwV

 

개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴

COUPANG

www.coupang.com

 

본문의 내용은 위 책에서 정리한 내용 일부이며, 자세히 알고 싶은 분들은 위 책을 구매해서 보시는 것을 추천드립니다.

씹고 뜯고 맛보고 즐겨도 내용이 지루하지 않고, 정말 돈이 아깝지 않은 책!

 

 

 

 

절차 지향과 객체 지향

객체 지향

절차 지향과 달리 객체 지향은 데이터 및 데이터와 관련된 프로시저를 객체(object)라고 불리는 단위로 묶는다. 객체는 프로시저를 실행하는데 필요한 만큼 데이터를 가지며, 이 객체들이 모여 프로그램을 구성한다.

 

객체 지향적으로 만든 코드에서는 객체의 데이터를 변경하더라도 해당 객체로만 변화가 집중되고 다른 객체에는 영향을 주지 않기 때문에 요구 사항의 변화가 생겼을 때 절차 지향 방식보다 프로그램을 더 쉽게 변경할 수 있는 장점을 가진다.

 

 

객체

객체의 핵심은 기능을 제공하는 것

객체를 정의할 때 사용되는 것은 객체가 제공해야 할 기능이다. 객체가 내부적으로 어떤 데이터를 갖고 있는 지로는 정의하지 않는다.

 

ex : 소리 크기 제어 객체의 경우 아래 기능을 제공할 것으로 추측할 수 있다.

- 소리 크기 증가

- 소리 크기 감소

- 음소거

 

인터페이스 (시그니처) 명세

  • 기능 식별 이름
  • 파라미터 및 파리미터 타입
  • 기능 실행 결과 값
오퍼레이션 이름 파라미터 결과
getVolumeRate() 없음 0~1 사이 실수 값

 

메시지

요청을 받은 읽기 객체는 해당 요청에 해당하는 기능을 실행한 뒤에 응답을 전달하게 된다.

오퍼레이션의 실행을 요청(request) 하는 것을 메시지(message)를 보낸다. 라고 표현한다.

 

자바에서는 메서드를 호출하는 것이 메시지를 보내는 과정에 해당된다.

 

FileInputStream is = new FileInputStream(fileName);

byte[] data = new byte[512];

int readBytes = is.read(data);

 

객체의 책임과 크기

한 객체가 갖는 책임을 정의한 것이 타입/인터페이스이다.

객체가 갖는 책임은 어떻게 결정할까? 이 결정을 하는 것이 객체 지향 설계의 출발점이다.

 

처음 프로그램을 만들기 위해서는 필요한 기능 목록을 정리를 해야 한다,.

 

아래는 기능 예시

  • 파일의 byte 데이터를 제공한다
  • 파일에 byte 데이터를 쓴다.
  • byte 데이터를 암호화해서 새로운 byte 데이터를 생성한다.
  • 전체 흐름을 제어한다.

 

위 4개 기능을 객체들에 어떻게 분배하느냐에 따라서 객체의 구성이 달라진다. 아래 이미지는 객체들에 분배하는 구성 중 몇 가지 이다.

 

 

모든 상황에 들어맞는 객체-책임 구성 규칙은 존재하지 않는다.

하지만 객체가 얼마나 많은 기능을 제공할 것인가에 대한 확실한 규칙은 하나 존재한다.

그것은, 객체가 갖는 책임의 크기는 작을수록 좋다는 것이다.

객체가 갖는 책임이 작아야 한다는 것은 객체가 제공하는 기능의 개수가 적다는 것을 의미한다.

 

객체가 갖는 책임이 커질수록 절차 지향적으로 구조가 변경되며, 절차 지향의 가장 큰 단점인 기능 변경의 어려움 문제가 발생하게 된다,

 

따라서 객체가 갖는 책임의 크기는 작아질수록 객체 지향의 장점인 변경의 유연함을 얻을 수 있게 된다.

 

단일 책임 원칙을 따르다 보면 자연스럽게 기능의 세부 내용이 변경될 때, 변경해야 할 부분이 한 곳으로 집중된다.

 

 

의존

객체 지향적으로 프로그램을 구현하다 보면, 다른 객체가 제공하는 기능을 이용해서 자신의 기능을 완성하는 객체가 출현하게 된다.

위 예시에서 '흐름 제어' 객체는 'byte 암호화' 객체와 '파일 읽기' 객체, '파일 쓰기' 객체를 이용해서 파일 데이터 암호화 프로그램의 실행 흐름 기능을 완성하였다.

 

한 객체가 다른 객체를 이용한다는 것은, 실제 구현에서 한 객체의 코드에서 다른 객체를 생성하거나 다른 객체의 메서드를 호출한다는 것을 의미한다.

'흐름 제어' 객체의 실제 코드는 다른 객체의 메서드를 호출해서 기능을 완성해 나간다.

 

public class FlowController {
	
	// fileName 필드 초기화 코드 생략
	
	public void process() {
		FileDataReader reader = new FileDataReader(fileName); // 객체 생성
		byte[] plainBytes = reader.read(); // 메서드 호출
		
		ByteEncryptor encryptor = new ByteEncryptor(); // 객체 생성
		byte[] encryptedBytes = encryptor.encrypt(plainBytes); // 메서드 호출
		
		FileDataWriter writer = new FileDataWriter(); // 객체 생성
		writer.write(encryptedBytes); // 메서드 호출
	}

}

 

캡슐화 (내부 구현에 유연성을 준다)

아래는 캡슐화를 위한 법칙 2개

  • Tell, Don't Ask
  • 데미테르의 법칙

객체 지향의 장점은 한 곳의 구현 변경이 다른 곳에 변경을 가하지 않도록 해준다는 것.

즉, 수정을 좀 더 원활하게 할 수 있도록 하는 것이 객체 지향적으로 프로그래밍을 하는 이유이다.

객체 지향은 캡슐화를 통해서 한 곳의 변화가 다른 곳에 미치는 영향을 최소화 한다.

 

캡슐화는 객체가 내부적으로 기능을 어떻게 구현하는지를 감추는 것이다.

캡슐화를 통해 내부 기능 구현이 변경되더라도, 그 기능을 사용하는 코드는 영향을 받지 않도록 만들어 준다.

-> 내부 구현 변경의 유연함을 줄 수 있는 기법이 캡슐화이다.

 

 

절차 지향적으로 구현하는 방식(데이터에 직접 접근하여 로직 구현)은 그 데이터를 사용하여 로직을 구현하는 부분이 여러 곳에 널려있기 때문에(동일한 로직, 다른 로직), 변경점이 생기는 경우 그 데이터를 사용하여 로직을 구현한 곳을 모두 찾아서 수정을 해야 하고, 그 과정에서 변경점 찾느라 많은 시간을 소모하고 놓친 부분 때문에 버그가 발생할 가능성도 높아진다.

 

 

객체 지향적인 캡슐화 방법으로 객체마다 본인이 가지고 있는 데이터와, 그 데이터를 이용하는 특정 기능 제공을 위한 로직(인터페이스)을 구현하고, 클라이언트에서는 해당 객체의 데이터가 아니라 객체에 구현된 인터페이스로 필요한 기능을 사용하게 된다면, 나중에 추가적인 변경사항에 의해 불가피하게 특정 기능 제공을 위한 로직이 수정 필요하게 될 경우 그 데이터를 가지고 있는 객체에서만 로직을 수정하면 된다. 해당 기능(인터페이스)를 사용하는 클라이언트에서는 변경사항이 발생하지 않게 된다.

=> 결국 객체에 데이터랑, 그 데이터를 사용하는 기능을 같이 묶음으로써, 그렇지 않은 경우랑 비교했을 때 코드의 중복 사용 제거와 추후 변경 시 변경점을 모으는 효과가 큰 것이 캡슐화의 장점인듯 하다.

 

 

 

객체 지향 설계 과정

객체 지향 설계란 아래의 작업을 반복하는 과정이라고 볼 수 있다.

  1. 제공해야 할 기능을 찾고 & 세분화 하고, 그 기능을 알맞은 객체에 할당한다.
    1. 기능을 구현하는데 필요한 데이터를 객체에 추가한다. 객체에 데이터를 먼저 추가하고, 그 데이터를 이용하는 기능을 넣어도 된다.
    2. 기능은 최대한 캡슐화해서 구현한다.
  2. 객체 간에 어떤 메시지를 주고받을 지 결정한다.
  3. 처음부터 완전한 설계는 없다! 개발을 진행하는 동안 위 1,2번 과정을 반복한다.

 

 

 

댓글