We will find a way, we always have.

-interstellar

OOP

[오브젝트] 4장 설계 품질과 트레이드오프

Redddy 2023. 12. 2. 00:44

객체지향 설계의 핵심은 역할, 책임, 협력이다. 올바른 객체에게 책임을 할당하면서 낮은 결합도와 높은 응집도를 가진 구조를 창조하는 활동이다. 훌륭한 설계란 합리적인 비용 안에서 변경을 수용할 수 있는 구조를 만드는 것이다. 적절한 비용 안에서 쉽게 변경할 수 있는 설계는 응집도가 높고 서로 느슨하게 결합돼 있는 요소로 구성된다. 이를 달성하기 위한 중요한 원칙은 객체의 상태가 아니라 객체의 행동, 더 나아가 책임에 초점을 맞추는 것이다. 객체를 단순한 데이터의 집합으로 바라보는 시각은 객체의 내부 구현을 퍼블릭 인터페이스에 노출시키는 결과를 낳기 때문에 결과적으로 설계가 변경에 취약해진다. 
 
이번 장에서는 영화 예매 시스템을 책임이 아닌 상태를 표현하는 데이터 중심의 설계를 먼저 살펴보고 객체지향적으로 설계한 구조와 비교하여 차이점을 살펴본다. 
 
 

1. 데이터 중심의 영화 예매 시스템

 
설게를 할 때, 상태를 분할의 중심축으로 삼을 수도 있고, 책임을 분할의 중심축으로 삼을 수 있다. 데이터 중심의 관점에서는 객체는 자신이 포함하고 있는 데이터를 조작하는 데 필요한 오퍼레이션을 정의한다. 책임 중심의 관점에서 객체는 다른 객체가 요청할 수 있는 오퍼레이션을 위해 필요한 상태를 보관한다. 데이터 중심의 관점은 객체의 상태에 초점을 맞추고 책임 중심의 관점은 객체의 행동에 초점을 맞춘다. 전자는 객체를 독립된 데이터 덩어리로 바라보고 후자는 객체를 협력하는 공동체의 일원으로 바라본다. 
 
객체지향 설계가 책임에 초점을 두는 이유는 변경과 관련이 있다. 
객체의 상태는 구현에 속한다. 구현은 불안정하기에 변하기 쉽다. 상태를 객체 분할의 중심축으로 삼는다면 구현에 관한 세부사항도 객체 인터페이스에 스며들어 캡슐화의 원칙이 무너지고, 만약 상태가 변하게 된다면 이 인터페이스에 의존하는 모든 객체에게 변경의 영항이 퍼지게 된다. 
반면에 객체의 책임은 인터페이스에 속한다. 객체는 책임을 드러내는 안정적인 인터페이스 뒤로 책임을 수행하는 데 필요한 캡슐화를함으로써 구현 변경에 대한 파장이 외부로 퍼져나가는 것을 방지한다. 
 
 

데이터를 준비하자

데이터 중심의 Movie 클래스는 행동 중심의 Movie 클래스와는 다르게 할인 조건의 목록(discountConditions)이 인스턴스 변수로 Movie 안에 직접 포함되어 있다. 그리고 할인 정책을 별도로 분리하지 않고, 할인 금액과 할인 비율을 직접 정의하고 있다. 어떤 할인 정책을 적용하는지는 MovieType의 값을 사용한다. 
 
 

1
2
3
4
5
6
7
8
9
10
public class Movie {
    private String title;
    private Duration runningTime;
    private Money fee;
    private List<DiscountCondition> discountConditions;
 
    private MovieType movieType;
    private Money discountAmount;
    private double discountPercent;
}
cs

 
이것이 말 그대로 데이터 중심의 접근 방법이다. 
 
데이터 중심의 설계에서는 객체가 포함해야 하는 데이터에 집중한다. 이 객체가 가져야 하는 책임은 무엇인가? 라는 질문보다 이 객체가 포함해야 하는 데이터는 무엇인가? 라는 질문을 더 먼저 던졌다면 데이터 중심의 설계에 매몰되어 있었을 수도 있다. 
 
이제 접근자(getter)수정자(setter)를 추가하여 내부 데이터를 캡슐화한다.
 
