14장. 일관성 있는 협력


객체지향 패러다임의 장점은 설계를 재사용할 수 있다는 것이다.
하지만 재사용은 꽁짜로 얻어지지 않는다.
재사용을 위해서는 객체들의 협력 방식을 일관성 있게 만들어야 한다

아래 예제를 통해 일관성 있는 협력 패턴이, 이해하기 쉽고 직관적이라는 것을 알아보자


01. 핸드폰 과금 시스템 변경하기

기본 정책 확장

11장에서 구현한 핸드폰 과금 시스템의 요금 정책을 아래와 같이 수정해보자

  • 고정요금 방식
    • ex) 10초당 18원
  • 시간대별 방식
    • ex) 00 ~ 19시 10초당 18원
    • ex) 19 ~ 24시 10초당 15원
  • 요일별 방식
    • ex) 평일 10초당 38원
    • ex) 공휴일 10초당 19원
  • 구간별 방식
    • ex) 초기 1분 10초당 50원
    • ex) 1분 이후 10초당 20원

조합 가능한 요금 계산 순서


위의 사진처럼 무수히 많은 조합이 나오기에 설계가 중요해졌다.

클래스 구조

고정요금 방식 구현하기

가장 간단한 고정요금이다. 일반요금제와 동일하다.

public class FixedFeePolicy extends BasicRatePolicy {
    @Override
    protected Money calculateCallFee(Call call) {
        return amount.times(call.getDuration().getSeconds() / seconds.getSeconds());
    }
}

시간대별 방식 구현하기

시간대별 방식에 따라 요금을 계산하기 위해서는 통화 기간을 정해진 시간대별로 나눈후 시간대별로 서로 다른 계산 규칙을 적용해야 한다.
시간대별 방식의 통화 요금일 계산하기 위해서는 통화의 시작, 종료시간, 시작일자, 종료일자도 함께 고려되어야 한다.

시간대별 통화 시간을 관리하는 클래스 DateTimeInterval

public class DateTimeInterval {
    public static DateTimeInterval of(LocalDateTime from, LocalDateTime to) {
        return new DateTimeInterval(from, to);
    }

    public static DateTimeInterval toMidnight(LocalDateTime from) {
        return new DateTimeInterval(from, LocalDateTime.of(from.toLocalDate(), LocalTime.of(23, 59, 59, 999_999_999)));
    }

    public static DateTimeInterval fromMidnight(LocalDateTime to) {
        return new DateTimeInterval(LocalDateTime.of(to.toLocalDate(), LocalTime.of(0, 0)), to);
    }

    public static DateTimeInterval during(LocalDate date) {
        return new DateTimeInterval(
                LocalDateTime.of(date, LocalTime.of(0, 0)),
                LocalDateTime.of(date, LocalTime.of(23, 59, 59, 999_999_999)));
    }

    private DateTimeInterval(LocalDateTime from, LocalDateTime to) {
        this.from = from;
        this.to = to;
    }

    public Duration duration() {
        return Duration.between(from, to);
    }

    public LocalDateTime getFrom() {
        return from;
    }

    public LocalDateTime getTo() {
        return to;
    }

    public List<DateTimeInterval> splitByDay() {
        if (days() > 0) {
            return split(days());
        }
        return Arrays.asList(this);
    }

    private long days() {
        return Duration.between(from.toLocalDate().atStartOfDay(), to.toLocalDate().atStartOfDay()).toDays();
    }

    private List<DateTimeInterval> split(long days) {
        List<DateTimeInterval> result = new ArrayList<>();
        addFirstDay(result);
        addMiddleDays(result, days);
        addLastDay(result);
        return result;
    }

    private void addFirstDay(List<DateTimeInterval> result) {
        result.add(DateTimeInterval.toMidnight(from));
    }

    private void addMiddleDays(List<DateTimeInterval> result, long days) {
        for(int loop=1; loop < days; loop++) {
result.add(DateTimeInterval.during(from.toLocalDate().plusDays(loop)));
        }
    }

    private void addLastDay(List<DateTimeInterval> result) {
        result.add(DateTimeInterval.fromMidnight(to));
    }

    public String toString() {
        return "[ " + from + " - " + to + " ]";
    }
}

요일별 방식 구현하기

요일별 방식은 요일별로 요금 규칙을 다르세 설정할 수 있다.
각 규칙은 요일의 목록, 단위 시간, 단위 요금이라는 3가지 요소로 구성된다.

public class DayOfWeekDiscountRule {
    public Money calculate(DateTimeInterval interval) {
        if (dayOfWeeks.contains(interval.getFrom().getDayOfWeek())) {
            return amount.times(interval.duration().getSeconds() / duration.getSeconds());
        }
        return Money.ZERO;
    }
}

요일별 방식 역시 통화 기간이 여러 날에 걸쳐 있을 수 있다.
시간대별 방식과 동일하게 통화 기간을 날짜 경계로 분리하고 각 통화 기간을 요일별로 설정된 요금 정책에 따라 적절하게 계산해야 한다.

