JAVA 웹개발 과정 국비 12일차 정리

12일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


1. Static 키워드

멤버필드는 인스턴스를 만들어야 생성된다고 배웠다.
하지만 아래와 같은 애들은 쓰기 전에 인스턴스를 생성하지 않아도 쓸 수 있었다.

Math.random();
Integer.parseInt();

또 보통 멤버필드를 사용할 때는 std.setId(1001);과 같이 인스턴스변수에 .을 찍어서 사용했었는데, 얘네들은 클래스에 .을 찍어서 사용하는 것을 볼 수 있다.
바로 이런 상황을 Static 키워드로 이해할 수 있다.

public class Temp {
	public int a;		// Instance Member Field
	public static int b;	// Class Member Field
}

멤버필드는 보통 정보은닉 때문에 public 키워드를 쓰지 않지만 이해를 돕기 위해 두가지 멤버필드를 만들었다.

a 변수는 new로 인스턴스가 만들어지기 전까지는 존재하지 않는 변수이다.
그러나 static 키워드가 붙은 b는 이 규칙에서 벗어난다. 인스턴스의 생명주기와는 관련이 없어지며 main이 실행이 되면 같이 선언이 된다. 즉, 딱 1번만 만들어지는 것이다.

1) Class Member Field

  • static 키워드가 붙은 Member Field
  • 인스턴스가 없어도 이미 만들어진 변수.
  • 바로 사용할 수 있는 변수.
  • 클래스 단계에서도 사용할 수 있는 멤버필드이다.
  • 언제나라는 타이틀이 붙는다.

2) public static (정적 변수)

C, C++에서는 언제나 어디서나 쓸 수 있는 public static 변수를 전역변수(전 지역에서 쓰는 변수)라고 한다.
그러나 자바에서는 전역변수라는 개념이 존재하지 않는다.
그래서 자바에서는 public static 키워드로 전역변수를 흉내내는 것이다.

다시 코드로 돌아가서,
변수 b는 heap 메모리에 저장되는 것이 아니라 data 메모리에 저장된다.
즉, static이 붙은 변수는 data 메모리에 저장된다.
heap 메모리에 인스턴스들이 저장되는데, static이 붙은 변수는 data 메모리에 저장된다?
이 말은, 인스턴스 안에 static이 붙은 변수 b가 저장되지 않는다는 것이다.

static 이 붙은 애들은 클래스의 멤버라고 보기 어렵다.

근데 왜 b를 Class Member Field라고 하는가?
여기서의 b는 인스턴스 내에 b가 멤버필드로 존재한다는 의미로 참조변수를 통해 진짜 b가 저장된 data 메모리의 위치를 가져오는 변수가 되기 때문이다.
따라서 b는 하나밖에 없고, 공유가 된다.

3) public static VS private static

  • public static
    • 프로그램 전체에서 공유되는 변수
  • private static
    • 클래스 내에서만 공유되는 변수

4) t.a VS Temp.b

변수 a에 값을 넣으려면, new로 인스턴스를 생성해줘야 한다.

Temp t = new Temp();
t.a = 10;

변수 b는 인스턴스를 생성하지 않아도 클래스에 .으로 접근 가능하다.

Temp.b = 10;

5) static 남발 금지

static 변수를 마구잡이로 쓰게 되면 아래와 같은 단점이 생긴다.

  1. 메모리 효율성이 떨어진다.
    메모리 효율성 : 지역변수 > 멤버변수 > static
    static 변수는 main이 실행하면서 생성되고, 프로그램이 종료되는 순간까지 사라지지 않기 때문에, 지역변수나 멤버변수보다는 메모리를 많이 잡아먹는다.
  2. 동시성 이슈가 있다.
    나중에 배우는 개념이지만, 코드 실행 흐름이 여러개가 동시에 생성될 수 있는데 그 흐름들이 동시에 static 변수에 접근하게 되면 엉뚱한 값을 덮어쓰게 되는 경우가 생길 수 있다.

