[자바] - MVC 패턴의 등장(feat: static 키워드를 곁들여..)

yeom yaloo·2023년 11월 22일
0

FISA

목록 보기
3/61
post-thumbnail

MVC

[등장 배경]

1. 서버 아키텍처

  • 웹 서버, WAS, 데이터 서버로 나누어진 서버 구조를 볼수 있다.
  • 웹 서버: 정적 컨턴츠 처리는 웹 서버가 담당하고 있다.
  • WAS: 동적 컨텐츠 처리를 위해서는 WAS가 이를 담당하고 있는데 주로 데이터베이스와 같이 수행하며 데이터 베이스 조회 및 로직 처리를 담당한다.
    • WAS의 경우엔 비지니스 로직( = 핵심로직 = 비지니스 모델 = 모델 = Biz ) 처리만으로도 바빠 웹 서버에게 정적 컨텐츠 처리를 맡겨두고 HTTP를 이용해 클라이언트(=사용자)와의 요청을 처리한다.
  • DB 서버개
    • 개발 환경이 발전함에 따라서 DB 서버 역시 하드웨어가 아닌 클라우드를 많은 기업에서는 사용하고 있다.
    • 클라우드 DB Server의 경우엔 물리적인 하드웨어를 직접 구축하지 않아도 회사가 제공하는 클라우드 서비스를 통해 이를 사용한 만큼만 사용자가 많을 땐 많이 사용자가 적을 땐 적게 사용하는 추세이다.

2. MVC 탄생

2-1. 서버의 분리, 비지니스 모델에 집중

  • 위의 서버 아키텍처가 분리됨에 따라서 WAS와 DB 서버는 비지니스 모델에 집중하고자 한다. 그리하기 위해서 MVC 패턴을 적용해 해당 핵심 로직에 집중해 개발을 시작하게 됐다.

2-2. MVC?

  • M(model): 핵심 비지니스 로직을 이 레이어에서 처리한다.
    • 데이터를 저장, 수정, 삭제, 접근 등의 작업을 이곳에서 진행한다.
    • 우리는 흔히 Model이라고 하면 비지니스 모델, 핵심 기능, 핵심 로직, Biz를 떠올리면 된다.
    • 핵심적으로 클라이언트가 요구하는 기능을 이곳에서 처리한다고 생각하면 된다.
      -V(view): 사용자에게 제공되는 화면으로 본래는 JSP에서 Model과 View의 경계가 모호한 문제 때문에 Model2라는 개념이 등장하여 철저하게 이를 분리해 사용하도록 하고 있다.
    • 클라이언트가 보는 화면이 뷰에 해당한다.
    • 사용자와 서버 간의 인터랙션이 일어나는 공간을 생각하면 된다.
  • C(controller): 모델과 뷰 사이의 존재하는 레이어로 클라이언트의 요청을 받으면 어떤 모델, 어떤 뷰를 호출해야 할지를 이곳에서 처리한다.

[순수 자바로 구현하는 MVC 패턴]

1. MVC의 흐름

  • 웹 환경이 아닌 자바를 이용해서 입력을 받고 출력을 하는 상황을 그려보자.
  • MVC의 흐름을 그림으로 보자
  1. 사용자의 요청이 들어오면 이를 컨트롤러로 보낸다.
  2. 컨트롤러는 사용자의 요청을 어떤 모델로 처리할지를 살펴보고 해당하는 모델로 해당 요청을 보낸다.
  3. 모델은 비지니스 로직을 이용해서 해당 요청을 처리하고 이를 다시 컨트롤러를 통해 보낸다.
  4. 컨트롤러는 해당 응답을 뷰에게 보내준다.
  5. 해당 응답을 받은 뷰는 사용자에게 이를 가시화하여 보여준다.

2. 들어가기에 앞서서 자바의 static

  • 해당 static 개념을 사용하는 이유는 우리가 데이터베이스를 실제 사용하는 대신 자바 코드를 통해서 데이터베이스를 대체하려고 하기 때문이다.
  • 이때 데이터베이스에 저장하여 사용하고 있는 데이터를 static 형식으로 저장하여 사용할 예정이기 때문에 해당 문제를 짚어보고 가려고 한다.
public class Model {

	Customer[] allcust; // 객체 생성 전까진 아무런 의미 없는 필드
	static Customer[] allcust2; // 정적 필드로 


	// 1)
	void a() {

		allcust = new Customer[] { new Customer() };
		allcust2 = new Customer[] { new Customer() };

	}


	// 2)
	static void a2() {
		allcust2 = new Customer[] { new Customer() };
		
		allcust = new Customer[] { new Customer() }; //문제 발생
		
		

	}

	/** 3)
	 * static block으로 주로 static 변수를 초기화할 때 사용한다. 
	 * */
	static {
		allcust = new Customer[] { new Customer() };
		allcust2 = new Customer[] { new Customer() };
	}

