상속의 목적은 코드 재사용이 아니다. 상속의 목적은 타입 계층을 구조화하기 위함이다. 클라이언트 관점에서 인스턴스들을 동일하게 행동하는 그룹으로 묶기 위해서이다. 상속의 목적은 (서브타입) 다형성을 위한 서브타입 계층을 구축하는 것이다. 객체 지향 패러다임은 데이터와 행동을 하나의 실행 단위로 본다. 이런 측면에서 보면 상속은 데이터와 행동을 서브 타입에게 물려주는 것이고 이것이 상속의 목적을 오해하게 한다. 행동 관점에서 상속과 다형성의 기본적인 개념을 이해하기 위해서는 상속 관계로 연결된 클래스 사이의 메서드 탐색 과정을 이해하는 것이 가장 중요하다. 업캐스팅 : 부모 타입으로 선언된 변수에 자식 클래스의 인스턴스를 할당하는 것 동적 바인딩 : 메시지 처리를 실행 시점에 결정 위의 두 메커니즘은 OC..
상속 vs 합성 합성은 전체를 표현하는 객체가 부분을 표현하는 객체를 포함해서 부분 객체의 코드를 재사용한다. 상속에서 부모 자식 클래스간의 의존성은 컴파일 타임에 해결되지만, 합성에서는 런타임에 해결된다. 상속 관계는 is-a 관계, 합성 관계는 has-a 관계라고 부른다. 클래스 상속은 화이트박스 재사용이라고 부르는데, 이는 부모 클래스의 내부가 자식 클래스에게 곤개되기 때문이다. 반대로 합성은 블랙박스라 재사용이라고 부른다. 객체의 내부는 공개되지 않고 인터페이스를 통해서만 재사용되기 때문이다. 상속으로 인한 클래스 폭발 상속은 부모 자식간의 결합도를 높이고, 코드를 수정하는데 필요한 작업량이 과도하게 늘어날 수 있다. 일반적으로 다음 두 가지 문제점이 발생한다. 하나의 기능을 추가하거나 수정하기 ..
결합도 문제 상속은 간단해보이지만 사실 많은 한계점이 있다. 상속 관계는 슈퍼-서브 클래스간의 높은 결합도를 낳는다. 상속을 이용해 코드를 재사용하기 위해선 부모 클래스의 개발자가 세운 가정이나 배경을 추론하고 정확히 이해해야 한다. 상속은 자식 클래스가 부모 클래스의 구현 세부사항에 의존하도록 만들기 때문에 캡슐화를 약화시킨다. 상속은 재사용성을 위해서 캡슐화를 희생한다. 만약 어떤 객체가 상속을 여러번 사용해야 한다면? java 에서는 다중 상속을 허용하지 않는다. 슈퍼 클래스 내부의 변경이 있을 때 모든 서브 클래스를 함께 수정해야 할 수도 있다. 취약한 기반 클래스 문제 = 부모 클래스의 변경에 의해 자식 클래스가 영향을 받는 현상 취약한 기반 클래스는 캡슐화를 약화시키고 결합도를 높인다. 자식 ..
오브젝트(조영호 저)를 읽고 작성한 글입니다. 개방 폐쇄 원칙은 "확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다."를 의미한다. 조금 더 쉽게 풀어 쓰자면, "객체의 기능을 확장할 수 있으면서 기존의 코드(클라이언트 코드)는 수정하지 않는다." 를 뜻한다. 런타임 의존성과 컴파일타임 의존성 OCP는 런타임 의존성과 컴파일타임 의존성에 관한 이야기다. Movie는 인터페이스인 DiscountPolicy에 의존하기 때문에, 자식 클래스로 OverlappedDiscountPolicy 클래스를 추가하더라도 기존의 코드(Movie, DiscountPolicy, AmountDiscountPolicy, PercentDiscountPolicy)는 변하지 않는다. 새로운 컨텍스트에 사용되도록 확장할 수 있는 것..
오브젝트(조영호 저)를 읽고 작성한 글입니다. 협력은 필수적이지만 과도한 협력은 설계를 곤경에 빠뜨릴 수 있다. OOP의 핵심은 협력을 위해 필요한 의존성을 유지하면서도 변경을 방해하는 의존성은 제거하는 것에 있다. 의존성 전이 의존성은 전이될 수 있다. PeriodCondition이 Screening에 의존한다는 것은 Screening의 의존 대상에 대해서도 자동으로 의존하게 된다는 것이다. 의존성이 실제로 전이될지 여부는 변경의 방향과 캡슐화의 정도에 따라 달라진다. 의존성 해결 컴파일타임 의존성(추상화)은 런타임 의존성(구현체)으로 대체되어야 한다. 이를 의존성 해결이라고 부른다. 의존성 해결에는 3가지 방법이 있다. 생성자 수정자 메서드 실행시 인자를 통해 의존성과 결합도 의존성 = 두 요소 사이..
전통적인 기능 분해 방법 : 하향식 접근법 OOP가 전통적인 기능 분해 방법에 비해 효과적이라고 말하는 이유가 무엇인가? 하향식 접근법 = 최상위 기능을 더 작은 단계의 하위 기능으로 분해해나가는 방법. = 메인 함수부터 시작해 시스템에서 필요한 프로시저들을 만들어가는 단순한 방법 하향식 접근법의 문제점 : 시스템은 항상 하나의 메인 함수로 구성되지 않는다. 요구사항 변경과 기능 추가로 인해 메인 함수가 빈번히 수정되어야 한다. (OCP 위반) 비즈니스 로직이 인터페이스와 강하게 결합 실행 순서가 너무 이른 시기에 고정되기 때문에 함수간의 높은 결합도 -> 재사용성과 유연성 저해 데이터 변경에 따른 파급 효과 예측 어려움 결국 설계가 필요한 이유는 변경에 대비하기 위한 것이다. 변경은 성공적인 소프트웨어..
퍼블릭 인터페이스와 오퍼레이션 퍼블릭 인터페이스 = 객체가 의사소통을 위해 외부에 공개하는 메시지의 집합 오퍼레이션 = 퍼블릭 인터페이스에 포함된 메시지. 객체지향 관점에서 오퍼레이션을 퍼블릭 인터페이스처럼 생각하면 될듯하다. 수행 가능한 어떤 행동에 대한 추상화. 오퍼레이션에 명시된대로 실제로 실행되는 코드는 메서드라고 부른다. 따라서 퍼블릭 인터페이스의 관점에서 보면 "메서드 호출"보다는 "오퍼레이션 호출"이라고 표현하는 것이 더 적절하다. 디미터 법칙 - 묻지 말고 시켜라 디미터 법칙은 훌륭한 메시지는 객체의 상태를 묻지 말고 원하는 것을 시켜야 한다는 사실을 강조한다. 객체 외부에서 해당 객체의 상태를 기반으로 결정을 내리는 것은 객체의 캡슐화를 위반한다. 이 과정에서 자연스럽게 진정한 캡슐화를 ..
책임 할당의 기준 정보 전문가 패턴 메시지를 전송할 객체(클라이언트 코드)는 무엇을 원하는가? 메시지를 수신할 적합한 객체는 누구인가? 우리는 객체가 상태와 행동을 통합한 캡슐화의 단위라는 사실에 주목해야 한다. 객체는 자율적인 존재여야 한다. 따라서 객체에게 책임을 할당하는 첫번째 원칙은 책인을 수행할 정보를 알고 있는 객체에게 책임을 할당하는 것이다. 이를 통해 높은 응집도가 가능해진다. 여기서 이야기하는 정보는 데이터와 다르다. 책임을 수행하는 객체가 정보를 알고 있다고 해서 그 정보를 저장하고 있을 필요는 없다. 객체는 해당 정보를 제공할 수 있는 다른 객체를 알고 있거나, 필요한 정보를 계산해서 제공할 수도 있다. 여기서 정보는 도메인 모델상 떠올리는 개념의 추상적인 속성에 가깝다. 설계중에 너..
데이터 중심 설계와 책임 중심 설계의 차이 객체를 단순한 데이터의 집합으로 바라보는 시각은 객체의 내부 구현을 퍼블릭 인터페이스에 노출시키는 결과를 낳는다. 결과적으로 설계가 객체들의 내부 구현 변경에 취약해진다. 데이터 중심 설계에서는 객체의 행위보다 상태를 우선 생각한다. 데이터 중심의 관점에서 객체는 자신의 데이터를 조작하는데 필요한 오퍼레이션을 정의한다. 데이터 중심 설계에서도 형식적인 데이터 캡슐화는 가능하다. 객체에서 스스로의 데이터는 private으로 숨기고 대신 접근자와 수정자(getter, setter)를 드러내는 것이다. 그러나 이는 public으로 드러낸 것과 마찬가지이며 진짜 캡슐화가 아니다. 모든 인스턴스 변수를 접근자로 드러내기 때문에 그러한 인스턴스 변수들의 정보를 퍼블릭 인터..
What? 이전에는 @Value 를 사용해 *.yml , *.properties 의 프로퍼티 값들을 가져왔습니다. 개인적으로는 기능에 문제가 없었기 때문에 개선의 필요성을 인지하지 못하고 있었는데, 지인분의 추천으로 @ConfigurationProperties 과 @ConstructorBinding 어노테이션을 알게 되어 공유합니다. Why? 기존의 문제점부터 짚고 넘어가겠습니다. 아래와 같은 yml이 있다고 가정합니다. server: port: 8080 domain: http://localhost:8080 동적 타입 문제 @Value("${server.port}") private String stringPort; @Value("${server.port}") private Integer integerPo..