public class DayOfWeekDiscountPolicy extends BasicRatePolicy {
    @Override
    protected Money calculateCallFee(Call call) {
        Money result = Money.ZERO;
        for(DateTimeInterval interval : call.getInterval().splitByDay()) {
            for(DayOfWeekDiscountRule rule: rules) { result.plus(rule.calculate(interval));
            }
        }
        return result;
    }
}

잠시 지금까지 구현한 고정요금, 시간대별, 요일별 방식의 클래스를 다시 살펴보자.
겉으로 보기에는 문제가 없이 잘 구현된 것 같아 보인다. FixedFeePolicy, TimeOfDayDiscountPolicy, DayOfWeekDiscountPolicy
세 클래스는 통화 요금을 정확하게 계산하고 있고 응집도와 결합도 측면에서도 특별히 문제는 없어 보인다.
그러나 이 클래스들을 함께 모아놓고 보면 문제점이 보인다

문제는 이 클래스들이 유사한 문제를 해결하고 있음에도 설계의 일관성이 없다는 것이다


02. 설계에 일관성 부여하기

설계에 일관성을 부여하기 위해서는 다음과 같은 연습이 필요하다

  • 다양한 설계 경험 익히기
  • 디자인 패턴을 학습하고 변경이라는 문맥안에 적용해보기
  • 협력을 일관성 있게 만들기 위해 다음과 같은 기본 지침을 따르기
    • 변하는 개념을 변하지 않는 개념으로부터 분리
    • 변하는 개념을 캡슐화

조건 로직 대 객체 탐색

먼저, 조건 로직에 대해서 코드를 통해서 살펴보자.
4장에서 나왔던 ReservationAgency 예제이다.

public class ReservationAgency {
    public Reservation reserve(...) {
        for (DiscountCondition condition : movie,getDiscountConditions()) {
            if (condition.getType() == DiscountCondtionType.PERIDOD) {
                // 기간조건
            } else {
                // 회차조건
            }
        }

        if (discountable) {
            switch(movie.getMovieType)) {
                case AMOUNT_DISCOUNT: // 금액 할인 정책
                case PERCENT_DISCOUNT: // 비율 할인 정책
                case NONE_DISCOUNT: // 할인 정책 없음
            }
        } else {
            // 할인 정책이 불가능한 경우
        }
    }
}

위와 같이 조건 로직으로 구현한다면 변경에 취약하고 유지보수의 어려움성이 생긴다.
조건로직이란 위와 같이 'if ~ else' 를 통해 비즈니스 로직에 덕지덕지 붙여 놓은 것이다.

우리는 추상화와 다형성이라는 것을 배웠기에 인터페이스를 이용해 객체 탐색으로 변경할 수 있다.

public class Movie {
    private DiscountPolicy discountPolicy;
    public Money calculteMovieFee(Screening screening) {
        return fee.minus(discountPolicy.calculateDiscountAmount(screening));
    }
}

이처럼 다형성은 조건 로직을 객체 사이의 이동으로 바꾸어 준다.
따라서, 할인 조건에 맞는 메시지만 주고 받으면 되기 때문에 변경이 용이해진다


03. 일관성 있는 기본 정책 구현하기

변경 분리하기

일관성 있는 협력을 만들기 위한 첫 단계는 변하는 개념과 변치 않는 개념을 분리하는 것이다
앞에서 본 핸드폰 과금 시스템의 기본 정책에서 변하는 부분과 변하지 않는 부분을 다시 살펴보자


단위요금이 시간당 요금을 계산하는 반면, 적용조건은 형식이 다르다른 것을 알 수 있다.
이것으로 적용조건은 변하는 부분이고, 단위요금은 변치 않는 다는 것으로 분리할 수 있다는 것을 유추할 수 있다.

변경 캡슐화하기

협력을 일관성 있게 만들기 위해서는 변경을 캡슐화해서 파급효과를 줄여야 한다.
여기서 변하지 않는 것은 ‘규칙’이다. 변하는 것은 ‘적용조건’이다.
따라서 규칙으로부터 적용조건을 분리하여 추상화한 후 시간대별, 요일별, 구간별 방식을 이 추상화의 서브타입으로 만들어야 한다.
그 후에 규칙이 적용조건을 표현하는 추상화를 합성관계로 연결하는 것이 객체의 캡슐화이다.

추상화 수준에서 협력 패턴 구현하기

먼저, ‘적용조건’을 표현하는 추상화인 FeeCondtion

public interface FeeCondition {
    List<DateTimeInterval> findTimeIntervals(Call call);
}

다음으로 FeeFule, 단위요금과 적용조건을 인스턴스변수로 사용한다

public class FeeRule {
    private FeeCondition feeCondition;
    private FeePerDuration feePerDuration;

    public Money calculateFee(Call call) {
        return feeCondition.findTimeIntervals(call)
                .stream()
                .map(each -> feePerDuration.calculate(each))
                .reduce(Money.ZERO, (first, second) -> first.plus(second));
    }
}

