[개체지향] 상속 개체 모델링
상속이 어려운 이유
경험에 기초해 상속을 이해하기가 어려움
두가지 관점에서 바라볼 수 있음
- 일반화/특정화 관점 : 분류
- 기능의 관점 : 공통부분 재활용
일반화 능력: 공통된 부분을 찾는 능력, 실존하지 않는 개념일 수 있음
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을 모두 부름
범위를 넘는 인자에대한 해결법
- 무책임한 해결법
public void setHours(byte hours){
if(hours <= 0 || hours > 12){
throw new IllegalArgumentException("hours must be[1,12]");
}
this.hours = hours;
}
- 구현
- 입력값이 최솟,최댓값을 넘지못하게 제한(clamp)
- 최댓값을 넘으면 최댓값, 최솟값을 넘으면 최솟값으로 래핑(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을 뺏다가 더함
- 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(){
//벽에 시계를 검
}
아날로그 벽시계 이정도면 얼추 다 구현함
디지털 벽시계 분석
시계의 본 목적은 동일
디지털 벽시계의 차이점
- 7 세그먼트 디스플레이
- 오전 오후를 구분
- 태엽X 시/분/초 모두 따로따로 설정
-> 공통점은 부모클래스 차이점은 자식 클래스
Clock
공통점 : 부모 클래스
- seconds
- getHours
- getMinutes
- getSeconds
- tick
- mount
addSeconds는 아날로그 메소드
Clock <- AnalogClock
- getSecondHandAngle
- getMinuteHandAngle
- getHourHandAngle
- addSeconds
추가 수정
- addSeconds를 사용하는 tick()메서드 수정
public void tick(){
final int HALF_DAY_IN_SECONDS = 60*60*12;
this.seconds = (this.seconds+1) % HALF_DAY_IN_SECONDS;
}
- AnalogClock에서 Clock의 private 접근 불가 seconds를 protected로 수정
- 같은 상수를 사용하니 메서드 밖에 정의 부모클래스에 protected로 static 정의
protected final static int HALF_DAY_IN_SECONDS = 60*60*12;
디지털 벽시계 전용기능
- 7 세그먼트 디스플레이
- 오전 오후를 구분
- 태엽X 시/분/초 모두 따로따로 설정
오전 오후 구분하기
- AM/PM을 기억하는 별도의 boolean 변수 추가
- 내부적으로 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시간으로 보여주려면 어떻게 해야할까?
- get 시/분/초 를 각 메서드로 옮김 / 단점 : Clock형 변수들을 통해 해당 메서드를 호출할 수 없음
- AnalogClock클래스에서 1~12를 출력하는 전용 메서드를 추가한다.
- 다형성을 통해서 제대로 고칠 수 있음
시간 맞추는 방식
- 숫자를 증가 혹은 감소
- addHours()
- addMinutes()
- addSeconds() 부활
- 숫자를 직접 입력
- setHours()
- setMinutes()
- setSeconds()
- setIsBeforeMidday
일반적으로 통용되는 두번째 방법 선택
7 세그먼트 디스플레이
- 시/분/초는 7세그먼트 디스플레이를 두 개씩 사용
- 크기가 2인 배열 사용 배열의 각 요소는 7 세그먼트 중 어떤 선분이 켜져 있는지 저장
on/off여부 저장 방법
- 불리언 요소를 7개 가진 배열
- 비트 플래그 : enum,EnumSet사용
- 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정도는 사용하면 안됨 최상위 클래스를 여러번 상속 받음.
다중상속이 생기는 원인
전혀 다른 양상의 특징을 상속받으려함.
- 시간을 어떻게 표현하는가
- 시계를 어디에 장착하는가
해결
- ware()와 mount()의 추상화
- 둘 다 어딘가에 붙이는 개념
- 두 매서드를 attach()로 합쳐서 표현
- clock에 attach란 메서드 추가
- 인터페이스 : 나중에 배움
책추천 : The Object-Oriented Thought Process