[Java] 14. 객체 지향(OOP) 프로그래밍의 4대 원칙(캡슐화, 추상화, 상속, 다형성)


Study/Java  2020. 5. 12. 20:16

안녕하세요. 명월입니다.


이 글은 Java의 객체 지향(OOP)의 4대 원칙(캡슐화, 추상화, 상속, 다형성)에 대한 글입니다.


객체 지향(Object-Oriented Programming)이라는 것은 프로그래밍 방식 중 하나입니다. 여기서 프로그램 방식이란, 프로그램을 개발할 때에 어떤 목적을 중심으로 개발을 하는가에 대한 방식입니다.

그 중 객체 지향은 객체(Object)를 중심으로 프로그램을 설계, 개발해 나가는 것을 말합니다.


예를 들면, "업무 계획서 작성 -> 계획 실행 -> 테스트 -> 결과 확인 -> 보고서 작성 -> 결제 -> 승인"으로 된 하나의 업무 프로세스를 생각해 봅니다.

여기서 먼저 전체 업무 단위(Controller)로 구성하고 계획서 데이터, 테스트 데이터, 결과 데이터, 보고서 데이터, 결제 데이터의 객체를 프로세스에 배치하는 것입니다.

프로그램 언어로 생각하면 가장 최소 단위를 클래스로 두고 클래스에 데이터의 특성을 부여하여 관리하는 것이 객체 지향이지 않을까 싶습니다.


이러한 객체 지향에는 4가지의 원칙이 있는데 이것이 캡슐화, 추상화, 상속, 다형성입니다.

이 원칙에 대해서는 부분적으로 다른 글에서 설명한 적이 있습니다.

링크 - [Java] 09. 접근 제한자와 static

링크 - [Java] 12. 인터페이스(Interface)

링크 - [Java] 08. 클래스의 상속과 this, super 키워드 사용법

링크 - [Java] 06. 함수 사용법(함수의 오버로딩과 재귀적 방법에 대한 설명)


이 글에서는 그러한 특성을 좀 더 자세하게 정리하겠습니다.

캡슐화

캡슐화는 언어적 표현은 무언가 감싸는 뜻입니다. 즉, 접근 제한자로 클래스의 데이터와 함수를 은닉화하라는 뜻이지만 제 생각은 클래스의 특성을 정확하게 구분을 정하는 방법이지 않을까 싶습니다.

이해하기 쉽게 학급의 반에 10명의 인원이 있고 국어, 영어, 수학의 성적의 객체를 두고 만들어 보겠습니다.

import java.util.ArrayList;
import java.util.List;
// 국어 클래스
class Korean {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public Korean(int score) {
    this.score = score;
  }
}
// 영어 클래스
class English {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public English(int score) {
    this.score = score;
  }
}
// 수학 클래스
class Math {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public Math(int score) {
    this.score = score;
  }
}
// 학생 클래스
class People {
  // 이름
  private String name;
  // 국어 성적
  private Korean korean;
  // 영어 성적
  private English english;
  // 수학 성적
  private Math math;
  // 생성자로 이름과 점수를 받는다.
  public People(String name, int korean, int english, int math) {
    this.name = name;
    this.korean = new Korean(korean);
    this.english = new English(english);
    this.math = new Math(math);
  }
}
// 학급 클래스
class SchoolClass {
  // 학급 인원 리스트
  private final List<People> peoples = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 학생을 추가한다.
    peoples.add(new People(name, korean, english, math));
  }
}
// 실행 함수가 있는 클래스
public class Example1 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass schoolclass = new SchoolClass();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
  }
}

먼저 최소 단위인 학생의 객체가 있고, 각각의 국어, 영어, 수학의 객체를 두었습니다. 그리고 학생은 각각 국어, 영어, 수학의 객체를 가지고 있고 반은 학생을 가지고 있습니다.

제가 생각하기에 가장 이상적으로 객체를 구분했습니다. 근데 여기서 이 데이터를 굳이 귀찮게 여러 개의 클래스를 만들 필요가 있을까라는 의문이 생기네요.


그럼 이 데이터를 하나로 합치겠습니다.

import java.util.ArrayList;
import java.util.List;
// 학급 클래스
class SchoolClass2 {
  // 학생 리스트
  private final List<String> peoples = new ArrayList<>();
  // 국어 리스트
  private final List<Integer> korean = new ArrayList<>();
  // 수학 리스트
  private final List<Integer> english = new ArrayList<>();
  // 영어 리스트
  private final List<Integer> math = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 리스트의 배열 위치로 학생의 성적을 찾는다.
    // 학생 추가
    this.peoples.add(name);
    // 국어 성적 추가
    this.korean.add(korean);
    // 영어 성적 추가
    this.english.add(english);
    // 수학 성적 추가
    this.math.add(math);
  }
}
// 실행 함수가 있는 클래스
public class Example2 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass2 schoolclass = new SchoolClass2();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
  }
}

좀 객체 지향스럽지 않습니다. 근데 사람에 따라 내 스타일이 있는데 귀찮게 클래스 여러 개로 나눌 필요가 있을까 하는 사람도 있을 것 같습니다.


여기서 기능을 확장해 나가겠습니다. 총점을 구하고 평균을 구합니다. 그리고 석차를 구하겠습니다.