FeePerDuartion클래스는 단위 시간당 요금이라는 개념을 표현한다.
또한, 이정보를 이용해 일정기간의 요금을 계산하는 calculate 메서드를 구현한다

public class FeePerDuration {
    private Money fee;
    private Duration duration;

    public Money calculate(DateTimeInterval interval) {
        return fee.times(Math.ceil((double)interval.duration().toNanos() / duration.toNanos()));
    }
}

구체적인 협력 구현하기

이제 맨 처음 앞서 구현한 구체적인 협력 클래스를 인터페이스를 이용해 구현해보자
TimeOfDayFeeCondition 시간대별 정책

public class TimeOfDayFeeCondition implements FeeCondition {
    private LocalTime from;
    private LocalTime to;

    @Override
    public List<DateTimeInterval> findTimeIntervals(Call call) {
        return call.getInterval().splitByDay()
                .stream()
                .filter(each -> from(each).isBefore(to(each)))
                .map(each -> DateTimeInterval.of(LocalDateTime.of(each.getFrom().toLocalDate(), from(each)),
              LocalDateTime.of(each.getTo().toLocalDate(), to(each))))
                .collect(Collectors.toList());
    }

    private LocalTime from(DateTimeInterval interval) {
        return interval.getFrom().toLocalTime().isBefore(from) ?
                from : interval.getFrom().toLocalTime();
    }

    private LocalTime to(DateTimeInterval interval) {
        return interval.getTo().toLocalTime().isAfter(to) ?
                to : interval.getTo().toLocalTime();
    }
}

DayOfWeekFeeCondition, 요일별정책

public class DayOfWeekFeeCondition implements FeeCondition {
    private List<DayOfWeek> dayOfWeeks = new ArrayList<>();

    @Override
    public List<DateTimeInterval> findTimeIntervals(Call call) {
        return call.getInterval()
                .splitByDay()
                .stream()
                .filter(each ->
 dayOfWeeks.contains(each.getFrom().getDayOfWeek()))
                .collect(Collectors.toList());
    }
}

DurationFeeCondition 구간별정책

public class DurationFeeCondition implements FeeCondition {
    private Duration from;
    private Duration to;

    @Override
    public List<DateTimeInterval> findTimeIntervals(Call call) {
        if (call.getInterval().duration().compareTo(from) < 0) {
            return Collections.emptyList();
        }

        return Arrays.asList(DateTimeInterval.of(
                call.getInterval().getFrom().plus(from),
                call.getInterval().duration().compareTo(to) > 0 ?
                       call.getInterval().getFrom().plus(to) :
                        call.getInterval().getTo()));
    }
}

맨처음 짯던 정책들과 비교를 해보자. 각 클래스마다 일관성이 생기게 되었다.
우리는 변하는 개념과 변치 않는 개념을 분리해서 변경을 캡슐화 하였다.
이처럼 변경을 캡슐화해 협력을 일관성 있게 만들면 어떤 장점을 얻을 수 있는지 명확하게 보여준다


마찬가지로, 추가적인 정책이 필요하다면 FeeCondition으로부터 구현을 해주면 된다.

일관성 있는 협력의 핵심은 변경을 분리하고 캡슐화하는 것이다.
변경을 캡슐화 하는 방법이 협력에 참여하는 객체들의 역할과 책임을 결정하고 이렇게 결정된 협력이 코드의 구조를 결정한다.
따라서, 변경의 방향을 파악할 수 있는 감각을 기르는 것이 중요하다.


참조

  • 조영호님의 오브젝트 '일관성 있는 협력'

'Books > Object' 카테고리의 다른 글

[Object] 일관성 있는 협력  (0) 2021.08.16
[Object] 다형성  (0) 2021.08.12
[OBJECT] 합성과 유연한 설계  (2) 2021.08.08
[Object] 상속과 코드 재사용  (1) 2021.08.03
[Object] 유연한 설계  (0) 2021.06.28
[Object] 의존성 관리하기  (0) 2021.06.10
블로그 이미지

사용자 yhmane

댓글을 달아 주세요

역할, 책임, 협력

객체지향의 본질이란 무엇일까?

객체지향의 본질은 ”협력하는 객체들의 공동체” 를 창조하는 것이다

객체지향 설계의 핵심

  • 협력을 구성하기 위해 적절한 객체를 찾고 적절한 책임을 할당하는 과정이다
    즉, 객체지향 패러다임 관점에서 핵심은 역할, 책임, 협력이다

영화예매 시스템

  • 다양한 객체들이 상호작용을 하고 있다

  • 제어흐름은 한 객체에 의한 통제가 아닌 다양한 객체들 사에이 균형있게 분배되어 있다

  • 이처럼 객체들이 기능 구현을 위해 수행하는 상호작용을 협력 이라고 한다

  • 객체가 협력에 참여하기 위해 수행하는 로직을 책임 이라고 한다

  • 객체들이 협력 안에서 수행하는 책임들이 모여 객체가 수행하는 역할을 구성한다


협력

협력

