BankApp 설계

Jan 28, 2024
BankApp 설계

1. BankApp의 목적

고객이 계좌이체 할 수 있는 서비스를 만든다. > 클래스로 설계할 것
notion image
💡
자료형 하나로 표현되는 건 클래스로 만들지 않는다. (스칼라 x, 벡터는 x)
하나의 데이터 타입으로 연속적으로 이루어진 것 > 벡터
 

2. BankApp의 Class 설계

Class 설계 (model)
notion image
💡
클래스로 설정할 만한 건, 계좌, 고객, 신용, 은행, 은행원 등이 있으나 ’현재 서비스’에서 필요한 부분은 ‘계좌와 고객’ 뿐! 계좌와 고객만 클래스로 만들 것.
이런 오브젝트를 패키지에 따로 구분 해 놓는데, 이걸 model이라고 부름
notion image
지금 서비스에 필요한 것 : 계좌이체 / 필요 없는 것 : 입금 출금 * 지금 제공하려는 서비스에서 가장 필요한 것만 객체로 만든다. (최소한만 봐야 함!) 내가 궁예해서 아 이것도 필요할 것 같은데? 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>
notion image
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 = 식별자 (고유번호)
 
notion image
이렇게 생성자를 만드니 final int id 에러가 사라짐. 기본 생성자가 final과 같이 있으면 에러! (초기화 안될 수도 있어서)
 
 
<BankApp Class>
notion image
💡
balance의 접근제한자를 default로 사용 -> 같은 패키지 안에서만 접근 가능함. 그래서 에러!! (Account랑 BankApp은 다른 패키지라) > public으로 바꿔주면 에러가 사라짐
💡
long타입 끝에 꼭! L 붙이는 거 잊지 말기!
 

 
notion image
notion image

+) void에 return이?! > 트랜젝션 때문에

💡
메인은 void라 return못하는데, 이 return은 지금 실행되고 있는 큐를 종료하는 것!! > void라 돌려줄게 없으니 프로그램을 종료하는 코드!! = 메소드를 종료 시킴!
notion image
 
 
💡
계좌 이체라는 행위를 user가 들고 있나? 아니다! 계좌 이체는 Account 클래스가 들고 있어야 하는 행위 ! user가 하는 일은 계좌 클래스가 들고 있는 메소드를 호출하는 것이지만, 그게 마치 user가 하는 행위처럼 느껴진다
 
ex. TV를 키는 것 - 나 TV가 나오게 작동하는 것 (= 티비의 상태가 변경 되는 것) - TV
 

4. transaction (= 일의 최소단위) = 서비스 ★

하나라도 실패하면 동작하지 않는다!! > '절차적으로 코딩한다.' 라고 함
💡
메서드는 하나의 일(책임/기능)만 하게 만들기!! > 그래야 오류 잡기 쉽다! 그래서, 일의 단위를 쪼개야한다. ex. 출금 메소드 하나로 ‘금액 수정’과 ‘출금 계좌’를 동시에 확인하면, 오류 확인이 어려움 -> 출금은 출금만, 계좌 확인은 계좌 확인만 하게 끔 일의 최소 단위를 나눠라.
 

4-1. 트랜잭션이란?

notion image
이체는 출금,입금이 동시에 일어나는 것. 즉, 출금과 입금을 한번에 관리할 수 있는 파일을 만들어야 편함 >> 이걸 서비스라고 함 . (트랜젝션 = 서비스)★ >> 트랜젝션은 개발자의 오류나 실수 (빼먹는 것 등)을 줄여줄 수 있음. * BankService 클래스에 코드 작성했음
💡
* 출금만 하는 것 > 출금만 하는게 일의 최소 단위 (트랜젝션) * 이체 > 출금, 입금 둘 다 하는게 ‘이체’의 일의 최소 단위 (트랜젝션) * 트랜젝션은 가변적이다.
 
트랜잭션 예시
<간단한 트랜잭션> > ???
notion image
 
<트랜잭션 예시> - 출금의 트랜잭션
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 클래스에서 바로 사용
 
<결과>
notion image
 
<패키지 정리>
notion image
 
 

<상태 확인 코드 > - 상태 확인은 메소드 x

notion image
 

<코딩에서 if의 사용법>

if의 역할 펼쳐보기
  1. 기본기. 긍정으로 물어보는 코드
notion image
 
  1. 유용. 부정으로 물어보는 코드 (걸러내는 코드로 사용) ★
notion image
<부정으로 물어봤을 때 징점> 1. 비정상적인 경우를 걸러내는 역할. 2. 데이터의 신뢰도를 높이기 위한 필터 역할. 3. 코드 깔끔 (if 로 정상적인 경우를 걸러내서 실행하려 하면 코드가 더러워진다.)
 
코드 짤때 if 는 비정상적인 경우를 걸러내는 역할로 사용하라
 

 
💡
파일을 이렇게 Account, User, BankApp, BankService 등으로 나누면 어디가 잘못됐는지 파악하기가 더 쉬워짐
  • 코드가 잘못 됐을 때, service를 봐라!
 
 
Share article

codingb