	public static void main(String[] args) {

		a2();

	}
}
  • 정적 필드, 정적 메서드 같은 경우엔 해당 클래스를 인스턴스화(=> new Class()) 작업을 하지 않더라도 해당 클래스가 메모리에 올라갈 때 생성이 된다.
  • non static method인 allcust가 문제가 되는 상황들이다.
  • Customer[] allcust;: non static 변수로 클래스를 인스턴스화해서 인스턴스로 만들어 사용하기 전까진 아무런 의미 없는데이터이다.
  • static Customer[] allcust2;: static 변수로 해당 클래스가 메모리에 올라가는 순간 객체 생성 없이도 해당 필드(속성)는 값이 생긴다.
  • 1) 상황을 살펴보면 일반 메서드로 해당 작업은 아무런 문제도 일으키지 않는다. (왜냐하면 클래스를 객체로 만들어 사용하지 않으면 아무 의미 없어 동작하지 않는 코드이기 때문이다.)
  • 2) static 메서드로 해당 메서드의 경우엔 문제가 생기게 된다.
    • non static 필드인 allcust가 문제를 일으키는 주범인데, 이는 둘의 생성 시기가 다르기 때문이다.
    • 살펴보면 static 메서드인 allcust2는 이미 클래스가 메모리에 올라가기만 해도 사용이 가능해지기 때문에 해당 정적 메서드 내에서 사용을 해도 문제가 없는데, allcust 같은 경우엔 non static 메서드로 객체 생성이 없는 상황에 클래스가 메모리에만 올라가 있는 상황이라면 해당 배열이 생성이 안됐기 때문에 문제가 발생하게 되는 것이다.
  • 3) static 속성(필드) 정적 필드가 static block 안에서 객체 생성도 없이 클래스가 메모리에 올라가 동작할 때 문제가 발생한다.
    • 해당 문제는 정적 필드 초기화에 사용되는 static block인데 이 경우에 문제가 되는 이유는 위와 유사하다.
    • 위의 작업이 클래스를 인스턴스화시키지 않아서 아무 의미 없는 값일 때 초기화 하려 하면 생성되지 않은 것에 접근하려 하는 것이기 때문에 문제가 된다.

2-1. 그럼 중요 포인트?

  • static 키워드가 붙은 클래스 멤버의 경우엔 해당 클래스가 메모리에 올라가기만 해도 생성이 된다. 이는 객체 생성(=즉 인스턴스화) 없이도 사용이 가능하다는 장점이 있지만 주의해야 한다는 점이기도 하다.
  • 객체 생성 없이 클래스가 메모리에 올라가기만 하면 생성되는 static 멤버를 선언, 사용할 때는 non static 멤버와의 생성 시기가 다른 점을 고려해서 코드를 구현해야 할 것이다.

3. 순수 자바 코드로 구현한 MVC 패턴

3-1. Model


package main.java.model;

import main.java.fisa.model.domain.Customer;

public class Model {

	static Customer[] allCust;

	void a() {

		allCust = new Customer[] { new Customer() };
	}

	static void a2() {

		allCust = new Customer[] { new Customer() };

	}

	// 데이터베이스 구축으로 간주
	static {
		allCust = new Customer[] { new Customer("연아", 10), new Customer("영숙", 20), new Customer("엽전", 30) };
	}

	// 고객 정보 반환 요청
	public static Customer[] read() {
		return allCust;
	}

	public static boolean update(String name, int age) {
		Customer[] customers = read();
		for (Customer customer : customers) {

			if (customer.getName().equals(name)) {
				customer.setAge(age);
				return true;
			}
		}
		return false;

	}
	
	

	public static Customer[] delete(String findName) {
		// TODO Auto-generated method stub
		
		
		Customer[] customers = read();
		int deleteCustomerCount = extractDeleteCount(findName, customers);
		
		Customer[] newCustomer = new Customer[customers.length-deleteCustomerCount];
		
		int i = 0;
		for (Customer customer : customers) {
			
			if (!customer.getName().equals(findName)) {
				newCustomer[i] = customer;
				i++;
			}
		}
		return newCustomer;
	}

	private static int extractDeleteCount(String findName, Customer[] customers) {
		int deleteCustomerCount =0;
		for (Customer customer : customers) {
			
			if (customer.getName().equals(findName)) {
				deleteCustomerCount ++;	
			}
		}
		return deleteCustomerCount;
	}

	
	public static void main(String[] args) {

		Model model = new Model();
		model.a();

		a2();

	}

}
  • 핵심 비지니스 로직을 처리하는 부분이다.
  • 그래서 데이터 베이스를 사용해서 데이터에 접근, 수정, 삭제 등의 작업을 진행한다.

3-2. View

[사용자 입력을 받는 StartView - client 화면을 생각해주세요]