객체들이 기능 구현을 위해 수행하는 상호작용

  • 메세지 전송은 객체 사이의 협력을 위해 사용하는 유일한 커뮤니케이션 수단
  • 외부의 객체는 오직 메세지만 전송할 수 있다
  • 메세지를 수신한 객체는 어떻게 처리할지 스스로 결정한다
    • ‘객체가 자신의 일을 스스로 처리할 수 있는 자율적인 존재’


상영객체는 메세지를 전송하고, 영화 객체는 메서드를 수행한다. 상영객체는 영화 객체 내부의 구현을 알 수 없다. -> 자율적인 객체를 만드는 가장 기본적인 방법은 “캡슐화”


책임

책임

객체가 협력에 참여하기 위해 수행하는 로직 또는 행동
객체의 책임은 ‘무엇을 알고 있는가?’ / ‘무엇을 할 수 있는가’로 구분된다

  • 하는것

    • 객체를 생성하거나 계산을 수행
    • 다른 객체의 행동을 시작시키는 것
    • 다른 객체의 활동을 제어하고 조절
  • 아는것

    • 사적인 정보에 관해 아는것
    • 관련된 객체에 관해 아는것
    • 자신이 유도하거나 계산할 수 있는것에 대해 아는것

책임은 하는것 & 아는것과 구분된다. 또한 협력할 객체를 상태값으로 가지고 있다.

책임 할당

  • 협력에 필요한 지식과 방법을 가장 잘 아는 객체에게 도움을 요청 - ‘정보 전문가’


영화를 예약하기 위해서는, 영화 회차/시간 정보를 알아야한다. 이 정보를 가장 잘 아는 것은 상영 객체이다. 또한 요금을 계산 -> 상영은 요금 정보가 없기에 Movie에게 책임을 위임한다

책임 할당시 고려해야 할 요소

  • 메세지가 객체를 결정

    • 객체에게 책임을 할당시 필요한 메세지를 먼저 식별
    • 메시지를 처리할 객체를 나중에 선택
      • 객체가 메시지를 선택하는 것이 아니라, 메시지가 객체를 선택한다
  • 행동이 상태를 결정

    • 객체의 행동은 객체가 협력에 참여할 수 있는 유일한 방법
    • 협력이 객체의 행동을 결정하고 행동이 객체의 상태를 결정한다. 그 행동이 바로 객체의 책임

역할

역할

객체가 어떤 특정한 협력 안에서 수행하는 책임의 집합

유연하고 재사용 가능한 협력

  • 역할을 통해 유연하고 재사용 가능한 협력을 얻을 수 있다


할인 요금을 계산하는 두개의 객체가 있다. 두 객체를 같이 사용하는 대신 하나의 슬롯으로 구성하여 교대로 바꿔뀔수 있게 구성한다. 이 슬롯이 ‘역할’

  • 역할을 구현하는 가장 일반적인 방법은 추상 클래스와 인터페이스 사용

참조

  • 오브젝트 코드로 이해하는 객체지향 설계 chapter3

'Books > Object' 카테고리의 다른 글

[Object] 메시지와 인터페이스  (0) 2021.05.25
[Object] 책임 할당하기  (0) 2021.05.16
[Object] 설계 품질과 트레이드오프  (0) 2021.05.08
[Object] 역할, 책임, 협력  (0) 2021.05.03
[Object] 객체지향 프로그래밍  (0) 2021.04.25
[Object] 객체 설계  (0) 2021.04.22
블로그 이미지

사용자 yhmane

댓글을 달아 주세요

객체 지향 프로그래밍

객체지향

객체지향

“말 그대로 객체를 지향하는 것”

클래스(Class)란?

  • 객체를 정의하고 만들어 내기 위한 설계도 혹은 틀
  • 상태값(변수)와 행위(메서드)로 구성된다

객체(Object)란?

  • 소프트웨어 세계에 구현할 대상
  • 클래스에 선언된 모양 그대로 생성된 실체

인스턴스(Instance)란?

  • 설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체

객체지향 프로그래밍을 하려면

  • 클래스가 아닌 객체에 초점을 맞추어야 한다
  • 객체를 먼저 정의하고 클래스를 정의하라
  • 객체를 독립적인 존재가 아닌, 협력/의존하는 공동체의 일원으로 봐라

도메인의 구조를 따르는 프로그램 구조

도메인

“문제를 해결하기 위해 사용자가 프로그래음 사용하는 분야”

  • 클래스 이름은 대응되는 도메인 개념의 이름과 동일하거나 유사해야함
  • 클래스 사이의 관계도 최대한 도메인 개념 사이에 맺어진 관계와 유사해야 함
  • 프로그램의 구조를 이해하고 예상하기 쉽게 만들어야 함


클래스

  • 클래스의 경계를 구분 짓는다
    • 상태값은 private, 행위는 public으로 구성한다
  • 클래스와 내부와 외부를 구분 짓는다
    • 경계의 명확성이 객체의 자율성을 보장한다
    • 프로그래머에게 구현의 자유를 보장한다