따라서 static을 사용할 때 가장 높은 효율을 보여주는 때에만 적절히 사용하자!

6) static을 어떤 때 사용해야 할까?

  • 프로그램 전체에 걸쳐서 단 한순간도 메모리에서 내려가면 안되는 것들.
    • 게임 속 주인공 정보 -> static
    • 몬스터, npc 정보 -> 지역변수
  • 클래스에 속하지 않고 프로그램과 별도로 독립적으로 실행되는 기능
    • Math.random()

2. MVC 패턴

프로그램을 만들 때 효율적으로 만들 수 있게하는 방법.
코드 유지보수와 팀플레이가 더 쉽게 가능하도록 한다.

회원 관리 시스템 예제 코드를 리팩토링 해보며 MVC 패턴을 이해해보려고 한다.
마찬가지로 원래 코드는 길지만, 부분만 따로 떼와서 정리할 예정이다.

1) 코드 리팩토링하기

① 코드를 분리하는 기준

  • 회원 정보를 저장하는 클래스 (Silver)
    • id, name, point를 멤버필드로 가지는 실버 등급 클래스.
  • 데이터를 관리하는 클래스 (Manager)
    • 데이터 보관, 꺼내기, 알림, 검색, 수정, 삭제, 정렬, 랭킹 조절 등의 기능을 담당.
    • UI랑 아무런 관련이 없어야 한다.
  • UI (User Interface) 관련 클래스 (Main)
    • 그래픽 관련은 Main 클래스에서 정리한다.

② 저장소 옮기기

members는 데이터를 저장하는 저장소의 기능을 하기 때문에 Manager 클래스로 옮겨준다.
index 변수도 마찬가지.
멤버변수로 가져가기 때문에 private 키워드를 붙여준다.

// <수정 전>
// Main class 
Silver[] members = new Silver[10];
int index = 0;
// <수정 후>
// Manager class 
private Silver[] members = new Silver[10];
private int index = 0;

③ 출력 코드들은 어디로?

nextLine은 콘솔로부터 입력을 받는 기능인데, 만약 이 기능을 Manager 클래스 안에 넣어버린다면 그래픽 프로그램으로 바꾸는 순간 Main 뿐 아니라 Manager 클래스도 같이 수정을 해주어야 한다.
콘솔에서 입력을 받는 기능은 필요가 없어지기 때문이다.
따라서 이부분은 Main에 남겨준다.

