[문제링크]

 

13549번: 숨바꼭질 3

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일

www.acmicpc.net

 

비슷한 문제

-숨바꼭질

-숨바꼭질2

 

0. 수빈이의 이동 방법 3가지를 활용해, 동생에게 가장 빠르게 갈 수 있는 경우 찾기

  - 3개의 선택지가 있는 탐색 문제.

  - 어떤 선택지를 먼저 선택하느냐에 따라 해가 나오지 않거나 큰 해가 나오는 경우가 존재하므로 DFS가 아닌 BFS 사용

  - 단, 걷는 방법과 순간이동 방법의 소모 시간이 다르므로, BFS의 형식만 따를 뿐 이론적으로 BFS로 작동하지 않는다

  - ex) +1+1+1+1과 *2 *2 *2 *2는 각각 4번의 이동을 하지만, 소모 시간은 4초 / 0초로 다름

  - 전체 경우의 수를 탐색한다

 

1. 수빈이의 시작지점 N에서 BFS방식으로 +1, -1, *2 지점을 큐에 넣으며 진행한다

 

2. 수빈이가 어떤 칸 X에 W초를 걸려 도달했는데, 이미 해당 칸에 W보다 적은 시간으로 도달한 기록이 있다면, 더이상 탐색하는것은 의미가 없다.

  - 어떤 칸 X에 몇초만에 도달하였는지를 num 배열에 저장하여, 현재의 시간이 더 작을때에만 진행하도록 한다

  - 숨바꼭질 기본 문제와는 달리, 순간이동의 소모 시간이 0이므로, num[X]가 0인지 아닌지만으로는 판단할 수 없다

 

3. 수빈이의 현재 위치가 동생의 위치보다 크다면, +1 *2 연산을 진행할 필요는 없다

  - 마찬가지로, 수빈이의 현재 위치가 동생의 위치보다 작다면, -1 연산을 진행할 필요는 없다

 

4. 정리하자면,

  - 현재 좌표 X가 동생의 좌표 K보다 작으며, X+1 좌표의 이전 접근 시간이 현재 시간보다 클때만 진행+갱신

  - 현재 좌표 X가 동생의 좌표 K보다 작으며, X*2 좌표의 이전 접근 시간이 현재 시간보다 클때만 진행+갱신

  - 현재 좌표 X가 동생의 좌표 K보다 크며, X-1 좌표의 이전 접근 시간이 현재 시간보다 클때만 진행+갱신하며,

  - K좌표에 도달하는 순간 연산 횟수를 출력, 종료한다

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		
		int N = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());
		int len=100001;
		int[] num = new int[len*2];
		int[] way = new int[len*2];
		Arrays.fill(num, Integer.MAX_VALUE);
		way[N]=1;
		num[N]=0;
		Queue<Integer> q = new LinkedList<Integer>();
		q.offer(N);
		
		while(!q.isEmpty()) {
			int temp = q.poll();
			if(temp < K && num[temp*2]>num[temp]) {
				way[temp*2]+=way[temp];
				if(num[temp*2]!=num[temp]+1)q.offer(temp*2);
				num[temp*2]=num[temp];
			}
			if(temp < K && num[temp+1]>num[temp]) {
				way[temp+1]+=way[temp];
				if(num[temp+1]!=num[temp]+1)q.offer(temp+1);
				num[temp+1]=num[temp]+1;
			}
			if(temp-1 >= 0 && num[temp-1]>num[temp]) {
				way[temp-1]+=way[temp];
				if(num[temp-1]!=num[temp]+1)q.offer(temp-1);
				num[temp-1]=num[temp]+1;
			}
		}
		System.out.println(num[K]);
	}
}

결과 화면

 

[문제링크]

 

12851번: 숨바꼭질 2

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일 때

www.acmicpc.net

 

비슷한 문제

-숨바꼭질

 