할인 조건은 순번 조건과 기간 조건이 있었다. 이 할인 조건의 타입은 DiscountConditionType 이라는 enum을 만들어서 정의한다.
 

1
2
3
4
public enum DiscountConditionType {
    SEQUENCE,       // 순번조건
    PERIOD          // 기간 조건
}
cs

 
 
할인 조건의 구현은 할인 조건의 타입을 저장할 인스턴스 변수를 가진다. 그리고 순번 조건에서만 사용되는 데이터인 상영 순번과 기간 조건에서만 사용되는 데이터인 요일, 시작 시간, 종료 시간을 함께 가진다. 
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class DiscountCondition {
    private DiscountConditionType type;
 
    private int sequence;
 
    private DayOfWeek dayOfWeek;
    private LocalTime startTime;
    private LocalTime endTime;
 
    public DiscountConditionType getType() {
        return type;
    }
 
    public void setType(DiscountConditionType type) {
        this.type = type;
    }
 
    public DayOfWeek getDayOfWeek() {
        return dayOfWeek;
    }
 
    public void setDayOfWeek(DayOfWeek dayOfWeek) {
        this.dayOfWeek = dayOfWeek;
    }
 
    public LocalTime getStartTime() {
        return startTime;
    }
 
    public void setStartTime(LocalTime startTime) {
        this.startTime = startTime;
    }
 
    public LocalTime getEndTime() {
        return endTime;
    }
 
    public void setEndTime(LocalTime endTime) {
        this.endTime = endTime;
    }
 
    public int getSequence() {
        return sequence;
    }
 
    public void setSequence(int sequence) {
        this.sequence = sequence;
    }
}
cs

 
 
Screening은 영화(Movie)와 순번, 그리고 상영 시간을 상태로 가지고, Reservation은 고객의 정보를 보관하는 Customer와 Screening, 영화값, 인원수를 상태로 가진다. Customer는 이름과 ID를 상태로 가진다. 
 

영화를 예매하자

 
영화를 예매하는 코드는 다음과 같다. 
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class ReservationAgency {
    public Reservation reserve(Screening screening, Customer customer,
                               int audienceCount) {
        Movie movie = screening.getMovie();
 
        boolean discountable = false;
        for(DiscountCondition condition : movie.getDiscountConditions()) {
            if (condition.getType() == DiscountConditionType.PERIOD) {
                discountable = screening.getWhenScreened().getDayOfWeek().equals(condition.getDayOfWeek()) &&
                        condition.getStartTime().compareTo(screening.getWhenScreened().toLocalTime()) <= 0 &&
                        condition.getEndTime().compareTo(screening.getWhenScreened().toLocalTime()) >= 0;
            } else {
                discountable = condition.getSequence() == screening.getSequence();
            }
 
            if (discountable) {
                break;
            }
        }
 
        Money fee;
        if (discountable) {
            Money discountAmount = Money.ZERO;
            switch(movie.getMovieType()) {
                case AMOUNT_DISCOUNT:
                    discountAmount = movie.getDiscountAmount();
                    break;
                case PERCENT_DISCOUNT:
                    discountAmount = movie.getFee().times(movie.getDiscountPercent());
                    break;
                case NONE_DISCOUNT:
                    discountAmount = Money.ZERO;
                    break;
            }
 
            fee = movie.getFee().minus(discountAmount).times(audienceCount);
        } else {
            fee = movie.getFee().times(audienceCount);
        }
 
        return new Reservation(customer, screening, fee, audienceCount);
    }
}
cs

 
 
reserve 메서드는 크게 두 부분으로 나눠진다. 첫 번째는 DiscountCondition에 대해 루프를 돌면서 할인 가능 여부를 확인하는 for 문이고, 두 번째는 discountable 변수의 값을 체크하고 적절한 할인 정책에 따라 예매 요금을 계산하는 if 문이다. 
 
할인 여부는 지역변수 discountable에 저장하고, 이 값이 false라면 할인 조건이 존재하지 않다는 의미로 영화의 기본 금액을 예매 금액으로 사용하고, true라면 할인 정책에 따라 할인 요금을 계산한 후 Movie의 기본 금액에서 차감한다. 
 
할인 요금 계산은 할인 정책의 타입에 따라 할인 요금을 계산하는 로직을 분기해야 한다. 금액 할인 정책이라면 discountAmount를 이용하고, 비율 할인 정책이라면 discountPercent를 이용해 요금을 계산한다. 
 
 

