Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[비트캠프] 43일차(9주차3일) - Java(리팩토링: 데이터 타입 정의로써 클래) 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[비트캠프] 43일차(9주차3일) - Java(리팩토링: 데이터 타입 정의로써 클래)

끈기JK 2023. 1. 4. 10:35

com.eomcs.oop.ex02

 

 

com.eomcs.oop.ex02.Exam01xx

 

① 낱개 변수 사용

 

② class 문법 : 새 데이터 타입 정의

    class Score 분리한다. 이를 User Defined Data Type이라 한다.

 

③ method 문법 : 중복 코드 추출

    출력 코드를 printScore()로 추출한다.

 

 

④ 리팩토링 : 1기능 1메서드

    sum, aver 계산 부분을 compute() 로 분리한다.

 

⑤ 리팩토링 : 메서드 이동

    compute() 를 실질적으로 사용하는 Score로 이동한다.

    compute() 메서드는 Score data 전용 operator라 볼 수 있다.

    class 문법 → data를 정의하고 그 데이터를 다루는 operator를 만든다.

 

⑥ 인스턴스에 더 쉽게 접근하는 법 : 인스턴스 메서드

 

→ GRASP의 Information Expert

 

 

⑦ 패키지 멤버 클래스

    nested class → package member class 로 변경

 

⑧ 클래스를 패키지로 분류(관리 용이)

    클래스를 데이터 타입별 분류한다 : domain (= vo = dto)

  • Score1의 접근 제어 문법
    • public : 완전 공개
    • protected : 서브 클래스, 같은 패키지
    • (default) : 같은 패키지
    • private : 비공개

 

⑨ 객체 초기화 문법 : 생성자

Score2() 라고 생성자(constructor)를 추가한다.

 

 

0) 낱개 변수 사용
public class Exam0100 {
  public static void main(String[] args) {
    String name;
    int kor;
    int eng;
    int math;
    int sum;
    float aver;

    name = "홍길동";
    kor = 100;
    eng = 90;
    math = 85;
    sum = kor + eng + math;
    aver = (float) sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", name, kor, eng, math, sum, aver);

    name = "임꺽정";
    kor = 90;
    eng = 80;
    math = 75;
    sum = kor + eng + math;
    aver = (float) sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", name, kor, eng, math, sum, aver);

    name = "유관순";
    kor = 80;
    eng = 70;
    math = 65;
    sum = kor + eng + math;
    aver = (float) sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", name, kor, eng, math, sum, aver);
  }
}

 

 

1) 성적 데이터를 저장할 사용자 정의 데이터 타입을 만든다.
public class Exam0110 {

  public static void main(String[] args) {

    // ## 사용자 정의 데이터 타입 만들기
    // - 학생의 성적 데이터를 담을 메모리(변수)를 설계한다.
    //
    class Score {
      // 인스턴스 변수(instance variable; instance field)
      // - new 명령으로 생성되는 변수이다.
      // - 데이터를 개별적으로 다루고 싶을 때 인스턴스 변수로 선언한다.
      //
      String name; // 변수 또는 필드
      int kor;
      int eng;
      int math;
      int sum;
      float aver;
    }

    // 사용자 정의 데이터 타입을 사용하는 방법
    // - new 명령을 사용하여 설계도에 기술된 대로 메모리(변수)를 준비한다.
    // - 변수는 Heap 영역에 생성된다.
    // - 변수들이 생성된 메모리의 주소를 레퍼런스(주소 변수)에 저장한다.
    Score s1 = new Score();

    // - 클래스로 만든 메모리는 레퍼런스를 통해 접근한다.
    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 90;
    s1.math = 85;
    s1.sum = s1.kor + s1.eng + s1.math;
    s1.aver = (float) s1.sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", 
        s1.name, s1.kor, s1.eng, s1.math, s1.sum, s1.aver);

