JAVA

인터페이스 (Interface)

태로미 2023. 4. 4. 23:45

 

목차

📍   인터페이스 (Interface)

–   인터페이스 정의 문법

–   인터페이스를 상속 받은 서브클래스 정의 문법

 

1.   인터페이스 정의

    1–1.   인터페이스를 상속 받아 구현하는 서브클래스 정의

    12.   추상클래스 정의

        –   인터페이스와의 차이

    1–3.  main 메서드에서 실행

        –   인터페이스 내의 상수

        –   업캐스팅

 

2.   클래스의 다중 상속 문제

    2–1.   인터페이스의 다중 상속

 

3.   인터페이스의 필요성

    3–1.    인터페이스를 통한 간접적인 클래스 사용

    3–2 .   서로 상속 관계가 없는 클래스 관계

    3–3.    모듈간 독립적 프로그래밍

 

 

 

 

 

 

 

 

 

 

📍   인터페이스 (Interface)

–   클래스가 아니므로 선언 시 calss 키워드를 사용하지 않고 interface 키워드를 사용하여 정의.
–   인터페이스는 상수와 추상메서드만 가질 수 있음.
–   모든 멤버변수는 public static final이 붙은 상수로 취급됨.
      →   public static final 생략 가능.
      →   final로 인해 값 변경 불가.
      →   static으로 인해 클래스명만으로 접근 가능.
      →   public으로 인해 누구나 접근 가능.
–   모든 메서드는 public abstract가 붙은 추상메서드로 취급됨.
      →   public abstract 생략 가능.
      →   메서드 바디를 가질 수 없으며,  public으로 인해 누구나 접근 가능.
–   추상클래스와 마찬가지로 추상메서드를 포함하므로 객체 생성 불가능.  
      →   단,  참조변수 타입으로는 사용 가능 (= 다형성을 활용한 업캐스팅 가능)
–   서브클래스에서 인터페이스를 상속받아 구현해야 하는 경우,  extends 대신 implements 키워드를 사용함.
      →   클래스는 상속 받아 확장 (extends)의 개념이 강하지만,
            인터페이스는 상속 받아 추상메서드를 구현 (implement)의 개념이 강함.
–   클래스는 서브클래스에서 다중 상속이 불가능하지만,  인터페이스는 서브클래스에서 다중 상속 (구현)이 가능함.
      →   implements 키워드 뒤에 복수개의 인터페이스 지정 가능.
–   인터페이스끼리 상속받을 경우,  implements가 아닌 extends로 상속.
      →   인터페이스는 추상메서드만 가질 수 있으므로

             인터페이스끼리 상속받으면 추상메서드를 구현(implements)하지 못하기 때문.

 

 

 

 

 

 

 

▸   인터페이스 정의 기본 문법


[접근제한자] interface 인터페이스명 {
        상수
        추상메서드
}

 

 

 

 

 

 

 

▸   인터페이스를 상속 받은 서브클래스 정의 문법


[접근제한자] class 클래스명 implements 인터페이스명 { }

–   인터페이스를 상속 받은 (= 구현 하는) 서브클래스를 '구현클래스' 라고도 함.

      →   인터페이스의 추상메서드를 서브클래스에서 반드시 구현해야 하기 때문. (오버라이딩의 강제성)


EX   )

 

•   인터페이스 정의

–   클래스 안에 인터페이스 정의 가능.

interface MyExInterface {
	
    	// 상수
	public static final int NUM1 = 10;
	int NUM2 = 20;
//	int NUM3;

	// 생성자 X
//	public MyExInterface() {}

	// 추상메서드
	public abstract void method1();
//	public void method2() {}
	public void method2();
	void method3();

}
코드 분석
4~6
  –   인터페이스 내의 모든 멤버변수는 상수.
  –   public static final 생략 가능.
  –   The blank final field NUM2 may not have been initialized 에러 발생
       →   상수는 초기화 값을 줘야 하므로 에러 발생.