// <수정 없음>
// Main class 
while(true) {
	Scanner sc = new Scanner(System.in);

	System.out.println("== 회원관리 시스템 ==");
	System.out.println("1. 신규 회원 등록");
	System.out.println("2. 회원 목록 출력");
	System.out.println("3. 시스템 종료");
	System.out.print(">> ");
	int menu = Integer.parseInt(sc.nextLine());
	 
	if(menu==1) {
        System.out.print("id를 입력해주세요. >> ");
        int id = Integer.parseInt(sc.nextLine());
        System.out.print("이름를 입력해주세요. >> ");
        String name = sc.nextLine();
        System.out.print("포인트를 입력해주세요. >> ");
        int point = Integer.parseInt(sc.nextLine());

회원 정보를 등록할 저장소(배열)이 있어야하므로 위쪽에 아래와 같이 Manager 인스턴스를 생성해준다.

// <수정 후>
// Main class 
Manager manager = new Manager();

④ Manager 클래스 호출해서 일 시키기

원래 코드를 보니, 회원의 정보를 가진 Silver 인스턴스가 생성되어 변수 s에 저장하고 있다.
이 인스턴스를 저장하는 배열을 Manager 클래스가 가지고 있으니, Manager를 불러서 s 변수 주소를 저장하라고 시켜야 한다.
그러나 members는 private 키워드를 가지고 있기 때문에, 접근이 불가능하다.
따라서 Manager 클래스가 members에 값을 넣어줄 수 있는 기능을 하는 메소드를 만들어주어야 한다.

// <수정 전>
// Main class 
Silver s = new Silver(id, name, point);
members[index++] = s; 
// <수정 후>
// Manager class 
public void addMember (Silver s) {
	this.members[index++] = s; 
}

// Main class
Silver s = new Silver(id, name, point);
manager.addMember(s);  // manager야 s 변수 주소 저장해줘!   

⑤ Manager야, 출력해줘..?

이제 1번 등록 코드는 리팩토링이 끝났다.
2번으로 넘어가보자.
2번의 기능은 회원 목록을 출력하는 것이다.
1번과 똑같이 메소드를 만들어 Manager를 불러서 시켜야지! 라고 생각했는데.. 음..?

Manager 클래스는 UI에 관여하지 않는다고 했는데..?
그럼 어떤 기능을 만들어야할까?
출력에는 관여하지 않고, 자신의 배열을 Main한테 던져주면서 ‘여기있어. 출력은 니가 알아서해’까지만 하는 기능을 만들어야 한다!
그 기능이 바로 getter이다.
getter 메소드를 이용하여 출력 기능은 건드리지 않고 코드를 리팩토링 할 수 있다.

// <수정 전>
// Main class 
for(int i=0;i<index;i++) {
	System.out.println(members[i].getId()+"\t"+members[i].getName()
			+"\t"+members[i].getPoint()+"\t"+members[i].getBonus());
}
// <수정 후>
// Manager class 
public Silver[] getMembers() {
	return members;
}

public int getIndex() {
	return index;
}


// Main class 
Silver[] members = manager.getMembers();
for(int i=0;i<manager.getIndex();i++) {
	System.out.println(members[i].getId()+"\t"+members[i].getName()
			+"\t"+members[i].getPoint()+"\t"+members[i].getBonus());
}

2) 개발자님, Gold 클래스 추가 좀 해주세요

코드를 리팩토링해서 뿌듯하게 잘 끝내서 의뢰한 업체에 프로그램을 주었다.
정말 성공적으로 끝냈군!
그러나 몇 주 뒤, Gold 클래스를 추가해달라는 요청을 해왔다.
별거없지 뭐! 라고 생각하고 일을 시작했는데…
Silver 등급과 보너스 포인트 비율만 다르고 다 똑같네? 그럼 복붙하면 되겠다!
조금 귀찮은 일이지만 할만 하겠다!
라고 생각했는데.. 만약 등급이 10개가 생긴다면? 100개가 생긴다면?

3) 치명적인 문제 3가지

이렇듯 이 회원관리 시스템은 리팩토링까지 했지만 좋은 프로그램이 아니다.
이 프로그램의 치명적인 문제점은 3가지가 있다.

  1. 코드 중복도가 높다. -> 상속 문법을 통해 해결
  2. 코드 결합도가 높다. -> 다형성 문법을 통해 해결
  3. 저장소 문제 -> Collection Framework를 통해 해결

앞으로 이 문제들을 하나씩 차근차근 해결해나가는 방법을 배울 것이다.

3. 상속

1) 상속이란

하나의 클래스가 가지고 있는 내용을 다른 클래스에 물려주는 것

2) 상속의 목적

  • 특정 클래스의 기능을 나의 클래스에도 상속받아 사용하기 위함.
  • 다형성 성질을 이용하기 위해서.
  • 코드 중복도를 낮추기 위해서.

3) IS - A 상속관계

아래와 같은 IS - A 관계가 성립할 때만 상속을 사용해라!

Tiger is a [ ]
Bear is a [ ]
-> 공통된 성질들만 뽑아서 Animal을 만든다.
이때, Animal과 Tiger, Bear는 상속관계라 한다.

Tiger와 Bear가 가지고 있던 공통 코드가 Animal 하나로 축약되며 코드 중복도가 낮아진다.
Animal 하나를 수정하면 Tiger와 Bear가 동시에 수정되므로 유지보수에 유리해진다.

4) 회원 관리 시스템에 적용하기

