Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[비트캠프] 56일차(12주차1일) - Java: myapp-22, 인터페이스(인터페이스 필드, default, static 메서드, 다중 상속) 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[비트캠프] 56일차(12주차1일) - Java: myapp-22, 인터페이스(인터페이스 필드, default, static 메서드, 다중 상속)

끈기JK 2023. 1. 25. 11:51

 

21. 인터페이스를 이용하여 loose coupling 구현

 

### 21. 인터페이스를 이용하여 객체 목록 관리를 규격화 하기 
- List 인터페이스를 구현 
- ArrayList와 LinkedList는 List 규칙에 따라 재구현 
- XxxDao 클래스에 인터페이스 적용 
- 인터페이스를 이용하여 느슨하게 결합(loose coupling)하는 방법

 

loose coupling 구현 : 커플링(종속성)을 느슨하게 만들기

App 에서 main() 실행한다. BoardHandler에서 service() 실행한다. BoardDao 에서 List 를 사용한다. insert() call 한다. BoardDao 에서 List의 insert() call 한다. List 는 LinkedList, ArrayList 의 규칙을 정하는 interface 이다. StudentDao, TeacherDao 에서도 List 사용한다.

 

 

 

22.  데이터 조회 기능을 규격화 → 구현 방식에 상관없이 일관된 조회 가능

 

### 22. 데이터 조회 로직을 객체화 하기: Iterator 패턴 적용 
- 데이터 조회 규격을 Iterator 인터페이스로 정의한다. 
- List 구현체에서 Iterator 규격을 따르도록 변경한다. 
- 객체 목록을 다룰 때 Iterator 구현체를 사용한다.

 

ArrayList : 배열을 이용하여 목록을 다룬다.

LinkedList : 노드 간의 연결을 이용해 목록을 다룬다.

→ 둘 다 index를 이용하여 데이터를 조회. add(인덱스, 값) get(인덱스)

Stack : Last In First Out (LIFO), First In Last Out (FILO). push(값), pop()

Queue : First In First Out (FIFO). offer(값), poll()

 

→ 데이터 저장 방식(mechanism)에 따라 데이터를 꺼내는 방법이 다르다! → 데이터 조회에 대해 일관성이 없다. → 일관성 해결 설계 기법 → Iterator 패턴

 

 

App 에서 데이터 조회 객체를 call 해서 get() 을 이용해 ArrayList, LinkedList 에서 데이터 꺼낸다.

                                                              pop() 을 이용해 Stack 에서 데이터 꺼낸다.

                                                              poll() 을 이용해 Queue 에서 데이터 꺼낸다.

↑ call 로 일관된 호출을 한다. 그러려면 메서드 signature 가 같아야 한다. = 인터페이스

 

 

《interface》 Iterator 로 호출 규칙을 정하고 데이터 조회 객체에서 구현한다.

hasNext(), next() 를 사용해 값을 꺼낸다.

↑ 규칙에 따라 호출하여 일관성 확보

 

 

App 이 ArrayList 에 ① iterator() 를 호출한다. ② return 으로 ListIterator (Iterator 구현체)를 반환한다. App 에서 ListIterator 를 call 하면 ListIterator 에서 ④ get() 사용해서 값을 요청하면 ArrayList는 ⑤ 값 리턴한다. ListIterator 는 App 에 ⑥ 값 리턴 한다.

 

 

App 의 main() 에서 BoardHandler 의 service() 사용한다. BoardHandler 에서 BoardDao 를 call 한다. BoardDao 에서 List 구현체를 call 한다. List 는 Iterable 을 상속받고 구현체로 ArrayList, LinkedList 가 있다. List 구현체는 Iterator 구현체를 사용한다. 구현체로 ListIterator 가 있다. 또한 BoardDao 에서 Iterator 구현체를 call 한다.

 

 

 

List 와 Iterator 인터페이스

 

BoardHandler 에서 BoardDao 를 사용해 ① findAll() 를 실행한다. findAll() 에서 ② toArray 에서 변경된 iterator() 를 실행한다. iterator() 는 List 구현체에 구현되어 있다. List 구현체인 LinkedList 에서 ③ return 으로 Iterator 구현체인 ListIterator 를 반환한다. BoardDao 에서 ListIterator 를 사용해 ④ hasNext(), next() 를 실행한다. Board[ ] 를 받아 BoardHandler 로 리턴한다.

 

《interface》List 에 iterator() 를 추가하지 않고 공통 규칙을 수퍼 interface 인 Iterable 에 정의한다. 《interface》List 와 《interface》Queue 가 《interface》Iterable 을 상속한다.

여러 인터페이스(예. List 와 Queue)에 공통으로 적용되는 규칙(메서드)이 있다면, 수퍼인터페이스를 만들어 공통규칙을 정의한 후, 상속 받는 것이 인터페이스 유지보수에 좋다.

 

 

 

 

예제 코드

 

com/eomcs/oop/ex09/a~d

 

 

 

인터페이스 사용 전

 