9
  –   Interfaces cannot have constructors 에러 발생.
       →   인터페이스는 생성자를 가질 수 없음.

13
  –    일반 메서드 사용 불가.  Abstract methods do not specify a body 에러 발생.
       →   인터페이스 내의 메서드는 추상메서드여야 하므로 바디를 가질 수 없다고 에러 발생.

14, 15
  –    추상메서드 'abstract' 생략 가능.
  –    'public abstract' 생략 가능. 

 

 

 

•   인터페이스를 상속 받아 구현하는 서브클래스 (구현클래스) MySubClass 정의

–   서브클래스 정의 시 클래스명 뒤에 implements 키워드를 쓰고 인터페이스명 지정.

–   인터페이스는 추상메서드만 가질 수 있으므로 서브클래스에서 반드시 오버라이딩 해야 함.

class MySubClass implements MyExInterface{

	@Override
	public void method1() {
		System.out.println("서브클래스에서 구현한 추상메서드 method1()");
	}

	@Override
	public void method2() {
		System.out.println("서브클래스에서 구현한 추상메서드 method2()");
	}

	@Override
	public void method3() {
		System.out.println("서브클래스에서 구현한 추상메서드 method3()");
	}

}

  –   인터페이스에서 method3() 정의 시 접근제한자를 생략했지만,
       인터페이스 내의 모든 메서드는 public 접근제한을 가지므로,
       오버라이딩을 통한 구현 시 무조건 public 접근제한자만 사용 가능.
       →   오버라이딩 메서드의 접근제한자같거나 넓은 범위로만 변경 가능,  좁아질 수 없음.

 

 

 

•   추상클래스 정의

–    추상클래스와 인터페이스의 차이

abstract class MyClass{
	
	public static final int NUM1 = 10;	// 상수
	public int NUM2 = 20;			// 인스턴스 멤버변수 O

	// 생성자
	public MyClass() {}

	// 추상메서드
	public abstract void method1();

	// 일반메서드
	public void method2() {}
	
}

–   추상클래스 내의 모든 멤버변수는 상수가 아니며,  선언 방법에 따라 달라짐.
–   추상클래스는 생성자와 일반메서드도 가질 수 있음.
–   추상메서드를 갖는 클래스는 반드시 추상클래스로 선언해야 함.

 

 

 

•   인스턴스 생성

// MyClass mc = new MyClass();
// MyExInterface mi = new MyExInterface();

MySubClass msc = new MySubClass();
msc.method1();
msc.method2();
msc.method3();
코드 분석
1
  –   추상클래스는 인스턴스 생성이 불가능.
        →   추상메서드의 바디가 구현되지 않았으므로 호출이 불가한데 인스턴스를 생성하면 호출 할 수 있기 때문.
2   –   인터페이스도 인스턴스 생성이 불가능.
        →   추상메서드때문에 추상클래스의 인스턴스 생성이 불가능하고,
               인터페이스는 추상메서드만 가질 수 있으므로 마찬가지로 인스턴스 생성 불가능.
4~7
  –   인터페이스를 구현한 서브클래스 (구현클래스) 인스턴스 생성 가능.

✓   실행 결과

더보기

서브클래스에서 구현한 추상메서드 method1()
서브클래스에서 구현한 추상메서드 method2()
서브클래스에서 구현한 추상메서드 method3()

 

 

 

•   인터페이스 내의 상수

// MyExInterface.NUM1 = 99;
// MySubClass.NUM1 = 99;
// msc.NUM1 = 99;

–   인터페이스 내의 모든 변수는 상수이므로 값 변경 불가.
      →   구현체 클래스 (서브클래스)도 동일한 상수가 상속되므로 값 변경 불가함.

 

 

 

•   업캐스팅

MyExInterface mi2 = new MySubClass();

mi2.method1();
mi2.method2();
mi2.method3();

