Java - 인터페이스, 마커(Marker)

2020. 6. 14. 17:45Java

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
public class Ex {
 
    public static void main(String[] args) {
        // 인터페이스는 손쉬운 모듈 교체를 지원한다!
        
        PrintClient pc = new PrintClient();
        pc.setPrinter(new DotPrinter()); // DotPrinter -> Printer 타입으로 업캐스팅
        pc.printThis("Hello.java");
        // => PrinterClient 의 printThis() 메서드를 호출하면
        //    인스턴스 내의 Printer 타입 변수에 저장된 인스턴스의 print() 메서드가 호출됨
        // => 결국, DotPrinter 인스턴스 내의 print() 메서드를 호출하게 됨
        
        System.out.println("--------------");
        
        pc.setPrinter(new LaserPrinter()); // 프린터 교체(LaserPrinter -> Printer)
        pc.printThis("Ex.java");
        // => PrintClient 내의 Printer 타입 객체에는 LaserPrinter 인스턴스가 저장되어 있고
        //    이 인스턴스의 print() 메서드를 호출하게 되므로
        //    결국, LaserPrinter 의 print() 메서드가 호출됨
        
        System.out.println("-------------------");
        
        // 새로 추가된 InkjetPrinter 를 사용
        pc.setPrinter(new InkjetPrinter());
        pc.printThis("Test.java");
        
    }
 
}
 
// 만약, Printer 인터페이스를 구현하는 새로운 프린터(클래스)가 추가되더라도
// 실제 사용하는 PrintClient 클래스의 코드는 수정할 필요가 없게됨
class InkjetPrinter implements Printer {
 
    @Override
    public void print(String fileName) {
        System.out.println("InkjetPrinter 로 출력 - " + fileName);
    }
    
}
 
// 각 프린터들을 직접 다루지 않고 
// 상위 타입인 Printer 인터페이스 타입으로 다루는 PrintClient 클래스 정의
class PrintClient {
    // 서브클래스 타입인 각각의 프린터 클래스를 다루기 위해 Printer 인터페이스 타입 변수 선언
    private Printer printer;
    
    // 외부로부터 각 서브클래스 타입 인스턴스를 전달받아 저장하는 Setter 정의
    public void setPrinter(Printer printer) {
        this.printer = printer;
    }
    
    // 외부로부터 출력할 파일명을 전달받아 실제 프린터에 해당하는 
    // 각 인스턴스의 print() 메서드를 호출하는 printThis() 메서드 정의
    public void printThis(String fileName) {
        // 저장된 Printer 타입 인스턴스의 print() 메서드를 호출
        printer.print(fileName);
    }
    
}
 
// Printer 인터페이스 정의 - 모든 프린터의 공통 기능인 print() 메서드를 추상메서드로 선언
interface Printer {
    public void print(String fileName);
}
 
// LaserPrinter 클래스 정의 - Printer 인터페이스 상속받아 구현
class LaserPrinter implements Printer {
    // 추상메서드 print() 오버라이딩 필수
    @Override
    public void print(String fileName) {
        System.out.println("LaserPrinter 로 출력 - " + fileName);
    }
    
}
 
// DotPrinter 클래스 정의 - Printer 인터페이스 상속받아 구현
class DotPrinter implements Printer {
    // 추상메서드 print() 오버라이딩 필수
    @Override
    public void print(String fileName) {
        System.out.println("DotPrinter 로 출력 - " + fileName);
    }
    
}
public class Ex2 {
 
    public static void main(String[] args) {
        badCase();
        goodCase();
    }
    
    public static void goodCase() {
        // 인터페이스를 사용하여 공통된 멤버를 갖는 상속 관계를 부여하면
        // 별도의 다운캐스팅 없이 업캐스팅 된 상태로 멤버에 접근 가능(코드 절약됨)
        // Phone, DigitalCamera 클래스의 부모인 Chargeable 인터페이스 타입으로 업캐스팅 가능
        Chargeable[] objs = {new HandPhone(), new DigitalCamera()};
        // charge() 메서드는 강제 오버라이딩 의무가 부여되어 있으므로 별도의 검사 필요없음
        for(Chargeable o : objs) {
            o.charge();
        }
    }
    
    public static void badCase() {
        // 아무런 상속관계가 없는 클래스를 공통으로 관리하기 위해서는
        // Object 타입으로만 업캐스팅이 가능함
        // => 단점 : 실제 인스턴스의 기능을 사용하려면 
        //           instanceof 연산자를 통해 해당 인스턴스 타입을 판별한 후
        //           다운캐스팅을 통해 해당 인스턴스 멤버에 접근할 수 있음(코드 길어짐)
        Object[] objs = {new HandPhone(), new DigitalCamera()}; // 업캐스팅
        
        // 만약, Object 타입에서 각 인스턴스의 메서드들을 호출하려면
        // instanceof 연산자를 사용하여 다운캐스팅 가능 여부를 판별한 후
        // 다운캐스팅하여 각 인스턴스의 기능을 사용해야함
        for(Object o : objs) {
            if(o instanceof HandPhone) { // HandPhone 타입 인스턴스 판별
                HandPhone hp = (HandPhone)o; // 다운캐스팅(필수)
                hp.charge();
            } else if(o instanceof DigitalCamera) { // DigitalCamera 타입 인스턴스 판별
                DigitalCamera dc = (DigitalCamera)o; // 다운캐스팅(필수)
                dc.charge();
            }
            
        }
        
    }
 
}
 
