1. 비지니스 알기
- 고객이 계좌 이체를 할 수 있는 서비스를 만들꺼다.
- 이것을 클래스로 설계한다.
- 어떤 오브젝트가 필요할까?
2. 무엇을 Class로 만드는 것이 좋을까?
실생활이랑 연관지어 생각해본다
계좌, 돈, 계좌번호, 고객, 은행원, 은행, 신용
이제부터 스칼라 데이터인지 오브젝트 데이터인지 구분해본다.
스칼라는 단일 데이터 1건
백터는 단일 데이터가 연속적인인거
오브젝트는 상태를 가지고 있다. —> 벡터가 아니다.
돈을 표현 할때는 ? 자료형에서 int, long 로 그럼 오브젝트가 아니다
고객은 이름 하나로 표현이 될까? —> 나이, 성별 … 여러개의 타입이 섞여 있다. 이건 오브젝트
돈, 계좌번호 오브젝트가 아님
계좌 —> 계좌번호, 돈(계좌안에 얼마 있는지), 계좌가 누구껀지 소유주 —> 오브젝트
신용 —> 신용점수, 점수만 있으면 누구의 신용인지 알까? —> 오브젝트
은행 —> 여러가지 정보를 필요로 한다. —> 오브젝트
은행원 —> 사원번호, 직급 —> 오브젝트
앱을 만들때 너무 거대하게 생각하지 않는다.
그러면 프로그램이 복잡해지기 때문에 최소 필요한 오브젝트를만 생각하고
요구사항에 맞춰 프로그램을 만든다.
위에 요구 사항을 고객이 계좌이체를 할 수 있는 서비스를 만든다.
필요한건 고객과 계좌 두개만 있으면 된다.
소프트웨어 개발자는 요구 사항에 맞게 유지보수 할 수 있는 프로그램을 짜야함
외주 받았는데 고객이 계좌이체를 할 수 있는 서비스를 만들어 달라고 하면
코드를 메인에 다 넣어서 만들면 유지보수 불가하게 만든거다.
먼저 코드를 작성 하기전에 패기지를 만들어해보자