System.out.println(mi2.NUM1);
System.out.println(mi2.NUM2);
코드 분석
1
  –   인터페이스는 인스턴스 생성은 불가능하지만,  참조변수 타입으로 사용은 가능.
        →   구현체 클래스 (서브클래스)의 인스턴스를 인터페이스 타입으로 업캐스팅 가능.
        →   다형성 활용 가능.

3~5
  –   업캐스팅 후에는 참조 영역이 축소되므로 상속된 메서드만 호출 가능.
        →   인터페이스의 추상메서드를 모두 오버라이딩 했기 때문에,
               인터페이스가 가진 모든 메서드는 참조 영역 축소 후에도 호출 가능.

7, 8
  –   상수는 인터페이스에서 선언했으므로 업캐스팅 후에도 당연히 접근 가능.

✓   실행 결과

더보기

서브클래스에서 구현한 추상메서드 method1()
서브클래스에서 구현한 추상메서드 method2()
서브클래스에서 구현한 추상메서드 method3()

10

20

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

▶   클래스의 다중 상속 문제점

–   Java에서는 클래스에서 다중 상속이 일어날 경우 발생하는 문제점이 있으므로 단일 상속만 허용하고 있음.

     →   해당 클래스들의 관계를 그리면 마치 다이아몬드 모양과 같아 다이아몬드 상속에서의 문제점 (이슈)라고도 함.


EX   )

 

•   동물 추상클래스 정의

abstract class 동물{
	public abstract void 번식();
}

 

 

•   상어 클래스 정의   — 추상클래스 동물 상속

class 상어 extends 동물{
	@Override
	public void 번식() {
		System.out.println("알을 낳아 번식");
	}
}

 

 

•   고래 클래스 정의   — 추상클래스 동물 상속

class 고래 extends 동물{
	@Override
	public void 번식() {
		System.out.println("새끼를 낳아 번식");
	}
	
}

–   번식() 메서드를 갖는 슈퍼클래스 동물 클래스를 정의하고,  고래와 상어클래스에서 동물 클래스를 상속 받기.

 

 

•  고래상어 클래스 정의    — 고래와 상어 클래스 상속

class 고래상어 extends 고래, 상어{
		
}

–   외부 또는 내부에서 고래와 상어클래스가 공통적으로 갖는 번식() 메서드를 호출하게 되면, 
     어느 객체의 번식() 메서드를 호출하는지 분명하지 않기 때문에 Java에서는 다중 상속이 금지되어 있음.

 

 

 

 

 

 

 

 

 

 

 

▶   인터페이스의 다중 상속

–   클래스는 서브클래스에서 다중 상속이 불가능하지만,  인터페이스는 서브클래스에서 다중 상속 (구현)이 가능함.
     →   implements 키워드 뒤에 복수개의 인터페이스 지정 가능.


EX   )

 

•   '동물' 인터페이스 정의

interface 동물{
	
	public abstract void 번식();
	
}

–   고래와 상어의 특징을 추상화하여 동물 인터페이스로 정의.
–   인터페이스 내의 모든 메서드는 추상메서드.

 

 

•   '헤엄칠수있는' 인터페이스 정의

interface 헤엄칠수있는 {}

–  헤엄칠 수 있는 특징을 갖는 인터페이스 정의.

 

 

•   '동물' 인터페이스를 상속받는 서브인터페이스 '고래'와 '상어' 정의

interface 고래 extends 동물, 헤엄칠수있는{}
interface 상어 extends 동물, 헤엄칠수있는{}

–    인터페이스끼리 상속은 implements가 아닌 extends 사용.
–    인터페이스끼리 상속다중 상속도 가능.

 

 

•   '고래'와 '상어' 인터페이스를 동시에 상속 받아 구현하는 서브클래스 '고래상어' 정의

class 고래상어 implements 고래, 상어{
	@Override
	public void 번식() {
		System.out.println("알을 낳아 번식");
	}
}

