디미터의 법칙

💡 리뷰 활동을 하면서 디미터의 법칙을 상기시켜보라는 리뷰를 받게 되었습니다. 객체 지향 프로그래밍에서 중요하게 적용되는 디미터의 법칙에 대하여 학습하였습니다.

 

디미터의 법칙?

  • Law of Demeter
  • 어떤 객체가 다른 객체에 대해 지나치게 많이 알다보니, 결합도가 높아지고 좋지 못한 설계가 발생합니다.
  • 이를 개선하고자 객체에게 내부 값을 숨기는 대신 함수를 공개하도록 하는 것이 디미터의 법칙입니다.
  • 다른 객체가 어떠한 내부 값을 가지고 있는지 속사정을 몰라아 햔다는 뜻으로, 아래와 같이 불리기도 합니다.
    • Don’t Talk to Stranger (낯선 이에게 말하지 마라)
    • Principle of lesat knowledge (최소 지식 원칙)
  • 또한 직관적인 이해를 위해서 여러 개의 .(도트)를 사용하지 않도록 권장하고 있습니다.

디미터의 법칙의 장점

  • 디미터의 법칙을 준수함으로써 캡슐화를 높혀 객체의 자율성과 응집도를 높일 수 있습니다.
  • 낮은 결합도
    • 객체들 간에 낮은 결합도를 유지하게 되어, 시스템이 유연해지고 유지보수가 쉬워집니다.
  • 향상된 모듈성
    • 각 객체가 자신의 책임에 충실하고 다른 객체의 내부 구조를 알 필요가 없으므로, 모듈성이 향상됩니다.
    • 이로인해 코드의 재사용성이 높아지고, 시스템을 더 쉽게 확장할 수 있습니다.
  • 테스트 용이성
    • 객체 간의 상호작용이 단순해지고 예측 가능해지므로, 단위 테스트가 쉬워집니다.
    • 이로인해 테스트 작성 시 모킹(mocking)을 간단하게 할 수 있습니다.
  • 가독성 향상
    • 객체 간의 상호작용이 단순하고 명확해지므로, 코드의 가독성이 향상됩니다.
  • 변경에 대한 탄력성
    • 내부 구조가 바뀌더라도, 외부에서 접근하는 방식이 일관성을 유지하기 때문에 변경에 대한 탄력성이 높아집니다.
    • 이로인해 시스템의 안정성을 높일 수 있습니다.

객체 지향 프로그래밍과의 관계

  • 객체 지향 프로그래밍에서 가장 중요한 것은 “객체가 어떤 데이터를 가지고 있는가?” 보단, “객체가 어떤 메세지를 주고 받는가?”이다.
  • 디미터의 법칙이 위배된다는 것은 올바른 객체 지향 프로그래밍을 하지 못한다고 할 수 있다.

OOP

  • OOP는 소프트웨어 설계와 개발에서 중요한 패러다임으로 아래와 같은 개념을 가지고 있습니다.
    • 추상화 : 복잡한 시스템을 이해하기 쉽게 단순화
    • 캡슐화 : 데이터와 메서드를 하나의 단위로 묶고, 내부 ****구현을 숨김
    • 상속 : 클래스 간의 계층 구조를 형성하여 코드의 재사용성을 높임
    • 다형성 : 동일한 인터페이스를 사용하여 다른 구현을 제공
  • 디미터의 법칙은 객체가 자신이 직접 소유하거나 생성한 객체와만 상호작용을 제한합니다.
  • 이를 활용해서 OOP에 중요한 설계를 할 수 있도록 도움을 줄 수 있습니다.
  • OOP의 핵심 개념과 디미터의 법칙은 상호 보완적인 관계에 있으며, 이를 잘 적용하면 견고하고 유연한 소프트웨어 시스템을 설계할 수 있습니다.

디미터의 법칙 활용하기

  • 우선 디미터의 법칙을 잘 지키지 못한 경우를 확인 해보겠습니다.
class Engine {
	fun start() {
		print("Engine Started")
	}
}

class Car(val endgine: Engine)

class Driver(private val car: Car) {
	fun drive() {
		car.engine.start()
		// driver가 car의 engine에 직접 접근하여 start() 메서드 실행
	}
}

fun main() {
	val engine = Engine()
	val car = Car(engine = engine)
	val driver = Driver(car)
	driver.drive()
	// Driver가 Car의 연산에 직접 접근 
	// 이는 결합도가 높다고 할 수 있다.
}
  • 위는 디미터의 법칙을 지키지 않았으며, 결합도가 높은 설계라고 할 수 있습니다.
class Engine {
	fun start() {
		print("Engine Started")
	}
}

class Car(val endgine: Engine) {
	fun start() {
		engine.start()
	}
}

class Driver(private val car: Car) {
	fun drive() {
		car.start()
	}
}

fun main() {
	val engine = Engine()
	val car = Car(engine = engine)
	val driver = Driver(car)
	driver.drive()
	// Driver가 Car의 start()만 호출하므로
	// Engine의 내부 구조를 알 수 없습니다.
}
  • Cart에 start() 함수를 생성해야 하는 번거로움이 생겼지만, 확실하게 아래와 같은 이점을 얻었습니다.
    • 캡슐화를 통해서 내부 구현을 숨김
    • 다중 도트 활용을 방지
    • 결합도를 낮출 수 있음