import java.util.ArrayList;
import java.util.List;
// 학급 클래스
class SchoolClass2 {
  // 학생 리스트
  private final List<String> peoples = new ArrayList<>();
  // 국어 리스트
  private final List<Integer> korean = new ArrayList<>();
  // 수학 리스트
  private final List<Integer> english = new ArrayList<>();
  // 영어 리스트
  private final List<Integer> math = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 리스트의 배열 위치로 학생의 성적을 찾는다.
    // 학생 추가
    this.peoples.add(name);
    // 국어 성적 추가
    this.korean.add(korean);
    // 영어 성적 추가
    this.english.add(english);
    // 수학 성적 추가
    this.math.add(math);
  }
  // 총점 취득 함수
  public int sum(int index) {
    // 국어, 영어, 수학 성적을 합친다.
    return this.korean.get(index) + this.english.get(index) + this.math.get(index);
  }
  // 평균 취득 함수
  public int avg(int index) {
    // 총점에서 3을 나눈다.
    return sum(index) / 3;
  }
  // 학급의 석차를 구한다.
  public int getRank(int index) {
    // 1등부터 시작
    int rank = 1;
    // 다른 학생과 비교한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 총점 취득
      int sum = sum(index);
      // 같은 순서이면 같은 학생이므로 넘긴다.
      if(i == index) {
        continue;
      }
      // 다른 학생이 성적이 더 높으면 석차를 내린다.
      if(sum(i) > sum) {
        rank++;
      }
    }
    return rank;
  }
  // 총점과 평균, 석차를 출력하는 함수
  public void print() {
    // 학급의 인원 전부 출력한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 석차 구하기
      int rank = getRank(i);
      // 콘솔 출력
      System.out.println(peoples.get(i) + " total = " + sum(i) + ", avg = " + avg(i) + ", ranking = " + rank);
    }
  }
}
// 실행 함수가 있는 클래스
public class Example2 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass2 schoolclass = new SchoolClass2();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 성적을 출력한다.
    schoolclass.print();
  }
}

총점, 평균, 석차만 구해도 꽤 복잡해집니다.


그럼 객체 지향 방식으로 만들면 다음과 같습니다.

import java.util.ArrayList;
import java.util.List;
// 국어 클래스
class Korean {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public Korean(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 영어 클래스
class English {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public English(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 수학 클래스
class Math {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public Math(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 학생 클래스
class People {
  // 이름
  private String name;
  // 국어 성적
  private Korean korean;
  // 영어 성적
  private English english;
  // 수학 성적
  private Math math;
  // 생성자로 이름과 점수를 받는다.
  public People(String name, int korean, int english, int math) {
    this.name = name;
    this.korean = new Korean(korean);
    this.english = new English(english);
    this.math = new Math(math);
  }
  // 이름 취득 함수
  public String getName() {
    return this.name;
  }
  // 총점 취득 함수
  public int sum() {
    // 국어, 영어, 수학 성적을 합친다.
    return this.korean.getScore() + this.english.getScore() + this.math.getScore();
  }
  // 평균 취득 함수
  public int avg() {
    // 총점에서 3을 나눈다.
    return sum() / 3;
  }
}
// 학급 클래스
class SchoolClass {
  // 학급 인원 리스트
  private final List<People> peoples = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 학생을 추가한다.
    peoples.add(new People(name, korean, english, math));
  }
  // 학급의 석차를 구한다.
  public int getRank(People people) {
    // 1등부터 시작
    int rank = 1;
    // 다른 학생과 비교한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 다른 학생 취득
      People other = peoples.get(i);
      // 본인 비교는 넘긴다.
      if(other == people) {
        continue;
      }
      // 다른 학생이 성적이 더 높으면 석차를 내린다.
      if(other.sum() > people.sum()) {
        rank++;
      }
    }
    return rank;
  }
  // 총점과 평균, 석차를 출력하는 함수
  public void print() {
    // 학급의 인원 전부 출력한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 학생 취득
      People people = peoples.get(i);
      // 석차 구하기
      int rank = getRank(people);
      // 콘솔 출력
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
    }
  }
}
// 실행 함수가 있는 클래스
public class Example1 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass schoolclass = new SchoolClass();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 성적을 출력한다.
    schoolclass.print();
  }
}

객체 지향이면 클래스가 많아서 소스가 길어지지만, 각 객체에서 총점을 구하고 평균을 구하는게 구별하기가 편합니다.


여기서 기능을 추가해 나갑니다. 상위 30% 성적과 하위 30%의 성적을 구합니다.

import java.util.ArrayList;
import java.util.List;
// 학급 클래스
class SchoolClass2 {
  // 학생 리스트
  private final List<String> peoples = new ArrayList<>();
  // 국어 리스트
  private final List<Integer> korean = new ArrayList<>();
  // 수학 리스트
  private final List<Integer> english = new ArrayList<>();
  // 영어 리스트
  private final List<Integer> math = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 리스트의 배열 위치로 학생의 성적을 찾는다.
    // 학생 추가
    this.peoples.add(name);
    // 국어 성적 추가
    this.korean.add(korean);
    // 영어 성적 추가
    this.english.add(english);
    // 수학 성적 추가
    this.math.add(math);
  }
  // 총점 취득 함수
  public int sum(int index) {
    // 국어, 영어, 수학 성적을 합친다.
    return this.korean.get(index) + this.english.get(index) + this.math.get(index);
  }
  // 평균 취득 함수
  public int avg(int index) {
    // 총점에서 3을 나눈다.
    return sum(index) / 3;
  }
  // 학급의 석차를 구한다.
  public int getRank(int index) {
    // 1등부터 시작
    int rank = 1;
    // 다른 학생과 비교한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 총점 취득
      int sum = sum(index);
      // 같은 순서이면 같은 학생이므로 넘긴다.
      if (i == index) {
        continue;
      }
      // 다른 학생이 성적이 더 높으면 석차를 내린다.
      if (sum(i) > sum) {
        rank++;
      }
    }
    return rank;
  }
  // 총점과 평균, 석차를 출력하는 함수
  public void print() {
    // 학급의 인원 전부 출력한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 석차 구하기
      int rank = getRank(i);
      // 콘솔 출력
      System.out.println(peoples.get(i) + " total = " + sum(i) + ", avg = " + avg(i) + ", ranking = " + rank);
    }
  }
  // 상위 30%와 하위 30%를 구하는 함수
  public void print2() {
    // 30%의 해당하는 카운트
    int count = peoples.size() / 3;
    // 석차를 구분으로 구한다.
    for (int i = 1, z = 1; i <= count; z++) {
      // 학생들의 인원 수 만큼
      for (int j = 0; j < peoples.size(); j++) {
        // 랭크를 구한다.
        int rank = getRank(j);
        // 랭크와 순위가 같다면
        if (rank == z) {
          // 콘솔 출력
          System.out.println("Top " + z + " - " + peoples.get(j));
          i++;
        }
      }
    }
    // 개행 추가
    System.out.println();
    // 석차를 구분으로 구한다.
    for (int i = 1, z = peoples.size(); i <= count; z--) {
      // 학생들의 인원 수 만큼
      for (int j = 0; j < peoples.size(); j++) {
        // 랭크를 구한다.
        int rank = getRank(j);
        // 랭크와 순위가 같다면
        if (rank == z) {
          // 콘솔 출력
          System.out.println("Sub " + z + " - " + peoples.get(j));
          i++;
        }
      }
    }
  }
}
// 실행 함수가 있는 클래스
public class Example2 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass2 schoolclass = new SchoolClass2();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 성적을 출력한다.
    schoolclass.print();
    // 개행
    System.out.println();
    // 상위 30%와 하위 30%를 구한다.
    schoolclass.print2();
  }
}

점점 소스가 암호화로 변해가고 있습니다.


객체 지향 방식으로 만들면 다음과 같습니다.

import java.util.ArrayList;
import java.util.List;
// 국어 클래스
class Korean {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public Korean(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 영어 클래스
class English {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public English(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 수학 클래스
class Math {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public Math(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 학생 클래스
class People {
  // 이름
  private String name;
  // 국어 성적
  private Korean korean;
  // 영어 성적
  private English english;
  // 수학 성적
  private Math math;
  // 생성자로 이름과 점수를 받는다.
  public People(String name, int korean, int english, int math) {
    this.name = name;
    this.korean = new Korean(korean);
    this.english = new English(english);
    this.math = new Math(math);
  }
  // 이름 취득 함수
  public String getName() {
    return this.name;
  }
  // 총점 취득 함수
  public int sum() {
    // 국어, 영어, 수학 성적을 합친다.
    return this.korean.getScore() + this.english.getScore() + this.math.getScore();
  }
  // 평균 취득 함수
  public int avg() {
    // 총점에서 3을 나눈다.
    return sum() / 3;
  }
}
// 학급 클래스
class SchoolClass {
  // 학급 인원 리스트
  private final List<People> peoples = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 학생을 추가한다.
    peoples.add(new People(name, korean, english, math));
  }
  // 학급의 석차를 구한다.
  public int getRank(People people) {
    // 1등부터 시작
    int rank = 1;
    // 다른 학생과 비교한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 다른 학생 취득
      People other = peoples.get(i);
      // 본인 비교는 넘긴다.
      if(other == people) {
        continue;
      }
      // 다른 학생이 성적이 더 높으면 석차를 내린다.
      if(other.sum() > people.sum()) {
        rank++;
      }
    }
    return rank;
  }
  // 총점과 평균, 석차를 출력하는 함수
  public void print() {
    // 학급의 인원 전부 출력한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 학생 취득
      People people = peoples.get(i);
      // 석차 구하기
      int rank = getRank(people);
      // 콘솔 출력
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
    }
  }
  // 상위 30%와 하위 30%를 구하는 함수
  public void print2() {
    // 성적 순으로 재분류하기 위한 소트 변수
    List<People> sort = new ArrayList<>();
    // 순위별 루프
    // 소트 알고리즘이 있는데, 이해하기 편하게 순위 별로 소트합니다.
    for (int i = 0; i < peoples.size(); i++) {
      // 학급 학생 취득
      for (int j = 0; j < peoples.size(); j++) {
        // 학생 취득
        People people = peoples.get(j);
        // 순위가 i+1, 즉 0일 때 1
        if (getRank(people) == i+1) {
          // 순위 별로 재정렬
          sort.add(people);
        }
      }
    }
    // 30%의 해당하는 카운트
    int count = sort.size() / 3;
    // 상위 30% 출력
    for (int i = 0; i < count; i++) {
      // 콘솔 출력
      System.out.println("Top " + (i + 1) + " - " + sort.get(i).getName());
    }
    // 개행 출력
    System.out.println();
    // 하위 30% 출력
    for (int i = sort.size() -1; i >= sort.size() - count; i--) {
      System.out.println("Sub " + (i + 1) + " - " + sort.get(i).getName());
    }
  }
}
// 실행 함수가 있는 클래스
public class Example1 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass schoolclass = new SchoolClass();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 성적을 출력한다.
    schoolclass.print();
    // 개행
    System.out.println();
    // 상위 30%와 하위 30%를 구한다.
    schoolclass.print2();
  }
}

객체 지향으로 작성한 소스는 상위 30%, 하위 30% 소스도 명확하게 보입니다.

여기서 객체 지향에서는 소트를 사용했으니 간단해 진다고 생각할 수 있습니다. 위 객체 지향이 아닌 소스는 먼저 소트를 하려면 소트 변수를 각 성적과 이름별로 4개를 선언해야 합니다.

그리고 sum과 avg함수는 맴버 변수에 대한 값이 나오기 때문에, 상위 30%, 하위 30%를 위한 sum과 avg 함수를 또 만들어야 한다는 결론이 됩니다. 즉, 사양이 추가될 때마다 수정되는 범위도 엄청나게 늘어납니다.


이런 간단한 예제조차 이런 차이를 보이는데 실무에서는 어떨까요? 객체 지향으로 작성하지 않는 프로그램은 거의 이해할 수 없는 괴상한 프로젝트로 변합니다.


그리고 캡슐화는 이것이 전부가 아닙니다.

위에 보시면 제가 멤버 변수를 모두 private로 묶었습니다. 사실 객체 지향에서 멤버 변수는 반드시 private로 설정하자는 건 거의 암묵적인 룰입니다.

왜냐하면 클래스를 하나의 최소 단위의 객체로 바라보는 데, 그 안의 객체를 직접 임의로 수정하는 건, 객체 지향스럽지 않습니다.


이런 예가 있습니다. 저는 위의 석차를 구할 때마다 계산을 하는데, 이번에는 데이터화 합시다. 이런 경우는 데이터가 많을 때 이렇게 사용합니다.

위처럼 한 10명 정도의 계산이면 석차를 구할 때마다 함수로 계산을 하면 된다고 하지만 그 데이터가 몇 만건 몇 억건이면 석차를 구할 때마다 계산을 한다는 건 엄청나게 느릴 것입니다.

import java.util.ArrayList;
import java.util.List;
// 국어 클래스
class Korean {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public Korean(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 영어 클래스
class English {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public English(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 수학 클래스
class Math {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public Math(int score) {
    this.score = score;
  }
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 학생 클래스
class People {
  // 이름
  private String name;
  // 국어 성적
  private Korean korean;
  // 영어 성적
  private English english;
  // 수학 성적
  private Math math;
  // 총점
  private int sum;
  // 평균
  private int avg;
  // 석차
  private int rank;
  // 생성자로 이름과 점수를 받는다.
  public People(String name, int korean, int english, int math) {
    this.name = name;
    this.korean = new Korean(korean);
    this.english = new English(english);
    this.math = new Math(math);
  }
  // 이름 취득 함수
  public String getName() {
    return this.name;
  }
  // 총점 취득 함수
  public int sum() {
    return this.sum;
  }
  // 평균 취득 함수
  public int avg() {
    return this.avg;
  }
  // 석차 취득 함수
  public int getRank() {
    return this.rank;
  }
  // 총점과 평균을 계산한다.
  public void init() {
    // 국어, 영어, 수학 성적을 합친다.
    this.sum = this.korean.getScore() + this.english.getScore() + this.math.getScore();
    // 총점에서 3을 나눈다.
    this.avg = this.sum / 3;
  }
  // 석차를 계산한다.
  public void initRank(List<People> peoples) {
    // 1등부터 시작
    int rank = 1;
    // 다른 학생과 비교한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 다른 학생 취득
      People other = peoples.get(i);
      // 본인 비교는 넘긴다.
      if (other == this) {
        continue;
      }
      // 다른 학생이 성적이 더 높으면 석차를 내린다.
      if (other.sum() > this.sum()) {
        rank++;
      }
    }
    this.rank = rank;
  }
}
// 학급 클래스
class SchoolClass {
  // 학급 인원 리스트
  private final List<People> peoples = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 학생을 추가한다.
    peoples.add(new People(name, korean, english, math));
  }
  // 초기화 함수
  public void init() {
    // 총점과 평균을 구한다.
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.init();
    }
    // 석차를 구한다.
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.initRank(peoples);
    }
  }
  // 총점과 평균, 석차를 출력하는 함수
  public void print() {
    // 학급의 인원 전부 출력한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 학생 취득
      People people = peoples.get(i);
      // 석차 구하기
      int rank = people.getRank();
      // 콘솔 출력
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
    }
  }
  // 상위 30%와 하위 30%를 구하는 함수
  public void print2() {
    // 성적 순으로 재분류하기 위한 소트 변수
    List<People> sort = new ArrayList<>();
    // 순위별 루프
    // 소트 알고리즘이 있는데, 이해하기 편하게 순위 별로 소트합니다.
    for (int i = 0; i < peoples.size(); i++) {
      // 학급 학생 취득
      for (int j = 0; j < peoples.size(); j++) {
        // 학생 취득
        People people = peoples.get(j);
        // 순위가 i+1, 즉 0일 때 1
        if (people.getRank() == i + 1) {
          // 순위 별로 재정렬
          sort.add(people);
        }
      }
    }
    // 30%의 해당하는 카운트
    int count = sort.size() / 3;
    // 상위 30% 출력
    for (int i = 0; i < count; i++) {
      // 콘솔 출력
      System.out.println("Top " + (i + 1) + " - " + sort.get(i).getName());
    }
    // 개행 출력
    System.out.println();
    // 하위 30% 출력
    for (int i = sort.size() - 1; i >= sort.size() - count; i--) {
      System.out.println("Sub " + (i + 1) + " - " + sort.get(i).getName());
    }
  }
}
// 실행 함수가 있는 클래스
public class Example1 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass schoolclass = new SchoolClass();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 총점과 평균, 석차를 계산한다.
    schoolclass.init();
    // 성적을 출력한다.
    schoolclass.print();
    // 개행
    System.out.println();
    // 상위 30%와 하위 30%를 구한다.
    schoolclass.print2();
  }
}