–   클래스에서 인터페이스를 상속 받을 경우 implements (구현)키워드를 사용하며,
     반드시 인터페이스가 가진 추상메서드를 오버라이딩하여 구현해야 함.
     →   또한,  여러개의 인터페이스를 상속받을 수 있음.
–   슈퍼클래스 (인터페이스)의 모든 메서드가 추상메서드이므로,
     중복되는 메서드에 대한 구별없이 직접 구현하면 되기 때문에 중복된 메서드로 인한 혼란이 없음
     →   다이아몬드 상속에서의 문제점이 사라짐.

 

 

•   main 메서드에서 실행

고래상어 고래상어 = new 고래상어();
고래상어.번식();

// 업캐스팅
동물 동물 = 고래상어;
동물.번식();

–   원래 업캐스팅 후에는 참조 영역이 축소되어 상속된 메서드만 호출 가능하지만,

     인터페이스가 가진 모든 메서드는 참조 영역 축소 후에도 호출이 가능함.

     →   인터페이스의 추상메서드를 모두 서브클래스에서 오버라이딩 했기 때문.

     →   서브클래스에서 오버라이딩 된 메서드가 출력됨.

   실행 결과

더보기

알을 낳아 번식
알을 낳아 번식

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

▶   인터페이스의 필요성

1.   구현의 강제로 코드의 통일성 향상 (= 표준화)
2.   인터페이스를 통한 간접적인 클래스 사용으로 모듈 교체가 용이함.
       →   특정 클래스를 직접 다루는 대신 부모 인터페이스 타입으로 클래스를 다루게 되면,
              실제 인스턴스가 바뀌더라도 기존 코드를 수정할 필요가 없어짐.
3.   서로 상속 관계가 없는 클래스간의 인터페이스를 통한 상속 관계 부여.
       →   다형성 확장.
4.   모듈간 독립적 프로그래밍으로 인한 개발 기간 단축.

 

 

–   부품을 갈아끼움 = 모듈 교체
–   공통부분을 인터페이스로 만들어서 공유가능.
–   extens는 상속할 때 한 번만 사용 가능하며 implements (구현)는 여러번 가능함.

 

 

 

 

 

 

 

 

 

 

▸   인터페이스를 통한 간접적인 클래스 사용

–   특정 클래스를 직접 다루는 대신 부모 인터페이스 타입으로 클래스를 다루게 되면,

     실제 인스턴스가 바뀌더라도 기존 코드를 수정할 필요가 없어짐.


EX   )

 

•   문서 등을 프린터로 출력하기 위한 각 프린터 클래스 정의

–   각 프린터의 출력 기능을 갖는 Printer 인터페이스를 정의하고,  각 프린터 클래스에서 상속받아 구현.

 

1.    Printer 인터페이스

interface Printer{
	
	public abstract void print(String fileName);

}

–   프린터기의 공통 기능인 출력 (print())기능을 추상메서드로 정의함.

 

2.    레이저 프린터   — Printer 인터페이스 상속

class LaserPrinter implements Printer{

	@Override
	public void print(String fileName) {
		System.out.println("Laser Printer로 " + fileName + " 출력하기");
	}	
	
}

–   파일 (String타입 fileName)을 전달받아 출력 작업을 수행하는 print() 메서드 오버라이딩.
      →   Printer 인터페이스로부터 상속받아 구현.

 

3.    잉크젯 프린터   — Printer 인터페이스 상속

class InkjetPrinter implements Printer{

	@Override
	public void print(String fileName) {
		System.out.println("InkjetPrinter로 " + fileName + " 출력하기");
	}
	
}

–   파일 (String타입 fileName)을 전달받아 출력 작업을 수행하는 print() 메서드 오버라이딩.
      →   Printer 인터페이스로부터 상속받아 구현.

 

4.    도트 프린터   — Printer 인터페이스 상속

class DotPrinter implements Printer{

	@Override
	public void print(String fileName) {
		System.out.println("DotPrinter로 " + fileName + " 출력하기");
	}
	
}