2. 설계 트레이드오프

캡슐화, 응집도, 결합도에 대해 짚고 넘어가자. 
 

캡슐화

상태와 행동을 하나의 객체 안에 모으는 이유는 객체의 내부 구현을 외부로부터 감추기 위해서다. 구현은 앞서 말했듯이 변경될 가능성이 높은 것을 가리킨다. 변경 가능성이 높은 부분은 내부에 숨기고 외부에는 상대적으로 안정적인 부분만 공개함으로써 변경의 여파를 통제할 수 있다. 
 
변경될 가능성이 높은 부분이 구현, 상대적으로 안정적인 부분을 인터페이스라는 것을 기억하자. 객체를 설계하기 위한 가장 기본적인 아이디어는 변경의 정도에 따라 구현과 인터페이스를 분리하고 외부에서는 인터페이스에만 의존하다록 관계를 조절하는 것이다. 
 
캡슐화는 외부에서 알 필요가 없는 부분을 감춤으로써 대상을 단순화하는 추상화의 한 종류다. 객체지향 설계의 가장 중요한 원리는 불안정한 구현 세부사항을 안정적인 인터페이스 뒤로 캡슐화하는 것이다. 변경될 수 있는 어떤 것이라도 캡슐화해야한다. 
 

응집도와 결합도

 
응집도는 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타낸다. 모듈 내의 요소들이 하나의 목적을 위해 긴밀하게 협력한다면 그 모듈은 높은 응집도를 가진다. 모듈 내의 요소들이 서로 다른 목적을 추구한다면 그 모듈은 낮은 응집도를 가진다. 객체지향의 관점에서 응집도는 객체 또는 클래스에 얼마나 관련 높은 책임들을 할당했는지를 나타낸다. 

 
변경의 관점에서 응집도란 변경이 발생할 때 모듈 내부에서 발생하는 변경의 정도로 측정할 수 있다. 간단히 말해 하나의 변경을 수용하기 위해 모듈 전체가 함께 변경된다면 응집도가 높은 것이고 모듈의 일부분만 변경된다면 응집도가 낮은 것이다. 또한 하나의 변경에 대해 하나의 모듈만 변경된다면 응집도가 높지만 다수의 모듈이 함께 변경돼야 한다면 응집도가 낮은 것이다. 
 
 
 
결합도는 의존성의 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도다. 어떤 모듈이 다른 모듈에 대해 너무 자세한 부분까지 알고 있다면 두 모듈은 높은 결합도를 가진다. 어떤 모듈이 다른 모듈에 대해 꼭 필요한 지식만 알고 있다면 두 모듈은 낮은 결합도를 가진다. 객체지향의 관점에서 결합도는 객체 또는 클래스가 협력에 필요한 적적한 수준의 관계만을 유지하고 있는지를 나타낸다. 
 

 
결합도 역시 변경의 관점에서 설명하자면, 한 모듈이 변경되기 위해서 다른 모듈의 변경을 요구하는 정도로 측정할 수 있다. 다시 말해 하나의 모듈을 수정할 때 얼마나 많은 모듈을 함께 수정해야 하는지를 나타낸다. 따라서 결합도가 높으면 높을수록 함께 변경해야 하는 모듈의 수가 늘어나기 때문에 변경하기가 어려워진다. 
 
 
캡슐화의 정도가 응집도와 결합도에 영향을 미친다. 
 
 

3. 데이터 중심의 영화 예매 시스템의 문제점

 
기능적인 측면으로 봤을 때 2장에서 구현한 책임 중심의 설계와 방금 구현한 상태 중심의 설계는 완전히 동일하다. 하지만 설계 관점에서는 완전히 다르다.
 
데이터 중심의 설계는 캡슐화를 위반하고 객체의 내부 구현을 인터페이스의 일부로 만든다. 반면 책임 중심의 설계는 객체의 내부 구현을 안정적인 인터페이스 뒤로 캡슐화한다. 데이터 중심의 설계가 가진 대표적인 문제점은 아래와 같다.
 

  • 캡슐화 위반
  • 높은 결합도
  • 낮은 응집도

 
 

캡슐화 위반

