컴퓨터/소프트웨어 공학

[OOP] 객체 지향 설계를 잘하는 방법

도도새 도 2023. 11. 22. 16:23

객체 지향 설계

 

객체 지향 설계란?

객체 지향 설계(Object-Oriented Design, OOD)는 소프트웨어를 개발할 때 객체 지향 프로그래밍 (OOP) 원칙을 기반으로 하는 설계 접근 방식이다. 이러한 설계는 소프트웨어 시스템을 여러 객체로 나누고, 각 객체가 협력하여 기능을 수행하도록 하는 것을 중요시한다. 이를 위해서는 객체를 큰 관계 속에서 파악하는 것이 필요하다.

객체 지향의 목적은 유지 보수가 가능한 좋은 코드를 작성하는 데에 있다.

 

 

객체란?

객체는 현실 세계의 객체의 은유이다. 즉 현실 속 객체처럼 말하고 달리고 밥을 먹는다. 현실의 객체와의 차이점은 객체 지향 세계의 객체들은 모두 능동적이라는 것이다. 현실에서는 펀치를 치면 사람에 의해 샌드백이 터진다. 그러나 객체 지향 세계의 샌드백은 스스로 터지게 된다.

객체는 아래의 요소를 가진다.

상태

  • 숫자, 문자열, 속도, 시간 등 단순한 값을 통해 객체의 상태를 표현하는 요소이다.
  • 예를 들어 이름, 키, 몸무게가 있다.
  • 다른 객체가 상태로 사용될 수도 있다. 이를 링크라고 한다.
  • 링크가 아닌 단순한 값을 속성(attribute)라고 한다.
  • 링크 + 속성을 객체 프로퍼티라고 부른다. 변화하는 동적 프로퍼티와 변하지 않는 정적 프로퍼티가 존재한다.

행동

  • 객체가 수행하는 동작이나 기능이다. 주로 메서드로 정의된다.
  • 상태를 변화 시킬 수 있다.
  • 행동의 결과는 상태에 의존한다.
  • 예를 들어 운동을 해서 살이 빠진다면, 이전 상태의 kg에서 nKg만큼 살이 빠지게 된다. 즉, 이전 상태에 의존하며 상태를 변화시킨다.

식별자

  • 객체를 고유하게 식별하는 데 사용하는 값이다.

 

객체는 행위를 수행하기 위해 존재한다. 중요한 것은 상태가 아닌 행위이다.

 

객체 예시

class Person{
	private String name;
	private int height;
	private int weight;
	private static final double WEIGHT_LOSS_RATE = 0.2;

	public void performPractice(int time) {
        this.looseWeight(time*WEIGHT_LOSS_RATE);
	}
	public void looseWeight(double loosingWeight) {
		this.weight -= loosingWeight;
	}
}

위 코드에서 상태인 name은 정적 프로퍼티, weight는 동적 프로퍼티이다. 행동으로 운동 하기와 살 빼기를 가진다.

 

 

좋은 객체를 만드는 방법