BlueWorker는 doFight() 실행하고, WhiteWorker는 doZingZing() 실행하고, JubuWorker는 doSsingSsing() 실행한다.

↑ 객체마다 사용법이 다르다. 프로그래밍에 일관성이 없다.

 

 

Worker 구현체 사용
package com.eomcs.oop.ex09.a1.before;

public class Exam01 {

  public static void main(String[] args) {

    BlueWorker w1 = new BlueWorker();
    WhiteWorker w2 = new WhiteWorker();
    JubuWorker w3 = new JubuWorker();

    // 각 노동자에게 일을 시키는 방법이 다르다.
    // 왜? 
    // => 메서드 호출 방법이 다르기 때문에
    w1.doFight();
    w2.doZingZing();
    w3.doSsingSsing();

    // 객체에 일을 시키는 방식은 엇비슷한데,
    // 메서드 시그너처가 다르기 때문에 
    // 호출할 때 일관성이 없다.
    // 즉 worker 객체를 사용하는 입장에서는 
    // 각 worker에 어떤 메서드가 있는지 확인해서 
    // 그 형식에 맞춰야 하기 때문에
    // 객체를 사용하기가 불편한다.
    // 또한,
    // 앞으로 worker를 추가 제작할 경우 
    // 개발자의 입맛에 맞춰 메서드 이름과 파라미터 형식이 결정될 것이기 때문에
    // 점점 더 worker 객체를 사용하기 힘들어진다.
    // 
    // 결론!
    // => 유사한 일을 하는데 객체 사용법이 다르다면 쓰기가 매우 불편하다.
    //
    // 해결책!
    // => 유사한 일을 하는 객체에 대해 사용법을 통일하자.
    // 
    // 방법!
    // => 객체 사용 규칙을 정의한다.
    // => 그리고 클래스를 정의할 때 그 규칙에 따라 만든다.
    // => 그러면 규칙에 따라 만든 클래스를 사용할 때는
    //    일관된 방법으로 메서드를 호출할 수 있어 
    //    코딩하기가 훨씬 편해지고, 
    //    유지보수가 쉬워진다.
    //
    // 이렇게 객체의 사용 규칙(호출 규칙)을 정의하는 문법이 "인터페이스(interface)"이다.
    //
    // 
  }
}
package com.eomcs.oop.ex09.a1.before;

public class BlueWorker {

  public void doFight() {
    System.out.println("육체 노동자가 일을 합니다!");
  }

}
package com.eomcs.oop.ex09.a1.before;

public class WhiteWorker {
  
  public void doZingZing() {
    System.out.println("사무직 노동자가 일을 합니다.");
  }
}
package com.eomcs.oop.ex09.a1.before;

public class JubuWorker {
  
  public void doSsingSsing() {
    System.out.println("주부로 일합니다.");
  }
}

 

 

 

인터페이스 사용 후

 

《interface》Worker 에 execute() 규칙을 정의한다.

Client 에서 BlueWorker, WhiteWorker, JubuWorker의 execute() 를 실행한다.

호출 규칙 중요 → 프로그래밍이 쉽다.

 

 

Worker 구현체 사용
package com.eomcs.oop.ex09.a1.after;

// 작업:
// 1) worker 객체의 사용 규칙을 정의한다.
//    => Worker 인터페이스 정의
// 2) 클래스를 정의할 때 Worker 규칙에 따라 만든다.
//    => BlueWorker, WhiteWorker, JubuWorker 클래스 변경
// 3) worker를 사용하는 측에서는 Worker 인터페이스에 정의된 대로 메서드를 호출한다.
//    => Exam01 클래스 변경
//
public class Exam01 {
  public static void main(String[] args) {

    // 같은 사용규칙에 따라 만든 클래스는 
    // 한 인터페이스 레퍼런스에 그 객체 주소를 저장할 수 있어 편리하다.
    //
    // 인터페이스 레퍼런스
    // => 인터페이스를 구현한 클래스의 객체 주소를 저장하는 변수
    Worker w1 = new BlueWorker();
    Worker w2 = new WhiteWorker();
    Worker w3 = new JubuWorker();

    // Worker 인터페이스에 선언된 메서드를 구현한다고 해서 되는 게 아니다.
    // 반드시 implements 키워드를 사용해서 
    // Worker 인터페이스를 구현한다고 선언해야 한다.
    // 다음 HulWorker 클래스처럼 Worker 인터페이스 구현을 선언하지 않는다면,
    // 공식적으로 Worker 구현체가 아니기 때문에 
    // 레퍼런스에 담을 수 없다. 
    //    Worker w4 = new HulWorker(); // 컴파일 오류!

    // => 인터페이스를 구현하지 않은 클래스의 인스턴스 주소는 저장할 수 없다. 
    //    Worker w5 = new String();// 컴파일 오류!

    // 사용하려는 객체가 같은 인터페이스를 구현(사용규칙에 따라 작성)하였다면,
    // 다음과 같이 동일한 사용규칙에 따라 메서드를 호출할 수 있어 편리하다.
    // 호출에 일관성이 있어 유지보수에 좋다.
    w1.execute();
    w2.execute();
    w3.execute();

    // 역할?
    // => 사용 규칙: Worker
    // => caller : Exam01
    // => callee : BlueWorker, WhiteWorker, JubuWorker

    // 참고!
    Worker w;

    // 인터페이스 레퍼런스?
    // => 해당 인터페이스에 따라 작성된(사용규칙을 준수하는) 클래스의 인스턴스 주소를 저장한다.
    // 
    // 위 예제에서 w 레퍼런스를말로 표현하는 방법:
    // => Worker 사용규칙에 따라 작성된 클래스의 인스턴스 주소를 저장하는 변수 w.
    // => Worker 인터페이스를 구현한 클래스의 인스턴스 주소를 저장하는 변수 w.
    // => Worker 구현체의 인스턴스 주소를 저장하는 변수 w.
    // => Worker 구현체의 객체 주소를 저장하는 변수 w.
    // => Worker 구현 객체를 저장하는 변수 w.
    // => Worker 객체를 저장하는 (변수) w.
    // => Worker 객체를 가리키는 (변수) w.
    // => Worker 타입 객체 w.

    // 프로그램을 짜다가 인터페이스를 만나게 되면,
    // - 누가 호출자이고 누가 피호출자인지 확인하라.
    // - 본인이 맡은 개발 일이 호출자를 만드는 것인지 
    //   아니면 피호출자를 만드는 것인지 확인하라.

  }
}
// 인터페이스 - caller와 callee 사이의 호출 규칙을 정의하는 문법
package com.eomcs.oop.ex09.a1.after;