    Score s2 = new Score();
    s2.name = "임꺽정";
    s2.kor = 90;
    s2.eng = 80;
    s2.math = 75;
    s2.sum = s2.kor + s2.eng + s2.math;
    s2.aver = (float) s2.sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", s2.name, 
        s2.kor, s2.eng, s2.math, s2.sum, s2.aver);

    Score s3 = new Score();
    s3.name = "유관순";
    s3.kor = 80;
    s3.eng = 70;
    s3.math = 65;
    s3.sum = s3.kor + s3.eng + s3.math;
    s3.aver = (float) s3.sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", 
        s3.name, s3.kor, s3.eng, s3.math, s3.sum, s3.aver);
  }
}

 클래스 문법의 용도?
 1) 사용자 정의 데이터 타입 만들 때
    즉 새로운 구조의 메모리를 설계할 때 사용한다.
 2) 메서드를 묶을 때
    서로 관련된 기능을 관리하기 쉽게 묶고 싶을 때 사용한다.

 

 

2) 리팩토링: 메서드 추출(extract method), static nested class
public class Exam0120 {

  // 여러 메서드에서 공유하려면 클래스 멤버로 만들어야 한다.
  // - 특히 스태틱 멤버끼리 공유하려면 같은 스태틱 멤버로 만들어야 한다.
  static class Score {
    String name; // 변수 또는 필드
    int kor;
    int eng;
    int math;
    int sum;
    float aver;
  }

  public static void main(String[] args) {

    Score s1 = new Score();

    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 90;
    s1.math = 85;

    printScore(s1);

    Score s2 = new Score();
    s2.name = "임꺽정";
    s2.kor = 90;
    s2.eng = 80;
    s2.math = 75;
    printScore(s2);

    Score s3 = new Score();
    s3.name = "유관순";
    s3.kor = 80;
    s3.eng = 70;
    s3.math = 65;
    printScore(s3);
  }

  static void printScore(Score s) {
    s.sum = s.kor + s.eng + s.math;
    s.aver = (float) s.sum / 3;

    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", 
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }
}

 

 

3) 리팩토링: 메서드 추출(extract method) = 한 개의 메서드는 한 개의 기능을 수행해야 한다.
public class Exam0130 {

  static class Score {
    String name; 
    int kor;
    int eng;
    int math;
    int sum;
    float aver;
  }

  public static void main(String[] args) {

    Score s1 = new Score();

    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 90;
    s1.math = 85;
    compute(s1);
    printScore(s1);

    Score s2 = new Score();
    s2.name = "임꺽정";
    s2.kor = 90;
    s2.eng = 80;
    s2.math = 75;
    compute(s2);
    printScore(s2);

    Score s3 = new Score();
    s3.name = "유관순";
    s3.kor = 80;
    s3.eng = 70;
    s3.math = 65;
    compute(s3);
    printScore(s3);
  }

  static void printScore(Score s) {
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", 
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }

  static void compute(Score s) {
    s.sum = s.kor + s.eng + s.math;
    s.aver = (float) s.sum / 3;
  }
}

 

 

4) GRASP(General Responsibility Assignment Software Patterns) 패턴

     => Information Expert: 데이터를 다룰 때는 그 데이터를 갖고 있는 객체에게 묻는다.
   리팩토링: 메서드 이동(Move Method)
     => 메서드를 관련된 클래스로 이동시킨다. => 코드의 이해가 쉽다.

public class Exam0140 {

  static class Score {
    String name; 
    int kor;
    int eng;
    int math;
    int sum;
    float aver;

    // 메서드를 이용하여 이 타입의 데이터를 다룰 수 있는 연산자를 정의한다.
    // - 사용자 정의 데이터 타입 입장에서는 메서드가 연산자 역할을 한다.
    // - 즉 사용자 정의 데이터 타입에 메서드를 정의하는 것은
    //   그 데이터를 다룰 연산자를 정의하는 것이다.

    // Score 데이터 값을 다룰 수 있는 새 연산자를 정의해 보자.
    // - 다음 메서드는 Score 객체의 국,영,수 값의 합계와 평균을 계산하는 연산자이다.
    static void compute(Score s) {
      s.sum = s.kor + s.eng + s.math;
      s.aver = (float) s.sum / 3;
    }
    // 클래스 메서드
    // - static이 붙은 메서드이다.
    // - 특정 인스턴스에 대해 사용하는 것이 아니라, 모든 인스턴스에 대해 사용할 수 있다.
    // - 특정 인스턴스의 값을 다루고 싶다면 파라미터로 그 인스턴스의 주소를 받아야 한다.
  }

  public static void main(String[] args) {

    Score s1 = new Score();

    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 90;
    s1.math = 85;

    // 다음은 Score의 값을 다루는 연산자가 없을 때의 예이다.
    // core.sum = score.kor + score.eng + score.math; 
    // score.average = score.sum / 3f;

    // 사용자 정의 데이터 타입의 값을 연산자를 사용하여 다뤄보자!
    Score.compute(s1);

    printScore(s1);

    Score s2 = new Score();
    s2.name = "임꺽정";
    s2.kor = 90;
    s2.eng = 80;
    s2.math = 75;
    Score.compute(s2);
    printScore(s2);

    Score s3 = new Score();
    s3.name = "유관순";
    s3.kor = 80;
    s3.eng = 70;
    s3.math = 65;
    Score.compute(s3);
    printScore(s3);
  }

  static void printScore(Score s) {
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", 
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }


}

 

 

 

스태틱 메서드와 인스턴스 메서드

 

static 메서드(클래스 메서드) => Score.compute(s1);  에서 Score는 메서드가 소속된 클래스, s1은 파라미터 이다.

 

non-static 메서드(인스턴스 메서드) => s1. comput();  에서 s1은 메서드가 소속된 클래스의 인스턴스 주소이다.

↑ 이는 인스턴스를 보다 쉽게 다루는 메서드 문법이다.

 

 

 

인스턴스 메서드와 this

 

this: 메서드를 호출할 때 앞쪽에서 넘겨준 인스턴스 주소를 받는 built-in 로컬 변수.

non-static 메서드에만 존재한다.

인스턴스 주소는 this 내장 변수에 자동 저장된다. 인스턴스 주소를 받기 위해 따로 변수를 선언할 필요가 없다.

s1.compute();  에서 s1의 주소를 compute()에 전달한다.

 

void compute() {

  this.sum = this.kore + this.eng + this.math;

}

 

 

 

5) 인스턴스 메서드: 인스턴스 주소를 받는 더 쉬운 문법
public class Exam0150 {

  static class Score {
    String name; 
    int kor;
    int eng;
    int math;
    int sum;
    float aver;

    // static method ==> instance method
    // 클래스 메서드로 연산자를 정의하면,
    // - 계산을 수행할 때마다 인스턴스의 주소를 파라미터로 받아야 한다.
    // - 매우 번거롭다.
    //
    // public static void calculate(Score score) {
    //   score.sum = score.kor + score.eng + score.math;
    //   score.average = score.sum / 3f;
    // }
    // - 그래서 자바는 "인스턴스 메서드"라는 것을 제공한다.
    //

    // 인스턴스 메서드
    // - 메서드가 호출할 때 인스턴스의 주소를 파라미터로 넘기지 않는다.
    // - 메서드를 호출할 때(연산자를 사용할 때), 메서드 앞에 인스턴스 주소를 적는다.
    // - 이렇게 전달된 인스턴스 주소는 메서드에 내장된 this라는 변수에 자동 복사된다.
    // - 그래서 파라미터 대신 this를 사용하면 된다.
    // - 인스턴스 메서드는 static을 붙이지 않는다.
    void compute() {
      // 인스턴스 메서드를 호출할 때 넘겨준 인스턴스를 주소는
      // this 라면 내장 변수(built-in)에 보관된다.
      this.sum = this.kor + this.eng + this.math;
      this.aver = (float) this.sum / 3;
    }
  }