init함수와 initRank함수를 추가했습니다. 이 데이터는 각 학생들이 입력이 끝나면 한번에 일괄적으로 계산하는 것입니다. 그럼 누구의 석차를 구할 때, 저 데이터만 취득하면 됩니다.

여기서 만약에 저 변수를 직접 수정이 가능하게 된다면? 석차를 구하는 계산식은 무용지물이 되어 버립니다. 객체의 무결성이 망가지는 것입니다.

그럼 수정을 해도 되는 변수는 public으로 하고 계산이 필요한 변수면 private로 설정하면 되지 않을까 싶지만....... 개발에는 룰이 있습니다. 변수는 private로 선언합시다.

상속

상속은 비슷한 객체끼리 부모 클래스와 인터페이스를 정의하여 공통화한 다음 상속받아서 객체를 좀 더 다루기 쉽게 하는 특징입니다. 위 예제만 보더라도 객체 지향으로 작성을 하면 일단 소스가 길어 보입니다.

무언가 객체 지향이 확실히 가독성이 좋아 보이는데도.. 깨림직합니다. 이제 비슷한 객체끼리는 묶어 버립니다.

import java.util.ArrayList;
import java.util.List;
// 과목 인터페이스
interface ISubject {
  public int getScore();
}
// 과목 추상 클래스
abstract class AbstractSubject implements ISubject {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public AbstractSubject(int score) {
    this.score = score;
  }
  @Override
  // 점수 취득 함수
  public int getScore() {
    return this.score;
  }
}
// 국어 클래스
class Korean extends AbstractSubject {
  // 부모 생성자로 넘기기
  public Korean(int score) {
    super(score);
  }
}
// 영어 클래스
class English extends AbstractSubject {
  // 부모 생성자로 넘기기
  public English(int score) {
    super(score);
  }
}
// 수학 클래스
class Math extends AbstractSubject {
  // 부모 생성자로 넘기기
  public Math(int score) {
    super(score);
  }
}
// 학생 클래스
class People {
  // 이름
  private String name;
  // 0 - 국어, 1 - 영어, 2 - 수학
  final private List<ISubject> subjects = new ArrayList<>();
  // 총점
  private int sum;
  // 평균
  private int avg;
  // 석차
  private int rank;
  // 생성자로 이름과 점수를 받는다.
  public People(String name, int korean, int english, int math) {
    this.name = name;
    // 0 - 국어
    this.subjects.add(new Korean(korean));
    // 1 - 영어
    this.subjects.add(new English(english));
    // 2 - 수학
    this.subjects.add(new Math(math));
  }
  // 이름 취득 함수
  public String getName() {
    return this.name;
  }
  // 총점 취득 함수
  public int sum() {
    return this.sum;
  }
  // 평균 취득 함수
  public int avg() {
    return this.avg;
  }
  // 석차 취득 함수
  public int getRank() {
    return this.rank;
  }
  // 총점과 평균을 계산한다.
  public void init() {
    // 국어, 영어, 수학 성적을 합친다.
    this.sum = 0;
    for (int i = 0; i < this.subjects.size(); i++) {
      this.sum += this.subjects.get(i).getScore();
    }
    // 총점에서 3을 나눈다.
    this.avg = this.sum / this.subjects.size();
  }
  // 석차를 계산한다.
  public void initRank(List<People> peoples) {
    // 1등부터 시작
    int rank = 1;
    // 다른 학생과 비교한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 다른 학생 취득
      People other = peoples.get(i);
      // 본인 비교는 넘긴다.
      if (other == this) {
        continue;
      }
      // 다른 학생이 성적이 더 높으면 석차를 내린다.
      if (other.sum() > this.sum()) {
        rank++;
      }
    }
    this.rank = rank;
  }
}
// 학급 클래스
class SchoolClass {
  // 학급 인원 리스트
  private final List<People> peoples = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 학생을 추가한다.
    peoples.add(new People(name, korean, english, math));
  }
  // 초기화 함수
  public void init() {
    // 총점과 평균을 구한다.
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.init();
    }
    // 석차를 구한다.
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.initRank(peoples);
    }
  }
  // 총점과 평균, 석차를 출력하는 함수
  public void print() {
    // 학급의 인원 전부 출력한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 학생 취득
      People people = peoples.get(i);
      // 석차 구하기
      int rank = people.getRank();
      // 콘솔 출력
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
    }
  }
}
// 실행 함수가 있는 클래스
public class Example1 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass schoolclass = new SchoolClass();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 총점과 평균, 석차를 계산한다.
    schoolclass.init();
    // 성적을 출력한다.
    schoolclass.print();
    // 소스가 길어지는 관계로 상위 30%와 하위 30%는 생략함
  }
}