자율적인 객체

  • 객체는 상태와 행위를 함께 가지는 복합적인 존재
  • 캡슐화와 접근 제어
    • 객체를 자율적인 존재로 만들기 위해서
    • 객체의 상태는 숨기고 행위만 외부에 공개
public class Money {

    private final BigDecimal amount;

    public static Money wons(long amount) {
        return new Money(BigDecimal.valueOf(amount));
    }

    Money(BigDecimal amount) ... 중략
    public Money plus(Money amount) ... 중략
    public Money minus(Money amount) ... 중략
    public Money times(double percent) ... 중략
    public boolean isLessThan(Money other) ... 중략
    public boolean isGreaterThanOrEqual(Money other) ... 중략
}

객체화
“하나의 인스턴스 변수만 포함하더라도 비즈니스 로직에 관여한다면 객체로 만들어라”

  • 명확하게 의미를 전달할 수 있다
  • 설계의 명확성과 유연성을 높여준다

협력
“메세지를 통해 객체가 다른 객체와 상호 작용을 한다”

  • 객체는 캡슐화를 통해 상태값을 내부로 숨긴다
  • 다른 객체와 협력을 하는 유일한 방법은 메세지를 전송하는 것뿐
  • 다른 객체에 요청이 도착하면 메세지를 수신, 이처럼 수신된 메세지를 처리하는 자신만의 방법을 메서드라 부른다

시간 의존성

컴파일 시간 의존성과 실행 시간 의존성
“컴파일 시간 의존성과 실행 시점의 의존성이 서로 다를 수 있다”

// 금액 할인정책에 의존
Movie avatar = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000), new AmountDiscountPolicy(Money.wons(800), ...));
// 비율 할인정책에 의존
Movie avatar = new Movie("아바타", Duration.ofMinutes(120), Money.wons(10000), new PercentDiscountPolicy(0,1, ...));

public class Movie {
    public Money calculateMovieFee(Screening screening) {
        return fee.minus(discountPolicy.calculateDiscountAmount(screening));
    }
}
  • Movie.calculateMovieFee()는 코드레벨에서 어떠한 DiscountPolicy에 의해 calculateDiscountAmount() 실행될지 몰라도 된다
    • 코드의 의존성과 실행 시점의 의존성이 다르면 코드를 이해하기 어려워 질 수 있다
    • 반면, 코드는 유연해지고 확장 가능성이 생기는 트레이드오프의 산물

상속과 다형성

상속
“상속은 객체지향에서 코드를 재사용하기 위해 가장 널리 사용되는 방법”

상속

  • 기존 클래스가 가지고 있는 모든 속성과 행동을 새로운 클래스에 전이
  • 부모 클래스의 구현을 공유하면서도 행동이 다른 자식 클래스에 대하여 구현이 용이
  • 업캐스팅 - 자식 클래스가 부모클래스를 대신함
    • 컴파일러는 코드상에서 부모클래스가 나오는 모든 장소에서 자식 클래스를 허용

다형성

* 컴파일 시간 의존성과 실행 시간 의존성을 다르게 만들수 있는 객체지향의 특성을 이용해 서로 다른 메서드를 실행
* 동일한 메세지를 전달하지만, 실제로 어떤 메서드가 실행 될지는 객체의 클래스에 의해 결정

즉, 상속은 코드의 재사용이 아닌 다형적인 협력을 위해 사용해야 한다

단점

* 상속을 하게 되면 캡슐화가 위반될 수 있다
* 객체 설계가 유연하지 않게 된다

단점을 극복하기 위해 다음과 같이 인스턴스 변수를 이용해 실행 시점에 할인 정책을 변경할 수 있도록 코드를 추가해주자

public class Moive {
    private DiscountPolicy discountPolicy;

    public void chanseDiscountPolicy(DiscountPolicy discountPolicy) {
        this.discountPolicy = discountPolicy;
    }
}
Movie avatar = new Movie(“아바타”, Duration.ofMinutes(120), Money.wons(10000), new AmountDiscountPolicy(Money.wons(800), …));
avatar.chanseDiscountPolicy(new PercentDiscountPolicy(0,1, …));
  • 코드의 재사용이 목적이라면 상속보다는 합성을 고려해보자!!

참조

Object 코드로 이해하는 객체지향 설계 chapter2

'Books > Object' 카테고리의 다른 글

[Object] 메시지와 인터페이스  (0) 2021.05.25
[Object] 책임 할당하기  (0) 2021.05.16
[Object] 설계 품질과 트레이드오프  (0) 2021.05.08
[Object] 역할, 책임, 협력  (0) 2021.05.03
[Object] 객체지향 프로그래밍  (0) 2021.04.25
[Object] 객체 설계  (0) 2021.04.22
블로그 이미지

사용자 yhmane

댓글을 달아 주세요

spread

ES6에 spread와 rest라는 문법이 도입 되었습니다.
spread는 펼치다 또는 퍼뜨리다라는 의미를 가집니다. 객체 혹은 배열에 사용되고 값들을 펼쳐줍니다.

  • spread 미사용
const student = {
    name: '학생1'
}

const smartStudent = {
    name: '학생1',
    attribute: '똑똑한'
}