package main.java.view;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import main.java.fisa.controller.CustomerController;

/**
 * 
 * web의 경우엔 html로 개발하는 것이 startView에 해당한다.
 * 
 * client(사용자, end User, 고객)의 첫 실행 화면에 해당한다.
 */
public class StartView {

	public static void main(String[] args) throws NumberFormatException, IOException {

		System.out.println("1 - 정보 출력, 2 - 회원 수정, 3 - 회원 삭제 ");
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		int no = Integer.parseInt(br.readLine());

		CustomerController.reqProcess(no);

	}

}

[사용자에게 응답을 출력할 EndView - 클라이언트 화면인데 이제 응답을 출력하는 상황을 생각해주세요]


package main.java.view;

import main.java.fisa.model.domain.Customer;

public class EndView {

	public static void printAll(Customer[] read) {
		// TODO Auto-generated method stub

		System.out.println("모든 회원 정보를 출력합니다.");
		for (Customer customer : read) {
			System.out.println(customer.getName());
			System.out.println(customer.getAge());
		}

		System.out.println();
	}

	public static void printSetAgeCustomer(String name, int age) {
		// TODO Auto-generated method stub
		System.out.println("사용자 " + name + "의 이름을 변경 했습니다.");
		System.out.printf("사용자 %s의 나이는 %d로 변경됐습니다.", name, age);

	}

}

3-3. 컨트롤러

package main.java.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import main.java.model.Model;
import main.java.model.domain.Customer;
import main.java.view.EndView;

/**
 * startView로부터 요청 받고 Model의 Biz 메서드 호출 결과값들을 EndView에게 출력 위임
 * 
 * startView : 1 - 모든 검색 / 2 - 수정 / 3 - 삭제
 */
public class CustomerController {

	public static void reqProcess(int no) throws NumberFormatException, IOException {

		if (no == 1) {

			EndView.printAll(Model.read());
			return;

		} else if (no == 2) {
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
			String findName = br.readLine();
			int newAge = Integer.parseInt(br.readLine());

			if (Model.update(findName, newAge)) {
				EndView.printSetAgeCustomer(findName, newAge);

				return;
			}

			System.out.println("해당 이름을 가진 사용자 정보가 존재하지 않습니다.");
			return;

		} else if (no == 3) {

			String findName = inputCustomerName();
			Customer[] customers = Model.read();

			Customer[] newCustomers = Model.delete(findName);

			if (newCustomers.length != customers.length) {

				System.out.printf("%s 이름을 가진 사용자를 성공적으로 삭제 했습니다.\n", findName);
				return;
			}

			System.out.println("해당 이름을 가진 사용자 정보가 존재하지 않습니다.");
		}

		System.out.println("올바르지 않은 값을 입력하셨습니다. 다시 입력해주세요");
		return;

	}

	private static String inputCustomerName() throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		String findName = br.readLine();
		return findName;
	}

}
  • StartView에서 넘어온 사용자 요청(request)를 받아 model을 이용해서 해당 작업을 처리합니다.
  • 또한 해당 컨트롤러에서는 받은 요청을 처리할 뿐만 아니라 받은 요청을 모델에게 넘겨 해당 요청에 대한 응답을 다시 EndView에게 넘겨주고 있습니다.
  • 살펴보면 컨트롤러의 원래 역할인 모델과, 뷰 사이에서의 로직 처리를 진행하고 있음을 알수 있습니다.

4. 그럼 왜 앞서 살펴본 static 관련을 구구절절 말한걸까?

  • view단을 살펴보면 해당 클래스들 안에는 모델과 달리 필드가 없다. 이는 데이터를 굳이 이 객체에서 가지고 있지 않음을 뜻한다.
  • 그렇기 때문에 해당 기능을 static 메서드로 만들어서 어디서든 객체 생성 없이 사용할 수 있게 하고 있다.

5. main() 메서드가 왜 static인지 아니?

javac Test.java //컴파일
ㅤㅤㅤTest.main([...]) // 메인 메서드 실행

  • 메인 메서드의 경우엔 프로그램이 실행되면 제일 먼저 호출되는 메서드이기 때문에 객체를 생성하지 않은 채로 바로 작업을 수행해야 하기 때문에 static이어야 합니다.

[MVC 패턴을 적용한 구현]

	1. MVC
		화면단 처리 로직
		요청 구분 로직
		서비스 - 트랜잭션 처리 로직으로 주로 권장 영역
		데이터 활용 로직
		- 계좌이체 로직 세분화하기
			유효한 계좌번호, 잔액, 유효한 유저, 마이너스, 계좌번호...
			전체 성공만 - 성공
			특정 단계에서만 실패 하더라도 - 실패
			단일 작업으로 간주 : 트랜잭션
			
profile
즐겁고 괴로운 개발😎

0개의 댓글