데이터 중심으로 설계한 Movie 클래스를 살펴보면 필드 값을 가져오기 위해 getter를 사용하고, 필드 값을 수정하기 위해 setter를 사용하고 있다. private로 선언해 직접 객체의 내부에 접근할 없어 캡슐화의 원칙을 지키고 있는 것처럼 보이지만 접근자와 수정자 메서드는 객체 내부의 상태에 대한 어떤 정보도 캡슐화하지 않는다. 
 
 

높은 결합도

ReservationAgency 코드를 다시 살펴보면 movie.getFee().minus(...).times(...); 를 통해 Movie fee 를 얻는다. 이때 fee 의 타입을 변경한다고 가정해볼때, 이를 위해서 getFee 메서드의 반환 타입도 함꼐 수정해야 하고, getFee 메서드를 호출하는 ReservationAgency의 구현도 변경된 타입에 맞게 함께 수정해야 할 것이다. 
 
fee의 타입 변경으로 인해 협력하는 클래스가 변경되기 때문에 getFee 메서드는 fee를 정상적으로 캡슐화하지 못한다. 
 
결합도 측면에서 데이터 중심 설계가 가지는 또 다른 단점은 여러 데이터 객체들을 사용하는 제어 로직이 특정 객체 안에 집중되기 때문에 하나의 제어 객체가 다수의 데이터 객체에 강하게 결합된다는 것이다. 
 

낮은 응집도

ReservationAgency를 예로 들어 변경과 응집도 사이의 관계를 살펴보자. 아래와 같은 수정사항이 생긴다면 ReservationAgency 코드를 수정해야 한다.
 

  • 할인 정책이 추가될 경우
  • 할인 정책별로 할인 요금을 계산하는 방법이 변경될 경우
  • 할인 조건이 추가되는 경우
  • 할인 조건별로 할인 여부를 판단하는 방법이 변경될 경우
  • 예매 요금을 계산하는 방법이 변경될 경우

하나의 요구사항 변경을 반영하기 위해 동시에 여러 모듈을 수정해야 한다. 응집도가 낮을 경우 다른 모듈에 의지해야 할 책임의 일부가 엉뚱한 곳에 위치하게 되기 때문이다. 
 

자율적인 객체를 향해

 

캡슐화를 지켜라

getter와 setter로 도배된 코드 대신 변경하는 주체를 외부의 객체 말고 객체 내부로 옮겨라. 객체가 자기 스스로를 책임지는 형태로 만들어라. 
 

스스로 자신의 데이터를 책임지는 객체

객체는 단순한 데이터 제공자가 아니다. 객체 내부에 저장되는 데이터보다 객체가 협력에 참여하면서 수행할 책임을 정의하는 오퍼레이션이 더 중요하다. 
 

  • 이 객체가 어떤 데이터를 포함해야 하는가?
  • 이 객체가 데이터에 대해 수행해야 하는 오퍼레이션은 무엇인가?

 
위의 두 가지 질문을 조합하면 객체의 내부 상태를 저장하는 방식과 저장된 상태에 대해 호출할 수 있는 오퍼레이션의 집합을 얻을 수 있다. 다시 말해 새로운 데이터 타입을 만들 수 있는 것이다. 
 
책임을 이동시켜보자. 
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class DiscountCondition {
    private DiscountConditionType type;
 
    private int sequence;
 
    private DayOfWeek dayOfWeek;
    private LocalTime startTime;
    private LocalTime endTime;
 
    public DiscountCondition(int sequence){
        this.type = DiscountConditionType.SEQUENCE;
        this.sequence = sequence;
    }
 
    public DiscountCondition(DayOfWeek dayOfWeek, LocalTime startTime, LocalTime endTime){
        this.type = DiscountConditionType.PERIOD;
        this.dayOfWeek= dayOfWeek;
        this.startTime = startTime;
        this.endTime = endTime;
    }
 
    public DiscountConditionType getType() {
        return type;
    }
 
    public boolean isDiscountable(DayOfWeek dayOfWeek, LocalTime time) {
        if (type != DiscountConditionType.PERIOD) {
            throw new IllegalArgumentException();
        }
 
        return this.dayOfWeek.equals(dayOfWeek) &&
                this.startTime.compareTo(time) <= 0 &&
                this.endTime.compareTo(time) >= 0;
    }
 
    public boolean isDiscountable(int sequence) {
        if (type != DiscountConditionType.SEQUENCE) {
            throw new IllegalArgumentException();
        }
 
        return this.sequence == sequence;
    }
}
cs

 
 
