[안드로이드] 디자인 패턴(Design Pattern) 이란? 이전 Design Pattern 개념 정리에 이어서  아키텍처(Architectural) 에 해당하는 MVC 패턴에 대해 정리하려고 합니다.         1. MVC 패턴 모델-뷰-...

[안드로이드] MVC 패턴 (+예제)

 

[안드로이드] 디자인 패턴(Design Pattern) 이란?

이전 Design Pattern 개념 정리에 이어서

 아키텍처(Architectural) 에 해당하는 MVC 패턴에 대해 정리하려고 합니다.

 

 

 

 

1. MVC 패턴

모델-뷰-컨트롤러(Model-View-Controller, MVC)는 소프트웨어 공학에서 사용되는 소프트웨어 디자인 패턴이다. 이 패턴을 성공적으로 사용하면, 사용자 인터페이스로부터 비즈니스 로직을 분리하여 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직을 서로 영향 없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있다. (자료 출처 : Wikipedia)

 

 

 

 

2. MVC 구성 요소

    ① Controller : 데이터와 비즈니스 로직 사이의 상호 동작을 관리한다.

    ② Model : 애플리케이션의 정보·데이터를 나타낸다.

    ③ View : 사용자 인터페이스 (텍스트, 버튼, 체크박스 등등)

    (*비즈니스 로직 : 프로그램에서 실세계의 규칙에 따라 데이터를 생성·표시·저장·변경하는 부분을 일컫는다.)

 

 

 

 

3. MVC 관계 묘사

 

(자료 출처 : Wikipedia)

 

    ① 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>

 

 


[참고자료] 

Wikipedia

https://academy.realm.io/kr/posts/eric-maxwell-mvc-mvp-and-mvvm-on-android/










댓글 1개: