강의) 네오 3단계 피드백(블랙잭 상속)
상속(+조합), 추상클래스, 업/다운캐스팅
📜 제목으로 보기
- 스타버즈 커피전문점을 통한 상속 이해
- 참고) 상속대신 주입하는 조합으로 중복제거
- cf) 조합을 적용하여 중복코드를 객체로 박기(정리용)
- [공통(중복)메서드를 뽑은 클래스]를 뽑는후 [조합으로 중복제거] like 전략패턴
- 참고 01 공통/중복 메서드들을 잘라내기해서 뽑아낸 클래스를 따로 만든다(상속의 부모클래스)
- 참고 02 중복제거하고 싶은 클래스(Coffee)를 복붙해서 클래스2(Coffee2)를 만들어 조합적용을 준비한다.
- 참고 03 조합은, 객체 생성자로부터 외부에서 받은 전략객체같은 조합객체를 받아서 상태로 가지고 있는다 (생성자 -> 상태 초기화)
- 참고 04 조합객체.공통메서드() 들을 호출시켜 중복을 제거한다.
- my) 조합으로 중복제거란, 전략패턴 중 전략1개만 외부에서 받는 형태인 것 같다.
- 추상화
- 04 공통(중복)메서드를 뽑아 extrends 일반상속으로 사용하고 있다면, 이것 부모클래스를 활용해 [나머지 개별메서들을 추상화]하는 데 활용 할 수 있다.(공통메서드 없어도 빈 공통 클래스로 뽑아만 놓는다면) -> 추상화 후보들(추상메서드 후보들)을 추상화 -> 뽑힌다면 추상클래스로 변경해야한다.
- CF) 추상클래스 되는 순간 -> 객체생성X -> Test코드는 에러가 난다
- 05 구현부는 다르지만 메서드이름만 추상화 시도 -> 커피를내린다 + 티백을 담근다의 추상화 -> abstract returnType 내린다() 로 추상화 in 상위클래스
- 06 추상메서드를 가지는 순간 -> 추상클래스로 바꾼다.
- my) 추상클래스 -> 공통메서드뽑은 일반부모에 + 구현부는 다르지만 메서드 이름만 추상화하여 -> 자식은 extends -> super생략한 체, 부모에서 정의한 공통메서드 갖다쓰기 + 추상메서드 개별구현은 override 강제하도록 하는 일부 구현강제된 부모클래스로 해석하자.
- 07 추상화가 끝났다면, -> 추상화전 메소드들 정의부가 아닌 사용(호출)처부터 추상화된 이름으로 변경하여 메소드 호출하도록 바꿔준다.
- 08 추상화된 메서드 정의부는 impl(오버라이딩을 impl로) -> 내용 옮겨 주고 - 삭제의 과정을 가진다.
- 09 추상화를 끝내고 -> 추상화 호출부 이름변경(통일)로 인한 공통(중복)메서드가 추가로 생길 수 있다 -> 다시 부모로 빼준다.
- 10 extends -> 정의도 안되어있는데 super생략한 체로 공통메서드를 실제 내부 or 외부에서 인스턴스가 사용 확인해보기
- 추상화부터 하는 방법도 있다. 정답은 없다. 처음부터 완벽하게 추상화 x -> 공통처리부분이 발견될 시 처리하는게 좋다.
- cf) 추상클래스 인스턴스화(=구체화) 안되는 이유: 구현부 없는 추상메서드 때문에 추상클래스가 됬으므로..ㅋ -> 구현부정의와 같이 객체를 만들어줄 수도 있다.
- quiz) 추상클래스는 추메가 무조건 있어야할까?
- cf) 조합을 적용하여 중복코드를 객체로 박기(정리용)
- 업캐스팅 vs 다운캐스팅
스타버즈 커피전문점을 통한 상속 이해
-
요구사항
Starbuzz 커피 전문점은 커피와 차를 판매한다. 커피와 차를 준비하는 과정은 각각 다음과 같다.
- 커피(coffee)
- 물을 끓인다.
- 필터를 활용해 커피를 내린다.
- 컵에 붓는다.
- 설탕과 우유를 추가한다.
- 차(tea)
- 물을 끓인다.
- 차 티백을 담근다.
- 컵에 붓는다.
- 레몬을 추가한다.
- 커피(coffee)
기본 클래스 2개 작성
Coffee 클래스부터 작성한다
-
요구사항은
/*
로 시작하도록 옮겨놓는다. -
테스트에서 각각의 메서드부터 작성한다.
- my) 전체를 모아두는 메서드는… 각각 작성하고 통합호출시 묶어주는 것일 듯?
메서드들을 한눈에 볼 모든 메서드 호출 메서드 정의하기
-
같은 인스턴스메서드(객체 행위) 단위로서,
현재까지 만든 객체 메서드(행위들)을 모두 한번에 호출해주는 메서드
도Test
에서 만든 뒤,테스트에서의 코드를 잘라내서 내부로 가져가자
-
같은 객체가 호출하던 메서드를 한꺼번에 호출할려고 가져가면, ->
바깥에서 만든 객체로 호출
빨간줄 뜬다. 지워주자. 현재 class내부 = 현재 객체this가 호출하고 있는 중 -
모든 메서드들보다위에 위치하도록, 가장 위로 올려준다.
비슷한 클래스 Tea클래스도 복붙해서 작성해주자
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
System.out.println("물을 끓인다.");
}
public void brewCoffeeGrinds() {
System.out.println("필터를 활용해 커피를 내린다.");
}
public void pourInCup() {
System.out.println("컵에 붓는다.");
}
public void addSugarAndMilk() {
System.out.println("설탕과 우유를 추가한다.");
}
}
public class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boilWater() {
System.out.println("물을 끓인다.");
}
public void steepTeaBag() {
System.out.println("티백을 담근다.");
}
public void pourInCup() {
System.out.println("컵에 붓는다.");
}
public void addLemon() {
System.out.println("레몬을 추가한다.");
}
}
상속으로 중복코드제거 -> 공통메서드를 부모클래스를 만들어 올려서 제거(super를 생략하고 쓰는 것)
-
boilWater()
와pourInCup()
2개 메서드가 중복이다.
클래스 다이어그램을 통해 본 코드 중복(메서드 중복)
중복된 메서드
만 빼내서 따로 클래스로 분리한다.
01 중복메서드만을 메서드로 가지는 부모클래스 만들기
-
중복된 2 메서드를 + 각각의 클래스명을 보고
부모클래스
를 만든다. -
중복된 메서드 2개를 복사해서 가져온다.
package starbuzz; public class CaffeineBeverage { public void boilWater() { System.out.println("물을 끓인다."); } public void pourInCup() { System.out.println("컵에 붓는다."); } }
자식에서 공메 정의부 제거
-> 자식들은 자신만의 메서드만 가지고 있기
-> 사용은 super.생략하고 자기것처럼 쓰기
02 자식들은 상속한 뒤, 중복메서드 -> 공통메서드로 올렸으니 - 자식들이 만약 상속한 상태에서 부모가 가져간 공통메서드들을 제거해줘야한다.
- 부모가 줄 공통메서드를 삭제해도 빨간줄이 뜨지 않는다.
부모한테 공통적으로 물려받아 쓰는
것들에 대해서는 super.
을 붙혀도 된다.
03 -
ctrl+ 클릭
하면 부모로 타고 올라간다. -
부모한테 물려받아 쓰는 것들은
super.
를 앞에 달아서부모클래스한테 물려받아 쓰고 있는 메서드
라고 써도 된다.
강제 각자구현용 추상메서드
를 추가할 수도 있다.
my) 추상클래스로 만든다면, 참고) 상속대신 주입하는 조합으로 중복제거
cf) 조합을 적용하여 중복코드를 객체로 박기(정리용)
[공통(중복)메서드를 뽑은 클래스]를 뽑는후 [조합으로 중복제거] like 전략패턴
참고 01 공통/중복 메서드들을 잘라내기해서 뽑아낸 클래스를 따로 만든다(상속의 부모클래스)
- 아래 메서드 중에
boilWater()
과pourInCup()
은 다른 곳과도 중복이라 뽑아낼 준비한다.
public class Coffee {
public void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public class CaffeineBeverage {
public void boilWater() {
System.out.println("물을 끓인다.");
}
public void pourInCup() {
System.out.println("컵에 붓는다.");
}
}
- 중복된 메서드들을 빨간줄 상태
참고 02 중복제거하고 싶은 클래스(Coffee)를 복붙해서 클래스2(Coffee2)를 만들어 조합적용을 준비한다.
생성자로부터 외부에서 받은
전략객체같은 조합객체
를 받아서 상태
로 가지고 있는다 (생성자 -> 상태 초기화)
참고 03 조합은, 객체 - 나는 CoffeeTest에서 Coffee2 생성자 자동생성을 위해 코드를 작성하고 있다.
참고 04 조합객체.공통메서드() 들을 호출시켜 중복을 제거한다.
public class Coffee2 {
private final CaffeineBeverage caffeineBeverage;
public Coffee2(final CaffeineBeverage caffeineBeverage) {
this.caffeineBeverage = caffeineBeverage;
}
public void prepareRecipe() {
caffeineBeverage.boilWater();
brewCoffeeGrinds();
caffeineBeverage.pourInCup();
addSugarAndMilk();
}
public void brewCoffeeGrinds() {
System.out.println("필터를 활용해 커피를 내린다.");
}
public void addSugarAndMilk() {
System.out.println("설탕과 우유를 추가한다.");
}
}
my) 조합으로 중복제거란, 전략패턴 중 전략1개만 외부에서 받는 형태인 것 같다.
추상화
04 공통(중복)메서드를 뽑아 extrends 일반상속으로 사용하고 있다면, 이것 부모클래스를 활용해 [나머지 개별메서들을 추상화]하는 데 활용 할 수 있다.(공통메서드 없어도 빈 공통 클래스로 뽑아만 놓는다면) -> 추상화 후보들(추상메서드 후보들)을 추상화 -> 뽑힌다면 추상클래스로 변경해야한다.
- 공통(중복)메서드들은 100% 똑같아서 뽑아서 클래스를 만들고 ->
super 생략하고 니것처럼 갖다쓸 수 있게
뽑은 클래스에서 정의해줬다.- 공통메서드 빼기 -> 상속하기 -> 개별 메서드 추상화 살펴보기의 차례다
-
나머지 서로 다른 작업들은???
-
이제
메서드 추상화 후보
-> 부모클래스도 추상클래스 후보들일 확인해서 추상화 해야한다.
-
이제
- 중복이 아닌 메서드들을 추상화에 도전해봐야한다.
CF) 추상클래스 되는 순간 -> 객체생성X -> Test코드는 에러가 난다
-
공통메서드 추출한 부모 클래스 -> 객체 생성가능
-
공통메서드 + 추상화 메서드 포함 부모클래스 -> 객체 생성 불가능. 상속만 이용가능
커피를내린다 + 티백을 담근다
의 추상화 -> abstract returnType 내린다()
로 추상화 in 상위클래스
05 구현부는 다르지만 메서드이름만 추상화 시도 -> -
2개 메서드를 추상화해서 추상메서드를 정의한다.
public class CaffeineBeverage { abstract void brew(); public void boilWater() { System.out.println("물을 끓인다."); } public void pourInCup() { System.out.println("컵에 붓는다."); } }
06 추상메서드를 가지는 순간 -> 추상클래스로 바꾼다.
-
추상화한 astract 메서드를 명시한 순간부터 추상클래스로 바꿔줘야 에러가 사라진다.
-
나머지 메서드도 추상화해주자.
extends -> super생략한 체, 부모에서 정의한 공통메서드 갖다쓰기
+ 추상메서드 개별구현은 override 강제
하도록 하는 일부 구현강제된 부모클래스
로 해석하자.
my) 추상클래스 -> 공통메서드뽑은 일반부모에 + 구현부는 다르지만 메서드 이름만 추상화하여 -> 자식은
추상화전 메소드들
정의부가 아닌 사용(호출)처
부터 추상화된 이름으로 변경
하여 메소드 호출하도록 바꿔준다.
07 추상화가 끝났다면, -> - 추상화된 메서드들은 -> 오버라이딩으로 다시 적용해야한다. 일단 둔다.
- 추상화된 메서드들을의 사용처부터 이름을 바꿔준다.
- 추상화후보 메서드들이 주석처리 되서 회색상태였다. 추상화된 이름의 메서드로
호출
해주자.
추상화된 메서드 정의부
는 impl(오버라이딩을 impl로) -> 내용 옮겨 주고 - 삭제
의 과정을 가진다.
08 -
Generate -> Override
을 하면 너무 많은 메소드가 잡히니implements method
를 통해추상메서드만 구현하여 오버라이딩
해준다.
-
오버라이딩이 아니라
impl로 오버라이딩
-
기존 개별 구체메서드들의 내용을 옮겨준다
-
기존 구체메서드들은 삭제한다.
-
동일하게 다른 클래스(Tea)에서도
추메 호출부 이름바꿔주기
->추메 정의부 impl로 내용옮겨주고 삭제
하기 를 진행해준다.public class Tea extends CaffeineBeverage{ void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } @Override void brew() { System.out.println("티백을 담근다."); } @Override void addCondiments() { System.out.println("레몬을 추가한다."); } }
public class Coffee extends CaffeineBeverage{ public void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } @Override void brew() { System.out.println("필터를 활용해 커피를 내린다."); } @Override void addCondiments() { System.out.println("설탕과 우유를 추가한다."); } }
추상화 호출부
이름변경(통일)로 인한 공통(중복)메서드가 추가로 생길 수 있다 -> 다시 부모로 빼준다.
09 추상화를 끝내고 -> -
추상화 IMPL 오버라이딩된 정의부
는 서로 다르게 구현되므로이름은 같지만 공통메서드로 볼수 없다
-
추상화 사용처()
는 이름이 자식들이 같아지므로,공통메서드/중복메서드
가 생길 수 있다 -> 보이면부모 클래스로 빼면 된다.
-
메서드 순서 맞춰주기(현재 추상메서드만 위에서 정의해놨을 듯..)
정의도 안되어있는데 super생략한 체로 공통메서드
를 실제 내부 or 외부에서 인스턴스가 사용
확인해보기
10 extends -> -
현재 Coffee클래스
- **
extends로 상속
하고 있다. **super는 생략되어있지만 부모로 빼놨던 공통메서드를 내부에서 쓰고 있다
public class Coffee extends CaffeineBeverage{ @Override void brew() { System.out.println("필터를 활용해 커피를 내린다."); } @Override void addCondiments() { System.out.println("설탕과 우유를 추가한다."); } }
- **
-
**메인 메서드를 만들고 -> 상속한 자식클래스 인스턴스를 만든 뒤 **
Coffee 내부에는 정의되어있지 않지만, 부모에게 상속된 공통 메소드
를 바로 호출할 수 있다.
public class Application { public static void main(String[] args) { final Coffee coffee = new Coffee(); coffee.prepareRecipe(); // 정의 안된 메서드인데 extends 공통메서드라면 바로 사용 가능 } }
추상화부터 하는 방법도 있다. 정답은 없다. 처음부터 완벽하게 추상화 x -> 공통처리부분이 발견될 시 처리하는게 좋다.
cf) 추상클래스 인스턴스화(=구체화) 안되는 이유: 구현부 없는 추상메서드 때문에 추상클래스가 됬으므로..ㅋ -> 구현부정의와 같이 객체를 만들어줄 수도 있다.
-
공통메서드를 추출한 부모클래스를 활용해서, 구현부는 다르지만
이름만 공통으로 추상화
하였다 = 추상메서드 들고 있다 = 추상클래스가 부모다 > 인스턴스화 안된다. -
인스턴스화를 하려면 내부에 구현부 없이 이름만 추상화한 추상메서드를
인스턴스화 하면서, {}]중괄호 블럭로 자식클래스처럼 바로 클래스 구현
하면 된다.
-
중괄호{}로 클래스를 구현하는 것을
람다
와 동일하게익명클래스
라 부른다.- 익명클래스를 자식클래스와 동일하게 작성하더라도, 별개의 익명클래스다
quiz) 추상클래스는 추메가 무조건 있어야할까?
-
추메 없어도 된다.
-
추메 없으면 객체 생성해도될까? -> 구현부없는 놈을 안가지고 있어도 객체생성은 안안된다.
- 구현할 내용이 없어도
{ }
중괄호 블럭 가진체로 만들어야한다.
- 구현할 내용이 없어도
추메를 안가지는데 추상클래스를 구현했다면 고민해봐야한다.
- 추메 -> 구현부는 다르지만 공메를 가진 부모클래스에서
구현부제외 이름만 추상화
해놓은 것- 추메가 없다? 그냥 부모가 가진 공통메서드만 super생략 갖다쓰는 일반 상속이면 된다.
업캐스팅 vs 다운캐스팅
업캐스팅 = 다형성 = 추상체로 변수,파라미터,응답값 받기
-
변수/파라미터/응답Type은 추상체(up)로 받을 수 있다. 구상체들을 한번에 처리할 수 있다.
-
List쓸 때 많이 쓰는
업캐스팅
개념이다.public class Application { public static void main(String[] args) { //업캐스팅 final CaffeineBeverage caffeineBeverage = new Tea(); final List<Tea> list = new ArrayList<>(); } }
다운캐스팅 = 추상체상태에서 -> 구상체로 돌아가 구상체만의 메서드/상태 사용가능해진다.
01 구상체 or 자식만의 메서드는 편하게 가질 수 있다.
-
Tea에 Tea만의 메서드(some_t())가 있다고 치자.
02 문제는 추상체/부모형으로 업캐스팅 된 상태에서는, 개별구상체 본인이라도, 메서드를 호출이 안된다.
- 업캐스팅된 부모타입이라면, Type자체가 부모상태라 안된다.
03 추상체 상태의 구상체를 (다운캐스팅)하면, 자신만의 메서드를 사용할 수 있다.
-
변수를 캐스팅하려면 괄호가 2개가 필요하다.
final CaffeineBeverage beverage = new Tea(); // beverage.some_t(); //(Tea)beverage.some_t(); ((Tea) beverage).some_t();
04 문제는 엉뚱한 구상체를 다운캐스팅해도 호출()된다 == 컴파일 타임에 처리 못하는 에러 -> 캐스팅에러가 무섭다. -> 신중하게 써야한다. -> 다운캐스팅은 instanceof + .class.cast()메서드와 함께 !!
-
new Coffee 인스턴스 -> 추상체 -> (Tea)로 다운캐스팅 해도 some_t()가 호출시 에러는 안난다 == 컴파일시 에러가 안난다.
public class Application { public static void main(String[] args) { final CaffeineBeverage beverage = new Coffee(); ((Tea) beverage).some_t(); } }
-
하지만 돌려보면, 캐스팅 에러가 난다.
-
다운캐스팅후 자신만의 메서드호출 ->
반드시 instanceof로 직접 확인하고 호출해줄 것
public static void main(String[] args) { final CaffeineBeverage beverage = new Coffee(); if (beverage instanceof Tea) { ((Tea) beverage).some_t(); } }
-
괄호2개 캐스팅 말고
가독성을 위해 -> 해당클래스.class.cast( )
로 다운캐스팅해서 써도 된다.if (beverage instanceof Tea) { Tea.class.cast(beverage).some_t(); }
-
isinstanceof도 가독성을 위해 아래와 같이 수정할 수 있다.
if (Tea.class.isInstance(beverage)) { Tea.class.cast(beverage).some_t(); }