// caller(호출자;사용자) : Exam01
// callee(피호출자;도구) : BlueWorker, JubuWorker, WhiteWorker
// 문법:
//    interface 사용규칙명 {...}
//
public interface Worker {

  // 호출 규칙?
  // => 메서드 형식을 의미한다.
  // => 메서드의 몸체는 정의하지 않는다.
  // => 메서드의 몸체는 이 규칙에 따라 만드는 클래스에서 정의하는 것이다.
  //
  // 문법
  // => public abstract 리턴타입 메서드명(파라미터,...);
  // => public과 abstract는 모두 생략 가능.
  // => public 외에 다른 접근 범위는 사용할 수 없다.
  //    (규칙은 공개되어야 하니까!)
  //
  void execute();
  //=> public abstract void execute();
}
// 구현체(implementor) - 인터페이스(사용규칙)에 따라 만든 클래스 
package com.eomcs.oop.ex09.a1.after;

// 문법
// => class 클래스명 implements 인터페이스명, 인터페이스명, ... {...}
public class BlueWorker implements Worker {

  // 인터페이스(약속,규칙,규격,법률)를 이행하는 클래스는
  // 인터페이스에 선언된 모든 메서드를 반드시 구현해야만 일반 클래스(concrete class)가 된다.
  // 만약에 구현하지 않으면, 추상 메서드인채로 존재한다.
  // 따라서 졸지에 추상 메서드를 가진 클래스가 된다. 
  // 추상 메서드는 추상 클래스만이 가질 수 있기 때문에 
  // 추상 클래스로 바꿔야 한다.
  // 

  @Override
  public void execute() {
    System.out.println("육체 노동자가 일을 합니다!");
  }
}
// Worker 인터페이스를 이행하는 클래스 - 줄여서 "Worker 구현체"라 부른다.
package com.eomcs.oop.ex09.a1.after;

public class WhiteWorker implements Worker {
  // 인터페이스 구현체(인터페이스를 구현하는 클래스)는 
  // 반드시 인터페이스에 선언된 모든 메서드를 구현해야 한다.
  @Override
  public void execute() {
    System.out.println("사무직 노동자가 일을 합니다.");
  }
}
// Worker 구현체 
package com.eomcs.oop.ex09.a1.after;

public class JubuWorker implements Worker {
  // 인터페이스 구현체는 인터페이스에 선언된 모든 메서드를 구현해야 한다.
  @Override
  public void execute() {
    System.out.println("주부로 일합니다.");
  }
}
package com.eomcs.oop.ex09.a1.after;

// 클래스를 정의할 때 Worker 인터페이스를 구현하겠다고 선언하지는 않았지만,
// Worker 인터페이스에 존재하는 메서드를 모두 구현했을 때,
// 이 클래스는 Worker 규칙을 따른다고 할 수 있는가?
// 답: 아니오!
// 
// 운전 면허가 없더라도 운전을 할 수 있다고 해서 
// 도로로 차를 몰 수는 없다.
public class HulWorker {
  public void execute() {
    System.out.println("헐....^^");
  }
}

 

 

 

인터페이스와 caller/callee

 

Caller 가 인터페이스 Worker를 사용한다. Callee 는 Worker 규칙에 맞게 작성해 BlueWorker 를 만든다.

 

 

인터페이스 사용 전
package com.eomcs.oop.ex09.a2.before;

public class Exam01 {