위에 공통되는 국어, 영어, 수학 클래스는 인터페이스를 선언하고 추상 클래스로 하나로 묶어 버렸습니다. 처음부터 과목 클래스 사이즈가 작았기 때문에 크게 소스가 줄어든 것 같지 않지만 보통 이렇게 클래스를 묶고 상속하면 소스가 상당히 줄어들게 됩니다.

처음 소스에서는 과목이 추가될 때마다 클래스를 작성하고 똑같이 복사 붙여넣기로 만들겠지만 이렇게 공통 클래스로 버리면 과목을 추가하는 데도 큰 수정이 없어질 것입니다.

추상화

위 상속할 때 이미 추상화를 했지만 여기서는 메서드 추상화와 재정의(Override)에 대한 설명으로 이어가겠습니다.

여기서 우리는 국어, 영어, 수학이 100점을 기준으로 했지만, 요번의 교육 개편으로 국어는 예전 100에서 120으로, 영어는 100에서 80점으로, 수학은 그대로 100으로 과목의 점수 배점을 바꾸어야 합니다.

import java.util.ArrayList;
import java.util.List;
// 과목 인터페이스
interface ISubject {
  public int getScore();
}
// 과목 추상 클래스
abstract class AbstractSubject implements ISubject {
  // 점수
  private int score;