class Phone {}
 
class Camera {}
 
// Object 클래스 외에 공통 슈퍼클래스가 없는 HandPhone, DigitalCamera 의 공통 인터페이스 정의
interface Chargeable {
    // 두 클래스에서 공통으로 사용하는 충전(charge()) 기능을 추상메서드로 정의
    public abstract void charge();
}
 
// Phone 클래스와 Chargeable 인터페이스를 상속받는 HandPhone 클래스 정의
class HandPhone extends Phone implements Chargeable {
    
    public void charge() {
        System.out.println("HandPhone 충전!");
    }
    
}
 
// Phone 클래스와 Chargeable 인터페이스를 상속받는 DigitalCamera 클래스 정의
class DigitalCamera extends Camera implements Chargeable {
    
    public void charge() {
        System.out.println("DigitalCamera 충전!");
    }
    
}
public class Ex3 {
 
    public static void main(String[] args) {
        병사 병사 = new 병사();
        탱크 탱크 = new 탱크();
        비행기 비행기 = new 비행기();
        
        정비공 정비공 = new 정비공();
        정비공.수리(탱크);
        정비공.수리(비행기);
//        정비공.수리(병사); // 수리가능 인터페이스 타입으로 업캐스팅이 불가능하므로 전달 불가!
        
    }
 
}
 
interface 공격가능 {
    public void 공격();
}
 
interface 이동가능 {
    public void 이동(int x, int y);
}
 
// 아무런 메서드나 상수 선언 없이 껍데기만 존재하는 인터페이스 정의
// => 특정 클래스에 표시를 하기 위한 용도로 사용하는 마커(Marker) 인터페이스
interface 수리가능 {}
 
class 유닛 {
    int 체력;
}
 
class 병사 extends 유닛 implements 공격가능, 이동가능 {
 
    @Override
    public void 이동(int x, int y) {
        System.out.println(x + ", " + y + "좌표로 도보로 이동!");
    }
 
    @Override
    public void 공격() {
        System.out.println("공격 대상을 총으로 공격!");
    }
    
}
 
class 탱크 extends 유닛 implements 공격가능, 이동가능, 수리가능 {
    @Override
    public void 이동(int x, int y) {
        System.out.println(x + ", " + y + "좌표로 빠르게 이동!");
    }
 
    @Override
    public void 공격() {
        System.out.println("공격 대상을 대포로 공격!");
    }
}
 
class 비행기 extends 유닛 implements 이동가능, 수리가능 {
 
    @Override
    public void 이동(int x, int y) {
        System.out.println(x + ", " + y + "좌표로 날아서 이동!");
    }
}
 
class 정비공 {
    
    public void 수리(수리가능 수리유닛) {
        System.out.println("대상을 수리!");
//        수리유닛.체력++;
        
        // 비행기 또는 탱크 등을 체크하여 별도로 각각 다운캐스팅해도 되지만
        // 비행기와 탱크의 공통 슈퍼클래스인 유닛 타입으로 다운캐스팅 해도 됨
        if(수리유닛 instanceof 유닛) {
            유닛 유닛 = (유닛)수리유닛;
            유닛.체력++;
        }
        
    }
    
}
 
cs

인터페이스를 활용하는 여러 방법을 배워 보았는데 개념 자체는 쉽다고 생각이 들었지만 역시나 활용하는 방법을 배워보니 생각보다 복잡해서 어려웠다. 이해는 되는데 적용을 해보려니 막히는 게 많은 연습이 필요할 것 같다. 마커 인터페이스를 활용하여 아무 관계없는 클래스끼리 연관관계를 만들어 줄 수도 있었고 인터페이스를 상속받은 클래스를 인터페이스로 업 캐스팅하여 활용하여 여러 가지를 만들어 보았는데 확실히 몇 번 해볼수록 좀 익숙해지는 느낌은 들었지만 아직은 연습이 많이 필요하다고 느꼈다. 코드의 중복을 없애기 위해 여러 가지로 활용하는데 많은 경험을 해보아야 자연스럽게 나도 활용을 할 수 있겠다고 느껴졌다. 업 캐스팅이 되는지 확인하기 위하여 instanceof를 사용하지만 제일 상위의 클래스인 Object클래스로 업 캐스팅하여 instanceof를 생략하는 방법도 있었고 하나의 공통된 메서드를 활용하기 위하여 업과 다운을 이리저리 하게 되는데 복잡하지만 배우는 재미가 있어서 시간 가는 줄 모르고 배웠던 거 같다. 확실히 복습을 여러 번 하여서 자연스레 활용할 수 있게 여러 번 보아야 할 것 같다.

'Java' 카테고리의 다른 글

Java - Set,List  (0) 2020.06.27
Java - toString(),equals()  (0) 2020.06.27
Java - 상수,인터페이스,implements  (0) 2020.06.14
Java - 추상화,final  (0) 2020.06.14
Java - instanceof  (0) 2020.06.14