  public static void main(String[] args) {

    // 객체의 기능을 사용해보자!
    // => 각 도구(객체)의 사용법이 다르기 때문에 각 도구에 맞춰서 기능을 사용해야 한다.
    // => 그래서 각 도구에 맞는 use() 메서드를 각각 따로 준비했다.

    // 1) ToolA 객체 사용하기
    use(new ToolA());

    // 2) ToolB 객체 사용하기
    use(new ToolB());
  }

  static void use(ToolA tool) {
    // ToolA 객체를 사용하려면 m1() 메서드를 호출해야 한다.
    tool.m1();
  }

  static void use(ToolB tool) {
    // ToolB 객체를 사용하려면 m2() 메서드를 호출해야 한다.
    tool.m2();
  }

}
package com.eomcs.oop.ex09.a2.before;

public class ToolA {

  // 각각의 클래스는 자신만의 이름으로 메서드를 제공한다.
  public void m1() {
    System.out.println("ToolA.m1()");
  }
}
package com.eomcs.oop.ex09.a2.before;

public class ToolB {

  //각각의 클래스는 자신만의 이름으로 메서드를 제공한다.
  public void m2() {
    System.out.println("ToolB.m2()");
  }
}

 

 

인터페이스 레퍼런스와 인스턴스의 관계
package com.eomcs.oop.ex09.a2.after;

public class Exam01 {

  public static void main(String[] args) {

    // 인터페이스 레퍼런스 선언
    // => Spec 인터페이스를 구현한 클래스의 인스턴스 주소를 저장하겠다는 의미다.
    // => Spec 규칙에 따라 작성한 클래스의 객체를 담겠다는 의미다.
    // => Spec 객체를 담겠다는 의미다.
    Spec tool;

    // ToolA 클래스는 Spec 인터페이스의 규칙에 따라 만든 클래스이기 때문에
    // 이 클래스의 인스턴스 주소를 tool 레퍼런스 변수에 저장할 수 있다.
    tool = new ToolA();

    // ToolB 클래스도 ToolA와 마찬가지로
    // Spec 구현체(implementer; 인터페이스 규칙에 따라 만든 클래스)이기 때문에
    // 해당 객체를 저장할 수 있다.
    tool = new ToolB();

    tool = new ToolC();

    // String 클래스는 Spec 구현체가 아니기 때문에 해당 객체를 레퍼런스에 저장할 수 없다.
    //    tool = new String("Hello"); // 컴파일 오류!
  }
}
// 인터페이스 - 호출 규칙(사용 규칙)을 정의하는 문법
package com.eomcs.oop.ex09.a2.after;

// 인터페이스
// => 사용자(클래스)와 도구(클래스) 사이의 호출 규칙을 정의할 때 사용한다.
// => 도구를 사용하기 위해 어떤 메서드를 호출해야 하는지 정의하는 것이다.
//
public interface Spec {

  // 호출 규칙을 정의할 때,
  // => 규칙은 공개되어야 하기 때문에 무조건 public 이다.
  // => 규칙이기 때문에 당장 구현할 필요는 없다. 그래서 추상 메서드이다.
  public abstract void m1();

  // 물론 다음과 같이 public 과 abstract를 모두 생략할 수 있다.
  //  void m1();
}
// 인터페이스 구현 - 규칙에 따라 클래스를 만든다.
package com.eomcs.oop.ex09.a2.after;

// 규칙에 따라 클래스를 만들면 도구를 사용하는 입장에서
// 일관된 코딩을 할 수 있어 유지보수에 좋다.
public class ToolA implements Spec {

  // 인터페이스의 규칙에 따라 클래스를 작성할 때는
  // 인터페이스에 선언된 모든 추상 메서드를 반드시 구현해야 한다.
  // 구현하지 않으면 추상 메서드를 그대로 갖고 있는 것이 된다.
  // 추상 메서드를 갖고 있는 클래스는 추상 클래스만이 가능하다.

  // => 인터페이스에 선언된 메서드가 public 이기 때문에 이 메서드를 구현하는 클래스에서는
  //    public 보다 낮은 접근을 지정할 수 없다.
  //    즉 무조건 public 메서드이다.
  //
  @Override
  public void m1() {
    System.out.println("ToolA.m1()");
  }
}
// 인터페이스 구현 - 규칙에 따라 클래스를 만든다.
package com.eomcs.oop.ex09.a2.after;

// 규칙에 따라 클래스를 만들면 도구를 사용하는 입장에서
// 일관된 코딩을 할 수 있어 유지보수에 좋다.
public class ToolB implements Spec {

  // 인터페이스의 규칙에 따라 클래스를 작성할 때는
  // 인터페이스에 선언된 모든 추상 메서드를 반드시 구현해야 한다.
  // 구현하지 않으면 추상 메서드를 그대로 갖고 있는 것이 된다.
  // 추상 메서드를 갖고 있는 클래스는 추상 클래스만이 가능하다.

  // => 인터페이스에 정의된 규칙을 따르기로 했으면
  //    원래 클래스에 있던 메서드의 이름을 포기하고 인터페이스에 있는 메서드 이름을 따르라!
  //    m2() ---> m1() 으로 변경한다.
  @Override
  public void m1() {
    System.out.println("ToolB.m1()");
  }
}
// 인터페이스 구현 - 규칙에 따라 클래스를 만든다.
package com.eomcs.oop.ex09.a2.after;

// 누가 도구를 만들든지 간에 항상 Spec 에 따라 만들기 때문에,
// - 이 도구를 사용하는 입장에서는 Spec에 맞춰 사용함으로써
//   프로그래밍에 일관성을 확보할 수 있다.
//
public class ToolC implements Spec {
  @Override
  public void m1() {
    System.out.println("ToolC.m1()");
  }
}
// 인터페이스 미구현
package com.eomcs.oop.ex09.a2.after;

// ToolD 클래스는 Spec 인터페이스에 선언된 메서드를 구현했지만,
// 문법적으로는 Spec 인터페이스와 상관이 없다.
//
public class ToolD {
  public void m1() {
    System.out.println("ToolC.m1()");
  }
}

 

 

인터페이스 규칙에 따라 만든 클래스를 사용하기
package com.eomcs.oop.ex09.a2.after;

public class Exam02 {

  public static void main(String[] args) {
    // 도구 사용하기

    // Spec 규칙(interface)에 따라 만든 도구(클래스)를 use() 메서드에 넘긴다.
    use(new ToolA());
    use(new ToolB());

    // 새로 추가한 도구(클래스 ToolC)도 Spec 규칙에 따라 만들었기 때문에
    // use() 메서드에 그대로 넘길 수 있다.
    use(new ToolC());

    //    use(new ToolD()); // 컴파일 오류!
    // 비록 ToolD 클래스에 Spec에 정의된 m1() 메서드가 있다 하더라도
    // 문법적으로 ToolD 클래스는 Spec 인터페이스를 구현한 것이 아니기 때문에
    // use 파라미터 값으로 넘길 수 없다.
  }

  static void use(Spec tool) {
    // 파라미터 tool에 넘어오는 객체는 Spec 규칙에 따라 만든 객체일 것이다.
    // Spec 규칙에 따라 만든 도구를 사용할 때는
    // Spec 규칙에 따라 일관된 방식으로 사용(메서드를 호출)하면 된다.
    // 그러면 해당 인스턴스의 클래스를 찾아 그 클래스에서 구현한 메서드를 호출할 것이다.
    tool.m1();
  }

}

 

 

 

인터페이스의 구현

 

《interface》MyInterface 에 m1(), m2(), m3() 규칙을 정한다.

《concrete》MyObject 에서 MyInterface 규칙에 따라 m1() {-}, m2() {-}, m3() {-} 를 구현한다.

《abstract》MyObject2 에서 MyInterface 규칙에 따라 m1() {-}, m2() {-} 를 구현했지만 m3() 를 구현하지 않아 abstract 이다.

 

MyInterface ref;

ref = new MyObject();  하면 인터페이스를 구현한 클래스의 인스턴스 주소가 들어간다.

 

 

인터페이스 - 정의와 구현
package com.eomcs.oop.ex09.b;

// 1) 인터페이스 정의
interface MyInterface {
  // 규칙1) 인터페이스에 선언되는 모든 메서드는 public 이다.
  //   - 인터페이스에 정의하는 메서드는 호출 규칙이다.
  //   - 규칙은 공개되어야 한다.
  // 규칙2) 인터페이스에 선언되는 모든 메서드는 추상 메서드로 선언한다.
  //   - 인터페이스에 선언하는 메서드는 호출 규칙을 정의한 것이다.
  //   - 규칙은 클래스가 따라야 한다.
  //   - 그래서 인터페이스에 선언되는 모든 메서드는 몸체를 구현하지 않는다.
  public abstract void m1();

  // public 을 생략할 수 있다.
  abstract void m2(); // public 이 생략된 것이다. (default) 아니다!

  // abstract 를 생략할 수 있다.
  public void m3();

  // public, abstract 모두 생략할 수 있다.
  void m4();

  // => private, protected, (default)는 없다.
  //  private void m5(); // 컴파일 오류!
  //  protected void m6(); // 컴파일 오류!
  void m7(); // 이건 (default) 아니라, public 이 생략된 것이다.

}

// 2) 인터페이스 구현
abstract class MyInterfaceImpl implements MyInterface {
  @Override
  public void m1() {}

  // public 보다 접근 범위를 좁힐 수는 없다.
  @Override
  //  private void m2() {}  // 컴파일 오류!
  //  protected void m2() {} // 컴파일 오류!
  //  void m2() {} // 컴파일 오류!
  public void m2() {} // OK!

  // 인터페이스의 모든 메서드를 구현해야 한다.
  // 한 개라도 빠뜨린다면 concrete 클래스가 될 수 없다.
  // 추상 클래스로 선언해야 한다.
}

class MyInterfaceImpl2 implements MyInterface {
  @Override
  public void m1() {}

  @Override
  public void m2() {} // OK!

  @Override
  public void m3() {}

  @Override
  public void m4() {}

  @Override
  public void m7() {}
}