0. 수빈이의 이동 방법 3가지를 활용해, 동생에게 가장 빠르게 갈 수 있는 경우 + 그 갯수 찾기

  - 3개의 선택지가 있는 탐색 문제.

  - 어떤 선택지를 먼저 선택하느냐에 따라 해가 나오지 않거나 큰 해가 나오는 경우가 존재하므로 DFS가 아닌 BFS 사용

 

1. 수빈이의 시작지점 N에서 BFS방식으로 +1, -1, *2 지점을 큐에 넣으며 진행한다

 

2. 수빈이가 어떤 칸 X에 W초를 거쳐 도달했는데, 이미 해당 칸에 W보다 적은 시간으로 도달한 기록이 있다면, 더이상 탐색하는것은 의미가 없다.

  - 하지만 같은 칸에 동일하게 W초만에 도달했을 경우는 고려해야한다 (가짓수를 구하기 위해)

  - way배열을 두어 W초를 사용해 X번 칸에 도달할 수 있는 가짓수를 저장한다.

 

3. 수빈이의 현재 위치가 동생의 위치보다 크다면, 반드시 작아져야하므로 +1 *2 연산을 진행할 필요 없다

  - 마찬가지로, 수빈이의 현재 위치가 동생의 위치보다 작다면, 반드시 커져야하므로 -1 연산을 진행할 필요 없다

 

4. 정리하자면,

  - 현재 좌표 X가 동생의 좌표 K보다 작으며, X+1 좌표에 진행해본 적 없거나 || 현재 이동수 W와 같을때만 진행

  - 현재 좌표 X가 동생의 좌표 K보다 작으며, X*2 좌표에 진행해본적이 없거나 || 현재 이동수 W와 같을때만 진행

  - 현재 좌표 X가 동생의 좌표 K보다 크며, X-1 좌표에 진행해본적이 없거나 || 현재 이동수 W와 같을때만 진행하며,

  - K좌표에 도달하는 순간 연산 횟수를 출력, 종료한다

  - 시작할 때 큐의 크기 = W단계에서 연산해야할 크기이므로, 큐의 크기만큼 반복할때마다 1씩 증가하는 act 변수를 두어, 이동 수를 관리한다

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		
		int N = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());
		int len=100001;
		int[] num = new int[len*2];
		int[] way = new int[len*2];
		way[N]=1;
		num[N]=1;
		Queue<Integer> q = new LinkedList<Integer>();
		q.offer(N);
		
		int act=1;
		while(num[K]==0) {
			act++;
			int qlen=q.size();
			for (int i = 0; i < qlen; i++) {
				int temp = q.poll();
				if(temp < K) {
					if(num[temp*2]==0) {
						q.offer(temp*2);
						num[temp*2]=act;					
					}
					if(num[temp*2]==act)way[temp*2]+=way[temp];
				}
				if(temp < K) {
					if(num[temp+1]==0) {
						q.offer(temp+1);
						num[temp+1]=act;						
					}
					if(num[temp+1]==act)way[temp+1]+=way[temp];
				}
				if(temp-1 >= 0) {
					if(num[temp-1]==0) {
						q.offer(temp-1);
						num[temp-1]=act;						
					}
					if(num[temp-1]==act)way[temp-1]+=way[temp];
				}
			}
		}
		System.out.println(num[K]-1);
		System.out.println(way[K]);
	}
}

결과 화면

[문제링크]

 

1697번: 숨바꼭질

수빈이는 동생과 숨바꼭질을 하고 있다. 수빈이는 현재 점 N(0 ≤ N ≤ 100,000)에 있고, 동생은 점 K(0 ≤ K ≤ 100,000)에 있다. 수빈이는 걷거나 순간이동을 할 수 있다. 만약, 수빈이의 위치가 X일

www.acmicpc.net

 

0. 수빈이의 이동 방법 3가지를 활용해, 동생에게 가장 빠르게 갈 수 있는 경우 찾기

  - 3개의 선택지가 있는 탐색 문제.

  - 어떤 선택지를 먼저 선택하느냐에 따라 해가 나오지 않거나 큰 해가 나오는 경우가 존재하므로 DFS가 아닌 BFS 사용

 