  public static void main(String[] args) {

    Score s1 = new Score();

    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 90;
    s1.math = 85;

    // 다음은 Score의 값을 다루기 위해 non-instance 메서드를 호출하는 예이다.
    // => non-instance 메서드 = static 메서드 = 클래스 메서드
    //
    //    Score.calculate(score);
    //

    // 클래스 메서드를 사용할 때 마다 매번 인스턴스의 주소를 파라미터로 넘겨줘야 했다.
    // 그러나 인스턴스 메서드를 사용하면 인스턴스 주소를 넘기기가 더 편하다.
    // 메서드 호출 앞에다 둔다.
    // 소스 코드의 목적을 이해하는데 더 직관적이다.
    s1.compute(); // 마치 변수 뒤에 연산자를 놓는 i++ 의 예와 비슷하다.

    printScore(s1);

    Score s2 = new Score();
    s2.name = "임꺽정";
    s2.kor = 90;
    s2.eng = 80;
    s2.math = 75;
    s2.compute();
    printScore(s2);

    Score s3 = new Score();
    s3.name = "유관순";
    s3.kor = 80;
    s3.eng = 70;
    s3.math = 65;
    s3.compute();
    printScore(s3);
  }

  static void printScore(Score s) {
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", 
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }
}

 

 

6) 패키지 멤버 클래스:

 => 여러 곳에서 사용할 클래스라면 다른 클래스에 안에 두지 말고 패키지의 멤버 클래스로 둬라!

public class Exam0160 {

  public static void main(String[] args) {

    Score s1 = new Score();

    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 90;
    s1.math = 85;
    s1.compute();
    printScore(s1);

    Score s2 = new Score();
    s2.name = "임꺽정";
    s2.kor = 90;
    s2.eng = 80;
    s2.math = 75;
    s2.compute();
    printScore(s2);

    Score s3 = new Score();
    s3.name = "유관순";
    s3.kor = 80;
    s3.eng = 70;
    s3.math = 65;
    s3.compute();
    printScore(s3);
  }

  static void printScore(Score s) {
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", 
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }


}
// 다른 패키지에서도 사용할 수 있도록 public 으로 공개한다.
public class Score {
  String name; 
  int kor;
  int eng;
  int math;
  int sum;
  float aver;

  void compute() {
    this.sum = this.kor + this.eng + this.math;
    this.aver = (float) this.sum / 3;
  }
}

 

 

7) 클래스를 역할에 따라 패키지로 분류

   => 클래스가 많을 경우 유지보수하기 쉽도록 적절한 패키지로 분산 배치한다.
   => 데이터 타입의 역할을 하는 클래스의 경우 
      보통 domain, vo(value object), dto(data transfer object) 라는 이름을 가진 패키지에 분류한다.
   => 패키지가 다르면 modifier 옵션에 따라 접근 범위가 달라진다. 
 멤버의 접근 범위 설정
   => public: 모두 공개
   => protected: 서브 클래스와 같은 패키지의 멤버는 접근 가능
   => (default): 같은 패키지의 멤버는 접근 가능
   => private: 접근 불가! 그 멤버가 속한 클래스의 내부에서만 접근 가능

package com.eomcs.oop.ex02;

import com.eomcs.oop.ex02.domain.Score1;

public class Exam0170 {

  public static void main(String[] args) {

    Score1 s1 = new Score1();

    s1.name = "홍길동";
    s1.kor = 100;
    s1.eng = 90;
    s1.math = 85;
    s1.compute();
    printScore(s1);

    Score1 s2 = new Score1();
    s2.name = "임꺽정";
    s2.kor = 90;
    s2.eng = 80;
    s2.math = 75;
    s2.compute();
    printScore(s2);

    Score1 s3 = new Score1();
    s3.name = "유관순";
    s3.kor = 80;
    s3.eng = 70;
    s3.math = 65;
    s3.compute();
    printScore(s3);
  }

