[안드로이드] 디자인 패턴(Design Pattern) 이란?
이전 Design Pattern 개념 정리에 이어서
아키텍처(Architectural) 에 해당하는 MVC 패턴에 대해 정리하려고 합니다.
1. MVC 패턴
모델-뷰-컨트롤러(Model-View-Controller, MVC)는 소프트웨어 공학에서 사용되는 소프트웨어 디자인 패턴이다. 이 패턴을 성공적으로 사용하면, 사용자 인터페이스로부터 비즈니스 로직을 분리하여 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직을 서로 영향 없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있다. (자료 출처 : Wikipedia)
2. MVC 구성 요소
① Controller : 데이터와 비즈니스 로직 사이의 상호 동작을 관리한다.
② Model : 애플리케이션의 정보·데이터를 나타낸다.
③ View : 사용자 인터페이스 (텍스트, 버튼, 체크박스 등등)
(*비즈니스 로직 : 프로그램에서 실세계의 규칙에 따라 데이터를 생성·표시·저장·변경하는 부분을 일컫는다.)
3. MVC 관계 묘사
① Controller : 모델에 명령을 보냄으로써 모델의 정보·데이터를 변경 할 수 있다.
② Model : 모델의 상태에 변화가 있을 때 컨트롤러와 뷰에 이를 통보한다.
③ View : 사용자가 볼 결과물을 생성하기 위해 모델로부터 정보를 읽어온다.
2. MVC 예제
좀 더 이해를 돕기 위해 예제와 함께 주요 포인트를 집고 넘어 가려고 합니다.
해당 예제는 Tic-Tac-Toe 이라는 게임입니다. (자료 출처 : realm)
크게 화면(Activity) 과 데이터(Model) 로 구성되어있고 아래와 같이 구성도를 나타낼 수 있습니다.
주요포인트
- 안드로이드에서는 View 와 Controller 가 공존한다. (Activity 또는 Fragment)
- Model 은 따로 분리하여 새로운 Class 로 관리한다.
- 사용자가 발생한 Event 에 따라서 Controller 에서 Model 상태를 업데이트 해준다.
- 변경된 Model 상태는 View 를 통해 사용자에게 알려준다.
특징
- Activity 또는 Fragment 에서 모든 동작을 처리하기 때문에 쉽게 구현가능하지만,
- 그 만큼 한 Class 에 코드양이 방대해질 수 있기 때문에 유지보수가 어려울 수 있다.
해당 예제의 전체소스코드는 아래와 같습니다.
TicTacToeActivity (View/Controller)
public class TicTacToeActivity extends AppCompatActivity {
private static String TAG = TicTacToeActivity.class.getName();
private Board model;
private ViewGroup buttonGrid;
private View winnerPlayerViewGroup;
private TextView winnerPlayerLabel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tictactoe);
winnerPlayerLabel = (TextView) findViewById(R.id.winnerPlayerLabel);
winnerPlayerViewGroup = findViewById(R.id.winnerPlayerViewGroup);
buttonGrid = (ViewGroup) findViewById(R.id.buttonGrid);
model = new Board();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_tictactoe, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_reset) {
reset();
return true;
}
return super.onOptionsItemSelected(item);
}
public void onCellClicked(View v) {
Button button = (Button) v;
String tag = button.getTag().toString();
int row = Integer.parseInt(tag.substring(0, 1));
int col = Integer.parseInt(tag.substring(1, 2));
Log.i(TAG, "Click Row: [" + row + "," + col + "]");
Player playerThatMoved = model.mark(row, col);
if (playerThatMoved != null) {
button.setText(playerThatMoved.toString());
if (model.getWinner() != null) {
winnerPlayerLabel.setText(playerThatMoved.toString());
winnerPlayerViewGroup.setVisibility(View.VISIBLE);
}
}
}
private void reset() {
winnerPlayerViewGroup.setVisibility(View.GONE);
winnerPlayerLabel.setText("");
model.restart();
for (int i = 0; i < buttonGrid.getChildCount(); i++) {
((Button) buttonGrid.getChildAt(i)).setText("");
}
}
}
Board / Cell / Player (Model)
public class Board {
private Cell[][] cells = new Cell[3][3];
private Player winner;
private GameState state;
private Player currentTurn;
private enum GameState { IN_PROGRESS, FINISHED };
public Board() {
restart();
}
/**
* Restart or start a new game, will clear the board and win status
*/
public void restart() {
clearCells();
winner = null;
currentTurn = Player.X;
state = GameState.IN_PROGRESS;
}
/**
* Mark the current row for the player who's current turn it is.
* Will perform no-op if the arguments are out of range or if that position is already played.
* Will also perform a no-op if the game is already over.
*
* @param row 0..2
* @param col 0..2
* @return the player that moved or null if we did not move anything.
*
*/
public Player mark( int row, int col ) {
Player playerThatMoved = null;
if(isValid(row, col)) {
cells[row][col].setValue(currentTurn);
playerThatMoved = currentTurn;
if(isWinningMoveByPlayer(currentTurn, row, col)) {
state = GameState.FINISHED;
winner = currentTurn;
} else {
// flip the current turn and continue
flipCurrentTurn();
}
}
return playerThatMoved;
}
public Player getWinner() {
return winner;
}
private void clearCells() {
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
cells[i][j] = new Cell();
}
}
}
private boolean isValid(int row, int col ) {
if( state == GameState.FINISHED ) {
return false;
} else if( isOutOfBounds(row) || isOutOfBounds(col) ) {
return false;
} else if( isCellValueAlreadySet(row, col) ) {
return false;
} else {
return true;
}
}
private boolean isOutOfBounds(int idx) {
return idx < 0 || idx > 2;
}
private boolean isCellValueAlreadySet(int row, int col) {
return cells[row][col].getValue() != null;
}
/**
* Algorithm adapted from http://www.ntu.edu.sg/home/ehchua/programming/java/JavaGame_TicTacToe.html
* @param player
* @param currentRow
* @param currentCol
* @return true if <code>player</code> who just played the move at the <code>currentRow</code>, <code>currentCol</code>
* has a tic tac toe.
*/
private boolean isWinningMoveByPlayer(Player player, int currentRow, int currentCol) {
return (cells[currentRow][0].getValue() == player // 3-in-the-row
&& cells[currentRow][1].getValue() == player
&& cells[currentRow][2].getValue() == player
|| cells[0][currentCol].getValue() == player // 3-in-the-column
&& cells[1][currentCol].getValue() == player
&& cells[2][currentCol].getValue() == player
|| currentRow == currentCol // 3-in-the-diagonal
&& cells[0][0].getValue() == player
&& cells[1][1].getValue() == player
&& cells[2][2].getValue() == player
|| currentRow + currentCol == 2 // 3-in-the-opposite-diagonal
&& cells[0][2].getValue() == player
&& cells[1][1].getValue() == player
&& cells[2][0].getValue() == player);
}
private void flipCurrentTurn() {
currentTurn = currentTurn == X ? O : X;
}
}
public class Cell {
private Player value;
public Player getValue() {
return value;
}
public void setValue(Player value) {
this.value = value;
}
}
public enum Player { X , O }
tictactoe.xml (layout)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tictactoe"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.acme.tictactoe.controller.TicTacToeActivity">
<GridLayout
android:id="@+id/buttonGrid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:columnCount="3"
android:rowCount="3">
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="00" />
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="01" />
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="02" />
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="10" />
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="11" />
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="12" />
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="20" />
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="21" />
<Button
style="@style/tictactoebutton"
android:onClick="onCellClicked"
android:tag="22" />
</GridLayout>
<LinearLayout
android:id="@+id/winnerPlayerViewGroup"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/winnerPlayerLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:textSize="40sp"
tools:text="X" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/winner"
android:textSize="30sp" />
</LinearLayout>
</LinearLayout>
[참고자료]
https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/
이거 xml 파일 안맞음 구라침
답글삭제