isDiscountable 메서드가 할인 조건 여부를 판단해주고 있다. 
 
 
 
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class Movie {
    private String title;
    private Duration runningTime;
    private Money fee;
    private List<DiscountCondition> discountConditions;
 
    private MovieType movieType;
    private Money discountAmount;
    private double discountPercent;
 
    public Movie(String title, Duration runningTime, Money fee, double discountPercent, DiscountCondition... discountConditions) {
        this(MovieType.PERCENT_DISCOUNT, title, runningTime, fee, Money.ZERO, discountPercent, discountConditions);
    }
 
    public Movie(String title, Duration runningTime, Money fee, Money discountAmount, DiscountCondition... discountConditions) {
        this(MovieType.AMOUNT_DISCOUNT, title, runningTime, fee, discountAmount, 0, discountConditions);
    }
 
    public Movie(String title, Duration runningTime, Money fee) {
        this(MovieType.NONE_DISCOUNT, title, runningTime, fee, Money.ZERO, 0);
    }
 
    private Movie(MovieType movieType, String title, Duration runningTime, Money fee, Money discountAmount, double discountPercent,
                  DiscountCondition... discountConditions) {
        this.movieType = movieType;
        this.title = title;
        this.runningTime = runningTime;
        this.fee = fee;
        this.discountAmount = discountAmount;
        this.discountPercent = discountPercent;
        this.discountConditions = Arrays.asList(discountConditions);
    }
 
    public MovieType getMovieType() {
        return movieType;
    }
 
    public Money calculateAmountDiscountedFee() {
        if (movieType != MovieType.AMOUNT_DISCOUNT) {
            throw new IllegalArgumentException();
        }
 
        return fee.minus(discountAmount);
    }
 
    public Money calculatePercentDiscountedFee() {
        if (movieType != MovieType.PERCENT_DISCOUNT) {
            throw new IllegalArgumentException();
        }
 
        return fee.minus(fee.times(discountPercent));
    }
 
    public Money calculateNoneDiscountedFee() {
        if (movieType != MovieType.NONE_DISCOUNT) {
            throw new IllegalArgumentException();
        }
 
        return fee;
    }
 
    public boolean isDiscountable(LocalDateTime whenScreened, int sequence) {
        for(DiscountCondition condition : discountConditions) {
            if (condition.getType() == DiscountConditionType.PERIOD) {
                if (condition.isDiscountable(whenScreened.getDayOfWeek(), whenScreened.toLocalTime())) {
                    return true;
                }
            } else {
                if (condition.isDiscountable(sequence)) {
                    return true;
                }
            }
        }
 
        return false;
    }
}
cs

 
Movie는 DiscountCondition의 목록을 포함하기 때문에 할인 여부를 판단하는 오퍼레이션 역시 포함해야 한다. 각 할인조건을 체크하여 DiscountCondition의 메서드를 호출한다. 
 
Screening은 Movie가 금액 할인 정책이나 비율 할인 정책을 지원할 경우 Movie의 isDiscountable 메서드를 호출해 할인이 가능한지 여부를 판단한 후 적절한 Movie의 메서드를 호출해서 요금을 계산한다. 할인이 불가능하거나 할인 정책이 적용되지 않은 영화의 경우 Movie의 calculateNoneDiscountedFee 메서드로 영화 요금을 계산한다. 
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Screening {
    private Movie movie;
    private int sequence;
    private LocalDateTime whenScreened;
 
    public Screening(Movie movie, int sequence, LocalDateTime whenScreened) {
        this.movie = movie;
        this.sequence = sequence;
        this.whenScreened = whenScreened;
    }
 
    public Money calculateFee(int audienceCount) {
        switch (movie.getMovieType()) {
            case AMOUNT_DISCOUNT:
                if (movie.isDiscountable(whenScreened, sequence)) {
                    return movie.calculateAmountDiscountedFee().times(audienceCount);
                }
                break;
            case PERCENT_DISCOUNT:
                if (movie.isDiscountable(whenScreened, sequence)) {
                    return movie.calculatePercentDiscountedFee().times(audienceCount);
                }
            case NONE_DISCOUNT:
                movie.calculateNoneDiscountedFee().times(audienceCount);
        }
 
        return movie.calculateNoneDiscountedFee().times(audienceCount);
    }
}
cs

 
 