–   파일 (String타입 fileName)을 전달받아 출력 작업을 수행하는 print() 메서드 오버라이딩.
      →   Printer 인터페이스로부터 상속받아 구현.

 

 

 

•   main 메서드에서 실행

// 인스턴스 생성
LaserPrinter lp = new LaserPrinter();
lp.print("lp.java");

InkjetPrinter ip = new InkjetPrinter();
ip.print("ip.java");

DotPrinter dp = new DotPrinter();
dp.print("dp.java");

// 업캐스팅
Printer p = new LaserPrinter();
p.print("하이");

p = new InkjetPrinter();
p.print("바이");

–   인터페이스 사용 시,  손쉬운 모듈 교체를 지원함.

   실행 결과

더보기

Laser Printer로 lp.java 출력하기
InkjetPrinter로 ip.java 출력하기
DotPrinter로 dp.java 출력하기

Laser Printer로 하이 출력하기
InkjetPrinter로 바이 출력하기

 

 


 

 

•   인터페이스를 통한 간접적인 클래스 사용

–    각 프린터를 직접 다루지 않고,
      상위 타입 (부모)인 Printer 인터페이스를 다루는 PrinterClient 클래스를 정의함.

–    마치 부품을 갈아 끼우는 것과 같음 (= 모듈 교체)

class PrinterClient{
	
    	// Printer 인터페이스 타입 변수 선언
	private Printer printer;
	
    	// Setter 메서드 정의
	public void setPrinter(Printer printer) {
		this.printer = printer;
	}
	
   	 // print 메서드 정의
	public void print(String fileName) {
		printer.print(fileName);
	}
}
코드 분석
4
  –   각각의 프린터 클래스를 다루기 위한 슈퍼클래스 타입에 해당하는 Printer 인터페이스 타입 변수 선언.

7~9
  –   Setter 메서드를 통한 Printer 타입 변수 초기화.

12~14
  –   외부로부터 출력할 파일을 전달받아,
       실제 프린터에 해당하는 각 인스턴스의 print() 메서드를 호출한 뒤 파일을 전달하여 출력 작업을 수행.
  –   Printer 타입 변수에 저장된 각 프린터의 인스턴스를 통해,
       print() 메서드를 호출하면 해당 프린터의 출력 기능을 사용 가능함.


 

 

•   main 메서드에서 실행

PrinterClient pc = new PrinterClient();

pc.setPrinter(new LaserPrinter());
pc.print("Hello.java"); 

pc.setPrinter(new InkjetPrinter());
pc.print("Hello.java");

pc.setPrinter(new DotPrinter());
pc.print("Hello.java");
코드 분석
3
  –   PrintClient 인스턴스의 setPrinter() 메서드를 호출하여
       각 프린터기 인스턴스 파라미터로 전달하면 업캐스팅 일어남.
       →   부품을 LaserPrinter로 바꿈.

4
  –   LaserPrinter → Printer 업캐스팅
       →   PrinterCilent 인스턴스의 print() 메서드를 호출하면
             인스턴스 내의 Printer 타입 변수에 저장된 인스턴스의 print() 호출됨.
       →   결국 실제 저장된 LaserPrinter 인스턴스의 print() 메서드가 호출됨.

6, 7
  –   InkjetPrinter → Printer 업캐스팅
  –   위의 LaserPrintr를 InkjetPrinter 로 교체 시,
        setPrinter() 메서드에서 InkjetPrinter 인스턴스만 전달하면 자동으로 출력 대상이 변경됨.

9, 10
  –   DotPrinter → Printer 업캐스팅

   실행 결과

더보기

Laser Printer로 Hello.java 출력하기
InkjetPrinter로 Hello.java 출력하기
DotPrinter로 Hello.java 출력하기

 

 

 

 

 

 

 

 

 

 

▸   상속 관계가 없는 클래스간의 상속 관계 부여