1. 수빈이의 시작지점 N에서 BFS방식으로 +1, -1, *2 지점을 큐에 넣으며 진행한다

 

2. 수빈이가 어떤 칸 X에 W초를 거쳐 도달했는데, 이미 해당 칸에 W보다 적은 시간으로 도달한 기록이 있다면, 더이상 탐색하는것은 의미가 없다.

  - BFS는 W초에서 갈수있는 모든 지점을 완료한 후 W+1초의 지점들에 대해 작업하므로, 값의 유/무로만 판단해도 무방하다

  - X번 노드에 도달했을 때의 이동 횟수를 num배열에 저장하여, 그 값이 없는 좌표에 대해서만 진행한다

 

3. 수빈이의 현재 위치가 동생의 위치보다 크다면, 반드시 작아져야하므로 +1 *2 연산을 진행할 필요 없다

  - 마찬가지로, 수빈이의 현재 위치가 동생의 위치보다 작다면, 반드시 커져야하므로 -1 연산을 진행할 필요 없다

 

4. 정리하자면,

  - 현재 좌표 X가 동생의 좌표 K보다 작으며, X+1 좌표에 진행해본적이 없을때 진행

  - 현재 좌표 X가 동생의 좌표 K보다 작으며, X*2 좌표에 진행해본적이 없을때 진행

  - 현재 좌표 X가 동생의 좌표 K보다 크며, X-1 좌표에 진행해본적이 없을때 진행하며,

  - K좌표에 도달하는 순간 연산 횟수를 출력, 종료한다

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		
		int N = Integer.parseInt(st.nextToken());
		int K = Integer.parseInt(st.nextToken());
		int len=100001;
		int[] num = new int[len*2];
		num[N]=1;
		Queue<Integer> q = new LinkedList<Integer>();
		q.offer(N);
		
		while(num[K]==0) {
			int temp = q.poll();
			if(temp < K && num[temp+1]==0) {
				num[temp+1]=num[temp]+1;
				q.offer(temp+1);
			}
			if(temp-1 >= 0 && num[temp-1]==0) {
				num[temp-1]=num[temp]+1;
				q.offer(temp-1);
			}
			if(temp < K && num[temp*2]==0) {
				num[temp*2]=num[temp]+1;
				q.offer(temp*2);
			}
			
		}
		System.out.println(num[K]-1);
	}
}

결과 화면

MyBatis를 통해 실행한 쿼리문의 결과와, DBMS에서 직접 실행한 쿼리문의 결과가 다를 때,

 

commit이 정상적으로 된 것인지 확인할 필요가 있다

[문제링크]

 

10942번: 팰린드롬?

총 M개의 줄에 걸쳐 홍준이의 질문에 대한 명우의 답을 입력으로 주어진 순서에 따라서 출력한다. 팰린드롬인 경우에는 1, 아닌 경우에는 0을 출력한다.

www.acmicpc.net

 

0. j ~ j+i 까지의 수가 팰린드롬 수가 되려면

  - j, j+1번 자리의 수가 같아야하며

  - j+1 ~ j+i-1 까지의 수가 팰린드롬 수여야 한다

 

1. 1, 2자리 수에 대해 팰린드롬 수 여부를 계산한다

  - 1자리의 수는 전부 다 팰린드롬 수

  - 두 자리의 수가 연속이라면 팰린드롬 수

 

2. isP[x][y] 배열은 y번째 수부터, x개의 수를 고려했을때 해당 수가 팰린드롬인지 여부를 판단한다

  - isP[10][5] - 5~14의 수가 팰린드롬인지 정보 저장

 

3. 3자리부터 n자리까지 모든 가능한 경우에 대해 팰린드롬 수 여부를 검사한다

  - isP[i-2][j+1] : j+1 ~ j+i-1까지의 팰린드롬 여부

 

