포스트

Day30 실습프로젝트(리팩토링)

UML

UML이란?


  • UML case
  • UML은 크게 6가지로 구분 할 수 있다.





실습프로젝트 리팩토링하기

인터페이스의 활용

  • 기존 프로젝트의 List 항목들에서 중복된 코드를 줄이자.
  • 다음가 같은 UML을 가지도록 수정 한다.



  • 먼저 ArrayList와 LinkedList의 공통 코드를 추상 메소드(pulic abstract)로 선언한다.
  • ` //List 인터페이스 public interface List { void add(Object obj); Object remove(int index); Object[] toArray(); int indexOf(Object obj); int size(); Object get(int index); }`
  • 다음 ArrayList와 LinkedList의 동일한 코드를 Abstract class로 오버라이딩을 한다.
  • public abstract class AbstractList implements List { protected int size = 0; @Override public int size() { return size; } }
  • 이후 ArrayList와 LinkedList의 상세코드를 오버라이딩으로 수정한다.
  • ArrayList의 상세코드 코드 접기/펴기

    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
    
    public class ArrayList extends AbstractList {
      private static final int MAX_SIZE = 3;
      private Object[] list = new Object[MAX_SIZE];
      private int size = 0;
    
      @Override
      public void add(Object obj) {
        if (size == list.length) {
          int oldSize = list.length;
          int newSize = oldSize + (oldSize >> 1);
          list = Arrays.copyOf(list, newSize);
        }
        list[size++] = obj;
      }
    
      @Override
      public Object remove(int index) {
        if (index < 0 || index >= size) {
          return null;
        }
        Object deletedObj = list[index];
        for (int i = index + 1; i < size; i++) {
          list[i - 1] = list[i];
        }
        list[--size] = null;
        return deletedObj;
      }
    
      @Override
      public Object[] toArray() {
        Object[] arr = new Object[size];
        System.arraycopy(list, 0, arr, 0, arr.length);
        return arr;
      }
    
      @Override
      public int indexOf(Object obj) {
        for (int i = 0; i < size; i++) {
          if (list[i] == obj) {
            return i;
          }
        }
        return -1;
      }
    
      @Override
      public int size() {
        return size;
      }
    
      @Override
      public Object get(int index) {
        if (index < 0 || index >= size) {
          return null;
        }
        java.util.ArrayList l;
        return list[index];
      }
    
      public boolean contains(Object obj) {
        return indexOf(obj) != -1;
      }
    }
    
  • LinkedList의 상세코드 코드 접기/펴기

    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
    
      public class LinkedList extends AbstractList {
    
      Node first;
      Node last;
      int size;
    
      @Override
      public void add(Object value) {
        Node newNode = new Node(value);
    
        if (first == null) {
          last = first = newNode;
        } else {
          last.next = newNode;
          last = newNode;
        }
        size++;
      }
    
      @Override
      public Object get(int index) {
        if (index < 0 || index >= size) {
          return null;
        }
    
        Node cursor = first;
        int currentIndex = 0;
    
        while (cursor != null) {
          if (currentIndex == index) {
            return cursor.value;
          }
          cursor = cursor.next;
          currentIndex++;
        }
        return null;
      }
    
      @Override
      public Object remove(int index) {
        if (index < 0 || index >= size) {
          return null;
        }
    
        Node deletedNode = null;
        size--;
    
        if (index == 0) {
          deletedNode = first;
          first = first.next;
          if (first == null) {
            last = null;
          }
          return deletedNode.value;
        }
    
        Node cursor = first;
        int currentIndex = 0;
    
        while (cursor != null) {
          if (currentIndex == (index - 1)) {
            break;
          }
          cursor = cursor.next;
          currentIndex++;
        }
    
        deletedNode = cursor.next;
        cursor.next = cursor.next.next;
    
        if (cursor.next == null) {
          last = cursor;
        }
    
        return deletedNode.value;
      }
    
      @Override
      public int indexOf(Object value) {
        Node cursor = first;
        int currentIndex = 0;
    
        while (cursor != null) {
          if (cursor.value == value) {
            return currentIndex;
          }
          cursor = cursor.next;
          currentIndex++;
        }
        return -1;
      }
    
      @Override
      public Object[] toArray() {
        Object[] arr = new Object[size];
    
        Node cursor = first;
        for (int i = 0; i < size; i++) {
          arr[i] = cursor.value;
          cursor = cursor.next;
        }
    
        return arr;
      }
    
      @Override
      public int size() {
        return size;
      }
      }
    