–   인터페이스를 통한 상속 관계를 부여하면 다형성 확장이 일어남.
–   사실상 다형성 확장때문에 인터페이스를 많이 사용함.

EX   )

 

•   인터페이스 정의하지 않았을 경우

class Pc{}

class NoteBookPc extends Pc{
	public void charge() {
		System.out.println("노트북 충전 중...");
	}
}


class HandPhone{}

class SmartPhone extends HandPhone{
	public void charge() {
		System.out.println("스마트폰 충전 중...");
	}
}

–   서로 다른 class를 상속받는 NoteBookPc와 SmartPhone은 메서드명이 같은 charge() 메서드를 가지고 있음.

 

 

 

•   인터페이스 정의했을 경우

–   Object 클래스 외에는 공통 슈퍼클래스가 없는 NoteBookPc와 SamrtPhone의 공통 인터페이스 Chargeable 정의.

interface Chargeable{	
	public abstract void charge();
}


class NoteBookPc2 extends Pc implements Chargeable{
	@Override
	public void charge() {
		System.out.println("노트북 충전 중...");
	}
}

class SmartPhone2 extends HandPhone implements Chargeable{
	@Override
	public void charge() {
		System.out.println("스마트폰 충전 중...");
	}
}
코드 분석
1~3
  –   두 클래스에서 공통적으로 사용할 충전 기능 charge() 메서드를 추상메서드로 정의.

6~18
  –   기존에 Pc 클래스를 상속받고 있는 상태에서 추가로 인터페이스를 구현해야하는 경우,
       상속 코드 뒤에 구현 코드를 기술.
       →   implements Chargeable 코드 추가.
       →   아무 관계 없던 두 클래스에 동일한 부모 인터페이스가 추가되어 서로 상속 관계에 묶이게 됨.
       →   기존에 중복되었던 charge() 메서드를 각 클래스에서 오버라이딩 하여 사용함.
              ⇒   두 클래스의 공통적인 특징을 인터페이스로 추가해주는 것.
       

 

 

 

•  noRelationShip() 메서드 정의

–   인터페이스를 통해 공통된 멤버를 갖지 않는 경우 (NoteBookPc & SmartPhone 클래스)

–   main 메서드 아래와  본문 클래스 윗 부분에 정의함.

	} // main 메서드 끝

	public void noRelationShip() {
    
        // NoteBookPc notebook = new NoteBookPc();
        // notebook.charge();
        // SmartPhone smartphone = new SmartPhone();
        // smartphone.charge();

        // 두 개의 인스턴스를 하나의 배열로 관리 (업캐스팅)
        Object[] objArr = {new NoteBookPc(), new SmartPhone()};

        // 반복문을 사용하여 배열 크기만큼 반복
        for(int i=0; i<objArr.length; i++) {
    //      objArr[i].charge();
            // Object타입으로 charge()메서드 호출 불가 (참조 영역 축소)
            
            // instanceof 연산자 
            if(objArr[i] instanceof NoteBookPc) {
                NoteBookPc notebook = (NoteBookPc) objArr[i];
                notebook.charge();
            }else if(objArr[i] instanceof SmartPhone) {
                SmartPhone smartphone = (SmartPhone) objArr[i];
                smartphone.charge();
            }
        }
        
} // noRelationShip() 메서드 끝
코드 분석
5~8
  –   원래는 이렇게 인스턴스를 생성해서 참조변수.메서드로 메서드 호출했음.
       아래의 하나의 배열로 호출하는 부분과 중복되므로 주석 처리 함.

11
  –   두 개의 인스턴스를 하나의 배열로 관리해야 할 경우,
       NoteBookPc와 SamrtPhone의 공통 타입은 Object타입 밖에 없음.
       →   Object타입으로 업캐스팅.