const tallSmartStudent = {
    name: '학생1',
    attribute: '똑똑한',
    height: 185
}

console.log(student);          // { name: '학생1' }
console.log(smartStudent);     // { name: '학생1', attribute: '똑똑한' }
console.log(tallSmartStudent); // { name: '학생1', attribute: '똑똑한', height: 185 }
  • spread 사용
const student = {
    name: '학생1'
}

const smartStudent = {
    ...student,
    attribute: '똑똑한'
}

const tallSmartStudent = {
    ...smartStudent,
    height: 185
}

console.log(student);          // { name: '학생1' }
console.log(smartStudent);     // { name: '학생1', attribute: '똑똑한' }
console.log(tallSmartStudent); // { name: '학생1', attribute: '똑똑한', height: 185 }

rest

spread와 반대의 개념이라고 생각하면 됩니다. 퍼져있는 것을 다시 모아오는 역할을 합니다.
rest는 객체, 배열, 그리고 함수의 파라미터에서 사용이 가능합니다

  • 객체에 적용
const tallSmartStudent = {
    name: '학생1',
    attribute: '똑똑한',
    height: 185
}

const { height, ...attributes } = tallSmartStudent;
console.log(height);     // 185
console.log(attributes); // { name: '학생1', attribute: '똑똑한' }
  • 배열 적용
const numbers = [1, 2, 3, 4, 5];
const [one, two, ...rest] = numbers
console.log(one);  // 1
console.log(two);  // 2
console.log(rest); // [3, 4, 5]
  • 함수에 적용
function sum(a, b, c, d, e, f, g) {
    return a + b + c + d + e + f + g;
}
console.log(sum(1, 2, 3, 4, 5, 6, 7)); // 28

function sumRest(...rest) {
    return rest.reduce((acc, current) => acc + current, 0);
}
console.log(sumRest(1, 2, 3, 4, 5, 6, 7)); // 28
블로그 이미지

사용자 yhmane

댓글을 달아 주세요

프로토타입

객체 생성자

객체를 생성하는 함수를 생성자 함수라고 부릅니다. 대문자로 선언한고 new로 할당하여 사용합니다.

function Animal(type, name, sound) {
    this.type = type;
    this.name = name;
    this.sound = sound;
    this.say = function() {
      console.log(this.sound);
    }
}

const dog = new Animal('개', '두부', '멍멍');
const cat = new Animal('고양이', '휴지', '야옹');
dog.say();
cat.say();
  • this.say가 매번 할당 되고 있음

프로토타입

객체 생성자로 만든 함수에 공유할수 있는 값이나 함수를 설정하는 것

// 적용전
function Animal(type, name, sound) {
    this.type = type;
    this.name = name;
    this.sound = sound;
}

function say() {
    console.log(this.sound);
}

dog.say = say;
cat.say = say;
const dog = new Animal('개', '두부', '멍멍');
const cat = new Animal('고양이', '휴지', '야옹');
dog.say();
cat.say();
// 적용후
function Animal(type, name, sound) {
    this.type = type;
    this.name = name;
    this.sound = sound;
}

Animal.prototype.say = function() {
    console.log(this.sound);
}

Animal.prototype.value = 1;
const dog = new Animal('개', '두부', '멍멍');
const cat = new Animal('고양이', '휴지', '야옹');
dog.say();
cat.say();

객체 생성자 상속

call을 이용해 javascript도 상속을 구현할 수 있다

//  상속을 사용하지 않을 경우
function Dog(name, sound) {
    this.type = '개';
    this.name = name;
    this.sound = sound;
}

function Cat(name, sound) {
    this.type = '고양이';
    this.name = name;
    this.sound = sound;
}

Dog.prototype.say = function() {
    console.log(this.sound);
}

Cat.prototype.say = function() {
    console.log(this.sound);
}

const dog = new Dog('두부', '멍멍');
const cat = new Cat('휴지', '야옹');
// 상속을 적용
function Animal(type, name, sound) {
    this.type = type;
    this.name = name;
    this.sound = sound;
}

Animal.prototype.say = function() {
    console.log(this.sound);
}

function Dog(name, sound) {
    Animal.call(this, '개', name, sound);
}

function Cat(name, sound) {
    Animal.call(this, '고양이', name, sound);
}

Dog.prototype = Animal.prototype;
Cat.prototype = Animal.prototype;

const dog = new Animal('개', '두부', '멍멍');
const cat = new Animal('고양이', '휴지', '야옹');
dog.say();
cat.say();

클래스

ES6에서 클래스 문법이 도입 되었다. 프로토타입 작업을 조금 더 간략하게 사용할 수 있게 되었다.

class Animall {
    constructor(type, name, sound) {
      this.type = type;
      this.name = name;
      this.sound = sound;
    }
    say(){
        console.log(this.sound);
    }
}

const dog = new Animal('개', '두부', '멍멍');
const dog = new Animal('고양이', '휴지', '야옹');

dog.say();
cat.say();

클래스 상속

객체 생성자와 마친가지로 클래스 상속이 가능