// 3) 인터페이스 사용
public class Exam01 {

  public static void main(String[] args) {
    // 인터페이스 레퍼런스 선언
    MyInterface obj = null;

    // 인터페이스의 구현체 생성
    // - 인터페이스를 구현한 클래스의 객체라면
    //   언제든 해당 인터페이스의 레퍼런스에 담을 수 있다.
    obj = new MyInterfaceImpl2();

    // 인터페이스는 규칙이기 때문에
    // 구체적인 구현 내용이 없다.
    // 그래서 인스턴스를 생성할 수 없다.
    //
    //    obj = new MyInterface(); // 컴파일 오류!
  }

}

 

 

인터페이스 필드
package com.eomcs.oop.ex09.b;

// 인터페이스 필드 선언
interface MyInterface2 {
  // 인터페이스 필드는 public static final 이다.
  // - 인스턴스를 생성할 수 없기 때문에 인스턴스 필드를 선언할 수 없다.
  // - 규칙이기 때문에 무조건 public 이다.
  // - 인스턴스 필드가 아니기 때문에 값을 변경할 수 없다.
  public static final int v1 = 100;

  // public, static, final 을 생략할 수 있다.
  static final int v2 = 200;
  public final int v3 = 300;
  public static int v4 = 400;

  int v5 = 500; // 모두 생략된 상태!
}

// 인터페이스 필드 사용
public class Exam02 {
  public static void main(String[] args) {

    // 인터페이스에 선언한 필드는 public static final 이기 때문에 바로 사용할 수 있다.
    System.out.println(MyInterface2.v1);
    System.out.println(MyInterface2.v2);
    System.out.println(MyInterface2.v3);
    System.out.println(MyInterface2.v4);
    System.out.println(MyInterface2.v5);

    // 인터페이스 필드는 상수 필드이기 때문에 값을 변경할 수 없다.
    //    MyInterface2.v1 = 111; // 컴파일 오류!
    //    MyInterface2.v2 = 222; // 컴파일 오류!
    //    MyInterface2.v3 = 333; // 컴파일 오류!
    //    MyInterface2.v4 = 444; // 컴파일 오류!
    //    MyInterface2.v5 = 555; // 컴파일 오류!
  }

}

 

 

 

default 메서드 사용 전

 

interface Car 에 start(), stop(), run() 메서드가 있고 구현체 Tico, Sonata 를 작성했다.

interface Car에 자율주행 규칙이 필요하여 autoPilot(); 규칙을 추가한다. 그러면 기존의 구현체는 오류 발생한다. Car 인터페이스에 추가된 새 규칙을 구현(정의)하지 않았기 때문이다.

 

 

 

default 메서드 사용 후

 

interface Car에 default autoPilot() { } 규칙 추가한다. Car에 추가된 새 규칙은 구현된 메서드이기 때문에 기존 클래스에는 아무런 영향을 끼치지 않는다.

 

 

인터페이스 - 기본 메서드(default method)
package com.eomcs.oop.ex09.b;

interface MyInterface3 {
  /*public static final*/ int a = 100;

  /*public abstract*/ void m1();

  // default method:
  // - 기존 프로젝트에 영향을 끼치지 않으면서 기존 규칙에
  //   새 메서드를 추가할 때 유용한다.
  // - 인터페이스에서 미리 구현한 메서드이기 때문에
  //   클래스에서 구현을 생략할 수 있다.
  // - 반대로 구현을 강제할 수 없다는 것이 단점이다.
  default void m2() {
    System.out.println("default 출력!");
  }
  // 어차피 새 메서드는 새 프로젝트의 구현체가 오버라이딩 할 것이니
  // 여기에서는 자세하게 정의하지 않는다.
  // 다만 이 인터페이스를 구현한 예전 프로젝트에 영향을 끼치지 않으면서
  // 새 메서드를 정의할 때 사용하는 문법이다.

  default void m3() {
    System.out.println("MyInterface3.m3()");
  };
}

// 2) 인터페이스 구현
class MyInterface3Impl implements MyInterface3 {

  // 추상 메서드는 반드시 구현해야 한다.
  @Override
  public void m1() {
    System.out.println("MyInterfaceImpl.m1()");
  }

  // default 메서드는 오버라이딩 해도 되고 안해도 된다.
  @Override
  public void m2() {
    System.out.println("MyInterfaceImpl.m2()");
  }

  // default 메서드는 오버라이딩 해도 되고 안해도 된다.
  // => m3() 는 이 클래스에서 오버라이딩을 하지 않았다.
}

public class Exam03 {

  public static void main(String[] args) {
    MyInterface3 obj = new MyInterface3Impl();

    obj.m1();
    obj.m2();
    obj.m3();
  }

}

 

 

인터페이스 - private 메서드
package com.eomcs.oop.ex09.b;

interface MyInterface4 {

  void m1();

  default void m2() {
    System.out.println("MyInterface4.m2()");
    x();
  };

  default void m3() {
    System.out.println("MyInterface4.m3()");
    x();
  };

