애플리케이션을 만들 때는 설계 먼저 진행한다.
[설계 과정]
1. 요구사항 정의
2. CRUD(=비즈니스 메서드, 핵심 관심, 주요 기능) 구분하기
3. 사용자 시각에서 기능 이용해보기 >> User Flow
( + 개발자들이 기능에 대해 동일한 생각을 하고 있는지 확인)
설계 정말 꼼꼼히 하는 게 좋다.......
MVC 패턴 코드 디벨롭하기
1. M CRUD 오버로딩 : 하나의 메서드에서 모든 로직을 관리하여 응집도 높이기
■ 인자가 같아서 오버로딩 불가능할 때 : condition 사용
□ condition : 자바에서 개발용으로 생성해놓은 멤버변수. CRUD 메서드에 condition과 if문을 사용하여 오버로딩이 필요한 메서드들을 하나로 합친다. => 응집도 증가
2. M CRUD에 있는 모든 메서드 시그니쳐(인자)를 전부 DTO로 설정하여 결합도 낮추기
(속성이 늘거나 줄어도 메서드 시그니쳐가 바뀔 일이 없음)
* NullPointerException : DTO는 객체 생성(new) 후 NULL일 가능성이 절대 없다. (메모리 할당이 이루어진 상태)
* 쇼핑몰 프로그램에서 장바구니는 DB에 저장되어 있는 온전한 데이터가 아니라, 날아갈 수 있는 캐시 데이터이다.
-> 장바구니 기능은 컨트롤러에 작성. 장바구니는 사용자의 요청에 대한 즉각적인 응답이 필요하며, DB에 저장할 필요가 없는 데이터이기 때문에 비즈니스 로직에서 다루는 것보다는 컨트롤러에 작성되는 것이 좋다.
* 장바구니에 있는 재고가 DB에 있는 재고보다 많아지는 걸 막을 수는 없다. 다만, 구매하려고 할 때 막을 수는 있다.
-> 장바구니는 서버가 아니라 클라이언트 쪽에서 이루어지기 때문에 사용자가 장바구니에 상품을 담는다고 해서 DB에 있는 재고의 개수가 줄어들지는 않는다. 장바구니에 담을 때가 아니라, 구매&결제할 때 재고 확인해서 만약 재고보다 많이 담으면 막는 식으로 구현해야 한다.
[MVC 패턴 - 쇼핑몰 프로그램 ]
① Client : main 함수가 위치하는 곳. 프로그램을 실행하는 곳
package client;
import controller.Controller;
public class Client {
public static void main(String[] args) {
Controller app = new Controller(); // 다운
app.start(); // 실행
}
}
client에서는 C만 가져와서 사용하면 된다.
C를 import 해준 후, main 함수 안에 app 이라는 이름으로 컨트롤러 객체를 생성해준다.
그 후, 변수명.컨트롤러 내 함수명(); 으로 프로그램을 시작한다. 즉, 프로그램이 시작될 때 컨트롤러가 실행되는 것이다.
② Controller
package controller;
import java.util.ArrayList;
import model.ProductDAO;
import model.ProductDTO;
import view.AdminView;
import view.ClientView;
// private
// this
public class Controller {
ProductDAO model;
ClientView clientView;
AdminView adminView;
ArrayList<ProductDTO> cart;
public Controller() {
model=new ProductDAO();
clientView=new ClientView();
adminView=new AdminView();
cart=new ArrayList<>();
}
public void admin() {
while(true) {
adminView.printMenuAdmin();
int command=clientView.inputCommand();
if(command==0) {
break;
}
else if(command==1) {
String name=clientView.inputName();
int price=clientView.inputPrice();
int stock=clientView.inputStock();
ProductDTO dto=new ProductDTO();
dto.setName(name);
dto.setPrice(price);
dto.setStock(stock);
boolean flag=model.insert(dto);
if(flag) {
clientView.printTrue();
}
else {
clientView.printFalse();
}
}
else if(command==4) {
int num=clientView.inputNum();
ProductDTO dto=new ProductDTO();
dto.setNum(num);
boolean flag=model.delete(dto);
if(flag) {
clientView.printTrue();
}
else {
clientView.printFalse();
}
}
}
}
public void start() {
while(true) {
clientView.printMenu();
int command=clientView.inputCommand();
if(command==1234) {
admin();
}
else if(command==0) {
break;
}
else if(command==2) {
ProductDTO dto=new ProductDTO();
clientView.printDatas(model.selectAll(dto));
}
else if(command==3) {
int num=clientView.inputNum();
int count=clientView.inputCount();
ProductDTO dto=new ProductDTO();
dto.setNum(num);
dto.setCount(count);
cart.add(dto);
}
else if(command==5) {
for(int i=0;i<cart.size();i++) {
ProductDTO dto=new ProductDTO();
dto.setNum(cart.get(i).getNum());
dto.setCount(cart.get(i).getCount());
model.update(dto);
}
cart.clear();
}
}
}
}
package controller;
import java.util.ArrayList;
import model.ProductDAO;
import model.ProductDTO;
import view.AdminView;
import view.ClientView;
// private
// this
public class Controller {
ProductDAO model;
ClientView clientView;
AdminView adminView;
ArrayList<ProductDTO> cart;
public Controller() {
model=new ProductDAO();
clientView=new ClientView();
adminView=new AdminView();
cart=new ArrayList<>();
}
먼저 import로 사용할 뷰와 dao, dto를 모두 가져와준다.
그리고 클래스 안에서 멤버변수로 dao, clientview, adminview 모두 선언해준다.
컨트롤러는 모델과 뷰를 멤버변수로 갖는다!
참고로, 장바구니 기능이 필요하므로 상품DTO를 자료형으로 하는 ArrayList cart도 멤버변수로 선언한다.
그리고 생성자 안에서 선언한 4가지를 모두 객체 생성한다. 객체를 생성해주어야 뷰와 모델에 있는 메서드를 컨트롤러에서 사용할 수 있다.
public void start() {
while(true) {
clientView.printMenu();
int command=clientView.inputCommand();
if(command==1234) {
admin();
}
else if(command==0) {
break;
}
else if(command==2) {
ProductDTO dto=new ProductDTO();
clientView.printDatas(model.selectAll(dto));
}
else if(command==3) {
int num=clientView.inputNum();
int count=clientView.inputCount();
ProductDTO dto=new ProductDTO();
dto.setNum(num);
dto.setCount(count);
cart.add(dto);
}
else if(command==5) {
for(int i=0;i<cart.size();i++) {
ProductDTO dto=new ProductDTO();
dto.setNum(cart.get(i).getNum());
dto.setCount(cart.get(i).getCount());
model.update(dto);
}
cart.clear();
}
}
}
}
먼저 프로그램이 시작되는 start() 함수를 보자.
사용자가 프로그램 종료를 선택할 때까지 반복되어야 하므로 while(true)를 이용해주고, 종료조건은 0번을 누를 때로 작성한다.
클라이언트 뷰에 있는 클라이언트 메뉴를 먼저 화면에 띄워주고 사용자에게 입력을 받아야 한다.
그러므로 메뉴를 먼저 띄워준 후, 뷰에 있는 inputCommand()를 실행하여 사용자에게 입력을 받아 command 변수에 저장한다. 만약 이때 사용자 입력에서 1234를 입력한다면 관리자 모드로 이동하게 된다.
2번을 눌렀을 때 : 상품 전체 출력
ProductDTO dto = new ProductDTO();로 먼저 DTO 객체를 생성해준다. 이때 DTO 객체를 생성해주는 이유는 selectAll() 메서드 호출 시 매개변수로 보내주기 위해서이다. DTO는 데이터를 담는 객체로, 보통 데이터 전달 용도로 사용되는데 이 경우 DTO 생성 후에 값을 설정하지 않고 빈 객체를 보내고 있다. 즉, 단순히 selectAll()을 호출하기 위해 빈 DTO 객체를 생성하는 것이며, 이를 통해 조건 없이 전체 데이터를 검색하는 역할을 한다.
빈 객체인 DTO를 selectAll()로 넘김으로써, 조건 없이 모든 데이터를 검색해서 가져올 수 있는 것이다. 또한, 일관된 메서드 시그니처를 유지하기 위해 DTO 객체를 넘기도록 강제된 것이기도 하다.
그 후, 모델.selectAll() 함수를 호출해 클라이언트뷰에 있는 printDatas 함수로 전체 출력해준다.
3번을 눌렀을 때 : 장바구니에 제품 추가
사용자에게 구매하고 싶은 상품을 번호로 입력받고, 구매수량을 숫자로 입력받는다.
또, DTO 객체를 생성해준 후, DTO에 상품번호와 수량을 입력받은 숫자로 설정해서 장바구니에 저장해준다.
참고로, 이건 장바구니에 담는 것 뿐이라서 DB에 있는 상품의 재고 개수와 구매된 횟수가 변하지 않는다. 단지 사용자의 장바구니에만 들어있을 뿐 구매가 진행되지 않았기 때문. 장바구니 구매를 해야지만 DB에 있는 상품의 재고/구매 횟수가 바뀐다.
5번을 눌렀을 때 : 장바구니 구매
장바구니의 크기만큼 돌면서 우선 DTO를 객체 생성해주고 (상품 정보 저장용)
장바구니에 저장되어있는 상품번호와 구매 수량을 가져와서 DTO에 저장한 후 M의 UPDATE 함수에 보내준다.
(DTO는 장바구니에 담긴 상품 정보를 복사해서 보내주는 것)
개수 변경 로직은 M이 알아서 처리해줄 것이다.
정보를 보내준 후, 장바구니를 비운다.
위 코드에서는 수량 부족으로 구매가 안되더라도 장바구니가 비워지게 되어있는데, M에서 true, false를 반환하므로 boolean success = model.update(dto);를 사용하여 성공시에만 clear()를 실행하면 될 것 같다.
public void admin() {
while(true) {
adminView.printMenuAdmin();
int command=clientView.inputCommand();
if(command==0) {
break;
}
else if(command==1) {
String name=clientView.inputName();
int price=clientView.inputPrice();
int stock=clientView.inputStock();
ProductDTO dto=new ProductDTO();
dto.setName(name);
dto.setPrice(price);
dto.setStock(stock);
boolean flag=model.insert(dto);
if(flag) {
clientView.printTrue();
}
else {
clientView.printFalse();
}
}
else if(command==4) {
int num=clientView.inputNum();
ProductDTO dto=new ProductDTO();
dto.setNum(num);
boolean flag=model.delete(dto);
if(flag) {
clientView.printTrue();
}
else {
clientView.printFalse();
}
}
}
}
다음은 사용자가 사용자 메뉴에서 1234를 입력했을 때 관리자 메뉴로 들어가서 실행될 코드이다.
여기서도 마찬가지로 관리자 메뉴를 먼저 띄워준 후, 사용자에게 번호를 입력받는다.
만약 사용자가 0번을 누르면 종료된다.
1번을 눌렀을 때 : 제품 추가
상품의 이름, 가격, 재고를 사용자에게 입력받아서 생성해준 dto에 전부 저장한다.
그리고 모델의 insert 함수를 호출해서 상품을 추가해준다.
만약 성공하면 추가 성공, 실패하면 추가 실패 문구를 출력해준다.
4번을 눌렀을 때 : 제품 삭제
사용자에게 삭제할 상품 번호를 입력받는다.
dto를 생성한 후 입력받은 번호를 dto에 저장해준다.
그리고 모델의 delete 함수를 호출해서 dto를 보내준다.
성공하면 성공 문구, 실패하면 실패 문구를 출력해준다.
③ Model
-1 ) ProductDTO
package model;
public class ProductDTO {
private static int NUM=1001;
private int num; // PK
private String name;
private int price;
private int stock;
private int count;
private String condition;
public ProductDTO() {
}
public ProductDTO(String name,int price,int stock) {
this.num=ProductDTO.NUM++;
this.name=name;
this.price=price;
this.stock=stock;
this.count=0;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public String getCondition() {
return condition;
}
public void setCondition(String condition) {
this.condition = condition;
}
@Override
public String toString() {
if(this.stock<=0) {
return "품절상품";
}
return "ProductDTO [num=" + num + ", name=" + name + ", price=" + price + ", stock=" + stock + ", count="
+ count + "]";
}
}
DTO는 자료형을 정의하는 곳이다.
상품의 정보로 등록할 상품번호(PK), 이름, 가격, 재고, 구매 횟수를 멤버변수로 가진다.
이때, 객체와 무관한 클래스 변수 NUM을 선언하여 상품번호가 1001부터 순차적으로 1씩 자동 증가하도록 해줄 것이다.
또, 위에서 메서드 오버로딩 하기로 했으니, condition도 private로 선언해준다.
웹개발에서는 컨트롤러에서 쓸 목적으로 dto에 기본 생성자가 하나 필요하므로 기본 생성자도 하나 만들어주고,
이름, 가격, 재고를 입력받는 생성자를 하나 더 만들어준다. 이때 상품번호는 NUM이 1씩 증가되도록 해줄거라서 NUM++을 써주는데, NUM은 static 변수니까 클래스명.변수명으로 가져와준다.
그리고 상품의 구매 횟수는 0으로 기본 설정해둔다.
모두 private로 선언하였으니 외부에서 값을 안전하게 가져오고 변경하기 위해 getter/setter 추가 후, 객체의 데이터를 보기 쉽게 toString() 오버라이딩도 해준다.
- 2) ProductDAO
package model;
import java.util.ArrayList;
public class ProductDAO {
ArrayList<ProductDTO> datas;
public ProductDAO() {
datas=new ArrayList<>();
datas.add(new ProductDTO("콜라",1200,5));
datas.add(new ProductDTO("사이다",1100,2));
datas.add(new ProductDTO("커피",600,1));
}
public ArrayList<ProductDTO> selectAll(ProductDTO dto){
ArrayList<ProductDTO> datas=new ArrayList<>();
for(ProductDTO data:this.datas) {
ProductDTO productDTO=new ProductDTO();
productDTO.setNum(data.getNum());
productDTO.setName(data.getName());
productDTO.setPrice(data.getPrice());
productDTO.setStock(data.getStock());
productDTO.setCount(data.getCount());
datas.add(productDTO);
}
return datas;
}
public ProductDTO selectOne(ProductDTO dto){
ProductDTO data=null;
for(int i=0;i<datas.size();i++) {
if(datas.get(i).getNum() == dto.getNum()) {
data=new ProductDTO();
data.setNum(datas.get(i).getNum());
data.setName(datas.get(i).getName());
data.setPrice(datas.get(i).getPrice());
data.setStock(datas.get(i).getStock());
data.setCount(datas.get(i).getCount());
break;
}
}
return data;
}
public boolean insert(ProductDTO dto) {
try {
datas.add(new ProductDTO(dto.getName(),dto.getPrice(),dto.getStock()));
}
catch(Exception e) {
System.out.println("로그 : ProductDAO insert()에서 발생한 예외입니다.");
return false;
}
return true;
}
public boolean update(ProductDTO dto) {
for(int i=0;i<datas.size();i++) {
if(datas.get(i).getNum() == dto.getNum()) {
if(datas.get(i).getStock() < dto.getCount()) {
System.out.println("로그 : ProductDAO update()에서 발생한 false입니다. 재고가 부족합니다.");
return false;
}
datas.get(i).setStock(datas.get(i).getStock()-dto.getCount());
datas.get(i).setCount(datas.get(i).getCount()+dto.getCount());
return true;
}
}
System.out.println("로그 : ProductDAO update()에서 발생한 false입니다. 해당 num은 없습니다.");
return false;
}
public boolean delete(ProductDTO dto) {
for(int i=0;i<datas.size();i++) {
if(datas.get(i).getNum() == dto.getNum()) {
datas.remove(i);
return true;
}
}
System.out.println("로그 : ProductDAO delete()에서 발생한 false입니다. 해당 num은 없습니다.");
return false;
}
}
DAO에서는 상품DTO를 자료형으로 하는 ArrayList를 하나 만들어준다. 그리고 생성자 안에서 datas를 초기화하여 빈 리스트를 만들어주고, 샘플 데이터 3개를 추가해준다.
DAO 안에는 오로지 5가지의 CRUD 메서드만 들어갈 수 있다.
① selectAll()
전체 데이터를 전달해줘야하므로 ArrayList<ProductDTO> 타입을 반환하는 selectAll() 함수를 만든다.
이때 인자는 일관된 매개변수를 유지하기 위해서 ProductDTO dto로 모두 통일해준다.
먼저, 실제 데이터의 내용을 복사해줄 ArrayList를 하나 생성해준 후, foreach문을 통해서 상품 목록을 돌아줄 것이다.
안에서는 productDTO라는 변수명으로 ProductDTO 객체를 생성해주는데, 이 ProductDTO에 각각 객체가 저장된 후, ArrayList에 모든 객체가 담겨서 전달될 것이다.
productDTO.set~ 으로 실제 데이터(this.datas)의 모든 정보들을 productDTO에 복사+저장한 후, datas에 모든 productDTO 객체들을 담아서 반환해준다.
② selectOne()
하나의 데이터를 전달할 것이기 때문에 ProductDTO 타입을 반환하는 selectOne() 함수를 만든다.
이때도 마찬가지로 인자는 메서드 시그니쳐를 일관적으로 유지하기 위해 ProductDTO dto를 작성해준다.
찾은 상품을 저장할 변수인 data를 하나 선언해주는데, 이때 이 변수는 null로 초기화 해준다. 왜냐하면 지역변수는 초기화 하지 않으면 사용할 수 없고, 초기화하지 않으면 return할 때 컴파일 오류가 발생하기 때문이다.
자바에서는 지역변수를 초기화하지 않으면 사용할 수 없도록 설계되어 있다.
만약 데이터가 없다면 null이 반환되는데, data 변수가 존재하지 않는 상품을 찾을 경우에도 return할 수 있도록 하기 위해 null로 초기화해준 것이며, 이는 view에서 유효성 검사로 문구를 적당히 출력해주면 된다..
data를 초기화 한 후, 기본 데이터 크기만큼 순회하면서 사용자가 입력한 상품번호와(매개변수로 컨트롤러에게 받음)
기본 데이터에 존재하는 상품번호와 같다면, 해당 제품의 번호, 이름, 가격, 재고, 구매 횟수를 다 가져와서 저장한 후 반환해준다.
이때, 동일한 번호가 있다면 new ProductDTO()를 통해서 정보들을 저장할 data 객체를 생성해준다.
ProductDTO data=null;
는 단지 data를 초기화하여 null 상태로 둔 것이고,
data=new ProductDTO();
까지 해야 상품 정보를 저장할 새로운 메모리 공간이 생기는 것이다.
객체가 생성(new)되어야 실제 메모리 공간이 할당된다.
즉, 변수를 선언하고 초기화(null로 설정)하는 것만으로는 객체가 생성되지 않으며, 실제 메모리 공간도 할당되지 않는다.
ProductDTO data = null; 에서는
□ 메모리에 data 변수(참조 변수)가 저장될 공간만 할당되고, 실제 객체는 존재하지 않는다.
□ 즉, data는 메모리에서 객체를 가리키지 않고, 단순히 null 값을 가지고 있다.
□ 이 상태에서 data.setNum(1001); 같은 메서드를 호출하면 NullPointerException 이 발생한다.
③ insert()
public boolean insert(ProductDTO dto) {
try {
datas.add(new ProductDTO(dto.getName(),dto.getPrice(),dto.getStock()));
}
catch(Exception e) {
System.out.println("로그 : ProductDAO insert()에서 발생한 예외입니다.");
return false;
}
return true;
}
상품DTO를 자료형으로 , 이름, 가격, 재고를 입력받아 기존 데이터인 datas에 추가한다.
만약 예외가 발생하면 로그를 출력해주고 false 반환.
추가 성공 시 true를 반환한다.
④ update()
public boolean update(ProductDTO dto) {
for(int i=0;i<datas.size();i++) {
if(datas.get(i).getNum() == dto.getNum()) {
if(datas.get(i).getStock() < dto.getCount()) {
System.out.println("로그 : ProductDAO update()에서 발생한 false입니다. 재고가 부족합니다.");
return false;
}
datas.get(i).setStock(datas.get(i).getStock()-dto.getCount());
datas.get(i).setCount(datas.get(i).getCount()+dto.getCount());
return true;
}
}
System.out.println("로그 : ProductDAO update()에서 발생한 false입니다. 해당 num은 없습니다.");
return false;
}
기존 데이터를 순회하면서 사용자가 입력한 번호가 기존 데이터에 존재하는 번호와 동일하면, 해당 데이터의 재고와 구매 횟수를 설정해준다.
기존 데이터.재고 - 입력한 구매 수량
기존 데이터.구매 횟수 + 입력한 구매 수량
이때 만약 기존 데이터의 재고보다 입력받은 구매 수량이 더 크다면, 로그를 출력하고 false를 반환한다.
아닐 경우에만 위 연산들을 진행하고 true를 반환한다.
만약 기존 데이터에 입력받은 번호가 없다면 로그로 입력한 번호가 없다고 알려준다.
⑤ delete()
public boolean delete(ProductDTO dto) {
for(int i=0;i<datas.size();i++) {
if(datas.get(i).getNum() == dto.getNum()) {
datas.remove(i);
return true;
}
}
System.out.println("로그 : ProductDAO delete()에서 발생한 false입니다. 해당 num은 없습니다.");
return false;
}
기존 데이터를 순회하면서, 입력받은 번호가 기존 데이터에 존재한다면, 해당 데이터를 삭제하고 true를 반환한다. 만약 입력받은 번호가 존재하지 않는다면 로그로 해당 번호가 없다고 출력하고 false를 반환한다.
뷰는 ..... 이번 플젝에서 많이 해서 괜찮을 것 같다 ..^^!
'IT > JAVA' 카테고리의 다른 글
크롤링 + MVC 패턴 (0) | 2025.02.10 |
---|---|
웹 크롤링 (0) | 2025.02.10 |
MVC 패턴 - Model 분석 (0) | 2025.02.08 |
MVC 패턴 - Controller 분석 (0) | 2025.02.07 |
MVC 패턴 복습 (0) | 2025.02.07 |