class Animall {
    constructor(type, name, sound) {
      this.type = type;
      this.name = name;
      this.sound = sound;
    }
    say(){
        console.log(this.sound);
    }
}

class Dog extends Animal {
    constructor(name, sound) {
      super('개', name, sound);
    }
}

class Cat extends Animal {
    constructor(name, sound) {
      super('고양이', name, sound);
    }
}

const dog = new Dog('두부', '멍멍');
const dog = new Cat('휴지', '야옹');

dog.say();
cat.say();
블로그 이미지

사용자 yhmane

댓글을 달아 주세요

객체

'어떠한 이름을 선언하였을 때, 하나의 이름에 여러개의 값을 넣어줄 수 있는것'입니다

  • 객체는 여러개의 속성들로 이루어져 있고, 속성은 key(이름)과 value(값)으로 구성됩니다.
const yunho = {
  name: '황윤호',
  age: '20',
  grade: 'A'
}

console.log(yunho)
console.log(yunho.name)
console.log(yunho.age)

비구조화 할당

'구조분해'라고도 하며 배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 자바스크립트 표현식입니다.

  • 사용법
const yunho = {
    name: '황윤호',
    age: '20',
    grade: 'A'
}

function printInfo(student) {  
    const { name, age, grade } = student;  
    const str = `${name}는 ${age}이며 ${grade} 성적을 받았습니다`;  
    console.log(str)  
}

function printInfo2({ name, age, grade }) {  
    const str = `${name}는 ${age}이며 ${grade} 성적을 받았습니다`;  
    console.log(str)  
}

printInfo(yunho)  
printInfo2(yunho)


함수 선언

객체안에 속성값 뿐만 아니라 함수도 선언하여 사용할 수 있습니다.

const yunho = {
  name: '황윤호',
  age: '20',
  grade: 'A',
  hello: function() {
    console.log(this.name + '입니다')
  },
  hi() {
    console.log(this.name + '입니다')
  }
}
yunho.hello()
yunho.hi()

getter, setter

javascript도 java와 마찬가지로 getter, setter를 구현할 수 있습니다

  • getter는 get, setter는 set을 명시하여줍니다.
const yunho = {
  _name: '황윤호',
  age: '20',
  grade: 'A',
  get info() {
    console.log(this._name);
    return this._name
  },
  set name(value) {
    this._name = value;
  }
}
블로그 이미지

사용자 yhmane

댓글을 달아 주세요

직렬화

객체를 메모리, 데이터베이스, 혹인 파일로 저장하려면 어떻게 해야 할까? 이럴 때 필요한 것이 직렬화입니다.

  • 직렬화는 Serialization 이라고도 합니다.
  • 인스턴스의 상태를 그대로 저장하거나 네트웍으로 전상하고 이를 다시 역직렬화.복원(Deserialization) 하기도 합니다.

Serializable

  • 직렬화를 하려면 선언을 해주어야 합니다.
  • java.io.Serializable 인터페이스를 구현한 클래스의 객체만 직렬화 할 수 있습니다.
  • 직렬화 하지 말아야 할 값이 있다면 transient 키워드를 붙여줍니다.
    class Student implements Serializable {
    ...
    String name;
    transient String code;
    ...
    }
  • Serializable 인터페이스를 구현한 클래스는 자신의 serialVersionUID를 명시적으로 선언할 수 있습니다.
  • serialVersionUID는 필드의 이름이며 static, final이어야 하고 long type입니다. 선정하지 않으면 default 값이 선정됩니다.
    • class의 내용이 바뀌면 UID값 역시 바뀌게 되는데 네트웍 상황에서 값이 바뀌는 것을 방지하고자 사용합니다.

Serializable 사용해보기

class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private Integer grade;
    transient private Integer code;

    public Student(String name, Integer grade, Integer code) {
        this.name = name;
        this.grade = grade;
        this.code = code;
    }

    public String toString() {
        return "name : " + name + ", grade : " + grade + ", code : " + code;
    }
}