  // 인터페이스 내부에서 사용할 메서드라면
  // private 접근 범위를 갖는
  // 구현 메서드를 정의할 수 있다.
  // 언제?
  // - 해당 기능을 m2()와 m3() 처럼 여러 메서드에서 사용해야 할 경우
  //   그 공통 코드를 다음과 같이 private 구현 메서드로 정의하면 될 것이다.
  //
  private void x() {
    System.out.println("MyInterface4.x()");
  }
}

// 2) 인터페이스 구현
class MyInterface4Impl implements MyInterface4 {
  @Override
  public void m1() {

//    m2();  // 현재 클래스의 m2() 메서드

    // 인터페이스에 선언된 오버라이딩 전의 default 메서드를 호출하고 싶다면,
    // super.m2();  // 수퍼 클래스(Object)에서 m2()를 찾아 올라간다. Object에는 m2()가 없기 때문에 컴파일 오류!
    // MyInterface4.m2();  // m2()는 인스턴스(non-static) 메서드이기 때문에 인터페이스 이름으로 직접 호출 불가!
    MyInterface4.super.m2();

    System.out.println("MyInterface4Impl.m1()");
  }

  @Override
  public void m2() {
    System.out.println("MyInterface4Impl.m2()");
  }
}

public class Exam04 {

  public static void main(String[] args) {
    MyInterface4 obj = new MyInterface4Impl();

    obj.m1();
    System.out.println("-----------------------------");

    obj.m2();
    System.out.println("-----------------------------");

    obj.m3();
    System.out.println("-----------------------------");

    // 인터페이스에 정의된 private 메서드는 호출할 수 없다.
    //    obj.x(); // 컴파일 오류!

  }

}

 

 

 

static 메서드

 

MyInterface5Impl 클래스에서 Parent 를 상속하고 MyInterface5 를 구현한다.

 

 

인터페이스 - static 메서드
package com.eomcs.oop.ex09.b;

interface MyInterface5 {
  // 인터페이스도 클래스처럼 static 메서드를 정의할 수 있다.
  // => 접근 범위는 기본이 public 이다. 다른 접근 범위를 가질 수 없다.
  // => public 을 생략할 수 있다.
  //
  // 다음 메서드는 public 이 생략된거지, (package-private) 접근 범위가 아니다.
  static void m1() {
    System.out.println("MyInterface5.m1()");
  }
}

class Parent {
  static void m2() {
    System.out.println("Parent.m2()");
  }
}

class MyInterface5Impl extends Parent implements MyInterface5 {

}

public class Exam05 {
  public static void main(String[] args) {
    // 인터페이스의 스태틱 메서드 호출하기
    MyInterface5.m1();

    // 클래스의 스태틱 메서드 호출하기
    Parent.m2();

    MyInterface5Impl obj = new MyInterface5Impl();

    // 수퍼 클래스에 정의된 static 메서드는,    
    // => 다음과 같이 서브 클래스를 통해 호출할 수 있다.
    MyInterface5Impl.m2();

    // => 또는 다음과 같이 인스턴스 레퍼런스를 통해 호출할 수 있다.
    // => 물론 스태틱 메서드는 가능한 그 메서드가 선언된 클래스 이름으로 호출하는 것이 좋다.
    obj.m2(); 

    // 그러나, 인터페이스에 정의된 static 메서드는,
    // => 인터페이스를 구현한 클래스를 통해 호출할 수 없다.
    //    MyInterface5Impl.m1(); // 컴파일 오류!

    // => 또한 구현체의 레퍼런스를 통해서도 호출할 수 없다.
    //    obj.m1(); // 컴파일 오류!
  }

}

 

 

 

인터페이스 상속과 구현

 

레퍼런스를 통한 메서드 호출 범위

ProtocolA 를 ProtocolB 가 상속하고 ProtocolB 를 ProtocolImpl 가 구현한다.

 

ProtocolImpl obj = new ProtocolImpl(); 한다.

obj.m1(), obj.rule1(), obj.rule2() 가능하다.

 

ProtocolB obj2 = obj; 한다.

obj2.m1(), obj2.rule2(), obj2.rule1() 가능하다. obj 에 담긴게 ProtocolImpl 인스턴스라 하더라도 레퍼런스가 ProtocolB 주소이기 때문이다.

 

ProtocolA obj3 = obj; 한다.

obj3.m1(), obj3.rule2(), obj3.rule1() 가능하다. 레퍼런스가 ProtocolA 주소이기 때문이다.

 

 

인터페이스 상속과 구현
package com.eomcs.oop.ex09.c;


public class Exam0110 {

  interface ProtocolA {
    void rule1();
  }

  // 인터페이스도 다른 인터페이스를 상속 받을 수 있다.
  // - ProtocolB 는 ProtocolA의 입장에서 서브 인터페이스이다.
  // - ProtocolA 는 ProtocolB의 입장에서 수퍼 인터페이스이다.
  interface ProtocolB extends ProtocolA {
    void rule2();
  }