4. m개의 입력을 처리하며, 미리 구성된 isP 정보에 따라 1/0 출력 결정

 

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main{
	public static void main(String[] args)throws Exception {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringBuilder sb = new StringBuilder();
		
		int N = pint(br.readLine());
		int[] num = new int[N];

		StringTokenizer st = new StringTokenizer(br.readLine(), " ");
		for (int i = 0; i < N; i++) {
			num[i]=pint(st.nextToken());
		}
		//2.
		boolean[][]isP = new boolean[N][N];
        
		//1.
		Arrays.fill(isP[0], true);
		for (int i = 0; i < N-1; i++) {
			if(num[i]==num[i+1])isP[1][i]=true;
		}
        
		//3.
		for (int i = 2; i < N; i++) {
			for (int j = 0; j < N-i; j++) {
				if(num[j]==num[j+i]) {
					if(isP[i-2][j+1]) {
						isP[i][j]=true;
					}
				}
			}
		}
        
		//4.
		int m = pint(br.readLine());
		for (int i = 0; i < m; i++) {
			st=new StringTokenizer(br.readLine());
			int s = pint(st.nextToken()), e=pint(st.nextToken());
			sb.append(isP[e-s][s-1]?1:0).append("\n");
		}
		System.out.println(sb);
	}
	
	static int pint(String s) {
		return Integer.parseInt(s);
	}
}

결과 화면

1. 크롬 사용자 추가 (사용자 폴더 지정)

 

1. 기존 크롬 바로가기를 복사해 새 바로가기를 만든다

 

 

2. 마우스 우클릭 - 속성

3. 대상 항목에 --user-data-dir=폴더경로 를 추가한다

"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --user-data-dir=c:\chrome_profile\test2

// 폴더경로에 공백 등이 있으면, ""로 감싸줘야한다
// 이때, \는 \\로 처리한다

 

4. 바로가기 실행시 기존 사용자가 아닌 새 chrome창이 열리며, 해당 경로의 폴더가 자동 생성된다

 

2. 이클립스에서 해당 프로필의 chrome 사용하게 설정하기

 

1. 이클립스 메뉴의 Window - Preferences에서 browser 검색하기

2. 검색된 Browser중 Chrome 선택 - 우측의 Edit버튼 선택

3. Parameters에 --user-data-dir=폴더경로 추가하기

4. 해당 workspace에서 크롬을 사용하게 되면, 지정한 profile의 크롬으로 작동하게 된다

<한줄요약>

Spring : 자바 객체를 담고 있는 컨테이너, 객체의 생성/사용/제거 등 관리

 

1. Spring이란?

 

-  JEE에서 사용하는 대부분의 기능 + @를 제공하는 경량화 Framework

-  Low level을 자동으로 처리해주어 개발자가 상위 logic에 집중할 수 있도록 한다

-  외부 lib를 쉽게 가져다 쓸 수 있다

 

 

2. Spring 구조

 

-  1. POJO

-  Plain Old Java Object

-  특정 시스템/기술에 종속적이지 않은 자바 객체. 

-  종속성이 없어 생산/이식이 용이하다

 

-  2. PSA

-  Portable Service Abstraction

-  기술에 일관된 방식을 사용해 접근 ( 추상화 )

-  Service Abstraction이란? 어떤 기능을 사용자에게 드러내지 않고 시스템이 자동으로 처리해주는 것

-  Portable Service Abstraction은? 비슷한 여러 기능을 하나로 통합해 사용자에게 제공하는 것

-  ex) DB에 접근하는 법은 JDBC를 비롯해 여러가지가 있지만, @Trancaction으로 트랜잭션 일괄 처리

 

-  3. IoC/DI

-  Inversion of Control (제어 반전)

-  객체의 생성, 의존 등 생명주기 관리를 개발자가 하는게 아닌, 컨테이너가 하게 된다

-  Depedency Injection

-  객체를 생성해두고, 외부에서 의존성을 설정하면 객체가 주입되도록 한다

