글 작성자: beaniejoy
Spring은 객체 지향의 꽃이라고 들었는데... 면접에서도 단골 질문 메뉴인 객체 지향은 무엇일까.

📌 객체 지향(Object Oriented)

  • 개발자들이 더욱 편하게 개발할 수 있는 방법론을 고안하는 과정에서 나온 결과물 중 하나라고 생각
  • 현실 세계를 프로그래밍적으로 잘 표현할 수 있는 모델
  • But, 우리는 개발자이므로 사물 관련된 것보다 추상적 대상에 대한 클래스화에 초점
  • (본인 생각으로는 확장과 분리를 잘 하게끔 만들어주는 좋은 틀이라 생각, OCP를 생각해보면 그렇습니다.)

구조적 프로그래밍

  • 함수(function)가 가장 중요한 요소(함수 단위)
  • Divide and Conquer(분할 정복)에 기반해 명령어를 논리적 단위로 나눠 블록화해 작성

객체지향 프로그래밍 등장

  • Object(객체) 단위로 이루어짐
  • 객체지향 프로그래밍은 애플리케이션을 유연하고 변경이 쉽도록 만들어준다. (장점)
  • 단순히 0, 1로 이루어진 컴퓨터 세계에서 추상적인 세계로 확장
  • 객체와 객체간의 관계를 중요시하게 생각함

 

📌 객체 지향 대표 특성

  • 캡슐화
  • 추상화
  • 상속
  • 다형성

캡슐화(Encapsulation)

개인적으로 생각하기에 객체지향의 확장성을 가장 잘 표현해주는 특성이라 생각합니다.
가장 흔한 예시로 드는 리모컨 예시로 알아보겠습니다.

리모컨의 기능

  • 음량 높낮이 조절(volumeUp, volumeDown)
  • 채널 이동(goUp, goDown)
  • 설정 변경(changeEnv)

아주 조잡하게 리모컨의 기능을 정리해보았습니다. 우리는 이 TV 리모컨을 사용할 때 단순히 버튼을 조작하면서 사용할겁니다. 그리고 다른 회사의 TV 리모컨을 받아도 곧잘 사용할 수 있습니다. 왜냐하면 위에 언급한 리모컨의 기능은 어떤 회사 것이든 상관없이 동일할 것이거든요.

그런데 만약 리모컨을 조작하는데 있어서 리모컨 작동방식까지 알아야 된다고 가정해봅시다. 리모컨 볼륨 업을 누르는데 전압은 어느정도로 줄 것이며 어떤 선을 이용해야 하는지, TV와의 remote 연결 구성은 어떻게 이루어지는 다 알아야 볼륨 업을 작동할 수 있다고 말도 안되는 가정을 생각해봅시다. 상상만해도 끔찍합니다.

더 끔찍한 것은 다른 회사의 TV 리모컨을 받았을 때 이러한 세부 과정들은 전부다 다를 것입니다. 또 조작법을 공부해야되는 셈이죠.

현실세계에서는 인간이 리모컨을 사용하는 클라이언트에 해당하지만 객체지향 세계에서는 객체가 클라이언트가 됩니다. 객체가 리모컨이라는 기능을 담고 있는 오브젝트를 사용하려 할 때 어떤 특정 업체 기술의 조작법에 의존하고 있다면 다른 업체 기술로 교체하려 할 때 이를 구현하고 있는 코드를 싹다 갈아엎어야 될 것입니다.

하지만 캡슐화를 통해 클라이언트가 되는 객체(오브젝트)는 인터페이스에서 제공해주는 기능(메소드)들을 이용하기만 하면 됩니다. 다른 기술로 이전한다 해도 코드 수정없이 해당 오브젝트는 volumeUp, volumeDown만 알면되고 사용하기만 하면됩니다. 이것이 제가 생각하는 캡슐화의 묘미라 생각합니다.

추상화(Abstraction)

구체적인 것을 분해해 관심 영역(Application Boundary)에 있는 특성만 가지고 재조합
추상화 = 모델링

사람이라는 도메인을 가지고 한번 속성과 기능을 생각해보면 이렇습니다.

  • 사람의 속성: 나이, 키, 성별, 직업, 성향(MBTI), ... 
  • 사람의 기능: 먹다, 일하다, 운동하다, 놀다, 자다, 공부하다, ...

이렇게 하나의 애플리케이션에서 사람이라는 Entity를 이런 식으로 정의하면 끝도 없습니다. 그래서 관심 영역의 끝과 끝을 정하고 이 안에서 도메인을 정하게 됩니다.

은행 애플리케이션

  • 은행 고객의 속성: 나이, 성별, 직업, 연봉, 신용도, 계좌, ...
  • 은행 고객의 기능: 출금하다, 대출하다, 상환하다, 등록하다, ...

일반적인 사람의 범주에서 은행에 특화된 영역만을 가지고 재정의한 것입니다. 이를 추상화라고 할 수 있습니다.