15
  –   The method charge() is undefined for the type Object 에러 발생.
  –   참조 영역의 축소로 인해 Object 타입으로 charge() 메서드 호출 불가.
       →   다운캐스팅을 통해 각 인스턴스를 따로 접근해야 함.
       →   안전한 다운캐스팅을 위해 instanceof 연산자를 사용,  NoteBookPc와 SmartPhone타입 판별.

20~22
  –   Object → NoteBookPc타입으로 다운캐스팅 후 charge() 호출 가능.
       →   노트북 충전기로 노트북 충전함.

23~25
  –   Object → SmartPhone타입으로 다운캐스팅 후 charge() 호출 가능.
       →   스마트폰 충전기로 스마트폰 충전함.

 

 

 

•    hasRelationShip() 메서드 정의

–   main 메서드 아래와  본문 클래스 윗 부분에 정의함.

–   인터페이스를 통해 공통된 멤버를 갖는 상속 관계를 부여할 경우 (NoteBookPc2 & SmartPhone2 클래스)
     해당 인터페이스 타입으로 다운캐스팅 할 필요 없이 업캐스팅 된 상태 그대로 멤버에 접근 가능.
      →   다형성으로 인한 코드 절약.

	public void hasRelationShip() {

		// 방법1) 참조변수 2개로 관리 
                // → 업캐스팅
//		Chargeable c = new NoteBookPc2();
//		Chargeable c2 = new SmartPhone2();
		
		// 방법2) 배열 하나로 깔끔하게 관리 
    	        // → 한 번에 둘 다 업캐스팅
		Chargeable[] chargeableArr = {new NoteBookPc2(), new SmartPhone2()};
		
		// 다운캐스팅 없이 바로 메서드 호출 가능
		for(int i=0; i<chargeableArr.length; i++) {
			if(chargeableArr[i] instanceof NoteBookPc2) {
				chargeableArr[i].charge();
			}else if(chargeableArr[i] instanceof SmartPhone2) {
				chargeableArr[i].charge();
			}
		}
        
	}
	
} // Ex4 클래스 끝
코드 분석
5, 6
  –   방법1)   참조변수 2개로 관리
       →   NoteBookPc2 → Chargeable 업캐스팅,  SmartPhone → Chargeable 업캐스팅

10
  –   방법2)   배열 하나로 모두 관리
       →   배열 데이터 입력하는 곳에 두 인스턴스를 생성하여 한 번에 업캐스팅 함.

13~19
  –   업캐스팅 후에도 공통 메서드 charge()를 호출 가능하므로 별도의 다운캐스팅 없이 바로 접근 가능.
  –   instanceof 연산자를 사용하여 배열의 인덱스 i번째에 있는 인스턴스가 무엇인지에 따라
      해당 클래스에서 chargeableArr 인터페이스의 추상메서드를 오버라이딩 한 charge() 메서드 호출됨.
      (NoteBookPc2 클래스와 SmartPhone2 클래스는 Chargeable 인터페이스를 구현한 클래스.)

 

 

 

•    main 메서드에서 실행하기

Ex4 ex4 = new Ex4();

ex4.noRelationShip();
System.out.println("------------------------");
ex4.hasRelationShip();

  –   아래의 noRelationShip(), hasRelationShip() 메서드는 static 메서드가 아니므로

       Ex4 클래스의 인스턴스를 생성해야만 참조변수명.메서드명() 으로 접근 가능.
  –   만약 static 메서드로 정의했다면,  클래스명.메서드명() 으로 바로 호출 가능.

   실행 결과

더보기

노트북 충전 중...
스마트폰 충전 중...
------------------------
노트북 충전 중...
스마트폰 충전 중...

 

 

 

 

 

 

 

 

 

 

▸   모듈간 독립적 프로그래밍으로 인한 개발 기간 단축

 

EX   )

 

•   개발자와 디자이너의 '공통 부분 (= 기능)' 을 인터페이스로 정의.

interface LoginProcess{
	
	public abstract String login(String id, String pass);
	
}

–   공통기능으로 login() 추상메서드를 정의하여 파라미터와 리턴타입을 지정함.

 

 

 

