📜 제목으로 보기

스타버즈 커피전문점을 통한 상속 이해

  • 요구사항

      Starbuzz 커피 전문점은 커피와 차를 판매한다. 커피와 차를 준비하는 과정은 각각 다음과 같다.
    
    • 커피(coffee)
      • 물을 끓인다.
      • 필터를 활용해 커피를 내린다.
      • 컵에 붓는다.
      • 설탕과 우유를 추가한다.
    • 차(tea)
      • 물을 끓인다.
      • 차 티백을 담근다.
      • 컵에 붓는다.
      • 레몬을 추가한다.

기본 클래스 2개 작성

Coffee 클래스부터 작성한다
  1. 요구사항은 /* 로 시작하도록 옮겨놓는다.

    image-20220313162720996

    image-20220313162656474

  2. 테스트에서 각각의 메서드부터 작성한다.

    • my) 전체를 모아두는 메서드는… 각각 작성하고 통합호출시 묶어주는 것일 듯?

    image-20220313163310922

메서드들을 한눈에 볼 모든 메서드 호출 메서드 정의하기
  1. 같은 인스턴스메서드(객체 행위) 단위로서, 현재까지 만든 객체 메서드(행위들)을 모두 한번에 호출해주는 메서드Test에서 만든 뒤, 테스트에서의 코드를 잘라내서 내부로 가져가자

    image-20220313163505188 image-20220313163514767

    image-20220313163531128

  2. 같은 객체가 호출하던 메서드를 한꺼번에 호출할려고 가져가면, -> 바깥에서 만든 객체로 호출 빨간줄 뜬다. 지워주자. 현재 class내부 = 현재 객체this가 호출하고 있는 중

    image-20220313163811348 image-20220313163819861

  3. 모든 메서드들보다위에 위치하도록, 가장 위로 올려준다.

비슷한 클래스 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를 생략하고 쓰는 것)

image-20220313164316895

  • boilWater()pourInCup() 2개 메서드가 중복이다.
클래스 다이어그램을 통해 본 코드 중복(메서드 중복)

image-20220313164458009

  • 중복된 메서드만 빼내서 따로 클래스로 분리한다.

image-20220313164603837

01 중복메서드만을 메서드로 가지는 부모클래스 만들기
  1. 중복된 2 메서드를 + 각각의 클래스명을 보고 부모클래스를 만든다.

    image-20220313164859554

  2. 중복된 메서드 2개를 복사해서 가져온다.

    image-20220313164925478

     package starbuzz;
        
     public class CaffeineBeverage {
        
         public void boilWater() {
             System.out.println("물을 끓인다.");
         }
        
         public void pourInCup() {
             System.out.println("컵에 붓는다.");
         }
     }
    
02 자식들은 상속한 뒤, 중복메서드 -> 공통메서드로 올렸으니 자식에서 공메 정의부 제거 -> 자식들은 자신만의 메서드만 가지고 있기 -> 사용은 super.생략하고 자기것처럼 쓰기

image-20220313165034914

  • 자식들이 만약 상속한 상태에서 부모가 가져간 공통메서드들을 제거해줘야한다. image-20220313165134752
  • 부모가 줄 공통메서드를 삭제해도 빨간줄이 뜨지 않는다.image-20220313165148638
03 부모한테 공통적으로 물려받아 쓰는것들에 대해서는 super.을 붙혀도 된다.
  • ctrl+ 클릭하면 부모로 타고 올라간다.

  • 부모한테 물려받아 쓰는 것들은 super.를 앞에 달아서 부모클래스한테 물려받아 쓰고 있는 메서드라고 써도 된다.

    image-20220313165504928

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("컵에 붓는다.");
    }
}
  • 중복된 메서드들을 빨간줄 상태 image-20220313173054239
참고 02 중복제거하고 싶은 클래스(Coffee)를 복붙해서 클래스2(Coffee2)를 만들어 조합적용을 준비한다.

image-20220313172854779

image-20220313173015622

참고 03 조합은, 객체 생성자로부터 외부에서 받은 전략객체같은 조합객체를 받아서 상태로 가지고 있는다 (생성자 -> 상태 초기화)
  1. 나는 CoffeeTest에서 Coffee2 생성자 자동생성을 위해 코드를 작성하고 있다. image-20220313173417627 image-20220313173516262 image-20220313173524987
참고 04 조합객체.공통메서드() 들을 호출시켜 중복을 제거한다.

image-20220313173614222

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 생략하고 니것처럼 갖다쓸 수 있게뽑은 클래스에서 정의해줬다.
    • 공통메서드 빼기 -> 상속하기 -> 개별 메서드 추상화 살펴보기의 차례다
  • 나머지 서로 다른 작업들은???
    • 이제 메서드 추상화 후보 -> 부모클래스도 추상클래스 후보들일 확인해서 추상화 해야한다.

image-20220313180120475

  • 중복이 아닌 메서드들추상화에 도전해봐야한다.
CF) 추상클래스 되는 순간 -> 객체생성X -> Test코드는 에러가 난다
  • 공통메서드 추출한 부모 클래스 -> 객체 생성가능

  • 공통메서드 + 추상화 메서드 포함 부모클래스 -> 객체 생성 불가능. 상속만 이용가능

    image-20220313201218093

05 구현부는 다르지만 메서드이름만 추상화 시도 -> 커피를내린다 + 티백을 담근다의 추상화 -> abstract returnType 내린다() 로 추상화 in 상위클래스
  1. 2개 메서드를 추상화해서 추상메서드를 정의한다.

    image-20220313181416692

    public class CaffeineBeverage {
        
        abstract void brew();
        
        public void boilWater() {
            System.out.println("물을 끓인다.");
        }
        
        public void pourInCup() {
            System.out.println("컵에 붓는다.");
        }
    }
    
06 추상메서드를 가지는 순간 -> 추상클래스로 바꾼다.
  • 추상화한 astract 메서드를 명시한 순간부터 추상클래스로 바꿔줘야 에러가 사라진다. image-20220313181755409

    image-20220313181803767

    image-20220313181815922

  • 나머지 메서드도 추상화해주자.

    image-20220313181922041

    image-20220313181955349

my) 추상클래스 -> 공통메서드뽑은 일반부모에 + 구현부는 다르지만 메서드 이름만 추상화하여 -> 자식은 extends -> super생략한 체, 부모에서 정의한 공통메서드 갖다쓰기 + 추상메서드 개별구현은 override 강제하도록 하는 일부 구현강제된 부모클래스로 해석하자.
07 추상화가 끝났다면, -> 추상화전 메소드들 정의부가 아닌 사용(호출)처부터 추상화된 이름으로 변경하여 메소드 호출하도록 바꿔준다.
  • 추상화된 메서드들은 -> 오버라이딩으로 다시 적용해야한다. 일단 둔다.
  • 추상화된 메서드들을의 사용처부터 이름을 바꿔준다.
  1. 추상화후보 메서드들이 주석처리 되서 회색상태였다. 추상화된 이름의 메서드로 호출해주자. image-20220313195422044

image-20220313195441115

image-20220313195501491

08 추상화된 메서드 정의부impl(오버라이딩을 impl로) -> 내용 옮겨 주고 - 삭제의 과정을 가진다.
  • Generate -> Override을 하면 너무 많은 메소드가 잡히니 implements method를 통해 추상메서드만 구현하여 오버라이딩해준다.
  1. 오버라이딩이 아니라 impl로 오버라이딩 image-20220313200000102

    image-20220313200034925

  2. 기존 개별 구체메서드들의 내용을 옮겨준다

    image-20220313200105005

    image-20220313200122910

  3. 기존 구체메서드들은 삭제한다.

    image-20220313200142074

  4. 동일하게 다른 클래스(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 추상화를 끝내고 -> 추상화 호출부 이름변경(통일)로 인한 공통(중복)메서드가 추가로 생길 수 있다 -> 다시 부모로 빼준다.
  1. 추상화 IMPL 오버라이딩된 정의부는 서로 다르게 구현되므로 이름은 같지만 공통메서드로 볼수 없다

  2. 추상화 사용처()는 이름이 자식들이 같아지므로, 공통메서드/중복메서드가 생길 수 있다 -> 보이면 부모 클래스로 빼면 된다.

    image-20220313201005975

    image-20220313201048960

  3. 메서드 순서 맞춰주기(현재 추상메서드만 위에서 정의해놨을 듯..) image-20220313201306665

    image-20220313201345337

10 extends -> 정의도 안되어있는데 super생략한 체로 공통메서드를 실제 내부 or 외부에서 인스턴스가 사용 확인해보기
  1. 현재 Coffee클래스

    • **extends로 상속하고 있다. **
      • super는 생략되어있지만 부모로 빼놨던 공통메서드를 내부에서 쓰고 있다
     public class Coffee extends CaffeineBeverage{
         @Override
         void brew() {
             System.out.println("필터를 활용해 커피를 내린다.");
         }
        
         @Override
         void addCondiments() {
             System.out.println("설탕과 우유를 추가한다.");
         }
     }
        
    
  2. **메인 메서드를 만들고 -> 상속한 자식클래스 인스턴스를 만든 뒤 **

    • Coffee 내부에는 정의되어있지 않지만, 부모에게 상속된 공통 메소드를 바로 호출할 수 있다.
     public class Application {
         public static void main(String[] args) {
             final Coffee coffee = new Coffee();
        
             coffee.prepareRecipe(); // 정의 안된 메서드인데 extends 공통메서드라면 바로 사용 가능
         }
     }
    
추상화부터 하는 방법도 있다. 정답은 없다. 처음부터 완벽하게 추상화 x -> 공통처리부분이 발견될 시 처리하는게 좋다.
cf) 추상클래스 인스턴스화(=구체화) 안되는 이유: 구현부 없는 추상메서드 때문에 추상클래스가 됬으므로..ㅋ -> 구현부정의와 같이 객체를 만들어줄 수도 있다.
  1. 공통메서드를 추출한 부모클래스를 활용해서, 구현부는 다르지만 이름만 공통으로 추상화하였다 = 추상메서드 들고 있다 = 추상클래스가 부모다 > 인스턴스화 안된다.image-20220313213710830

  2. 인스턴스화를 하려면 내부에 구현부 없이 이름만 추상화한 추상메서드를 인스턴스화 하면서, {}]중괄호 블럭로 자식클래스처럼 바로 클래스 구현하면 된다. image-20220313214420188

    image-20220313214647373

  • 중괄호{}로 클래스를 구현하는 것을 람다 와 동일하게 익명클래스라 부른다.

    image-20220313215054992

    • 익명클래스를 자식클래스와 동일하게 작성하더라도, 별개의 익명클래스다

quiz) 추상클래스는 추메가 무조건 있어야할까?

  • 추메 없어도 된다.

  • 추메 없으면 객체 생성해도될까? -> 구현부없는 놈을 안가지고 있어도 객체생성은 안안된다.

    • 구현할 내용이 없어도 { } 중괄호 블럭 가진체로 만들어야한다.
추메를 안가지는데 추상클래스를 구현했다면 고민해봐야한다.
  • 추메 -> 구현부는 다르지만 공메를 가진 부모클래스에서 구현부제외 이름만 추상화해놓은 것
    • 추메가 없다? 그냥 부모가 가진 공통메서드만 super생략 갖다쓰는 일반 상속이면 된다.

업캐스팅 vs 다운캐스팅

업캐스팅 = 다형성 = 추상체로 변수,파라미터,응답값 받기

  • 변수/파라미터/응답Type은 추상체(up)로 받을 수 있다. 구상체들을 한번에 처리할 수 있다.

  • List쓸 때 많이 쓰는 업캐스팅개념이다.

    image-20220313215508140

      public class Application {
          public static void main(String[] args) {
              //업캐스팅
              final CaffeineBeverage caffeineBeverage = new Tea();
        
              final List<Tea> list = new ArrayList<>();
          }
      }
    

다운캐스팅 = 추상체상태에서 -> 구상체로 돌아가 구상체만의 메서드/상태 사용가능해진다.

01 구상체 or 자식만의 메서드는 편하게 가질 수 있다.
  1. Tea에 Tea만의 메서드(some_t())가 있다고 치자.

    image-20220313220308762

02 문제는 추상체/부모형으로 업캐스팅 된 상태에서는, 개별구상체 본인이라도, 메서드를 호출이 안된다.

image-20220313220440461

  • 업캐스팅된 부모타입이라면, Type자체가 부모상태라 안된다.
03 추상체 상태의 구상체를 (다운캐스팅)하면, 자신만의 메서드를 사용할 수 있다.

image-20220313220851635

  • 변수를 캐스팅하려면 괄호가 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()가 호출시 에러는 안난다 == 컴파일시 에러가 안난다.

    image-20220313221206001

      public class Application {
          public static void main(String[] args) {
              final CaffeineBeverage beverage = new Coffee();
        
              ((Tea) beverage).some_t();
        
          }
      }
    
  • 하지만 돌려보면, 캐스팅 에러가 난다. image-20220313221927823

  • 다운캐스팅후 자신만의 메서드호출 -> 반드시 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();
      }