  // 생성자로 점수를 받는다.
  public AbstractSubject(int score) {
    this.score = score;
  }
  @Override
  // 점수 취득 함수
  public int getScore() {
    // int형을 float형으로 캐스팅
    float buffer =(float)this.score;
    // 배율 계산
    return (int)(buffer * getRate());
  }
  // 배율을 받는다.
  protected abstract float getRate();
}
// 국어 클래스
class Korean extends AbstractSubject {
  // 부모 생성자로 넘기기
  public Korean(int score) {
    super(score);
  }
  // 국어 배율은 1.2로 정의
  @Override
  public float getRate() {
    return 1.2f;
  }
}
// 영어 클래스
class English extends AbstractSubject {
  // 부모 생성자로 넘기기
  public English(int score) {
    super(score);
  }
  // 영어 배율은 0.8로 정의
  @Override
  public float getRate() {
    return 0.8f;
  }
}
// 수학 클래스
class Math extends AbstractSubject {
  // 부모 생성자로 넘기기
  public Math(int score) {
    super(score);
  }
  // 수학 배율은 1로 정의
  @Override
  public float getRate() {
    return 1f;
  }
}
// 학생 클래스
class People {
  // 이름
  private String name;
  // 0 - 국어, 1 - 영어, 2 - 수학
  final private List<ISubject> subjects = new ArrayList<>();
  // 총점
  private int sum;
  // 평균
  private int avg;
  // 석차
  private int rank;
  // 생성자로 이름과 점수를 받는다.
  public People(String name, int korean, int english, int math) {
    this.name = name;
    // 0 - 국어
    this.subjects.add(new Korean(korean));
    // 1 - 영어
    this.subjects.add(new English(english));
    // 2 - 수학
    this.subjects.add(new Math(math));
  }
  // 이름 취득 함수
  public String getName() {
    return this.name;
  }
  // 총점 취득 함수
  public int sum() {
    return this.sum;
  }
  // 평균 취득 함수
  public int avg() {
    return this.avg;
  }
  // 석차 취득 함수
  public int getRank() {
    return this.rank;
  }
  // 총점과 평균을 계산한다.
  public void init() {
    // 국어, 영어, 수학 성적을 합친다.
    this.sum = 0;
    for (int i = 0; i < this.subjects.size(); i++) {
      this.sum += this.subjects.get(i).getScore();
    }
    // 총점에서 3을 나눈다.
    this.avg = this.sum / this.subjects.size();
  }
  // 석차를 계산한다.
  public void initRank(List<People> peoples) {
    // 1등부터 시작
    int rank = 1;
    // 다른 학생과 비교한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 다른 학생 취득
      People other = peoples.get(i);
      // 본인 비교는 넘긴다.
      if (other == this) {
        continue;
      }
      // 다른 학생이 성적이 더 높으면 석차를 내린다.
      if (other.sum() > this.sum()) {
        rank++;
      }
    }
    this.rank = rank;
  }
}
// 학급 클래스
class SchoolClass {
  // 학급 인원 리스트
  private final List<People> peoples = new ArrayList<>();

  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 학생을 추가한다.
    peoples.add(new People(name, korean, english, math));
  }
  // 초기화 함수
  public void init() {
    // 총점과 평균을 구한다.
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.init();
    }
    // 석차를 구한다.
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.initRank(peoples);
    }
  }
  // 총점과 평균, 석차를 출력하는 함수
  public void print() {
    // 학급의 인원 전부 출력한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 학생 취득
      People people = peoples.get(i);
      // 석차 구하기
      int rank = people.getRank();
      // 콘솔 출력
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
    }
  }
}
// 실행 함수가 있는 클래스
public class Example1 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass schoolclass = new SchoolClass();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 총점과 평균, 석차를 계산한다.
    schoolclass.init();
    // 성적을 출력한다.
    schoolclass.print();
    // 소스가 길어지는 관계로 상위 30%와 하위 30%는 생략함
  }
}