  // 인터페이스를 구현할 때는
  // 수퍼 인터페이스의 메서드까지 모두 구현해야 한다.
  class ProtocolImpl implements ProtocolB {
    // ProtocolA 규칙 준수!
    @Override
    public void rule1() {System.out.println("rule1()");}

    // ProtocolB 규칙 준수!
    @Override
    public void rule2() {System.out.println("rule2()");}

    // 인터페이스와 상관 없이 메서드 추가
    public void m1() {System.out.println("m1()");}
  }

  void test() {

    ProtocolImpl obj = new ProtocolImpl();

    // 1) 클래스의 레퍼런스 사용,
    // - 그 클래스에 정의된 메서드 호출 가능
    // - 그 클래스의 상위 클래스에 정의된 메서드 호출 가능
    obj.rule1(); // OK
    obj.rule2(); // OK
    obj.m1(); // OK

    System.out.println("------------------------------------");

    // 2) 인터페이스의 레퍼런스 사용
    // - 인터페이스에 정의된 메서드 호출 가능 
    // - 상위 인터페이스에 정의된 메서드 호출 가능
    ProtocolB b = obj;
    b.rule2(); // OK --> ProtocolB.rule2() 
    b.rule1(); // OK --> ProtocolA.rule1()
    //    b.m1(); // 컴파일 오류 --> ProtocolImpl.m1() 

    System.out.println("-------------------------------");

    // 3) 수퍼 인터페이스의 레퍼런스 사용
    // - 인터페이스에 정의된 메서드 호출 가능 
    // - 상위 인터페이스에 정의된 메서드 호출 가능
    // - ProtocolImpl 클래스가 ProtocolB 의 규칙에 따라 제작되었다면
    //   결국 그 수퍼 인터페이스의 규칙도 준수하는 것이 된다.
    // - 따라서 수퍼 인터페이스 레퍼런스에 객체를 담을 수 있다.
    //   즉 수퍼 인터페이스 레퍼런스로 객체를 가리킬 수 있다.
    //
    ProtocolA a = obj;
    a.rule1(); // OK --> ProtocolA.rule1()
    //    a.rule2(); // 컴파일 오류 --> ProtocolB.rule2() 
    //    a.m1(); // 컴파일 오류 --> ProtocolImpl.m1() 
  }

  public static void main(String[] args) {
    new Exam0110().test();
  }
}

 

 

 

인터페이스 다중 상속

 

인터페이스 ProtocolC 가 ProtocolA, ProtocolB 를 둘 다 상속한다. 이를 구현한 ProtocolImpl 에서 rule3() {-}, rule1() {-}, rule2() {-}, m1() {-} 를 작성한다.

인터페이스의 메서드는 추상메서드이기 때문에 다중상속을 해도 문제가 될게 없다 → 다중 상속 가능!

 

 

인터페이스 다중 상속과 구현
package com.eomcs.oop.ex09.c;


public class Exam0120 {

  interface ProtocolA {
    void rule1();
  }

  interface ProtocolB {
    void rule2();
  }

  interface ProtocolC extends ProtocolA, ProtocolB {
    void rule3();
  }

  // 인터페이스를 구현할 때는
  // 수퍼 인터페이스의 메서드까지 모두 구현해야 한다.
  class ProtocolImpl implements ProtocolC {
    // ProtocolA 규칙 준수!
    @Override
    public void rule1() {System.out.println("rule1()");}

    // ProtocolB 규칙 준수!
    @Override
    public void rule2() {System.out.println("rule2()");}

    // ProtocolC 규칙 준수!
    @Override
    public void rule3() {System.out.println("rule3()");}

    public void m1() {System.out.println("m1()");}
  }

  void test() {

    ProtocolImpl obj = new ProtocolImpl();
    obj.rule1(); // OK
    obj.rule2(); // OK
    obj.rule3(); // OK
    obj.m1(); // OK
    System.out.println("-------------------------------");

    // 1) 인터페이스 레퍼런스로 구현체의 주소 받기
    ProtocolC c = obj;
    ProtocolB b = obj;
    ProtocolA a = obj;

    // 2) 메서드 호출
    // - 해당 인터페이스의 규칙에 따라서만 호출할 수 있다.

    c.rule1(); // OK
    c.rule2(); // OK
    c.rule3(); // OK
    //    c.m1(); // 컴파일 오류!
    System.out.println("-------------------------------");

    //    b.rule1(); // 컴파일 오류!
    b.rule2(); // OK
    //    b.rule3(); // 컴파일 오류!
    System.out.println("-------------------------------");

    a.rule1(); // OK
    //    a.rule2(); // 컴파일 오류!
    //    a.rule3(); // 컴파일 오류!
    System.out.println("-------------------------------");
  }

  public static void main(String[] args) {
    new Exam0120().test();
  }
}

 

 

 


 

 

조언

 

*generalization으로 추상클래스 뽑아내는 기술을 나에게 배워야 한다. 문법은 책이나 예제로 충분히 배울 수 있다. 이 강의를 듣는 목적이 이것이다.

*프로그램을 잘 다루려면 추상화, 상징화, 가정을 잘 해야 한다.

 

 

 


 

과제

 

/