그리고 이제 ReservationAgency 클래스인데, 아까와는 달리 코드가 깔끔해졌다. 
 

1
2
3
4
5
6
public class ReservationAgency {
    public Reservation reserve(Screening screening, Customer customer, int audienceCount) {
        Money fee = screening.calculateFee(audienceCount);
        return new Reservation(customer, screening, fee, audienceCount);
    }
}
cs

 
 
두 번째 설계가 첫 번째 설계보다 내부 구현을 더 면밀하게 캡슐화하고 있다. 즉, 객체들은 스스로를 책임진다고 말할 수 있다.
 

5. 하지만 여전히 부족하다

 
사실 본질적으로는 두 번째 설계 역시 데이터 중심의 설계 방식에 속한다고 할 수 있다. 이유를 살펴보자. 
 

캡슐화 위반

분명히 수정된 객체들은 자기 자신의 데이터를 스스로 처리한다. 예를 들어 DiscountCondition은 자기 자신의 데이터를 이용해 할인 가능 여부를 스스로 판단한다. 
하지만 기간 조건을 판단하는 idDiscountable 메서드의 파라미터를 살펴보면 각 Condition의 인스턴스 변수의 타입과 일치하고 있어 변경시 해당 부분도 변경되어야 한다. 
 
 

높은 결합도

캡슐화 위반으로 DiscountCondition의 내부 구현이 외부로 노출됐기 때문에 Movie와 DiscountCondition 사이의 결합도는 높을 수 밖에 없다.
 

낮은 응집도

할인 조건의 종류가 변경된다면 DiscountCondition, Movie 그리고 Movie를 사용하는 Screening을 함께 수정해야 한다. 하나의 변경을 수용하기 위해 코드의 여러 곳을 동시에 변경해야 한다는 것은 설계의 응집도가 낮다는 증거이다. 
 

6. 데이터 중심 설계의 문제점

 
데이터 중심의 설계가 변경에 취약한 이유는 두 가지다. 
 

  • 데이터 중심의 설계는 본질적으로 너무 이른 시기에 데이터에 관해 결정하도록 강요한다.
  • 데이터 중심의 설계에서는 협력이라는 문맥을 고려하지 않고 객체를 고립시킨 채 오퍼레이션을 결정한다. 

 

데이터 중심 설계는 객체의 행동보다는 상태에 초점을 맞춘다

데이터는 구현의 일부다. 구현은 변경 가능성이 많다. 데이터 중심 설계는 설계를 시작하는 처음부터 데이터에 관해 결정하도록 강요하기 때문에 너무 이른 시기에 내부 구현에 초점을 맞추게 한다.
 
 

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

올바른 객체지향 설계의 무게 중심은 항상 객체의 내부가 아니라 외부에 맞춰져 있어야 한다.
객체가 내부에 어떤 상태를 가지고 그 상태를 어떻게 관리하는가는 부가적인 문제다.
 
안타깝게도 데이터 중심 설계에서 초점은 객체의 외부가 아니라 내부로 향한다. 실행 문맥에 대한 깊이있는 고민 없이 객체가 관리할 데이터의 세부 정보를 먼저 결정한다. 객체의 구현이 이미 결정된 상태에서 다른 객체와의 협력 방법을 고민하기 때문에 이미 구현된 객체의 인터페이스를 억지로 끼워맞출 수 밖에 없다.



 

 느낀점


데이터 중심의 단점을 확실하게 파악할 수 있었다.
이 책을 점점 읽다보니 객체지향을 신적으로 다루는 것 같은 느낌을 받았다.

'OOP' 카테고리의 다른 글

[OOP] 객체지향  (0) 2024.01.28
[오브젝트] 5장 책임 할당하기  (0) 2023.12.09
[오브젝트] 3장 역할, 책임, 협력  (0) 2023.11.28
[오브젝트] 2장 객체지향 프로그래밍  (0) 2023.11.25
[오브젝트] 1장 객체, 설계  (1) 2023.11.21