Hello, Dino
다형성(Polymorphism)을 활용한 예제 (Scheduler Program) 본문
Scheduler 프로그램을 개발해보자 👩💻
1. 기능
- 이벤트 등록 (Add Event)
- 이벤트 리스트 조회 (List)
- 이벤트 검색 (Show)
- 프로그램 종료 (Exit)
2. 이벤트 종류
- OneDay
- Duration
- Deadline
세 종류의 Event 정보를 배열로 관리한다고 했을 때,
OneDay [] oneDays = new OneDay [100];
Duration [] durations = new Duration [100];
Deadlined [] deadlineds = new Deadlined [100];
세 종류의 이벤트 클래스는 전혀 공통점이 없는 클래스가 아닌 이벤트라는 공통점이 있는 클래스이기 때문에
위 소스처럼 이벤트 별 배열을 만들어 관리를 하는 것은 객체지향 프로그래밍과는 조금 거리가 느껴진다.
그렇다면 세 종류의 Event를 어떻게 하나의 배열로 관리할까?
바로 다형성을 활용하는 것이다.
우선, 세 종류의 Event 클래스가 상속 받을 상위 클래스를 생성하자.
public class Event {
// OneDay/Duration/Deadlined Event의 공통 멤버를 관리하는 상위 클래스
public String title;
public Event(String title) {
this.title = title;
}
}
OneDay / Duration / Deadlined Event 클래스는 모두 'title'이라는 필드를 공통적으로 가지고 있다.
'title' 필드는 각 서브 클래스에서 상속 받아 사용하면 되기 때문에 Event 클래스 (상위 클래스)의 멤버로 분리한다.
// 다형성 활용 X
OneDay [] oneDays = new OneDay [100];
Duration [] durations = new Duration [100];
Deadlined [] deadlineds = new Deadlined [100];
int oneDaysCount, durationsCount, deadLindedCount;
// 다형성 활용
Event[] events = new Event [100];
int eventsCount = 0;
이벤트 클래스 별로 배열을 만들어 관리하던 소스를
상위 클래스의 데이터 타입인 배열 한 개로 세 종류의 이벤트 클래스를 관리할 수 있다.
만약, 배열의 크기보다 더 많은 데이터가 배열로 관리가 되어야한다면 ?
최초 배열의 크기를 100으로 지정했는데 이 보다 많은 200개의 데이터를 배열로 관리해야하는 상황이 발생했을 때
어떻게 해야할까? 최초 지정한 배열의 크기는 변경할 수 없다. 이때는 '배열 재할당' 하자
배열 재할당을 하는 방법은 기존 배열보다 더 큰 크기의 배열을 생성하고, 새로 생성한 배열에 기존 데이터를 복사하면 된다.
해당 내용을 코드로 표현하면 이렇게 된다.
// 현재 배열 수용 데이터 개수보다 큰 임시 배열을 생성한다.
Event[] temp = new Event [capacity * 2];
// 반복문을 사용하여 기존 배열 데이터를 새로 생성한 배열에 복사한다.
for (int i=0; i<eventsCount; i++) {
temp[i] = events[i];
}
// 새로 생성한 임시 배열을 참조한다.
// 기존 배열 데이터는 GC에 의해 정리된다.
events = temp;
package scheduler.data;
public class MyDate {
int year;
int month;
int day;
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public String toString() {
return year + "/" + month + "/" + day;
}
}
package scheduler.event;
public class Event {
// OneDay/Duration/Deadlined Event의 공통 멤버를 관리하는 상위 클래스
public String title;
public Event(String title) {
this.title = title;
}
}
package scheduler.event;
import scheduler.data.MyDate;
public class OneDay extends Event{
public MyDate date;
public OneDay(String title, MyDate date) {
// Event Class (부모 클래스)의 생성자 호출
super(title);
this.date = date;
}
public String toString() {
return title + ", " + date.toString();
}
}
package scheduler.event;
import scheduler.data.MyDate;
public class Duration extends Event {
public MyDate begin;
public MyDate end;
public Duration(String title, MyDate begin, MyDate end) {
super(title);
this.begin = begin;
this.end = end;
}
public String toString() {
return title + ", " + begin.toString() + "~" + end.toString();
}
}
package scheduler.event;
import scheduler.data.MyDate;
public class Deadlined extends Event {
public MyDate deadline;
public Deadlined(String title, MyDate deadline) {
super(title);
this.deadline = deadline;
}
public String toString() {
return title + ", " + "~" + deadline;
}
}
package scheduler;
import java.io.IOException;
import java.util.Scanner;
import scheduler.data.MyDate;
import scheduler.event.Event;
import scheduler.event.OneDay;
public class Scheduler {
private int capacity = 100;
Event[] events = new Event [100]; // 이 배열은 Event타입을 상속 받는 하위 클래스 타입의 데이터를 저장할 수 있음.
int eventsCount = 0;
private Scanner scanner;
public void ProcessCommand() throws IOException {
scanner = new Scanner(System.in);
while(true){
System.out.print("$ ");
String command = scanner.next();
if (command.equalsIgnoreCase("addEvent")) {
String type = scanner.next();
try {
if (type.equalsIgnoreCase("OneDay")) {
handleAddOneDayEvent();
} else if (type.equalsIgnoreCase("duration")) {
handleAddDurationEvent();
} else if (type.equalsIgnoreCase("deadline")) {
handleAddDeadlineEvent();
} else {
UndefinedCommand(type);
}
} catch (Exception e) {
e.printStackTrace();
}
} else if (command.equalsIgnoreCase("list")) {
handleList();
} else if (command.equalsIgnoreCase("show")) {
} else if (command.equalsIgnoreCase("exit")) {
break;
} else {
UndefinedCommand(command);
}
}
// resource leak를 막기 위해 scanner의 기능이 모두 완료되는 시점에 close 해줘야 한다.
// close() => scanner가 참조하고 있는 스트림을 닫는 기능. 즉, System.in 스트림을 닫는 것을 의미.
scanner.close();
}
private void handleList() {
for (Event event : events) {
if(event == null) break;
System.out.println(" " + event.toString());
}
}
private void UndefinedCommand(String command) {
System.out.println(String.format("'%s' is undefined command", command));
}
private void handleAddOneDayEvent() throws Exception {
System.out.print(" when: ");
String dateStr = scanner.next(); // Sample=> 2019/1/20
MyDate date = parseDateString(dateStr);
System.out.print(" title: ");
String title = scanner.next();
OneDay oneDay = new OneDay(title, date);
addEvent(oneDay);
}
private void handleAddDurationEvent() {
}
private void handleAddDeadlineEvent() {
}
private void addEvent(OneDay event) {
// 배열 재할당
if(capacity >= eventsCount) {
reallocate();
}
events[eventsCount++] = event;
}
private void reallocate() {
Event[] temp = new Event [capacity * 2];
for (int i=0; i<eventsCount; i++) {
temp[i] = events[i];
}
events = temp;
capacity = events.length;
}
private MyDate parseDateString(String dateStr) throws Exception {
String[] dateArray = dateStr.split("/");
if(dateArray.length != 3) {
// Invalid date format exception
throw new Exception("Invalid date format (date format 'yyyy/MM/dd')");
}
int year = Integer.parseInt(dateArray[0]);
int month = Integer.parseInt(dateArray[1]);
int day = Integer.parseInt(dateArray[2]);
return new MyDate(year, month, day);
}
public static void main(String[] args) {
Scheduler app = new Scheduler();
try {
app.ProcessCommand();
} catch (IOException e) {
e.printStackTrace();
}
}
}
'JAVA' 카테고리의 다른 글
인터페이스(Interface)를 활용한 예제 (Scheduler Program) (1) | 2020.03.18 |
---|---|
추상화(abstract) 클래스를 활용한 예제 (Scheduler Program) (0) | 2020.03.18 |
상속 (Inheritance) & 다형성 (Polymorphism) (0) | 2020.03.11 |
String.format() 자리수 맞추기 (1) | 2020.03.11 |
JDK 설치 / 환경 변수 설정 / Eclipse 설치 (0) | 2019.03.12 |