추상 클래스에 추상 메소드 getRate함수를 추가해서 하위 클래스로부터 그 배율을 강제 재정의를 시킨 다음, 값을 받게 했습니다.

객체 지향으로 작성을 하면 이렇게 어떤 사양이 와도 쉽게 수정할 수 있습니다. 만약, 위처럼 객체 지향이 아닌 방식으로 이런 조건이 왔다고 하면 어떻게 수정해야 할지, 생각만 해도 복잡해집니다.

다형성

다형성은 같은 메소드에 파라미터가 다른 형태의 구조를 만드는 것입니다.

예로 이번에 선택 과목을 추가해 봅시다.

import java.util.ArrayList;
import java.util.List;
// 과목 인터페이스
interface ISubject {
  public int getScore();
}
// 과목 추상 클래스
abstract class AbstractSubject implements ISubject {
  // 점수
  private int score;
  // 생성자로 점수를 받는다.
  public AbstractSubject(int score) {
    this.score = score;
  }
  @Override
  // 점수 취득 함수
  public int getScore() {
    // int형을 float형으로 캐스팅
    float buffer =(float)this.score;
    // 배율 계산
    return (int)(buffer * getRate());
  }
  // 배율을 받는다.
  protected abstract float getRate();
}
// 국어 클래스
class Korean extends AbstractSubject {
  // 부모 생성자로 넘기기
  public Korean(int score) {
    super(score);
  }
  // 국어 배율은 1.2로 정의
  @Override
  public float getRate() {
    return 1.2f;
  }
}
// 영어 클래스
class English extends AbstractSubject {
  // 부모 생성자로 넘기기
  public English(int score) {
    super(score);
  }
  // 영어 배율은 0.8로 정의
  @Override
  public float getRate() {
    return 0.8f;
  }
}
// 수학 클래스
class Math extends AbstractSubject {
  // 부모 생성자로 넘기기
  public Math(int score) {
    super(score);
  }
  // 수학 배율은 1로 정의
  @Override
  public float getRate() {
    return 1f;
  }
}
// 선택과목 클래스
class Select extends AbstractSubject {
  // 부모 생성자로 넘기기
  public Select(int score) {
    super(score);
  }
  // 선택 과목 배율은 1로 정의
  @Override
  public float getRate() {
    return 1f;
  }
}
// 학생 클래스
class People {
  // 이름
  private String name;
  // 0 - 국어, 1 - 영어, 2 - 수학
  final private List<ISubject> subjects = new ArrayList<>();
  // 총점
  private int sum;
  // 평균
  private int avg;
  // 석차
  private int rank;
  // 생성자로 이름과 점수를 받는다.
  public People(String name, int korean, int english, int math) {
    this.name = name;
    // 0 - 국어
    this.subjects.add(new Korean(korean));
    // 1 - 영어
    this.subjects.add(new English(english));
    // 2 - 수학
    this.subjects.add(new Math(math));
  }
  // 다형성으로 선택 과목이 추가되었다.
  public People(String name, int korean, int english, int math, int select) {
    // 기본 생성자로 등록
    this(name, korean, english, math);
    // 3 - 선택 과목 추가
    this.subjects.add(new Select(select));
  }
  // 이름 취득 함수
  public String getName() {
    return this.name;
  }
  // 총점 취득 함수
  public int sum() {
    return this.sum;
  }
  // 평균 취득 함수
  public int avg() {
    return this.avg;
  }
  // 석차 취득 함수
  public int getRank() {
    return this.rank;
  }
  // 총점과 평균을 계산한다.
  public void init() {
    // 국어, 영어, 수학 성적을 합친다.
    this.sum = 0;
    for (int i = 0; i < this.subjects.size(); i++) {
      this.sum += this.subjects.get(i).getScore();
    }
    // 총점에서 3을 나눈다.
    this.avg = this.sum / this.subjects.size();
  }
  // 석차를 계산한다.
  public void initRank(List<People> peoples) {
    // 1등부터 시작
    int rank = 1;
    // 다른 학생과 비교한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 다른 학생 취득
      People other = peoples.get(i);
      // 본인 비교는 넘긴다.
      if (other == this) {
        continue;
      }
      // 다른 학생이 성적이 더 높으면 석차를 내린다.
      if (other.avg() > this.avg()) {
        rank++;
      }
    }
    this.rank = rank;
  }
}
// 학급 클래스
class SchoolClass {
  // 학급 인원 리스트
  private final List<People> peoples = new ArrayList<>();
  // 학생 추가 함수, 이름과 국어, 영어, 수학 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math) {
    // 학생을 추가한다.
    peoples.add(new People(name, korean, english, math));
  }
  // 다형성으로 같은 함수 이름으로 학생 추가 함수, 이름과 국어, 영어, 수학, 선택 과목 성적을 받는다.
  public void addPeople(String name, int korean, int english, int math, int select) {
    // 학생을 추가한다.
    peoples.add(new People(name, korean, english, math, select));
  }
  // 초기화 함수
  public void init() {
    // 총점과 평균을 구한다.
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.init();
    }
    // 석차를 구한다.
    for (int i = 0; i < peoples.size(); i++) {
      People people = peoples.get(i);
      people.initRank(peoples);
    }
  }
  // 총점과 평균, 석차를 출력하는 함수
  public void print() {
    // 학급의 인원 전부 출력한다.
    for (int i = 0; i < peoples.size(); i++) {
      // 학생 취득
      People people = peoples.get(i);
      // 석차 구하기
      int rank = people.getRank();
      // 콘솔 출력
      System.out.println(people.getName() + " total = " + people.sum() + ", avg = " + people.avg() + ", ranking = " + rank);
    }
  }
}
// 실행 함수가 있는 클래스
public class Example1 {
  // 실행 함수
  public static void main(String... args) {
    // 학급을 할당한다.
    SchoolClass schoolclass = new SchoolClass();
    // 학생을 임의로 추가한다.
    schoolclass.addPeople("A", 50, 60, 70);
    schoolclass.addPeople("B", 70, 20, 50);
    schoolclass.addPeople("C", 60, 70, 40);
    schoolclass.addPeople("D", 30, 80, 30);
    schoolclass.addPeople("E", 50, 100, 50);
    schoolclass.addPeople("F", 70, 70, 60);
    schoolclass.addPeople("G", 90, 40, 40);
    schoolclass.addPeople("H", 100, 100, 90);
    schoolclass.addPeople("I", 40, 50, 10);
    schoolclass.addPeople("J", 60, 70, 30);
    // 두 학생은 선택 과목을 추가했다.
    schoolclass.addPeople("K", 60, 70, 30, 70);
    schoolclass.addPeople("L", 60, 70, 30, 100);
    // 총점과 평균, 석차를 계산한다.
    schoolclass.init();
    // 성적을 출력한다.
    schoolclass.print();
    // 소스가 길어지는 관계로 상위 30%와 하위 30%는 생략함
  }
}