패키지 example —> BankApp 먼저 만들고
model 패키지에 관리 편하게 Account, User오브젝트를 두개 만든다.
이제는 메인에 다 넣지 않는다.
첫번째로 설정했던 계획이 완벽 할 수 없다.
뭔가 경우의 수들이 나온다.
변수들을 잡아가면서 계속 계속 뱡향을 수정 해가면서 나아가야 함
처음 목표 중간 중간 방향 수정 계속일어남
계획 설계 완벽 할 수 없다.
목표가 바뀔 수 있다.
최대한 요구 사항이 있을때 최대한 작게 프로그램을짜서 빠르게 피드백을 받아야 함
그래야 빨리 바꿀 수있고 디버깅도 됨
3. 프로그램 설계 → Class로 설계 하기로 했다.
예제코트
3-1. User 클래스 설계
package ex04.example.modle; public class User { final int id; // --> 변경불가 // 모든 고객들은 고유번호를 가지는 것이 좋다. // 고유번호 필요 이유 // 동일한 데이터의 유저들을 사용할 수 있다. --> 코드 넘버 비교가 제일 빠르다. String name; // 고객 이름 String email; // 이메일 // 초기화 해야 되니까 반드시 생성자가 필요하다. // ait + insert 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 + '\'' + '}'; } }
3-2. BankApp에서 고객 생성하기
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"); } }
다음에 행위 선택한다.
예를 들어 자판기가 콜라를 내려주고 텔레비전 티비가 켜진다 누가 키냐? 사람이 키는데
사람이라는 오브젝트가 티비 on 이라는 메서드를 들고 있을까?
아니다 상태를 변경하는 행위는 그 상태를 들고 있는 애가 가져간다.
텔레비전이 티비 on 이라는 것을 들고 있다.
따라서 은행앱에서 유저가 계좌 이체를 하고 행위는 고객이 한다.
계좌 이체라는 메서드 행위를 계좌의 잔액을 변경하는 것이 계좌의 이체이다.
계좌가 들고 있는 행위를 보자
그러면 사람이 행위를 들고 있지 않으면 하는 일은 계좌가 들고 있는 메서드를 호출하는 것이다.
계좌의 메서드를 호출한다. = 그게 행동하는 것!!
계좌 이체 행위는 계좌가 들고 있어야 한다.
고객은 레퍼런스에 접근해서 그 메서드를 호출하는 것!
다른 예로는 자동차를 발로 달리게 하는 건 사람이고
엑셀은 메서드이다.
자동차의 상태가 변경 → 사람이 발로 엑셀을 밟으면 자동차가 달린다.
따라서 엑셀은 메서드는 자동차가 들고 있어야 한다.
처음에는 헷갈릴 수 있다.
내가 하는 행위 처럼 보이지만 내가 하는 행위가 아니다.
3-3. Account 설계
package ex04.example.modle; // 객체의 상태를 변경, 객체의 상태를 확인 public class Account { private final int id; // 4자리만 할꺼라서 int로 함 --> 변경불가 // 계좌번호, 유일한 값 // 이런거는 제약 조건을 걸어서 계좌번호는 숫자 4자리수로 만들어진다. // 만약 숫자 10자리로 만들어 준다고 하면 // 0000000000 max가 10자리면 99억 int에 담기지 못한다. // 고유값이기 때문에 문자열로 만들어야한다. // 계좌번호는 번호지만 문자열로 만든다. // 돈은 String 못만든다. private long balance; // 잔액 private int userId; // 누가 만들었는지가 필요 식별자는? // 식별하려면 유저의 id가 필요 // 유일하게 식별 할 수있는 것 // ex) ssar 이긴 ssar인데 이메일이 뭐인 쌀 이런 식으로 구별 // 누구 아이디인지 알아야함 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 + '}'; } }
3-4. 다시 BankApp에서 계좌 2개 생성
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개 만들기 // 계좌 만들때 1천원 입금해야함 --> 반드시 기본 금액이 들어감 // 로직상 무조건 넣어야지 안넣으면 오류 남 // 타입에 맞게 값을 전달 함 Account a1 = new Account(1111, 1000L, 1); Account a2 = new Account(2222, 1000L, 2); } }
3-5. 트랜잭션 잔액 검증 로직
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 조건문을 쓸때는 필터링하듯이 걸러내는 용도로 사용한다. // 정상적인 코트 if에 넣지 않고 비정상 적인 코드를 if에 넣는다. // 코드가 깔끔해진다. // 잔액 검증 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); } }
계좌이체 하려면 쌀이 100원을 코스한테 이체 할꺼다.
쌀 입장에서는 어떤 용도가 필요한가? 코스의 계좌번호가 필요하다.
그리고 계좌이체 검증 해봐야 한다.
- 0원 검증
- 잔액 검증 로직 해봐야 함
- 계좌번호 검증
- 이체
- 이체 결과 확인
여러개 각각 모여서 하나의 계좌이체 트랜잭션이다.
하나라도 실패하면 이 트랜잭션은 완료 되지 않고(프로그램 동작하면 안된다.)
전부 성공하면 트랜잭션이 완료 된다.
예를 들어 밥먹기 트랜잭션 있다.
밥먹기만 알려주면 밥먹기까지 해야 완료되고
밥먹고 설거지 하는 것 까지야 알려 주면 밥먹기, 설거지까지 해야 완료된다.
즉, 일의 최소 단위가 뭘지 정해 줘야 한다.
이처럼 검증하는 것이 프로그램에서는 일의 최소 단위라고 하고 트랜잭션 관리라고 한다.
이제 코트를 객체지향적 문법으로 바꿔보자!
3-6. 객체지향적으로 문법을 바꾼다.
아까 0원 검증은 상태 변경이 아니다.
잔액 변경 상태 변경아니다.
계좌변경 상태 변경아니다.
출금은 상태 변경이다.
입금 상태 변경이다.
그럼 0원 검증은 만들어야 될까?
상태 변경하는 것 모두 만들어야 한다.
상태 변경하는 행위는 메서드가 가지고 있다.
상태 변경하는 것은 누가 가지고 있나? 그 상태를 가지는 애가 만드는 것이다.
고객이 만들지 않았다.
즉, 고객이 출금을 들고 있지 않다.
Account에 이체를 넣지 않은 이유는 이체는 책임이 두 가지이기 때문이다.
아까 전에는 절차 적으로 코트를 나열했고 이제는 객체 지향적으로 코드를 짜보자.
모델링 패키지에서 User, Account 를 가지고 온다.
모델링한다는 것은 실제 세상으로 그 오브젝트를 들고 와서 모델링 한다는 것이다.
User 코드
package ex04.example2.model; public class User { // 여러가지 상태를 가지고 있어야 오브젝트이다. // 상태가 하나여도 행위가 있으면 오브젝트이다. // 상태는 private 다 붙여줌 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; } // 오버로딩 있는 toString() 메서드 확인용 @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + '}'; } }
Account 코드
package ex04.example2.model; // 객체의 상태를 변경, 객체의 상태를 확인 public class Account { private final int id; private long balance; private int userId; // --> Account의 id랑 혼동하지 말라고 userId 라고 지음 // 잔액확인 메서드도 Account에 작성하는게 좋다. // 실제 출금은 상태만 확인하는 건 안됨 // 검증이 필요 // 하나 일의 최소 단위를 하나의 명령어로 묶음 // 서비스로 다 묶어야 함 // 잔액 검증하기 // public boolean 잔액검증하기(long amount){ // if (balance < amount) { // balance가 amount 작으면 // //return fales; // return true; // // 잔액검증실패 // 보내는 금액이 잔액보다 작다. // 그럼 좋은 메서드 이름이 아니다 // 잔액부족하니로 해주고 true(부족하다) // 내가 물어본 의미에 대해 true, fales가 나와야 함 // 이거는 부정문이다 // 부정문은 잔액부족하니라고 물어 봤는데 true 라고 긍정을 주면 true가 잘못 된 것 // 부족한거니까 로직 실행이 안된다. 그러면 어떻게 물어 봐야 될까? // 잔액충분하니로 물어봐서 true 바꾼다. // 긍정으로 물어봐서 내가 원하는 결과를 true 로 바꾸는게 좋다. // 아래 코드로 수정 // // } // return fales; // } public boolean 잔액부족하니(long amount) { if (balance < amount) { return true; // 긍정으로 물어보는게 좋지만 걸러내는 방식으로 물어본다. // if를 걸러내는 용도로 다시 잔액 부족하니로 물어본다. // if eles를 다 안짜고 이렇게 걸러내기로 짜는 이유는 나중에 코드 짜기가 편하다. // BankService 로 가서 다시 확인 } return false; } // 메서드는 하나의 책임만 가진다. -> 객체지향 핵심 public void 출금(long amount) { this.balance = this.balance - amount; } // 이체로 하면 출금에서 잘못 됐는지 입금에서 인지 어디서 잘못 됐는지 확인하기가 어려워진다. public void 입금(long amount) { this.balance = this.balance + amount; } // 입금도 만들어야 한다. 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 + '}'; } }
BankApp
package ex04.example2; import ex04.example2.model.Account; import ex04.example2.model.User; public class BankApp { public static void main(String[] args) { // 1. 고객 2명 만들기 / 1개 더 추가 User ssar = new User(1, "ssar", "ssar@nate.com"); User cos = new User(2, "cos", "cos@nate.com"); User love = new User(3, "love", "love@nate.com"); // 계좌 2개 만들기 / 1개 더 추가 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 = 0L; long amount = 100L; // 4. 이체 (ssar -> cos 100원) // 이체는 출금과 입금이 동시에 일어 나야 함 // 복잡한 로직을 여기에 넣지 않고 한 번에 관리 할 수 있는 파일을 더 만들어야 함 // 패키지 BankService 트랜잭션 만듬 --> 실수 안하려면으로 묶어 놔야 한다. // 안 만들면 아까 절차 적으로 짠 코드 처럼 다 적어야 한다. // Account 이체를 만들지 않는 이유 두개의 핵신도 있지만 더 핵심적인 것은 // Account에 있는 balance 뿐만 아니라 새로 뜬 new도 다 건들여야 한다. // 내 객체의 상태만 변경하면 된다. // 이체 하려면 BankService.이체() 메서드만 불러 오면 된다. // BankService.이체(a1, a2, amount); // 헷깔리기때문에 이름도 a1, a2 짜주지 않는다. // 더 잘보이게 ssar, cos 이렇게 짜줌 // 4. 이체 (ssar -> cos 100원) BankService.이체(ssarAccount, cosAccount, amount); // 계좌 이름도 ssarAccount, cosAccount .. 변경 // 이렇게 짜주면 읽을 수 있는 코드가 된다. // 고객에게 100원을 받았구나 이체가 되었구나 // 5. 이체 (ssar -> love 100원) BankService.이체(ssarAccount, loveAccount, amount); // 6. 이체 (cos -> love 100원) BankService.이체(cosAccount, loveAccount, amount); // 이체 트랜잭션을 짠걸 가지고 cos가 love한테 100원 이체 시켜보자. // cos -> love amount를 이체 시키기만 하면 된다. // 코드가 엄청 간결해진다. // 7. 객체 상태 확인 System.out.println(ssarAccount); System.out.println(cosAccount); System.out.println(loveAccount); // 8. 출금 --> 검증을 해야함 --> 출금 트랜잭션을 만들어 줘야함 BankService.출금(ssarAccount, amount); BankService.출금(loveAccount, amount); // 출금 트랜잭션으로 묶어 뒀으면 // 그럼 이제 출금도 호출하면 된다. } }
BankService 트랜잭션 관리
package ex04.example2; import ex04.example2.model.Account; // 트랜잭션 관리 public class BankService { // 출금과 이체를 묶어 둔다. // 일의 최소 단위 public static void 출금(Account withdrawAccount, long amount) { // 출금 트랜잭션 // 출금 계좌를 알아야함 --> withdrawAccount 로 객체 이름을 지어준다. if (amount < 0) { System.out.println("0원 이하 금액을 출금할 수 없습니다."); return; } if (withdrawAccount.잔액부족하니(amount)) { System.out.println("잔액이 부족합니다"); return; } withdrawAccount.출금(amount); // 0원 검증부터 해야한다. // 잔액검증 // 출금하려면 이 3자기는 최소로 해야한다. } public static void 이체(Account senderAccount, Account receiverAccount, long amount) { // 이체 트랜잭션 if (amount <= 0) { System.out.println("0원 이하 금액을 이체할 수 없습니다."); return; } if (senderAccount.잔액부족하니(amount)) { System.out.println("잔액이 부족합니다"); return; } // 전달 받을걸 amount 금액을 확인 // 얘를 로직상 Account에 넣을 수 없다. // 이런것들을 밸리게이션이라고한다. --> 검증하는거 // if (amount <= 0) 값을 이체 할 수 없다. // 값을 이체 하지 못하니까 튕겨나간다. // 이것이 이체 트랜잭션이다. // 아직 예외처리를 배우지 않았기 때문에 아직은 이렇게만 코드를 짠다. // try-catch 사용하여 프로그램이 실행 중에 예외가 발생하더라도 // 해당 예외를 적절히 처리할 수 있다. senderAccount.출금(amount); receiverAccount.입금(amount); } }
// 실행 값 Account{id=1111, balance=800, userId=1} Account{id=2222, balance=1000, userId=2} Account{id=3333, balance=1200, userId=3}
상태를 변경하지 않는 로직은 트랜잭션으로 묶어둔다.
그럼 이제 코드가 잘 못 됐을때 트랜잭션을 확인한다.
객체지향적 코드는
- 객체를 만들고
- 객체 상태를 만들고
- 객체를 상태를 변경하는 메서드를 만들고
- 객체를 상태를 확인하는 메세드를 만들고
- 트랜잭션을 만든다. (단순하게 이체 할때 두번 적는거는 말이 안되기 때문에)
Share article