1.   디자이너의 경우

 

1 )   LoginProcess 인터페이스를 구현하는 Designer 클래스 정의

–   로그인 처리 과정은 중요하지 않고 전달 데이터와 리턴 데이터만 중요함.
–   LoginProcess 인터페이스를 구현하는 클래스를 정의하여 login() 메서드를 구현.

class Designer implements LoginProcess{

	@Override
	public String login(String id, String pass) {
		System.out.println("디자이너가 전달받은 아이디 : "+ id);
		System.out.println("디자이너가 전달받은 패스워드 : "+ pass);
		return "성공";
	}
}

–   메서드에 전달되는 데이터가 정확한지,  리턴값이 외부로 잘 전달되는지만 확인하면 됨.

 

2 )   DesignerClient 클래스 정의

class DesignerClient{
	
	// 인스턴스 생성
	Designer designer = new Designer();
	
	// login() 메서드
	public void login() {
		String id = "admin";
		String pass = "1234";
		String result = designer.login(id, pass);
		System.out.println("로그인 결과 : " + result);
	
		if(result.equals("성공")) {
			// 로그인 성공 페이지로 이동
		}else {
			// 로그인 실패 페이지로 이동
		}
	}	
}

–   Designer 클래스의 인스턴스를 생성하고 login() 메서드를 호출하여 바로 위의 변수 아이디, 패스워드를 전달하면,

     해당 메서드의 리턴값이 변수 result에 저장됨.
      →   로그인 결과로 리턴되는 값에 따라 확인 작업 수행.

–   '성공'이라는 데이터를 리턴값으로 받았다면 조건문은 '참'이 되어 if문 실행.
      →   로그인 성공 페이지로 이동 처리 작업 수행.
      →   '성공' 데이터가 아니라면 로그인 실패 페이지로 이동 처리 작업 수행.

 

 

 

•   개발자의 경우

–   전달받은 값은 중요하지 않고 전달받은 값을 사용하여 로그인 처리 작업 수행.
      →   로그인 처리 후 리턴되는 값이 정상적으로 전달되는지만 확인.
–   LoginProcess 인터페이스를 구현하는 Developer 클래스 정의.

 

1 )   LoginProcess 인터페이스를 구현하는 Developer 클래스 정의

class Developer implements LoginProcess{

	@Override
	public String login(String id, String pass) {

		System.out.println("개발자가 전달받은 아이디 : " + id);
		System.out.println("개발자가 전달받은 패스워드 : " + pass);
		System.out.println("아아디와 패스워드로 로그인 작업 처리 완료!");
		return "성공";
	}
}

–   디자이너로부터 전달받은 아이디와 패스워드를 사용하여 로그인 작업을 처리한 후,  '성공' 또는 '실패' 문자열 리턴.

 

2 )   DeveloperClinet 클래스 정의

class DeveloperClient{
	
  	  // 인스턴스 생성
	Developer developer = new Developer();
	
   	 // login() 메서드
	public void login() {
		String result = developer.login("admin", "1234");
		System.out.println("로그인 결과 : " + result);
	}
}

–   디자이너가 전달하게 될 아이디와 패스워드를 임의로 설정하여 전달,  개발자 코드를 통해 로그인 작업을 수행한 후

     리턴되어지는 값이 정상적인지만 확인하면 됨.

 

 

 

•    main 메서드에서 실행하기

// 디자이너의 경우
DesignerClient designer = new DesignerClient();
designer.login();

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

// 개발자의 경우
DeveloperClient developer = new DeveloperClient();
developer.login();

   실행 결과

더보기

디자이너가 전달받은 아이디 : admin
디자이너가 전달받은 패스워드 : 1234
로그인 결과 : 성공
--------------------------
개발자가 전달받은 아이디 : admin
개발자가 전달받은 패스워드 : 1234
아아디와 패스워드로 로그인 작업 처리 완료!
로그인 결과 : 성공