it 책/오브젝트: 코드로 이해하는 객체지향 설계

오브젝트: 코드로 이해하는 객체지향 설계 4장을 읽으며

lgvv 2024. 11. 10. 16:04

오브젝트: 코드로 이해하는 객체지향 설계 4장을 읽으며

 

느낀점

리플 이펙트와 강한 결합도의 문제를 다룬 코드 예제가 큰 인상으로 남음. 강한 결합도가 있을 때, 하나의 코드 변경이 시스템 전체에 걸쳐 어떤 파급 효과를 일으킬 수 있는지 직접 예제를 통해 보면서, 결합도를 낮추는 것에 대해서 고민하게 되었음. 코드의 유연성이 떨어지고, 작은 수정에도 많은 부분이 연쇄적으로 영향을 받는다는 점에서 코드의 유지보수성에 큰 장애물이 된다는 걸 체감.

 

enum으로 값을 정의하는 코드들이 정의 케이스가 바뀌면서 외부에서 대응해주는 것들이 많았는데 이게 강한 결합도와 리플 이펙트 영역과 예시가 많이 닮아 있어서 좋은 인사이트가 되었다!

 

설계 품질과 트레이드오프

객체지향 설계의 핵심은 역할, 책임, 협력임. 

  • 협력: 기능을 구현하기 위해 메시지를 주고받는 개체들 사이의 상호작용
  • 책임: 다른 객체와 협력하기 위해 수행하는 행동
  • 역할: 대체 가능한 책임의 집합

객체지향 설계란 올바른 객체에게 올바른 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동

  • 협력: 기능을 구현하기 위해 메시지를 주고받는 개체들 사이의 상호작용
  • 객체지향 설계의 핵심이 책임이라는 것
  • 책임을 할당하는 직업이 응집도와 결합도 같은 설계 품질과 깊이 연관돼 있다는 것.

 

설계 품질과 트레이드오프

데이터 중심 설계에서는 캡슐화에서 객체의 엷은 막을 빠져나가 외부에서 오염시키는 것을 막음

  • 내부의 데이터를 반환하는 접근자(accessor)와 데이터를 변경하는 수정자(mutator)를 추가
public final class Movie { 
   var fee: Int 
   
   init(fee: Int) { 
      self.fee = fee
   }

   /// ✅ 수정자
   public setFee(fee: Int) { 
      self.fee = fee
   }
   
   /// ✅ 접근자
   public getFee() -> Int { 
      return fee
   }
}

 

캡슐화

상태와 행동을 하나의 객체 안에 모으는 이유는 객체의 내부 구현을 외부로부터 감추기 위해서. 

  • 구현: 나중에 변경될 가능성이 높은 것을 가르킴.
  • 인터페이스: 상대적으로 안정적인 부분.
  • 객체지향이 강력한 이유는 한 곳에서 일어난 변경이 전체 시스템에 영향을 끼치지 않도록 파급효과를 적절하게 조절하는 장치를 제공.
  • 객체지향에서 가장 중요한 원리는 캡슐화

 

응집도와 결합도

응집도와 결합도는 구조적 설계 방법이 주도하던 시대에 소프트웨어 품질을 측정하기 위해 소개된 기준이지만 객체지향 시대에 여전히 유효

  • 응집도: 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타냄
    • 응집도가 높을수록 좋음
    • 변경의 관점에서 응집도란 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도로 측정
  • 결합도: 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는 척도
    • 어떤 모듈이 다른 모듈에 대해 너무 자세한 부분까지 알고 있다면 높은 결합도를 가짐
    • 결합도가 낮을수록 좋음
    • 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정


캡슐화 위반

데이터 중심으로 설계한 클래스를 보면 객체 내부 상태에 get-set을 통해 간접적으로 접근할 수 있음.

  • 하지만 이는 캡슐화의 원칙을 지키고 있는 것처럼 보임
    • 하지만 접근자와 수정자 메서드는 객체 내부의 상태에 대한 어떠한 정보도 캡슐화하지 못한다.
    • 무엇보다도 이름의 인스턴스 변수가 존재한다는 사실을 퍼블릭 인터페이스에 노골적으로 드러냄.
    • 접근자와 수정자에 과도하게 의존하는 설계 방식을 추측에 의한설계 전략(design-by-guessing straegy)
    • 하지만 함수의 내부 구현이 외부로 퍼져나가는 파급효과(ripple effect)가 아래 구현에서 존재
public class DiscountCondition { 
	enum DiscountConditionType { 
       case none
       case percentage
       case amout
    }

	private let type: DiscountConditionType
    
    // ✅ 여기서 DiscountConditionType가 외부로 노출되어 있음.
    func caclutate(type: DiscountConditionType) { 
    	...
    }
}


낮은 응집도

서로 다른 이유로 변경되는 코드가 하나의 모듈 안에 공존할 때 응집도가 낮다고 말함.

  • 단일 책임 원칙 (Single Resposiblility Principle, SRP)
    • 모듈의 응집도가 변경과 연관이 있다는 사실을 강조하기 위해 단일 책임 원칙이라고 설계 원칙을 제시

 

데이터 중심 설계의 문제점

객체의 행동보다는 상태에 초점을 맞춤

  • 접근자와 수정자는 public 속성과 큰 차이가 없기 때문에 객체의 캡슐화는 완전히 무너질 수 밖에 없음
  • 데이터를 먼저 결정하고 처리에 필요한 오퍼레이션을 결정하는 방식은 결과적으로 객체의 인터페이스 구현 캡슐화에 실패
  • 너무 이른 시기에 데이터에 대해 고민하기 때문에 캡슐화에 실패하게 되며, 객체 내부 구현이 객체의 인터페이스를 어지럽히고 객체의 응집도와 결합도에 나쁜 영향을 미치기 때문에 변경에 취약한 코드를 생성

데이터 중심의 설계는 객체를 고립시킨 채 오퍼레이션을 정의하도록 만듦

  • 올바른 객체지향 설계의 무게 중심은 항상 객체 내부가 아니라 외부에 맞춰져 있음.
  • 데이터 중심의 설계는 외부강 ㅏ니라 데이터 세부 정보를 먼저 결정하기에 내부에 중심이 맞춰짐.
  • 이로 인해 변경에 유연하게 대처하지 못했던 것.