리팩토링: GRASP의 High Cohesion

  • High Cohesion : 응집력은 클래스나 모듈의 내부 요소들이 얼마나 밀접하게 관련되어 있는지를 나타내는 개념
  • 관련성 높은 기능들의 집합
  • 유지보수성 향상
  • 재사용성 향상
  • 객체지향 원칙 준수

App과 Command 코드 수정

  • App에 subMenus에서 수행하는 기능들 혼재
  • APP에 있는 subMenus들의 기능을 각 Command 기능으로 옮기기



  • App()코드수정 코드 접기/펴기

    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
    
    public class App {
    
      String[] mainMenus = new String[] {"회원", "프로젝트", "게시판", "공지사항", "도움말", "종료"};
      UserCommand userCommand = new UserCommand("회원");
      BoardCommand boardCommand = new BoardCommand("게시판");
      BoardCommand noticeCommand = new BoardCommand("공지사항");
      ProjectCommand projectCommand = new ProjectCommand("프로젝트", userCommand.getUserList());
      public static void main(String[] args) {
        new App().execute();
      }
    
      void execute() {
          //생략
      }
    
      void processMenu(String menuTitle) {
        switch (menuTitle) {
          case "회원":
            userCommand.execute();
            break;
          case "프로젝트":
            projectCommand.execute();
            break;
          case "게시판":
            boardCommand.execute();
            break;
          case "공지사항":
            noticeCommand.execute();
            break;
          case "도움말":
            System.out.println("도움말입니다.");
            break;
          default:
            System.out.printf("%s 메뉴의 명령을 처리할 수 없습니다.\n", menuTitle);
        }
      }
    
      void printMenu() {
          //생략
      }
    
      boolean isValidateMenu(int menuNo, String[] menus) {
          //생략
      }
    
      String getMenuTitle(int menuNo, String[] menus) {
          //생략
      }
    }
    
  • Command()인터페이스 생성
  • public interface Command { void execute(); }
  • Command()코드수정 코드 접기/펴기

    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
    
    public class UserCommand implements Command {
    
      LinkedList userList = new LinkedList();
    
      String menuTitle;
      String[] menus = {"등록", "목록", "조회", "변경", "삭제"};
    
      public UserCommand(String menuTitle) {
        this.menuTitle = menuTitle;
      }
    
      @Override
      public void execute() {
        printMenus();
        while (true) {
          String command = Prompt.input(String.format("메인/%s>", menuTitle));
          if (command.equals("menu")) {
            printMenus();
            continue;
          } else if (command.equals("9")) { // 이전 메뉴 선택
            break;
          }
          try {
            int menuNo = Integer.parseInt(command);
            String menuName = getMenuTitle(menuNo);
            if (menuName == null) {
              System.out.println("유효한 메뉴 번호가 아닙니다.");
              continue;
            }
            processMenu(menuName);
          } catch (NumberFormatException ex) {
            System.out.println("숫자로 메뉴 번호를 입력하세요.");
          }
        }
      }
    
      private void processMenu(String menuName) {
        System.out.printf("[%s]\n", menuName);
        switch (menuName) {
          case "등록":
            this.addUser();
            break;
          case "조회":
            this.viewUser();
            break;
          case "목록":
            this.listUser();
            break;
          case "변경":
            this.updateUser();
            break;
          case "삭제":
            this.deleteUser();
            break;
        }
      }
    
      private void printMenus() {
        System.out.printf("[%s]\n", menuTitle);
        for (int i = 0; i < menus.length; i++) {
          System.out.printf("%d. %s\n", (i + 1), menus[i]);
        }
        System.out.println("9. 이전");
      }
    
      private boolean isValidateMenu(int menuNo) {
        return menuNo >= 1 && menuNo <= menus.length;
      }
    
      private String getMenuTitle(int menuNo) {
        return isValidateMenu(menuNo) ? menus[menuNo - 1] : null;
    
      }
    }
    