-  직접 생성자를 호출하거나, 함수를 통해 받을 필요가 없다

 

-  4. AOP

-  Aspect Oriented Programming

-  관점 지향 프로그래밍

-  관점을 기준으로 모듈화, 핵심 로직과 공통(부가) 로직으로 분리한다

-  공통 사용 부분은 공통 모듈로 분리하고 필요할 때 호출해서 사용한다

-  ex) 로그를 남기고 싶을때, 로그 기록 모듈을 별도로 생성 후 로깅이 필요한 부분에서 호출

-  재사용성의 증가

 

 

3. IoC

 

-  Object간의 연결 관계를 런타임에 결정한다

-  DI : IoC의 여러 방법 중 하나

-  객체 간 결합도를 떨어트려 유지보수 용이성을 높인다

 

IoC의 종류

-  Dependency Lookup

-  직접 lookup해서 필요한 자원을 얻는 방식

-  type casting 필요

 

-  Dependency Injection

-  클래스 간 의존관계를 표기해두면(xml, @ annotaion 등) 자동으로 연결해주는 것

-  lookup 불필요

 

Container란?

-  객체의 생성, 소멸 등 생애주기를 관리

-  객체 사용에 필요한 기능들 제공

-  Dependency 제공

-  Thread 처리 등

 

IoC Container

-  코드 대신 컨테이너가 object의 생성, 사용, 제거를 담당

-  컨테이너가 obj를 제어하므로 IoC Container라 한다

 

Bean

-  spring이 생성,관리하는 객체

 

Spring DI Container (BeanFactory)

-  Bean의 생명주기를 관리, BeanFactory라고도 호칭한다

-  Bean을 등록, 생성, 조회

-  getBean()으로 조회 가능

 

ApplicationContext

-  BeanFactory의 기능을 확장한 것

-  spring의 여러 서비스 추가 제공

 

IoC 장점

-  객체 간 결합도 감소

-  결합도가 높으면? 한 class의 코드만 변경되도 다른 코드들까지 다 변경이 일어날 가능성이 높다

 

-  결합도의 감소 예시

 

-  1. class가 아닌 interface를 사용

-  interface만 동일하면 class는 얼마든 수정되도 상관 없다

-  interface가 수정될 때는 수정 필요

 

-  2. Factory 이용

-  팩토리가 클래스를 생성, interface가 변경되도 팩토리만 수정한다

-  class는 팩토리만 호출

-  팩토리에 의존하는 형태

 

-  3. Assembler 이용

-  runtime에 클래스 관계를 설정, 객체를 주입해준다

-  Spring Container에서 Bean으로 객체를 생성하면, 이를 getBean으로 가져다 쓰는 식

 

 

4. DI

 

Bean 생성 방법

-  1. XML 파일

-  자기 정보를 기술

-  기본적으로 singleton이다.

-  singleton이 아니게 하려면 scope="prototype"설정

-  <bean>태그 사용

<bean id="world" class="com.hello.world" />
//not singleton
<bean id="world" class="com.hello.world" scope="prototype" />

 

-  2. annotation

-  자동으로 bean 등록, 주입

-  xml에 component-scan으로 검색할 패키지를 설정

//context 추가 필요
<context:component-scan base-package="com.hello.*" />

-  @annotation을 설정해두면 자동으로 검색, bean으로 생성한다

표기 의미, 종류
@Repository DAO와 같은, 데이터베이스 접근 클래스에 사용
@Service service 클래스에 사용
@Controller servlet같은 controller에 사용
@Component 그 외 나머지에 사용

 

Dependency Injection (DI)

-  객체 간 의존관계를 외부에서 대신 수행한다

-  IoC의 구현방법, 결합도가 느슨하다

 

DI 종류

-  외부 assembler에서 의존성 주입

 

-  1. xml 이용

-  주입할 객체를 xml파일에 설정

<bean id="world" class="com.test.hello.world" />
//not singleton
<bean id="world" class="com.test.hello.world" scope="prototype" />

