본문 바로가기

IT/오브젝트

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

안녕하세요 남갯입니다

 

오늘은 4장 설계품질과 트레이드 오프에 대해 포스팅 해보려고합니다.

 

 

- 역할 책임 협력중 가장 중요한것은 책임이다. 책임이 객체지향 어플리케이션 전체의 품질을 결정하는것이다.

- 객체지향의 첫번째 관점은 핵심은 책임이다란것, 두번째 관점은 응집도와 결합도 같은 설계품질과 연관되어있다.

 

- 훌륭한 설계란 적절한 비용안에서 쉽게 변경 할 수 있는 응집도 높고 결합도가 낮은 요소로 구성되어있는것

- 객체를 단순하 데이터 집합으로 바라보는 시각은 객체 내부구현을 퍼블릭 인터페이스에 노출시키는 결과를 낳기때문에설계가 변경에 취약해진다. 하지만 이런 문제를 피할 수 있는 방법은 객체에 초점을 맞추는것

 

객체지향 설계에서 두가지 방법을 이용해 객체를 분할 가능하다.

상태분할의 중심축 , 책임분할의 중심충

 

책임중심의 관점

1. 객체는 다른 객체가 요청할 수 있는 오퍼레이션을 위해 필요한 상태를 보관

2. 객체의 행동에 초점을 맞춘다.

 

 

데이터 중심의 관점

1. 객체는 자신이 포함하고 있는 데이터를 조작하는 데 필요한 오퍼레이션을 정의

2. 객체 상태에 초점을 맞추고 

 

훌륭한 객체지향 설계는 데이터 중심이 아니라 객체에 초점을 맞춰야한다.

 

데이터에 초점을 맞추다 보면 설계는 변경에 취약할 수 밖에 없다. 그에 비해 객체의 책임은 인터페이스에 속한다.

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;
}

데이터 중심의 Movie 클래스도 책임중심과 마찬가지로 제목과 상영시간, 기본요금을 인스턴스 변수로 포함한다.

두드러지는 차이점은 할인조건의 목록이 인스턴스 변수로 Movie안에 직접 포함되어 있다.

 

할인정책과 할인 금액을 Movie안에서 직접 정의하고있다.

 

MovieType이라는 enum 열거형 클래스를 통해 할인정책을 확인한다.

 

public enum MovieType {
    AMOUNT_DISCOUNT,
    PERCENT_DISCOUNT,
    NONE_DISCOUNT
}

 

영화 타입을 추가했다.

캡슐화를 위해 Movie에 게터 세터를 추가한다.

 

public enum  DiscountConditionType {
    SEQUENCE,
    PERIOD
}

 

할인조건 종류를 저장할 데이터가 필요해서 생성한다.

 

public class DiscountCondition {
    private DiscountConditionType type;
    private int sequence;
    private DayOfWeek dayOfWeek;
    private LocalTime startTime;
    private LocalTime endTime;
}

 

 

할인 조건을 구현하는 DiscountCondition은 할인조건 type을 포함한다.

 

캡슐화 정책에 따라 속성을 클래스 외부로 노출해서는 안된다.

 

public class Screening {
    private Movie movie;
    private int sequence;
    private LocalDateTime whenScreend;

    public Movie getMovie() {
        return movie;
    }

    public void setMovie(Movie movie) {
        this.movie = movie;
    }

    public int getSequence() {
        return sequence;
    }

    public void setSequence(int sequence) {
        this.sequence = sequence;
    }

    public LocalDateTime getWhenScreend() {
        return whenScreend;
    }

    public void setWhenScreend(LocalDateTime whenScreend) {
        this.whenScreend = whenScreend;
    }
}
public class Reservation {
    private Customer customer;
    private Screening screening;
    private Money fee;
    private int audienceCount;

    public Reservation(Customer customer, Screening screening, Money fee, int audienceCount) {
        this.customer = customer;
        this.screening = screening;
        this.fee = fee;
        this.audienceCount = audienceCount;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    public Screening getScreening() {
        return screening;
    }

    public void setScreening(Screening screening) {
        this.screening = screening;
    }

    public Money getFee() {
        return fee;
    }

    public void setFee(Money fee) {
        this.fee = fee;
    }

    public int getAudienceCount() {
        return audienceCount;
    }

    public void setAudienceCount(int audienceCount) {
        this.audienceCount = audienceCount;
    }
}

 

public class Customer {
    private String name;
    private String id;

    public Customer(String name, String id) {
        this.name = name;
        this.id = id;
    }
}

 

영화를 예매하는 클래스를 만들어보자

 

public class ReservationAgency {
    public Reservation reserve(Screening screening, Customer customer, int audienceCount) {
        Movie movie = screening.getMovie();
        ...

        if (discountable) {
            Money discountAmount = Money.ZERO;
            switch (movie.getMovieType()) {
                case AMOUNT_DISCOUNT:
                    break;
                case PERCENT_DISCOUNT:
                    break;
                    ...
            }
        }
    }
}

 

reserve에는 DiscountCondition에 대해 루프를 확인하는 코드와 discountbale 변수의 값을 체크하고 적절한 정책에 따라

요금을 계산하는 문이다.

 

 

변경될 가능성이 높은 부분을 구현, 안정적인 부분을 인터페이스라고 부른다.

객체를 설계하기 위한 가장 기본적인 아이디어는 변경의 정도에 따라 구현과 인터페이스를 분리하고 외부에서는

인터페이스만 의존하도록 관계를 조절하는 것이다.

객체지향에서 가장 중요한 원리는 캡슐화다. 

변경 가능성이 높은 부분을 객체의 내부로 숨기는것이 추상화 기법이다.

 

 

응집도는 모듈에 포함된 내부 요소들이 연관돼 있는 정도를 나타낸다.

결합도는 의존성 정도를 나타내며 다른 모듈에 대해 얼마나 많은 지식을 갖고 있는지를 나타내는 척도

 

Movie내부에 게터와 세터를 만드는것은 캡슐화에 위반한다. 왜냐하면 외부에서는 메소드를 보고 내부타입 변수를 확인할 수 있을뿐더러 수행할 책임이 아니라 내부에 저장할 데이터에 초점을 맞췄기 때문이다.

 

 

객체를 설계할때. 이 객체가 어떤 데이터를 포함하는가? 라는 질문은 두개의 개별적인 질문으로 분리해야 한다.

- 이 객체가 어떤 데이터를 포함해야하는가?

- 이 객체가 데이터에 대해 수행하는 오퍼레이션은 무엇인가?

 

public boolean isDiscountable(DayOfWeek dayOfWeek, LocalTime time){}

 

메서드가 객체 내부에 두 변수로 포함되어 있다는 사실을 인터페이스를 통해 외부에 노출하고있다.

 

데이터를 처리하는 작업과 데이터를 같은 객체안에 두더라도 데이터에 초점을 둔다면 나중에

만족스러운 캡슐화를 얻기 어렵다. 데이터를 먼저 결정하고 데이터를 처리하는데 필요한 오퍼레이션을 나중에

결정하는 방식은 데이터에 관한 지식이 객체의 인터페이스에 고스란히 드러나게 된다.