리팩토링: 상속의 Generalization 적용

  • 응집력을 높인 결과 각 Command 클래스에 동일한 코드 생성
  • 동일한 코드를 일반화를 통해 하나의 클래스에 넣는다.
  • 상속을 사용하여 일반화를 진행한다.
  • 수퍼클래스를 추상클래스로 설정하여 직접적인 클래스 사용을 막는다.
  • 수퍼클래스에서 결정되지 못하는 메소드는 추상메소드로 만든다.
  • 자식클래스에서 추상메소드에 방법을 제시해야한다.



  • 추상클래스를 상속받은 UserCommand 코드 접기/펴기

    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
    
      public class UserCommand extends AbstractCommand {
    
      LinkedList userList = new LinkedList();
    
      String menuTitle;
      String[] menus = {"등록", "목록", "조회", "변경", "삭제"};
    
      public UserCommand(String menuTitle) {
        super(menuTitle);
      }
    
      @Override
      public void processMenu(String menuName) {
        System.out.printf("[%s]\n", menuName);
        switch (menuName) {
          case "등록":
            this.addUser();
            break;
          case "조회":
            this.viewUser();
            break;
          case "목록":
            this.listUser();
            break;
          case "변경":
            this.updateUser();
            break;
          case "삭제":
            this.deleteUser();
            break;
        }
      }
    
      @Override
      public String[] getMenus() {
        return menus;
      }
    
      private void addUser() {
        User user = new User();
        user.setName(Prompt.input("이름?"));
        user.setEmail(Prompt.input("이메일?"));
        user.setPassword(Prompt.input("암호?"));
        user.setTel(Prompt.input("연락처?"));
        user.setNo(User.getNextSeqNo());
        userList.add(user);
      }
    
      private void listUser() {
        System.out.println("번호 이름 이메일");
        for (Object obj : userList.toArray()) {
          User user = (User) obj;
          System.out.printf("%d %s %s\n", user.getNo(), user.getName(), user.getEmail());
        }
      }
    
      private void viewUser() {
        int userNo = Prompt.inputInt("회원번호?");
        User user = (User) userList.get(userList.indexOf(new User(userNo)));
        if (user == null) {
          System.out.println("없는 회원입니다.");
          return;
        }
    
        System.out.printf("이름: %s\n", user.getName());
        System.out.printf("이메일: %s\n", user.getEmail());
        System.out.printf("연락처: %s\n", user.getTel());
      }
    
      private void updateUser() {
        int userNo = Prompt.inputInt("회원번호?");
        User user = (User) userList.get(userList.indexOf(new User(userNo)));
        if (user == null) {
          System.out.println("없는 회원입니다.");
          return;
        }
    
        user.setName(Prompt.input("이름(%s)?", user.getName()));
        user.setEmail(Prompt.input("이메일(%s)?", user.getEmail()));
        user.setPassword(Prompt.input("암호?"));
        user.setTel(Prompt.input("연락처(%s)?", user.getTel()));
        System.out.println("변경 했습니다.");
      }
    
      private void deleteUser() {
        int userNo = Prompt.inputInt("회원번호?");
        User deletedUser = (User) userList.get(userList.indexOf(new User(userNo)));
        if (deletedUser != null) {
          userList.remove(userList.indexOf(deletedUser));
          System.out.printf("'%s' 회원을 삭제 했습니다.\n", deletedUser.getName());
        } else {
          System.out.println("없는 회원입니다.");
        }
      }
    
      public LinkedList getUserList() {
        return userList;
      }
    
    }
    
  • 추상클래스를 상속받은 ProjectCommand 코드 접기/펴기

    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
    
    public class ProjectCommand extends AbstractCommand {
    
      LinkedList projectList = new LinkedList();
      LinkedList userList;
    
      String menuTitle;
      String[] menus = {"등록", "목록", "조회", "변경", "삭제"};
    
      public ProjectCommand(String menuTitle, LinkedList userList) {
        super(menuTitle);
        this.userList = userList;
      }
    
      @Override
      public void processMenu(String menuName) {
        System.out.printf("[%s]\n", menuName);
        switch (menuName) {
          case "등록":
            this.addProject();
            break;
          case "조회":
            this.viewProject();
            break;
          case "목록":
            this.listProject();
            break;
          case "변경":
            this.updateProject();
            break;
          case "삭제":
            this.deleteProject();
            break;
        }
      }
    
      @Override
      public String[] getMenus() {
        return menus;
      }
    
      private void addMembers(Project project) {
        while (true) {
          int userNo = Prompt.inputInt("추가할 팀원 번호?(종료: 0)");
          if (userNo == 0) {
            break;
          }
    
          User user = (User) userList.get(userList.indexOf(new User(userNo)));
          if (user == null) {
            System.out.println("없는 팀원입니다.");
            continue;
          }
    
          if (project.getMembers().contains(user)) {
            System.out.printf("'%s'은 현재 팀원입니다.\n", user.getName());
            continue;
          }
    
          project.getMembers().add(user);
          System.out.printf("'%s'을 추가했습니다.\n", user.getName());
        }
      }
    
      private void deleteMembers(Project project) {
        for (int i = 0; i < project.getMembers().size(); i++) {
          User user = (User) project.getMembers().get(i);
          String str = Prompt.input("팀원(%s) 삭제?", user.getName());
          if (str.equalsIgnoreCase("y")) {
            project.getMembers().remove(i);
            System.out.printf("'%s' 팀원을 삭제합니다.\n", user.getName());
          } else {
            System.out.printf("'%s' 팀원을 유지합니다.\n", user.getName());
          }
        }
      }
    
      private void addProject() {
        Project project = new Project();
        project.setTitle(Prompt.input("프로젝트명?"));
        project.setDescription(Prompt.input("설명?"));
        project.setStartDate(Prompt.input("시작일?"));
        project.setEndDate(Prompt.input("종료일?"));
    
        System.out.println("팀원:");
        addMembers(project);
    
        project.setNo(Project.getNextSeqNo());
    
        projectList.add(project);
    
        System.out.println("등록했습니다.");
      }
    
      private void listProject() {
        System.out.println("번호 프로젝트 기간");
        for (Object obj : projectList.toArray()) {
          Project project = (Project) obj;
          System.out.printf("%d %s %s ~ %s\n", project.getNo(), project.getTitle(),
              project.getStartDate(), project.getEndDate());
        }
      }
    
      private void viewProject() {
        int projectNo = Prompt.inputInt("프로젝트 번호?");
        Project project = (Project) projectList.get(projectList.indexOf(new Project(projectNo)));
        if (project == null) {
          System.out.println("없는 프로젝트입니다.");
          return;
        }
    
        System.out.printf("프로젝트명: %s\n", project.getTitle());
        System.out.printf("설명: %s\n", project.getDescription());
        System.out.printf("기간: %s ~ %s\n", project.getStartDate(), project.getEndDate());
        System.out.println("팀원:");
        for (int i = 0; i < project.getMembers().size(); i++) {
          User user = (User) project.getMembers().get(i);
          System.out.printf("- %s\n", user.getName());
        }
      }
    
      private void updateProject() {
        int projectNo = Prompt.inputInt("프로젝트 번호?");
        Project project = (Project) projectList.get(projectList.indexOf(new Project(projectNo)));
        if (project == null) {
          System.out.println("없는 프로젝트입니다.");
          return;
        }
    
        project.setTitle(Prompt.input("프로젝트명(%s)?", project.getTitle()));
        project.setDescription(Prompt.input("설명(%s)?", project.getDescription()));
        project.setStartDate(Prompt.input("시작일(%s)?", project.getStartDate()));
        project.setEndDate(Prompt.input("종료일(%s)?", project.getEndDate()));
    
        System.out.println("팀원:");
        deleteMembers(project);
        addMembers(project);
    
        System.out.println("변경 했습니다.");
      }
    
      private void deleteProject() {
        int projectNo = Prompt.inputInt("프로젝트 번호?");
        Project deletedProject = (Project) projectList.get(projectList.indexOf(new Project(projectNo)));
        if (deletedProject != null) {
          projectList.remove(projectList.indexOf(deletedProject));
          System.out.printf("%d번 프로젝트를 삭제 했습니다.\n", deletedProject.getNo());
        } else {
          System.out.println("없는 프로젝트입니다.");
        }
      }
    }
    
  • 추상클래스를 상속받은 BoardCommand 코드 접기/펴기

    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
    
    public class BoardCommand extends AbstractCommand {
    
      LinkedList boardList = new LinkedList();
    
      String menuTitle;
      String[] menus = {"등록", "목록", "조회", "변경", "삭제"};
    
      public BoardCommand(String menuTitle) {
        super(menuTitle);
      }
    
      @Override
      public void processMenu(String menuName) {
        System.out.printf("[%s]\n", menuName);
        switch (menuName) {
          case "등록":
            this.addBoard();
            break;
          case "조회":
            this.viewBoard();
            break;
          case "목록":
            this.listBoard();
            break;
          case "변경":
            this.updateBoard();
            break;
          case "삭제":
            this.deleteBoard();
            break;
        }
      }
    
      @Override
      public String[] getMenus() {
        return menus;
      }
      private void deleteBoard() {
        int boardNo = Prompt.inputInt("게시글 번호?");
        Board deletedBoard = (Board) boardList.get(boardList.indexOf(new Board(boardNo)));
        if (deletedBoard != null) {
          boardList.remove(boardList.indexOf(deletedBoard));
          System.out.printf("%d번 게시글을 삭제 했습니다.\n", deletedBoard.getNo());
        } else {
          System.out.println("없는 게시글입니다.");
        }
      }
    
      private void updateBoard() {
        int boardNo = Prompt.inputInt("게시글 번호?");
        Board board = (Board) boardList.get(boardList.indexOf(new Board(boardNo)));
        if (board == null) {
          System.out.println("없는 게시글입니다.");
          return;
        }
    
        board.setViewCount(board.getViewCount() + 1);
        board.setTitle(Prompt.input("제목(%s)?", board.getTitle()));
        board.setContent(Prompt.input("내용(%s)?", board.getContent()));
        System.out.println("변경 했습니다.");
      }
    
      private void viewBoard() {
        int boardNo = Prompt.inputInt("게시글 번호?");
        Board board = (Board) boardList.get(boardList.indexOf(new Board(boardNo)));
        if (board == null) {
          System.out.println("없는 게시글입니다.");
          return;
        }
    
        board.setViewCount(board.getViewCount() + 1);
        System.out.printf("제목: %s\n", board.getTitle());
        System.out.printf("내용: %s\n", board.getContent());
        System.out.printf("작성일: %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS\n", board.getCreatedDate());
        System.out.printf("조회수: %d\n", board.getViewCount());
      }
    
      private void listBoard() {
        System.out.println("번호 제목 작성일 조회수");
        for (Object obj : boardList.toArray()) {
          Board board = (Board) obj;
          System.out.printf("%d %s %tY-%3$tm-%3$td %d\n", board.getNo(), board.getTitle(),
              board.getCreatedDate(), board.getViewCount());
        }
      }
    
      private void addBoard() {
        Board board = new Board();
        board.setTitle(Prompt.input("제목?"));
        board.setContent(Prompt.input("내용?"));
        board.setCreatedDate(new Date());
        board.setNo(Board.getNextSeqNo());
        boardList.add(board);
      }
    
    }
    
  • HelpCommand

    • help커맨드는 인터페이스를 사용하여 직접구현

      1
      2
      3
      4
      5
      
      public class HelpCommand {
      public void execute() {
        System.out.println("도움말 입니다.");
      }
      }
      
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.