Silver is a [ ]
Gold is a [ ]
네모에 들어갈 말은 여러가지가 있지만 Grade 로 정해보자.

① Grade 클래스 생성

② 공통 속성을 Grade에 옮기기

Gold 클래스와 Silver 클래스의 있는 멤버변수와 메소드를 모두 Grade 클래스로 옮기는 바람에, Main에 있던 코드들이 다 에러가 뜬다.

③ extends

에러가 뜨는 것을 없애려면, Gold 클래스와 Silver 클래스가 Grade 클래스를 상속 받도록 하여 기능을 다시 살려주면 된다.
바로 extends를 이용하는 것이다.

public class Gold extends Grade{
	// Gold는 Grade를 상속받는다.
}

Grade와 Gold, Silver는 상속관계가 형성되었으므로,
Grade는 부모클래스(상위클래스 = Super Class)라 부르고,
Gold와 Silver는 자식클래스(하위클래스 = Derived Class)라 부를 수 있게 된다.

④ getBouns 메소드

Gold 등급의 보너스 포인트 이율은 3%이고 Silver 등급의 이율은 2%로 엄밀히 말하자면 아예 같은 기능은 아닌데, Grade 클래스에 같이 넣어야하나? 라고 생각될 수 있다.
뒤에서 자세한 건 배우므로 패스하지만 결론은 어쨌든 같은 이름으로 비슷한 기능을 쓰므로 Grade 클래스에 같이 넣어주는 것이 좋다고 한다.

⑤ super

②번 과정에서 생성자를 지우는 바람에 아래 코드들이 에러가 났다.

// 입력하기 귀찮으니 그냥 입력값 받았다치고 정보 입력하는 코드
manager.addMember(new Gold(1001, "Tom", 1000));
manager.addMember(new Silver(1002, "Jack", 2000)); 

다시 생성자를 써줘볼까?

// Gold class extends Grade 
public Gold(int id, String name, int point) {
	this.id = id;
	this.name = name;
	this.point = point;
}

그런데 id, name, point에 빨간 줄이 가면서 에러가 났다.
이유는 Grade 클래스에 옮겨준 멤버변수들이 private이기 때문에 상속받은 하위 클래스에서 사용할 수 없는 것이다.
해결할 수 있는 방법은 두가지이다.

  1. 상속받은 setter를 이용하여 해결.
    방법은 될 수 있지만 대부분 사용하지 않는 방법
  2. super()를 사용한다.
    상위(super)클래스의 생성자를 불러서 해결하는 방법
    자동으로 만들어져있는 생성자(암묵적 문법)

2번으로 해결하면 아래와 같이 코드를 수정해줄 수 있다.
또한 Grade에도 생성자를 작성해주어야 한다.

// Gold class extends Grade
public Gold(int id, String name, int point) {
	super(id, name, point);  // 상위 클래스의 생성자 호출
}

// Grade class
public Grade(int id, String name, int point) {
	this.id = id;
	this.name = name;
	this.point = point;
}

그런데, 갑자기 비어있는 Silver 클래스에 빨간 줄이 가면서 오류가 생겼다.
위에 언급했듯이, 상속받은 클래스는 자동으로 super클래스가 들어있는 생성자가 만들어진다.
Silver 클래스가 보기에는 비어있지만, 사실은 super()가 들어있는 생성자가 자동으로 생성되어 숨어있다.

// Silver class extends Grade
public silver() {
	super();  // 숨어있는 생성자 (default constructor)
}

위에서 Grade 클래스에 명시적으로 생성자를 생성하는 바람에 default 생성자가 무시되어, 자동으로 Silver 클래스가 오류가 났던 것이다.
따라서 Grade 클래스에 default 생성자를 생성해주거나 Gold처럼 매개변수가 있는 생성자를 생성해주면 에러는 제거된다.

// Silver class extends Grade
public silver(int id, String name, int point) {
	super(id, name, point);  
}

물론 default constructor도 필수적으로 생성해주자!

댓글남기기