Customer cust = new Customer(); // 클래스 : 객체
  • Customer(Class) : cust(Object)
  • 클래스 : 객체 관계는 붕어빵틀 : 붕어빵 이 아니다.
    (붕어빵틀이라는 범주에 붕어빵을 넣는 것도 그렇고 붕어빵틀이라는 기능을 확장해 붕어빵 클래스에 사용하는 것도 이상함)
  • 🔑 클래스 : 객체 관계는 집합과 원소 관계
    (은행고객이라는 범주 안에 여러 고객들이 존재. 자영업자, 회사원, 대학생, 특정 고객 등...  여러 고객들이 존재)

은행 애플리케이션에서 여러 고객 객체들을 정의해보면 각각 고유한 성질과 공통분모가 있을 것이다. 여기서 애플리케이션 경계 내에 관심 공통 분모를 뽑아서 클래스로 정의하는 것이 추상화라고 할 수 있다.

상속(InheritanceExtension)

  • 재사용 + 확장 (상속이라고 생각하지 말자, Inheritance X)
  • 상속은 계층 관계, 조직 관계를 의미하는 것이 아니라 분류 관계를 의미한다.
  • 상위 클래스 - 하위 클래스 관계로 이해할 것(상위 분류 - 하위 분류)

상속이 부모 - 자식 관계라고 해서 오해하면 안된다. 진짜 상속을 의미하는 것이 아닌 집합과 분류의 개념으로 접근해야 합니다. 이를 잘 표현해주는 것이 LSP입니다. 객체지향 5원칙 중 하나인 리스코프 치환 원칙(LSP)은 하위클래스는 상위클래스로 대체될 수 있다는 것이 핵심인데 이를 적용하면 왜 분류의 개념인지 명확해집니다.

Father 철수아빠 = new Son();	// ?
Animal 멍뭉이 = new Dog();		// 적용 가능

책에 나온 예시인데요. LSP에 따라 아들이 아버지로 대체될 수 있어야 하는데 말이 안되는 걸 한 번에 알아챌 수 있습니다. 이와 달리 강아지는 동물이기 때문에 충분히 대체 가능하다고 볼 수 있습니다. 즉 상속은 부모 - 자식 관계의 하위클래스로 물려주는 개념이 아니라 집합이라는 클래스에 속하는 하나의 원소인 객체 관계로 접근해야 합니다.

상위 클래스 인터페이스 

  • [상위클래스 : 하위클래스] is a kind of 관계
  • [인터페이스 : 구현클래스] be able to 관계
  • 상위클래스: 하위클래스에게 속성 + 메소드 상속
  • 인터페이스: 구현클래스에게 기능을 구현하도록 강제

상위클래스는 풍성할 수록 좋고 인터페이스는 구현을 강제할 메소드가 적을 수록 좋다고 할 수 있습니다. 각각 리스코프 치환 법칙(LSP), 인터페이스 분할 원칙(ISP)에 따른다고 볼 수 있습니다.

다형성(Polymorphism)

  • 주로 Overriding 관련
  • 상위 클래스 타입의 객체 참조 변수를 사용해도 하위 클래스에서 Overriding한 메소드가 호출
  • 인터페이스 : 구현클래스 간 대체 가능한 것과 관련이 깊음
public interface ContentService {
	void createContent();
}

public class MailContentService implements ContentService {
	@Override
    public void createContent() {
    	// Mail Content 생성
    }
}

public class SmsContentService implements ContentService {
	@Override
    public void createContent() {
    	// SMS Content 생성
    }
}

ContentService 인터페이스를 생성하고 이를 Mail, SMS 관련 서비스에서 구현하는 클래스가 있다고 생각해봅시다.

public class ClientController {
	
    private final ContentService contentService;
    
    public ClientController() {
    	this.contentService = new MailContentService();
        // this.contentService = new SmsContentService();
    }
}

해당 ContentService를 사용하는 클라이언트에서 기존에 Mail 관련 서비스를 사용하다 SMS로 바꾼다고 했을 때 단순히 MailContentService에서 SmsContentService로 교체하면 됩니다. 구현체들은 인터페이스 참조변수로 받을 수 있게 해준 것이 다형성이라고 할 수 있습니다.


 

📌 정리

  • 객체 지향 프로그래밍
    - 애플리케이션을 유연하고 변경이 쉽도록 만들어준다는 점에서 강점
    - 객체 단위로 이루어져있고 객체와 객체간의 관계를 중요시
    - Spring Framework는 java의 객체지향 프로그래밍의 장점을 극대화한 프레임워크
  • 객체 지향의 특징
    - 캡슐화(Encapsulation): 기능에 있어서 내부 동작원리를 감추고 클라이언트에서 기능만을 편리하게 사용 가능(인터페이스)
    - 추상화(Abstraction): 관심 영역(경계)에 있는 공통 분모의 속성들을 뽑아서 클래스로 정의하는 것
    - 상속(Inheritance): 정확히는 확장(Extension), 분류 관계를 뜻하는 것(집합의 개념)
    - 다형성(Polymorphism): 여러 구현체들을 하나의 인터페이스 참조로 표현, Overriding과 관련 깊음

 

틀린 내용이 있을 수 있습니다. 언제든 코멘트 환영입니다.

출처: "스프링 입문을 위한 자바 객체 지향의원리와 이해" - 김종민 지음
(위 책 내용 중 중요한 부분에 대한 저만의 생각과 내용을 정리한 게시글입니다.)