1. BankApp의 목적
고객이 계좌이체 할 수 있는 서비스를 만든다. > 클래스로 설계할 것
자료형 하나로 표현되는 건 클래스로 만들지 않는다. (스칼라 x, 벡터는 x)
하나의 데이터 타입으로 연속적으로 이루어진 것 > 벡터
2. BankApp의 Class 설계
Class 설계 (model)
클래스로 설정할 만한 건, 계좌, 고객, 신용, 은행, 은행원 등이 있으나
’현재 서비스’에서 필요한 부분은 ‘계좌와 고객’ 뿐! 계좌와 고객만 클래스로 만들 것.
이런 오브젝트를 패키지에 따로 구분 해 놓는데, 이걸 model이라고 부름
지금 서비스에 필요한 것 : 계좌이체 / 필요 없는 것 : 입금 출금 * 지금 제공하려는 서비스에서 가장 필요한 것만 객체로 만든다. (최소한만 봐야 함!) 내가 궁예해서 아 이것도 필요할 것 같은데? XXX 최초에 요구한 사항만 기능으로 만들어 줄 것! 그 이후에 필요한게 생기면 추후에 '업데이트'를 하라.
유지보수 > 기존에 짜둔 코드를 뜯어 고치는 것. 예쁘게 잘 짜면 유지보수도 쉽다.
3. BankApp 코드 작성
작성한 코드 설명
model 패키지
<User Class>
package ex04.example2.model; public class User { private final int id; private String name; private String email; public User(int id, String name, String email) { this.id = id; this.name = name; this.email = email; } }
1. User 클래스에 필드값과 생성자를 생성한다. 1-1. 고객 중 동일한 이름이 있다면? 고유번호로 비교(구별)하는게 더 빠르다! = 프라이머리키로 설정, 고유번호는 변경될 리가 없으니 final 1-2. 만약 String name = "홍길동";이라고 하면 홍길동이라는 고객만 만들 수 있음 1-3. email = 부식별자 (유일값). 식별자는 보통 2개정도 만든다 1-4. final 키워드가 붙은 변수는 반드시 객체가 생성될 때 초기화되어야 한다. final 키워드는 변수에 최초 할당된 값을 변경할 수 없도록 한다. * final 값이 있으니 생성자는 꼭 초기화 해줘야한다.
<Account Class>
1. Account에 적합한 상태값들을 생성한다. 1-1. 프라이머리키 역할을 하는 고유번호 (여기선 userId)를 꼭 생성해 줄 것! User 클래스에서 Id 부분. 1-2. 계좌번호는 int형이 아닌 String으로 받는다. (숫자가 너무 커서 int에 안담김) 1-3. 잔액(balance)는 int로 만들면 최대 21억까지만 이체되니까 long타입을 썼다. 1-4. final 키워드가 붙은 변수는 반드시 객체가 생성될 때 초기화되어야 한다. final 키워드는 변수에 최초 할당된 값을 변경할 수 없도록 한다. 2. 기본 생성자 (노란박스 부분) 있으면 > final 오류
id = 계좌 번호 / balance = 계좌 잔액 / userId = 식별자 (고유번호)
이렇게 생성자를 만드니 final int id 에러가 사라짐. 기본 생성자가 final과 같이 있으면 에러! (초기화 안될 수도 있어서)
<BankApp Class>
계좌 이체라는 행위를 user가 들고 있나? 아니다!
계좌 이체는 Account 클래스가 들고 있어야 하는 행위 !
user가 하는 일은 계좌 클래스가 들고 있는 메소드를 호출하는 것이지만,
그게 마치 user가 하는 행위처럼 느껴진다
ex. TV를 키는 것 - 나
TV가 나오게 작동하는 것 (= 티비의 상태가 변경 되는 것) - TV
4. transaction (= 일의 최소단위) = 서비스 ★
하나라도 실패하면 동작하지 않는다!! > '절차적으로 코딩한다.' 라고 함
메서드는 하나의 일(책임/기능)만 하게 만들기!! > 그래야 오류 잡기 쉽다!
그래서, 일의 단위를 쪼개야한다.
ex. 출금 메소드 하나로 ‘금액 수정’과 ‘출금 계좌’를 동시에 확인하면, 오류 확인이 어려움
-> 출금은 출금만, 계좌 확인은 계좌 확인만 하게 끔 일의 최소 단위를 나눠라.
4-1. 트랜잭션이란?
이체는 출금,입금이 동시에 일어나는 것. 즉, 출금과 입금을 한번에 관리할 수 있는 파일을 만들어야 편함 >> 이걸 서비스라고 함 . (트랜젝션 = 서비스)★ >> 트랜젝션은 개발자의 오류나 실수 (빼먹는 것 등)을 줄여줄 수 있음. * BankService 클래스에 코드 작성했음
* 출금만 하는 것 > 출금만 하는게 일의 최소 단위 (트랜젝션)
* 이체 > 출금, 입금 둘 다 하는게 ‘이체’의 일의 최소 단위 (트랜젝션)
* 트랜젝션은 가변적이다.
트랜잭션 예시
<간단한 트랜잭션> > ???
<트랜잭션 예시> - 출금의 트랜잭션
public class BankService { public static void 출금(Account withdrawAccount, long amount) { //출금의 트랜잭션 (과정 총 3개) //0원 검증 if(amount <= 0) { System.out.println("0원 이하 금액 출금 불가능"); return; } if(withdrawAccount.잔액부족하니(amount)){ System.out.println("잔액이 부족합니다"); return; } withdrawAccount.출금(amount); }
이 코드에서 '출금'의 트랜잭션은 총 3가지 과정으로 나뉘어져 있다. 1. 0원을 검증한다 > 실패시 withdrawAccount.출금(amount); 작동 안함! 2. 잔액이 충분한지 검증한다 > 실패시 withdrawAccount.출금(amount); 작동 안함! 이렇게, 위의 안전장치(?)들을 모두 통과해야만 출금 기능이 이루어진다.
매개변수를
Account
타입으로 받는 이유는 출금할 계좌 정보를 매개변수로 전달받기 위해서 > 이 변수에는 실제로 출금을 수행할 계좌의 정보가 전달<절차 지향 코드>
코드 보기
<User Class>
package ex04.example.model; public class User { final int id; //고객 중 동일한 이름이 있다면? 고유번호로 비교하는게 더 빠르다! (프라이머리키) //계좌번호는 변경될 리가 없으니 final String name; //String name = "홍길동";이라고 하면 홍길동이라는 고객만 만들 수 있음 String email; //부식별자 (유일값). 식별자는 보통 2개정도 만든다 public User(int id, String name, String email) { //초기화 해야하니까 생성자 필요! (alt + insert) this.id = id; this.name = name; this.email = email; } }
<Account Class>
package ex04.example.model; public class Account { //account가 왜 오브젝트냐? 하나 안들고있잔아 여러개를 들고 있잖아 public final int id; //(교육용이라 4자리, int로 만듬) 계좌번호 - 숫자 10자리로 만들어져있다. 계좌번호는 번호지만 int에 99억 담기냐? 안담김! 그래서 문자로 만듬 //final이라는 상태값은 반드시 new가되면 초기화가 되어야함. 초기화 코드가 없어서 에러! public long balance; //잔액. int로 만들면 21억만 이체 됨. 그래서 long public int userId; //식별하기 위함. 1이 들어왔다 > ssar 계좌 public Account(int id, long balance, int userId) { //생성자를 만드니 final int id 에러가 사라짐. 기본생성자가 같이 있으면 에러!(초기화 안될 수도 있어서) this.id = id; this.balance = balance; this.userId = userId; } //객체 내ㅔ부의 값을 볼때 toString을 만들면 편함 @Override public String toString() { return "Account{" + "id=" + id + ", balance=" + balance + ", userId=" + userId + '}'; } }
<BankApp Class>
package ex04.example; import ex04.example.model.Account; import ex04.example.model.User; public class BankApp { public static void main(String[] args) { // 고객 2명 만들기 User u1 = new User(1, "ssar", "ssar@nate.com"); User u2 = new User(2, "cos", "cos@nate.com"); // 계좌 2개 만들기 Account a1 = new Account(1111, 1000L, 1); Account a2 = new Account(2222, 1000L, 2); // 고객에게 받은 정보 int amount = 100; int sender = 1111; int receiver = 2222; // 일의 최소 단위 == 트랜잭션 // 계좌이체 하기 ------------------------------------------ // 0원 검증 if (amount <= 0) { System.out.println("0원 이하 금액을 이체할 수 없습니다"); return; } // 잔액 검증 if (a1.balance < amount) { System.out.println("잔액부족"); return; } // 계좌번호 검증 if (a1.id != sender) { System.out.println("보내는 사람 계좌번호는 존재하지 않습니다"); return; } if (a2.id != receiver) { System.out.println("받는 사람 계좌번호는 존재하지 않습니다"); return; } // 이체 a1.balance = a1.balance - amount; a2.balance = a2.balance + amount; ~~~여기까지 트랜젝션~~~ // 이체결과 확인 System.out.println(a1); System.out.println(a2); } }
<객체 지향 코드> ★
코드 보기
<User Class>
package ex04.example2.model; public class User { private final int id; private String name; private String email; public User(int id, String name, String email) { this.id = id; this.name = name; this.email = email; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + '}'; } }
<Account Class>
package ex04.example2.model; //객체의 상태를 변경, 객체의 상태를 확인 public class Account { private final int id; private long balance; private int userId; //account id랑 구분하기 위해서 userId //긍정으로 물어봐서 내가 원하는 결과를 true로 받자 public boolean 잔액부족하니(long amount) { if(balance < amount) { return true; } return false; } //메서드는 하나의 일(책임)만 하게 만들기. 애는 출금이니까 출금!!하는 기능만. //만약 이체라고 했으면 출금도 해야하고 입금도 해야하니까 나중에 문제가 생겼을때 출금이 잘못된건지 입금이 잘못된건지 구분하기가 어렵다. public void 출금(long amount) { this.balance = this.balance - amount; } public void 입금(long amount) { this.balance = this.balance + amount; } //상태가 나오면 생성자와 toString은 디폴트로 깔아주기 public Account(int id, long balance, int userId) { this.id = id; this.balance = balance; this.userId = userId; } @Override public String toString() { return "Account{" + "id=" + id + ", balance=" + balance + ", userId=" + userId + '}'; } }
<BankService Class> - 트랜젝션 관리 ★
package ex04.example2; import ex04.example2.model.Account; //트랜잭션 관리 public class BankService { public static void 출금(Account withdrawAccount, long amount) { //출금의 트랜지션 (과정 3개) //0원 검증 if(amount <= 0) { System.out.println("0원 이하 금액 출금 불가능"); return; } if(withdrawAccount.잔액부족하니(amount)){ //return으로 받은 값이 'true'면 해당 코드 실행 System.out.println("잔액이 부족합니다"); return; } withdrawAccount.출금(amount); //false면 이거 실행? } public static void 이체(Account senderAccount, Account receiverAccount, long amount) { //타입으로 객체를 전달받음!! //이 코드는 상태를 확인하는 것도, 변경하는 것도 아님 (검증하는 것 = 벨리데이션?) //이체의 트랜지션 if(amount <= 0) { System.out.println("0원 이하 금액 이체 불가능"); return; } if(senderAccount.잔액부족하니(amount)){ System.out.println("잔액이 부족합니다"); return; } //0원 이하를 이체하면 아래의 코드는 실행x (return 때문에) senderAccount.출금(amount); receiverAccount.입금(amount); } }
Account withdrawAccount를 매개변수로 받았다는 것은 withdrawAccount라는 이름의 변수를 선언하고, 그 타입이 Account 클래스인 것을 의미한다.
이 매개변수는 이미 생성된 Account 객체를 전달받을 수 있으며,
따라서 별도로 withdrawAccount 클래스를 만들 필요는 없다.
Account withdrawAccount == Account withdrawAccount = new Account()
Account를 withdrawAccount라는 변수명으로 부른다고
출금은 단일 계좌에서 해당 계좌의 잔액에서 돈을 감소시키는 것을 의미 이체는 두 개 이상의 계좌 간에 돈을 이동시키는 것을 의미
<BankApp Class>
package ex04.example2; import ex04.example2.model.Account; import ex04.example2.model.User; public class BankApp { public static void main(String[] args) { // 1. 고객 2명 만들기 User ssar = new User(1, "ssar", "hong@naver.com"); User cos = new User(2, "cos", "kim@naver.com"); User love = new User(3, "love", "love@naver.com"); // 2. 계좌 2개 만들기 Account ssarAccount = new Account(1111, 1000L, 1); Account cosAccount = new Account(2222, 1000L, 2); Account loveAccount = new Account(3333, 1000L, 3); // 3. 고객에게 정보 받기 (amount) //이체금액 얼마인지 long amount = 100L; // 4. 이체 (ssar -> cos 100원) BankService.이체(ssarAccount, cosAccount, amount); // 5. 이체 (ssar -> love 100원) BankService.이체(ssarAccount, loveAccount, amount); // 6. 이체 (cos -> love 100원) BankService.이체(cosAccount, loveAccount, amount); // 7. 객체 상태 확인 System.out.println(ssarAccount); System.out.println(cosAccount); System.out.println(loveAccount); // 8. 출금 BankService.출금(ssarAccount, amount); BankService.출금(loveAccount, amount); //이런 식으로 짜야 코드 유지보수가 쉬워짐!! } }
public으로 선언되어 있기 때문에 BankApp 클래스에서 바로 사용
<결과>
<패키지 정리>
<상태 확인 코드 > - 상태 확인은 메소드 x
<코딩에서 if의 사용법>
if의 역할 펼쳐보기
- 기본기. 긍정으로 물어보는 코드
- 유용. 부정으로 물어보는 코드 (걸러내는 코드로 사용) ★
<부정으로 물어봤을 때 징점> 1. 비정상적인 경우를 걸러내는 역할. 2. 데이터의 신뢰도를 높이기 위한 필터 역할. 3. 코드 깔끔 (if 로 정상적인 경우를 걸러내서 실행하려 하면 코드가 더러워진다.)
코드 짤때 if 는 비정상적인 경우를 걸러내는 역할로 사용하라
파일을 이렇게 Account, User, BankApp, BankService 등으로 나누면
어디가 잘못됐는지 파악하기가 더 쉬워짐
- 코드가 잘못 됐을 때, service를 봐라!
Share article