package ex_interface;
public class Ex2 {
public static void main(String[] args) {
/*
* 인터페이스의 필요성
*
* - 모듈 교체가 쉬움
*
*/
// 다형성을 활용하지 않는 방법
LaserPrinter lp = new LaserPrinter();
lp.print("Ex.java");
DotPrinter dp = new DotPrinter();
dp.print("Ex.java");
System.out.println("================================");
// 일반적인 다형성 활용
// 부모 인터페이스 타입으로 업캐스팅하여 사용
// => 인터페이스 내에 존재하는 멤버(상수, 추상메서드)에만 접근 가능
Printer p = new LaserPrinter();
p.print("Ex.java");
p = new DotPrinter();
p.print("Ex.java");
System.out.println("===================================");
// 별도의 클래스를 정의하여 부모 인터페이스 타입 객체를 전달받아 사용
// => 인터페이스의 멤버 외에 별도의 클래스에서 정의한 멤버도 사용 가능
// PrintClient 인스턴스 생성
PrintClient pc = new PrintClient();
pc.setPrinter(new LaserPrinter());
pc.print("Ex.java");
pc.setPrinter(new DotPrinter());
pc.print("Ex.java");
// 만약, InkjetPrinter가 추가되더라도 Printer를 구현했다면
// 별도로 PrintClient 클래스를 수정할 필요 없이
// setPrinter() 메서드 파라미터로 InkjetPrinter 객체만 교체하면
// 얼마든지 새로운 Printer 타입 객체를 다룰 수 있다!
}
}
interface Printer {
public void print(String fileName);
}
class LaserPrinter implements Printer {
@Override
public void print(String fileName) {
System.out.println("LaserPrinter로 출력중 - " + fileName);
}
}
class DotPrinter implements Printer {
@Override
public void print(String fileName) {
System.out.println("DotPrinter로 출력중 - " + fileName);
}
}
class PrintClient {
// 인터페이스 타입 Printer 인터페이스를 선언
private Printer printer; // has-a 관계
// 외부로부터 Printer 타입 인스턴스를 전달받아 초기화하는 Setter 정의
public void setPrinter(Printer printer) {
this.printer = printer;
}
public void print(String fileName) {
// Printer 타입 객체 내의 print() 메서드를 호출하여
// 전달받은 fileName에 내용 출력하도록 요청
printer.print(fileName);
}
}
package ex_interface;
public class Ex3 {
public static void main(String[] args) {
/*
* 인터페이스의 필요성
*
* - 상속 관계가 없는 클래스끼리 관계 부여 가능
* - 기존에 다른 클래스를 상속중일 때 다중 상속이 불가능한데 인터페이스를 활용하여
* 상속관계가 아닌 객체간에 공통 인터페이스를 제공으로 새로운 상속 관계를 부여가 가능
* - 관계가 없는 객체의 경우 공통 타입이 Object 타입으로 변환하여 관리는 할 수 있으나,
* 각 객체의 메서드 호출을 위해서는 다시 다운캐스팅이 필요하지만, 인터페이스를 통해 상속 관계를
* 부여하고, 인터페이스에서 공통 메서드를 추상메서드로 제공하는 경우에는 별도의 다운캐스팅 및
* 타입 판별 없이 바로 공통 메서드의 호출이 가능!
*
*/
Ex3 ex = new Ex3();
ex.badCase();
ex.goodCase();
} // main() 끝
// 상속관계가 아닌 객체들을 사용하여 다형성을 적용시켜야 할 경우
public void badCase() {
// HandPhone, DigitalCamera의 공통 타입은 Object 타입 밖에 없음
// => 이 때, Object 타입으로 업캐스팅 시 charge() 메서드 호출 불가
Object obj = new HandPhone(); // 업캐스팅
// obj.charge(); // Object 타입으로 호출 불가능한 메서드
// 다운캐스팅 통해 다시 HandPhone 타입으로 변경해야 charge() 호출 가능
HandPhone hp = (HandPhone) obj;
hp.charge();
obj = new DigitalCamera();
// obj.charge(); // 오류 발생! Object 타입으로는 호출 불가능한 메서드!
// -----------------------------------------------------------
// Object[] 타입으로 HandPhone, DigitalCamera 인스턴스 관리
Object[] objs = {new HandPhone(), new DigitalCamera()};
// HandPhone hp = new HandPhone();
// DigitalCamera dc = new DigitalCamera();
// Object[] objs = {hp, dc};
// for문을 사용하여 배열 objs의 모든 인스턴스에 차례대로 접근하여
// 각각의 타입에 맞는 다운캐스팅 수행 후 charge() 메서드를 호출
for (int i = 0; i < objs.length; i++) {
if (objs[i] instanceof HandPhone) { // HandPhone 타입인지 판별
// HandPhone 타입으로 다운캐스팅 가능
HandPhone hp2 = (HandPhone) objs[i];
hp2.charge();
} else if (objs[i] instanceof DigitalCamera) { // DigitalCamera 타입인지 판별
// DigitalCamre 타입으로 다운캐스팅
DigitalCamera dc = (DigitalCamera) objs[i];
dc.charge();
}
}
}
// 상속관계가 아닌 객체들에게 인터페이스를 활용하여
// 상속관계를 부여한 후 다형성에 활용할 경우
public void goodCase() {
// HandPhone2, DigitalCamera2 객체를 담기 위한 타입으로
// Object 타입 외에도 Chargealbe 타입도 가능
Chargeable[] objs = {new HandPhone2(), new DigitalCamera2()};
// for문을 사용하여 Chargealbe[] 타입 내의 모든 객체에 접근하여
// 상속받아 구현한 공통 메서드 charge() 메서드 호출
// => Chargealbe 인터페이스에 charge() 메서드가 존재하므로
// 별도의 다운캐스팅 없이도 charge() 메서드에 접근 가능
// for(Chargeable obj : objs) {
// obj.charge(); // 공통메서드를 직접 호출 가능(다운캐스팅 불필요)
// }
for (int i = 0; i < objs.length; i++) {
objs[i].charge();
}
}
}
class Phone {}
class Camera {}
class HandPhone extends Phone {
public void charge() {
System.out.println("HandPhone 충전!");
}
}
class DigitalCamera extends Camera {
public void charge() {
System.out.println("DigitalCamera 충전!");
}
}
// HandPhone과 DigitalCamera 사이에 특정 관계를 부여해주기 위해
// 공통 인터페이스인 Chargeable 인터페이스를 정의하고 해당 인터페이스 내에
// 추상 메서드로 charge() 메서드를 정의
interface Chargeable {
public abstract void charge();
}
// HandPhone2 클래스 정의 - Phone 클래스 상속, Chargeable 인터페이스 구현
class HandPhone2 extends Phone implements Chargeable {
@Override
public void charge() {
System.out.println("HandPhone2 충전!");
}
}
// DigitalCamera2 클래스 정의 - Camera 클래스 상속, Chargeable 인터페이스 구현
class DigitalCamera2 extends Camera implements Chargeable {
@Override
public void charge() {
System.out.println("DigitalCamera2 충전!");
}
}
package ex_interface;
public class Ex4 {
public static void main(String[] args) {
/*
* 인터페이스의 필요성
*
* - 모듈간 독립적 프로그래밍으로 개발 시간 단축
* - 여러 모듈간에 공통된 기능을 구현할 메서드를 인터페이스 내의 추상메서드로 제공하여
* 모듈간 통일성 부여
* - 각 모듈에서 추상메서드 구현을 통해 각자에게 필요한 기능을 따로 작업한 후 차후
* 결합 시 쉽게 결합 가능
* - 따라서, 상대방의 작업 진행상황과 관계없이 개발이 가능하므로 개발 비용이 줄어드는 효과를
* 가져오게 된다!
*
* ex) 숫자 2개를 입력하여 합을 계산 후 결과를 화면에 출력하는 프로그램
* => 디자이너(A) - 개발자(B) 협업 수행 가정 했을 때
* A는 입력받은 데이터를 B에 전달하고 결과값을 기다린 후
* B가 리턴하는 결과를 전달받아 화면에 출력해야하며,
* B는 A가 입력받은 데이터를 전달했을 때, 해당 데이터의 합계를 계산한 후
* 다시 A에게 리턴해야한다.
* 이 때, 서로 상대방의 작업이 완료되지 않으면 다음 작업 수행이 불가능하므로
* 상호간에 작업 내용이 같이 진행되어야 한다.
* 따라서, 한 쪽에서 작업이 지연되면 다른쪽도 함께 지연되므로 작업에 소요되는
* 비용이 증가하게 됨!
* => 이를 해결하기 위해 인터페이스 적용 가능
* A 입장 : "숫자 두 개 전달, 하나의 결과값 리턴받아 출력"
* B 입장 : "숫자 두개 전달받아 계산 후, 하나의 숫자 리턴"
*
*/
CalculatorDesigner cd = new CalculatorDesigner();
cd.add();
}
}
interface Calculator {
// 디자이너와 개발자 모두 사용할 공통 메서드를 추상메서드로 정의
public int add(int a, int b);
}
// 디자이너가 개발자에게 구현될 코드를 미리 간단히 작성하여 테스트 가능
class CalculatorDesignerDev implements Calculator {
@Override
public int add(int a, int b) {
// 디자이너 입장에서는 전달받은 두 수를
// 어떻게 계산할 지는 중요하지 않으며, 단지 확인만 수행하면 됨
System.out.println("전달된 파라미터 확인 a = " + a + ", b = " + b);
return 0;
}
}
// 디자이너 입장에서의 코드
class CalculatorDesigner {
public void add() {
// 외부로부터 두 개의 숫자를 입력받았다고 가정
int a = 10, b = 20;
// 개발자에게 두 정수를 전달한 뒤, 결과값으로 정수 1개 리턴받아 출력
// => 현재 개발자 코드가 완성되지 않았더라도
// 개발자가 사용할 메서드를 미리 구현한 클래스를 대신 사용 가능
// CalculatorDesignerDev cal = new CalculatorDesignerDev();
// int result = cal.add(a, b); // 입력받은 두 정수 전달 후 결과 리턴
// System.out.println(a + " + " + b + " 의 결과 = " + result);
// 차후 개발자의 코드가 완성되면 해당 객체를 통해 add() 메서드 호출
CalculatorDeveloper cal2 = new CalculatorDeveloper();
int result2 = cal2.add(a, b); // 입력받은 두 정수 전달 후 결과 리턴
System.out.println(a + " + " + b + " 의 결과 = " +result2);
}
}
// 개발자 입장에서의 코드
class CalculatorDeveloper implements Calculator {
// 외부로부터 정수를 입력받는 코드는 중요하지 않고
// 전달받은 2개의 정수에 대한 덧셈을 수행한 후 리턴이 잘 되는지 확인만 필요!
@Override
public int add(int a, int b) {
System.out.println("전달받은 파라미터 : " + a + ", " + b);
return a + b;
}
}
연습
package ex_interface;
public class Test1 {
public static void main(String[] args) {
LgTv lt = new LgTv();
System.out.println("넷플릭스 계정명 : " + lt.netflix);
lt.setNetflixAccount("admin");
System.out.println("넷플릭스 계정명 : " + lt.netflix);
System.out.println("넷플릭스 계정명 : " + lt.netflix.account);
lt.powerOn();
lt.volumeUp();
lt.volumeDown();
lt.changeChannel(10);
lt.func3D();
lt.powerOff();
// showNetflix() 메서드 호출
// LgTv 인스턴스가 갖고 있는 Netflix 인스턴스 내의
lt.netflix.showNetflix();
}
}
/*
* Tv와 Speaker 인터페이스의 공통메서드인 volumeUp(), volumeDown() 을 추출하여 부모인터페이스인
* VolumeControl 인터페이스로 추상화
*
* Tv 인터페이스 정의 - VolumeControl 상속
* - 메서드 : powerOn / powerOff / changeChannel - 매개변수(channel, 정수형)
*
* Speaker 인터페이스 정의 - VolumeControl 상속
* - 메서드 : powerOn / powerOff
*
* Netflix 클래스 정의
* - 멤버변수 : account(문자열)
* - 생성자 : 매개변수로 문자열 전달받아서 멤버변수에 초기화
* - 메서드 : showNetflix() => "넷플릭스 기능!" 출력
*
* Netflix 기능이 포함된 LgTv 클래스 정의 - Tv / Speaker 기능 초함
* => Netflix 기능 포함 : has-a 관계
* - 메서드 : func3D() => "LgTv 3D 기능!" 출력
*
*/
interface VolumeControl { // volume관련 메서드
void volumeUp(); // 추상메서드
void volumeDown();
}
interface PowerControl { // power관련 메서드
public abstract void powerOn();
public abstract void powerOff();
}
// Tv 인터페이스 정의
interface Tv extends VolumeControl, PowerControl { // 인터페이스 상속이므로 extends 사용
public abstract void changeChannel(int channel);
}
// Speaker 인터페이스 정의
interface Speaker extends VolumeControl, PowerControl {
}
class Netflix {
String account;
public Netflix(String account) {
super();
this.account = account;
}
public void showNetflix() {
System.out.println("넷플릭스 기능!");
}
}
class LgTv implements Tv, Speaker {
// 넷플릭스 기능은 별도의 상속을 거치지 않고 클래스 내에서
// has-a 관계로 포함시켜 사용!
// Netflix netflix = new Netflix("root"); // 인스턴스를 바로 생성
Netflix netflix;
public void setNetflixAccount(String account) {
// 넷플릭스에서 사용할 계정을 전달받아
// 넷플릭스 인스턴스를 생성하면서 생성자에 계정 전달
netflix = new Netflix(account);
}
@Override
public void volumeUp() {
System.out.println("LgTv volumeUp()");
}
@Override
public void volumeDown() {
System.out.println("LgTv volumeDown()");
}
@Override
public void powerOn() {
System.out.println("LgTv powerOn()");
}
@Override
public void powerOff() {
System.out.println("LgTv powerOff()");
}
@Override
public void changeChannel(int channel) {
System.out.println("LgTv changeChannel() - " + channel + " 번");
}
public void func3D() {
System.out.println("LgTv 3D 기능()");
}
}
class SamsungTv implements Tv, Speaker {
@Override
public void volumeUp() {
// TODO Auto-generated method stub
}
@Override
public void volumeDown() {
// TODO Auto-generated method stub
}
@Override
public void powerOn() {
// TODO Auto-generated method stub
}
@Override
public void powerOff() {
// TODO Auto-generated method stub
}
@Override
public void changeChannel(int channel) {
// TODO Auto-generated method stub
}
public void smartFunc() {
System.out.println("SamsungTv 스마트 기능!");
}
}