이 다형성을 통해서 클래스의 함수명도 크게 변경되지 않아서 클래스의 목적 그대로 표현이 가능합니다.


객체 지향 프로그래밍을 보면 객체 별로 데이터를 구분하기 때문에 꽤 가독성이 올라갑니다. 그리고 유지 보수를 하는데 있어서도 사양이 추가되는 것에 따라 크게 수정도 발생하지 않습니다.

최근에는 프로그램이 데이터 중심이고 빅 데이터로 돌아가기 때문에 객체 지향의 개발 방식이 매우 중요합니다.


그러나 이런 만능으로 보이는 객체 지향도 단점이 있습니다. 위 예제는 모두 데이터 형식으로 설명했기 때문에 꽤나 효율적으로 보이지만 모든 프로그램이 데이터 형식으로만 돌아가는 건 아닙니다.

예를 들면 로드 밸런싱 프로그램을 만든다거나 화상 채팅 툴을 만들게 되면, 이건 데이터 중심이 아닌 프로세스의 행위 중심이기 때문에 오히려 객체 지향으로 작성을 하면 더 복잡해집니다.


가까운 예로, Spring으로 WebSite를 만들 때, 데이터 베이스에서 데이터를 받고 클라이언트(브라우져)에 데이터를 넘겨주는 형식은 분명 데이터 중심이고 객체 지향이 좋습니다.

그러나 Controller의 경우는 맴버 변수 없이 오로지 클라이언트에서 접속을 받고 디비에 접속에서 데이터를 받고 다시 클라이언트에 넘겨주는 흐름 중심이기 때문에 오히려 이런 부분은 객체 지향이 좋지 않습니다.

그래서 Spring 프로젝트를 하면 디비의 데이터 Model 클래스나 폼 데이터의 Bean 클래스는 잘 정리가 되어 있는데 Controller의 경우는 각 페이지마다 클래스나 method를 생성해 버리기 때문에 의미없이 클래스만 늘어나는 형식이 되어버립니다.

그래서 Spring 웹 프로젝트를 보면 Model이나 Bean 클래스보다 Controller 클래스만 왕창 보이게 됩니다. 뭐 개발자의 능력에 따라 binding을 어떻게 하느냐에 달라지기는 하지만.. 기본적으로 그렇습니다.


여기까지 Java의 객체 지향(OOP) 프로그래밍의 4대 원칙(캡슐화, 추상화, 상속, 다형성)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.