객체 지향에서는 모든 객체는 능동적이며 자율적이다. 이 객체는 아래의 룰을 따를 경우 변화에 유연한 좋은 객체가 된다.

  • 객체의 용도와 목적이 명확해야한다.
  • SOLID원칙을 따른다. (https://doompa.tistory.com/303)
  • 각 언어에 맞는 코딩 컨벤션을 따른다.

 

 

객체 지향 설계를 잘 하는 방법

우선 객체 지향의 핵심적인 용어 세 가지를 기술한다.

 

협력

협력은 요청과 응답이다. 요청-응답을 통해 협력 관계가 형성되며, 여러 객체가 목적 달성을 위해 협력하게 된다. 객체 지향 설계는 객체의 설계가 아닌 적절한 협력의 설계에서 시작한다.

 

책임

객체가 협력에 참여하기 위해 수행해야 하는 행위를 개략적으로 서술한 것이다.  객체는 적절한 행동을 할 의무를 가진다. 즉 협력 관계 속에서 객체는 어떤 요청에 적절한 응답을 해야 할 책임이 있다. 이 책임은 두 가지 범주로 자세히 구분될 수 있다.

  • 하는 것(doing)
    • 객체를 생성하거나 계산을 하는 등 스스로 하는 것
    • 다른 객체의 행동을 시작시키는 것
    • 다른 객체의 활동을 제어하고 조절하는 것
  • 아는 것(knowing)
    • 개인적인 정보에 관해 아는 것
    • 관련된 객체를 아는 것
    • 자신이 계산할 수 있는 것에 대해 아는 것

즉, 책임은 객체의 외부에 제공 해 줄 수 있는 정보(knowing)와 외부에 제공해 줄 수 있는 서비스(doing)의 목록이라고 할 수 있다.

 

역할

어떤 객체가 수행하는 책임의 집합은 객체가 협력 안에서 수행하는 역할을 암시하게 된다. 역할 개념을 사용하면 유사한 협력을 추상화하여 인지 과부하를 줄일 수 있다.

즉, 역할이 대체 가능하다는 것은 행위가 호환 가능하다는 것을 의미하며, 이는 동일한 책임을 수행할 수 있다는 것을 의미하게 된다.

 

메시지란?

객체가 다른 객체에게 책임을 수행하도록 요청하는 것을 메시지 전송이라고 말한다. 그렇기에 두 객체 간 협력은 메시지를 통해 이뤄진다. 협력을 요청하는 객체를 송신자, 메시지를 받아 처리하는 객체를 수신자라고 한다.

 

책임 주도 설계(RDD)

책임 주도 설계는 시스템에서 책임을 누구에게 할당할 것인가, 에 대한 설계 관점이다. 이 관점을 통해 거대한 시스템을 작고 응집된 여러 작은 시스템으로 만드는 것을 목적으로 한다.

 

객체의 행위를 결정하는 것은 객체 자체의 속성이 아니다. 우리는 객체를 문맥 속에서, 큰 흐름 속에서 파악해야한다. 프로그래밍은 객체 간의 협력이다. 협력이라는 문맥 안에서 객체의 책임은 메시지가 결정하며, 책임이 앞서고 객체가 뒤따른다.

 

즉 책임 주도 설계는 아래 순서를 따르게 된다.

  1. 애플리케이션이 수행하는 기능을 시스템의 책임으로 상정한다.
  2. 시스템 책임을 구현하기 위해 협력 관계를 시작할 적절한 객체를 찾아 해당 시스템의 책임을 이 객체의 책임으로 할당한다.
  3. 객체가 책임을 완수하기 위해 다른 객체의 도움이 필요하면 어떤 메시지가 필요한지 결정한다.
  4. 메시지를 수신하기 적합한 객체를 선택한다.(이때 수신자는 송신자가 기대한대로 메시지를 책임져야한다, 즉 메시지가 수신자의 책임을 결정한다.)
  5. 수신 객체 역시 다른 객체의 도움이 필요할 경우 메시지를 보낸다.
  6. 3~5과정을 반복한다.

 

책임 주도 설계의 핵심은 어떤 행위가 필요한지 먼저 결정한 후 이 행위를 수행할 객체를 선택하는 것에 있다. 이를 What/Who 사이클이라고 부른다. 즉, 어떤 행위(What)을 누가(Who) 수행할 것인지 결정하는 것이다.

객체 지향 프로그래밍에서 이 어떤 행위가 바로 메시지이다. 즉 메시지를 우선 선택한 후 메시지를 수신하기 적합한 객체를 선택하는 것이다.

 

디자인 패턴

디자인 패턴은 특정 문제를 해결하기 위해 이미 식별한 역할, 책임, 협력의 모음이다. 즉 객체 지향에서 반복적으로 발생한 문제에 대해 이미 존재하는 솔루션을 의미한다. 대표적으로 GoF디자인 패턴이 존재한다.

 

테스트 주도 개발(Test-Driven Development, TDD)

코드를 작성하기 전에 테스트 케이스를 먼저 작성하고, 그 후에 해당 테스트 케이스를 통과시키기 위한 코드를 작성하는 개발 방식이다. 책임을 수행할 객체가 어떠한 메시지를 수신할 때 역할을 제대로 하는지, 어떤 객체와 협력할 것인지에 대한 기대를 코드 형태로 작성하는 것이라고 할 수 있다.

 

+) 스프링과 객체 지행 설계

스프링의 경우 Controller와 Repository는 데이터 관점으로 다뤄야 하는 영역이다. 이 영역은 절차지향적으로 작성해도 무관하다. 그러나 도메인 영역의 경우 변경이 잦기에 객체 지향 프로그래밍을 하는 것이 유리하다.


Reference: