상속이 어려운 이유

경험에 기초해 상속을 이해하기가 어려움

두가지 관점에서 바라볼 수 있음

  • 일반화/특정화 관점 : 분류
  • 기능의 관점 : 공통부분 재활용

일반화 능력: 공통된 부분을 찾는 능력, 실존하지 않는 개념일 수 있음

ex 벽시계 모델링

상태

  • 시침 위치
  • 분침 위치
  • 초침 위치

동작

  • 현재 시간
  • 바늘의 위치
  • 시간을 바꾼다
  • 벽에 건다

Clock

  • hours : byte = 12
  • minutes: byte = 0
  • seconds : byte = 0
  • hourHandAngle : short = 0
  • minuteHandAngle: short = 0
  • secondHandAngle: short = 0

한 그룹으로부터 다른그룹을 도출 할수있으면 중복으로 봄

따라서 Angle부분은 삭제

Clock

  • hours : byte = 12
  • minutes: byte = 0
  • seconds : byte = 0
  • 각각에대한 get,set,생성자 (7)
  • setTime(byte,byte,byte)//세가지set을 모두 부름

범위를 넘는 인자에대한 해결법

  1. 무책임한 해결법
public void setHours(byte hours){
    if(hours <= 0 || hours > 12){
        throw new IllegalArgumentException("hours must be[1,12]");
    }
    this.hours = hours;
}
  1. 구현
    1. 입력값이 최솟,최댓값을 넘지못하게 제한(clamp)
    2. 최댓값을 넘으면 최댓값, 최솟값을 넘으면 최솟값으로 래핑(wrap)
//1. 디지털 시계가 하는 방법
public void setHours(byte hours){
    this.hours = (byte) Math.min(Math.max(1,hours),12);
    // max min이 헷갈릴수있으니 따로 함수를 만드는것도 방법 
    //clamp(h,min,max)
}
//2. 아날로그 시계가 하는 방법
public void setHours(byte hours){
    int value = hours -1;
    while(value < 0){
        value += 12;
    }
    this.hours = (byte) (value % 12 + 1 );
}

두번째 방법 받아올림 적용

//2. 아날로그 시계가 하는 방법
public void setMinutes(byte minutes){
    int wrapCount = 0;

    while(minutes < 0){
        --wrapCount;
        minutes += 60;
    }

    wrapCount += minutes / 60;


    this.minutes = (byte) (minutes % 60);

    if(wrapCount != 0){
        setHours((byte) (this.hours + wrapCount));
    }
}

//순서를 생각한 setTime
public void setTime(...){
    setHours(hours);
    setMinutes(minutes);
    setSeconds(seconds);
}

시간적 결합을 해결한 시간 바꾸기

모든 setter 제거 addSeconds()메서드로 대체해서 모든 시간을 초로 환산해서 입력

public void addSeconds(short seconds){
    final int HALF_DAY_IN_SECONDS = 60*60*12;

    int value = this.seconds + seconds;
    while(value < 0){
        value += HALF_DAY_IN_SECONDS;
    }
    
    this.seconds = (byte) (value % 60);

    value = value /60;
    value += this.minutes;

    this.minutes = (byte) (value % 60);

    value = value /60;
    value += this.hours - 1;

    this.hours = (byte) (value % 12 + 1);
}

addSeconds 의 문제점

쓸데없이 복잡함

  1. 초가 음수일때 처리
  2. 시는 1을 뺏다가 더함
  3. setter의 매개변수는 하나인데 바뀌는 멤버변수가 세 개

시분초를 따로 나누는 대신 하나로 대신

seconds 하나로 관리

public class Clock{
    private int seconds;

    public byte getHours(){
        int hours = this.seconds / 60 /60;
        //0시이면 12시로 보여줌
        return hours == 0 ? 12 : (byte) hours;
    }

    public byte getMinutes(){
        return (byte) (this.seconds / 60 % 60);
    }

    public byte getSeconds(){
        return (byte) (this.seconds % 60);
    }

    public void addSeconds(short seconds){
        final int HALF_DAY_IN_SECONDS = 60*60*12;

        int value = this.seconds + seconds;
        while(value < 0){
            value += HALF_DAY_IN_SECONDS;
        }
        
        this.seconds = value % HALF_DAY_IN_SECONDS;
    }
}

시침, 분침, 초침 구하기 + tick mount

public short getSecondHandAngle(){
    return (short) (getSeconds()*6);
}

public short getMinuteHandAngle(){
    return (short) (getMinutes()*6);
}

public short getHourHandAngle(){
    final int ANGLE_PER_HOUR = 360 /12;

    int hours = getHours() % 12;
    return (short) (hours * ANGLE_PER_HOUR + getMinutes() * ANGLE_PER_HOUR / 60);
}

public void tick(){
    addSeconds((short) 1);
}

public void mount(){
    //벽에 시계를 검
}

아날로그 벽시계 이정도면 얼추 다 구현함

디지털 벽시계 분석

시계의 본 목적은 동일

디지털 벽시계의 차이점

  1. 7 세그먼트 디스플레이
  2. 오전 오후를 구분
  3. 태엽X 시/분/초 모두 따로따로 설정

-> 공통점은 부모클래스 차이점은 자식 클래스

Clock

공통점 : 부모 클래스

  • seconds
  • getHours
  • getMinutes
  • getSeconds
  • tick
  • mount

addSeconds는 아날로그 메소드

Clock <- AnalogClock

  • getSecondHandAngle
  • getMinuteHandAngle
  • getHourHandAngle
  • addSeconds

추가 수정

  1. addSeconds를 사용하는 tick()메서드 수정
public void tick(){
    final int HALF_DAY_IN_SECONDS = 60*60*12;
    this.seconds = (this.seconds+1) % HALF_DAY_IN_SECONDS;
}
  1. AnalogClock에서 Clock의 private 접근 불가 seconds를 protected로 수정
  2. 같은 상수를 사용하니 메서드 밖에 정의 부모클래스에 protected로 static 정의
    protected final static int HALF_DAY_IN_SECONDS = 60*60*12;
    

디지털 벽시계 전용기능

  1. 7 세그먼트 디스플레이
  2. 오전 오후를 구분
  3. 태엽X 시/분/초 모두 따로따로 설정

오전 오후 구분하기

  1. AM/PM을 기억하는 별도의 boolean 변수 추가
  2. 내부적으로 24시간을 사용 출력방법 변경

2 채택 : Clock 클래스에서 디지털/아날로그의 요구사항 모두 충족가능

boolean isBeforeMidday()
//in Clock class
protected final static int DAY_IN_SECONDS = 60*60*24;
// 12시간 체제에서 24시간 체제로 변경
//AnalogClock도 변경
public void addSeconds(short seconds){
    int value = this.seconds + seconds;
    while(value < 0){
        value += DAY_IN_SECONDS;
    }
    
    this.seconds = value % DAY_IN_SECONDS;
}
//DigitalClock class
public class DiigitalClock extends Clock{
    public boolean isBeforeMidday(){
        return (super.seconds / (DAY_IN_SECONDS / 2) == 0);
    }
}

디지털은 24시간 아날로그에서는 12시간으로 보여주려면 어떻게 해야할까?

  1. get 시/분/초 를 각 메서드로 옮김 / 단점 : Clock형 변수들을 통해 해당 메서드를 호출할 수 없음
  2. AnalogClock클래스에서 1~12를 출력하는 전용 메서드를 추가한다.
  3. 다형성을 통해서 제대로 고칠 수 있음

시간 맞추는 방식

  1. 숫자를 증가 혹은 감소
    1. addHours()
    2. addMinutes()
    3. addSeconds() 부활
  2. 숫자를 직접 입력
    1. setHours()
    2. setMinutes()
    3. setSeconds()
    4. setIsBeforeMidday

일반적으로 통용되는 두번째 방법 선택

7 세그먼트 디스플레이

  • 시/분/초는 7세그먼트 디스플레이를 두 개씩 사용
  • 크기가 2인 배열 사용 배열의 각 요소는 7 세그먼트 중 어떤 선분이 켜져 있는지 저장

on/off여부 저장 방법

  1. 불리언 요소를 7개 가진 배열
  2. 비트 플래그 : enum,EnumSet사용
  3. SevenSegmentDisplay class

세번째 채택 : 구현은 각자

public SevenSegmentDisplay[] getHoursDisplay(){
    return convertToTwoDigitDisplay(getHours());
}
getMinutesDisplay(): SevenSegmentDisplay[2]
getSecondsDisplay(): SevenSegmentDisplay[2]

private SevenSegmentDisplay[] convertToTwoDigitDisplay(byte number){
    SevenSegmentDisplay[] display = new SevenSegmentDisplay[2];

    for(int i = 1; i >= 0 ; --i){
        byte digit = (byte) (number % 10);
        displays[i] = new SevenSegmentDisplay(digit);
        number /= 10;
    }

    return displays;
}
// SevenSegmentDisplay class

public class SevenSegmentDisplay{
    public enum Segment{
        A,B,C,D,E,F,G
    }
    // member variation data ???
    public SevenSegmentDisplay(byte digit){
        // digit에 따라 7개의 선분을 알맞게 on/off
    }
    public void set(Segment segment, boolean on){
        //segment를 on/off
    }
    public boolean get(Segment segment){
        //segment의 on/off여부를 반환
    }
}

손목시계 추가

Clock의 mount때문에 Clock밑에 붙일 수없음

-> mount는 벽에만 붙이는 것이므로 이를 새 클래스로 분리 WallClock, WristWatch란 클래스로 Clock을 상속받는 새로운 자식이 추가 WristWatch와 DigitalClock을 상속받는 손목 전자시계 탄생가

다중 상속 : 자바에는 없음 80~90정도는 사용하면 안됨 최상위 클래스를 여러번 상속 받음.

다중상속이 생기는 원인

전혀 다른 양상의 특징을 상속받으려함.

  1. 시간을 어떻게 표현하는가
  2. 시계를 어디에 장착하는가

해결

  1. ware()와 mount()의 추상화
    • 둘 다 어딘가에 붙이는 개념
    • 두 매서드를 attach()로 합쳐서 표현
    • clock에 attach란 메서드 추가
  2. 인터페이스 : 나중에 배움

책추천 : The Object-Oriented Thought Process