  static void printScore(Score1 s) {
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n",
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }


}
package com.eomcs.oop.ex02.domain;

// 다른 패키지에서도 이 클래스를 사용할 수 있도록 하려면 public 으로 공개해야 한다.
public class Score1 {

  // 다른 패키지에서 이 설계도에 따라 만든 변수에 접근할 수 있도록 하려면
  // 접근 범위를 public으로 공개해야 한다.
  public String name;
  public int kor;
  public int eng;
  public int math;
  public int sum;
  public float aver;

  // 다른 패키지에서 메서드를 사용할 수 있도록 하려면 public 으로 공개해야 한다.
  public void compute() {
    this.sum = this.kor + this.eng + this.math;
    this.aver = (float) this.sum / 3;
  }
}

 

 

8) 생성자 도입: 인스턴스를 생성할 때 값을 초기화시키는 특별한 메서드
package com.eomcs.oop.ex02;

import com.eomcs.oop.ex02.domain.Score2;

public class Exam0180 {

  public static void main(String[] args) {

    Score2 s1 = new Score2("홍길동", 100, 90, 85);
    printScore(s1);

    Score2 s2 = new Score2("임꺽정", 90, 80, 75);
    printScore(s2);

    Score2 s3 = new Score2("유관순", 80, 70, 65);
    printScore(s3);
  }

  static void printScore(Score2 s) {
    System.out.printf("%s: %d, %d, %d, %d, %.1f\n", 
        s.name, s.kor, s.eng, s.math, s.sum, s.aver);
  }


}
package com.eomcs.oop.ex02.domain;

public class Score2 {

  public String name; 
  public int kor;
  public int eng;
  public int math;
  public int sum;
  public float aver;

  // new 연산자를 이용하여 인스턴스를 만들 때 자동으로 호출되는 특별한 문법의 메서드
  // => 생성자(constructor)
  //    - 메서드명은 클래스 이름과 같아야 한다.
  //    - 리턴 타입은 없다.
  //    - 오직 new 명령을 실행할 때 호출할 수 있다. 나중에 따로 호출할 수 없다.
  public Score2(String n, int k, int e, int m) {
    this.name = n;
    this.kor = k;
    this.eng = e;
    this.math = m;

    this.compute();
  }

  public void compute() {
    this.sum = this.kor + this.eng + this.math;
    this.aver = (float) this.sum / 3;
  }
}

Score2.class를 Eclipse의 Navigator로 보면 this 변수가 정의되어 있다. 인스턴스 메서드는 this가 내장된다.

      Local variable table:
        [pc: 0, pc: 30] local: this index: 0 type: com.eomcs.oop.ex02.domain.Score2
        [pc: 0, pc: 30] local: n index: 1 type: java.lang.String
        [pc: 0, pc: 30] local: k index: 2 type: int
        [pc: 0, pc: 30] local: e index: 3 type: int
        [pc: 0, pc: 30] local: m index: 4 type: int

 

 

 

생성자

 

생성자는 클래스 이름과 같아야 한다. 리턴타입이 없다.

 

 

 

생성자 사용 전/후

 

① 사용 전

인스턴스 생성 후 인스턴스 필드에 개별 접근하여 값 할당한다.

 

② 사용 후

인스턴스 생성시 생성자 이용해 값 할당한다. 인스턴스 생성 후 즉시 생성자 자동호출된다.

 

Score 설계도에 따라 인스턴스 생성 → 생성자 호출

 

 

 

Help > Eclipse Marketplace > "code together" 검색 후 설치

 

 


 

조언

 

*초보자로써 습관을 잘 들이는 방법은 처음에 안에서 클래스 정의하고 밖에서 쓰고싶으면 밖에 끄집어 내라. 이렇게 하다보면 밖에 클래스 정의하는 이유를 이해할 수 있다.

 

 

 

 

 


 

과제

 

/