-  class가 ~hello.world인 클래스를 주입받는 bean 객체 생성

-  world라는 id로 호출 가능하다

 

-  객체의 획득은 Container의 함수로 제공받는다

ApplicationContext context = new ClassPathXmlApplicationContext("com/test/hello/applicationContext.xml");
CommonService world = context.getBean("world", world.class);

 

-  bean 생성시 생성자에 인자가 필요할 경우, constructor-arg태그를 사용한다

-  다른 bean을 인자로 받을 경우 value 대신 ref 사용, 인자는 해당하는 id값이다

<bean id="world" class="com.test.hello.world">
	<constructor-arg value="50" />
</bean>
//위에서 생성한 world bean을 생성자의 인자로 받는 새 객체
<bean id="world2" class="com.test.hello.world2">
	<constructor-arg ref="world" />
</bean>

 

-  생성한 객체에 값을 주입할 경우, property 태그를 사용한다

-  단, 해당 이름의 setter가 설정되 있어야한다. setArg1이 있을 경우 name="arg1"로 사용 가능

-  동일하게 value, ref 사용

//50을 arg1로, world객체를 arg2로 받는 객체
//world2 클래스 내부에 setArg1, setArg2 함수 필수
<bean id="world2" class="com.test.hello.world2">
	<property name="arg1" value="50" />
	<property name="arg2" ref="world" />
</bean>

 

-  주입할 값에 따라 <list> <map> 등 태그 존재

//인자로 list를 받는 객체
<bean id="world2" class="com.test.hello.world2">
	<property name="argList">
		<list>
			<value type="java.lang.Integer">50</value> // int 50 저장
			<ref bean="world" />
		</list>
    </property>
</bean>

//인자로 map를 받는 객체
<bean id="world2" class="com.test.hello.world2">
	<property name="argMap">
		<map>
			<entry key="key1" value="val1" />
			<entry key="key2" value-ref="world" />
		</map>
    </property>
</bean>

 

-  2. annotation 사용

-  @Autowired

-  타입에 맞춰 주입해준다

-  동일한 type이 여러개일 경우 @Qualifier("name") Annotation을 추가해 구분

 

-  @Resource

-  타입에 맞춰 주입해준다

-  동일한 type이 여러개일 경우 @Resource(name="name1") 으로 구분

 

-  @Inject

-  이름으로 주입해준다(Spring 3 지원)

-  표준 Annotation으로, Spring 종속적이지 않다

-  javax.inject-x.x.x.jar 필요

 

<한줄요약>

MVC패턴 : Model / View / Controller로 나눠 각자 담당하는 부분을 처리

Model1 : 하나의 JSP가 처리/반환을 다 처리

Model2 : servlet / service(DAO) / JSP를 분리해 분업화, MVC패턴을 web에 적용

 

1. Model 1 구조

 

-  client의 요청에 대한 응답을 JSP 페이지에서만 처리한다

-  Java bean / service class를 두어 작업을 처리하고, 결과를 반환한다.

 

-  장점

-  구조가 단순하다

-  개발 기간이 짧다

 

-  단점

-  JSP 내 html과 java코드의 혼재

-  backend / frontend 구분이 희미해진다, 분업화가 힘들다

-  규모가 큰 project의 경우 코드의 규모도 커진다

 

 

2. Model 2 구조

 

-  MVC pattern을 Web에 적용한 것

-  client의 요청에 대한 처리 방법은 servlet이 결정하며  : controller

-  실제 처리는 servlet이 지정한 service(DAO)에서 수행  : model

-  service의 결과로 JSP는 페이지를 구성한다 : view

 

-  장점

-  출력/로직 코드의 분리, JSP의 크기가 크지 않다

-  분업이 용이하다

-  각 코드마다 하나의 기능을 수행하므로, 유지보수가 쉽다

 

-  단점

-  구조가 복잡, 초기 학습이 어렵다

+ Recent posts