public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("홍길동", 2, 4885);
        Student student2 = new Student("아무개", 3, 7531);
        System.out.println(student1);
        System.out.println(student2);

        try (FileOutputStream fos = new FileOutputStream("test.dat");
            ObjectOutputStream oos = new ObjectOutputStream(fos);) {
            oos.writeObject(student1);
            oos.writeObject(student2);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try (FileInputStream fis = new FileInputStream("test.dat");
             ObjectInputStream ois = new ObjectInputStream(fis);) {
            Student stduent3 = (Student) ois.readObject();
            Student stduent4 = (Student) ois.readObject();
            System.out.println(stduent3);
            System.out.println(stduent4);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

결과

name : 홍길동, grade : 2, code : 4885
name : 아무개, grade : 3, code : 7531
name : 홍길동, grade : 2, code : null
name : 아무개, grade : 3, code : null
블로그 이미지

사용자 yhmane

댓글을 달아 주세요

들어가며


 이전 포스팅까지는 자바의 기초문법에 대하여 정의하였습니다. 기본적인 문법구조나 변수 선언등에 대해서 알아보았고, 이번 포스팅에선 자바의 가장 두드러지는 특징인 객체지향의 대하여 알아보도록 하겠습니다.


객체지향의 개념


 객체지향이란 컴퓨터 프로그래밍 패러다임중의 한 종류입니다. 기존 명령어를 중심으로 나열하는 프로그래밍기법에서 벗어나, 객체 모델을 바탕으로 프로그램을 구체화하고 개발하는 프로그래밍 기법을 의미합니다. 객체지향의 특징에는 상속, 추상화, 다형, 추상화 이라는 특징이 있습니다.

 위에 언급된 특성은 객체지향의 핵심적인 특징으로 꼭 이해하시고 가야 합니다. 객체지향의 특징을 설명하기 전에 클래스, 객체, 인스턴스를 짚고 넘어가도록 하겠습니다.


  • 클래스

멤버변수와 함수로 구성된 논리적인 설계도


class Student {

private String name;

private int age;

}


  • 객체

클래스에 선언된 모응 그대로 생성된 실체


public class Main {

public static void main(String[] args) {

Student student;

}

}


  • 인스턴스

생성되어진 객체가 메모리에 올려진 것


public class Main {

public static void main(String[] args) {

Student student = new Student();

}

}


객체와 인스턴스는 일반적으로는 통용하여 표현하나, 메모리에 올려진 여부로 객체와 인스턴스를 판단합니다.



객체지향의 특징


 객체지향에는 다음과 같은 특징이 있습니다. 캡슐화, 상속, 다형성, 추상화 모두 중요한 개념입니다. 


  • 캡슐화

 캡슐화는 생성한 객체를 어떤 메서드와 필드로 어떻게 일을 수행할지 외부에 숨기는 특성을 말합니다. 캡슐화 은닉화 라고 하며 보호하고자 하는 데이터의 값을 외부에서 직접 접근하는 것을 방지하기 위해 나온 개념입니다. 접근제어자를 이용하여 값을 은닉하고, public method로 값을 통제합니다.


캡슐화 예제

 class Student {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge() {
this.age = age;
}
}


public class Main {

public static void main(String[] args) {

Student student = new Student();


student.setName("윤호");

student.setAge(20);


System.out.println("학생의 이름 : " + student.getName());

System.out.println("학생의 나이 : " + student.getAge());

}

}


  • 상속

 클래스는 추상화된 슈퍼클래스와 구체화된 서브 클래스로 구성됩니다. 예를 들면 사람(슈퍼클래스)와 학생(서브클래스)
 지정 예약어 extends를 이용하여 상속을 이용합니다. 하나의 부모클래스는 여러 자식을 가질 수 있지만, 반대는 성립하지 않습니다.
 자식은 부모의 값이나 행위를 상속받아 사용할 수 있습니다. 

상속 예제

class Person {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge() {
this.age = age;
}

public void run() {
System.out.println("달리기를 합니다");
}
}

class Student extends Person() {
public void study() {
System.out.println("공부를 합니다");
}
}


public class Main {

public static void main(String[] args) {

Student student = new Student();


student.setName("윤호");

student.setAge(20);


System.out.println("학생의 이름 : " + student.getName());

System.out.println("학생의 나이 : " + student.getAge());


student.run();

student.study();

}

}


  • 다형성

 클래스의 상속 관계를 이용하여 슈퍼클래스가 같은 서브 클래스의 동일한 요청을 다르게 처리할 수 있는 특징을 말합니다. 예를 들어, 사람이라는 클래스가 있고 아침에 이동하다는 행위를 합니다. 사람을 상속받은 학생클래스는 아침에 학교로 이동하고, 사람을 상속박은 직장인 클래스는 아침에 회사로 이동합니다. 아래 예제를 보도록 하겠습니다.

다형성 예제


class Person {
public void move() {
System.out.println("이동합니다");
}
}

class Student extends Person() {
@Override
public void move() {
System.out.println("학교로 이동합니다");
}
}

class Student extends Worker() {
@Override
public void move() {
System.out.println("일터로 이동합니다");
}
}

public class Main {

public static void main(String[] args) {

Student student = new Student();

Worker worker = new Worker();


student.move();

worker.move();

}

}


  • 추상화

 매우 중요한 개념입니다. 객체의 공통된 특징을 묶어서 추출한다는 개념인데, 다음 포스팅에서 클래스에 대하여 설명하며 추상클래스, 인터페이스와 함께 덧붙이도록 하겠습니다.


정리

 

 이번 포스팅에선 자바의 핵심 개념인 객체지향에 대하여 글을 작성하였습니다. 자바는 프로그램을 구조화하는 특징을 가졌으며 추상화, 상속, 캡슐화, 다형성라는 중요한 특성이 있습니다. 이러한 기능으로 구조적으로 재활용성이 높아져 생산성이나 유지보수 효율성을 높인 프로그래밍 언어입니다!!


출처


- 자바의 정석

- just 자바

블로그 이미지

사용자 yhmane

댓글을 달아 주세요