이 글은 과거 POJO로 만들었던 콘솔 게시판, 회원, 과제 관리 시스템을 정리한 글입니다.
순수 자바로만 어플리케이션을 만들어보며 그 과정에서 객체지향 프로그래밍을 이해하기 위해 만들었습니다.
1. 문제
먼저 어떠한 가공도 되지 않은 하나의 클래스 파일에 모든 시스템의 과정이 담겨 있습니다.
현재 이 시스템에는 다음과 같은 문제가 있습니다.
1. 가독성이 전혀 없다.
2. 코드를 재활용 할 수 없다.
3. 따라서 시스템이 커지고 복잡해진다면 유지보수에 드는 시간은 곱절로 늘어난다.
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
List<Board> boardList = new ArrayList<>();
List<Member> memberList = new ArrayList<>();
System.out.println("콘솔 게시판 프로그램 시작");
while (true) {
System.out.println("1. 회원 메뉴");
System.out.println("2. 게시글 메뉴");
String menuNo = in.readLine();
if (menuNo.equals("1")) {
System.out.println("1. 회원가입");
System.out.println("2. 회원 상세");
System.out.println("3. 회원정보 수정");
System.out.println("4. 회원 탈퇴");
System.out.println("5. 회원 목록");
String memberMenuNo = in.readLine();
switch (memberMenuNo) {
case "1":
System.out.println("이름: ");
String name = in.readLine();
System.out.println("이메일: ");
String email = in.readLine();
System.out.println("비밀번호: ");
String password = in.readLine();
Member newMember = new Member(name, email, password);
memberList.add(newMember);
break;
case "2":
System.out.println("열람할 회원 번호를 입력하세요");
int memberViewNo = Integer.parseInt(in.readLine());
Member member = memberList.get(memberViewNo - 1);
System.out.println(member.allToString());
case "3":
System.out.println("수정할 회원 번호를 입력하세요");
int memberModiNo = Integer.parseInt(in.readLine());
Member member = memberList.get(memberModiNo - 1);
System.out.println("이름: ");
String name = in.readLine();
member.setName(name);
System.out.println("이메일: ");
String email = in.readLine();
member.setEmail(email);
System.out.println("비밀번호: ");
String password = in.readLine();
member.setPassword(password);
System.out.println("회원정보 수정이 완료되었습니다.");
break;
case "4":
System.out.println("탈퇴할 회원의 번호를 입력하세요");
int memberResignNo = Integer.parseInt(in.readLine());
memberList.remove(memberResignNo - 1);
System.out.println("회원 탈퇴가 완료되었습니다.");
break;
case "5":
if (loginMember != null) {
loginMember = null;
System.out.println("로그아웃이 완료되었습니다.");
} else {
System.out.println("로그인이 필요합니다.");
}
break;
case "6":
memberList.forEach(System.out::println);
break;
}
} else if (menuNo.equals("2")) {
...
} else {
...
}
}
}
}
그렇기 때문에 이 시스템의 코드를 리펙토링하며 Class와 객체지향 프로그래밍의 필요 이유와 적용과정을 정리해보려고 합니다.
2. 시스템 구조
하나의 시스템은 크게 3개의 서비스를 제공하고 있습니다. 회원, 그리고 게시판 이 둘은 각각 도메인이라 볼 수 있고
각 도메인에는 세부적인 서비스들을 제공하고 있습니다.
이 과정은 전체 시스템을 여러개의 틀(기준)으로 나누고 있습니다 즉, Class가 필요한 이유입니다.
Class는 정해진 기준에 따라 데이터(필드) 그리고 데이터를 조작하는 기능(메서드)를 한곳에 모은 템플릿을 의미합니다.
그렇다면 MainMenu, MemberMenu, AssignmentMenu, BoardMenu 클래스를 생성하여 시스템을 나눠보겠습니다.
3. 의존성 주입
그런데 Class를 나누다 보면 문제가 하나 발생합니다.
public class BoardMenu {
private List<Board> boardList = new ArrayList<>();
public void create() throws IOException {
System.out.println("작성자:");
String writer = in.readLine(); //에러
System.out.println("제목:");
String title = in.readLine(); //에러
System.out.println("내용:");
String content = in.readLine(); //에러
Board newBoard = new Board(writer, title, content);
boardList.add(newBoard);
System.out.println("게시물 작성이 완료되었습니다.");
}
...
}
바로 BoardMenu에는 BufferedReader 객체가 없습니다. 사용자 입력은 Main 클래스의 main메서드에서 받아
BoardMenu에서 입력받은 값을 이용해야 하는데 난감해졌습니다.
이때 사용되는 개념이 의존성 주입입니다.
즉 BoardMenu에서 필요하지만 생성하기 곤란한 경우 외부에서 BoardMenu객체에 필요한 객체를 넣어 줄 수 있습니다.
이때, BoardMenu의 생성자를 통해 넣어줄 것이기 때문에 생성자 주입이라고 합니다.
public class BoardMenu {
private BufferedReader in;
private List<Board> boardList = new ArrayList<>();
public BoardMenu(BufferedReader in) {
this.in = in;
}
public void create() throws IOException {
System.out.println("작성자:");
String writer = in.readLine(); //에러
System.out.println("제목:");
String title = in.readLine(); //에러
System.out.println("내용:");
String content = in.readLine(); //에러
Board newBoard = new Board(writer, title, content);
boardList.add(newBoard);
System.out.println("게시물 작성이 완료되었습니다.");
}
...
}
따라서 전체 메뉴를 다루는 MainMenu도 생성자 주입을 통해 각 메뉴 객체를 주입하겠습니다.
public class MainMenu {
private BufferedReader in;
private BoardMenu boardMenu;
private AssignmentMenu assignmentMenu;
private MemberMenu memberMenu;
public MainMenu(BufferedReader in, BoardMenu boardMenu, AssignmentMenu assignmentMenu, MemberMenu memberMenu) {
this.in = in;
this.boardMenu = boardMenu;
this.assignmentMenu = assignmentMenu;
this.memberMenu = memberMenu;
}
public void execute() throws IOException {
System.out.println("콘솔 게시판 프로그램 시작");
while(true) {
System.out.println("1. 게시글 메뉴");
System.out.println("2. 과제 메뉴");
System.out.println("3. 회원 메뉴");
String menuNo = in.readLine();
switch (menuNo) {
case "1":
boardMenu.execute();
break;
case "2":
assignmentMenu.execute();
break;
case "3":
memberMenu.execute();
break;
default:
System.out.println("존재하지 않는 메뉴입니다.");
}
}
}
}
4. 인터페이스 활용하기
MainMenu를 제외한 하위 메뉴들은 그 모습이 매우 유사하고 비슷한 쓰임새를 갖습니다.
따라서 다음과 같은 Interface를 선언하고
public interface Menu {
void execute() throws IOException;
}
다음과 같이 활용할 수 있습니다.
public class MainMenu {
private BufferedReader in;
private Menu[] menus;
public MainMenu(BufferedReader in, Menu... menus) {
this.in = in;
this.menus = menus;
}
public void execute() throws IOException {
System.out.println("콘솔 게시판 프로그램 시작");
while(true) {
for (int i = 1; i <= menus.length; i++) {
System.out.println(i + ". " + menus[i - 1].getName());
}
int menuNo = Integer.parseInt(in.readLine()) - 1;
if (menuNo > menus.length - 1) {
System.out.println("존재하지 않는 메뉴입니다.");
continue;
}
menus[menuNo].execute();
}
}
}
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
MemberMenu memberMenu = new MemberMenu(in);
BoardMenu boardMenu = new BoardMenu(in);
AssignmentMenu assignmentMenu = new AssignmentMenu(in);
MainMenu mainMenu = new MainMenu(in, boardMenu, assignmentMenu, memberMenu);
mainMenu.execute();
}
}
각 메뉴들이 인터페이스를 구현하도록 하면 각 메뉴들을 Menu[] 타입으로 받을 수 있게 됩니다.
그리고 반복문을 돌며 메뉴명을 출력하고 각 메뉴에서 구현한 execute() 메서드를 실행 할 수 있습니다.
타입을 인터페이스로 받게 되면 더 넓은 범위의 객체들을 주입할 수 있고 이는 프로그래밍의 유연성과 연결됩니다.
5. 결론
객체지향 프로그래밍은 각 객체들을 적절하게 쪼게며 연결을 유연하게 하고 의존성을 관리하는 것이 중요합니다.
다음 글에서는 디자인 패턴을 사용하여 더 객체지향스러운 프로그래밍에 대해 알아보겠습니다.
전체코드:
'OOP' 카테고리의 다른 글
VO 객체 도입하기 (0) | 2024.11.12 |
---|---|
콘솔 게시판 앱에 Composite, Command 패턴 적용하기 - 2 (0) | 2024.10.10 |