본문 바로가기
안드로이드 자바

[JAVA][Android] MVC 패턴 익혀보기.

by teamnova 2021. 10. 1.

안녕하세요. 이번 시간에는 디자인 패턴과 패턴 중 MVC 패턴에 대해서 알아보고 안드로이드에서는 이를 어떤 식으로 구현하는지 알아보겠습니다.

 

디자인 패턴

디자인 패턴을 위키피디아에서 치면 아래와 같이 설명하고 있습니다.

소프트웨어 개발 방법에서 사용되는 디자인 패턴은 프로그램 개발에서 자주 나타나는 과제를 해결하기 위한 방법 중 하나로, 과거의 소프트웨어 개발 과정에서 발견된 설계의 노하우를 축적하여 이름을 붙여, 이후에 재이용하기 좋은 형태로 특정의 규약을 묶어서 정리한 것이다. 
알고리즘 과 같이 프로그램 코드로 바로 변환될 수 있는 형태는 아니지만, 특정한 상황에서 구조적인 문제를 해결하는 방식을 설명해 준다.

읽어보시면 어떠신가요? 저는 이해가 안 돼서 유튜브나 블로그를 참고해본 결과 다음과 같이 결론이 났습니다.

컴퓨터 프로그램의 소스 코드들을 각 역할에 따라 구조화를 하고 이 구조대로 코드는 짜는 것

 

제가 왜 이렇게 생각하는지는 MVC 패턴을 소개하면서 말씀드리도록 하겠습니다.

 

MVC 패턴 구성

MVC 패턴은 기존 역할 분담이 안 되어 한곳에 산재되어 있는 코드를 역할에 따라 나누기 위해 만들어졌습니다.

 

Model

- 출력할 데이터와 행동을 갖는 객체

- 비즈니스 로직 수행(상태 변화 처리, 상태 정보 반환)

 

V(iew)

- 데이터를 시각화 하는 부분(사용자에게 보여지는 부분)

- 모델이 처리한 데이터를 받아서 사용(Controller 통해서 받음)

- 어떠한 데이터나 로직이 들어가 있으면 안됨

 

C(ontroller)

- 사용자의 요청을 해석하여 처리하고 반환

- Model과 View를 느슨하게 이어주는 부분

- MVC패턴에서 유일하게 Model과 View를 알고 있으므로(의존하므로) 데이터의 흐름 제어 역할

 

MVC 패턴 장점

아래 사진을 확인해보시면 왼쪽 코드는 출력부와 UI제어부가 혼재 되어 있어 있는 경우이고 오른쪽 코드는 각 역할이 구분되어 있음을 확인 할 수있습니다.

즉 이렇게 만들게 되면 유지보수도 쉽고 분업화 하기도 좋아지게 됩니다.

MVC 패턴을 사용하는 이유 (출처 : 유튜브 뉴렉처 )

 

 

MVC를 지키면서 코딩하는 방법

1. Model은 Controller와 View에 의존하지 않아야 한다.

2. View는 Model에만 의존해야 하고, Controller에는 의존하면 안 된다.

3. View가 Model로부터 데이터를 받을 때는, 사용자마다 다르게 보여주어야 하는 데이터에 대해서만 받아야 한다.

4. Controller는 Model과 View에 의존해도 된다.

5. View가 Model로부터 데이터를 받을 때, 반드시 Controller에서 받아야 한다.

 

안드로이드에서 MVC 패턴 

안드로이드에서 View와 Controller의 역할을 Activity, Fragment가 수행하게 되며, Model은 Data Class에 해당합니다.

 

MVC 패턴대로 리사이클러뷰를 구현해 보았습니다. 코드를 직접 보면서 확인해보세요.

 

MVC 패턴 예제 구조

 

MainActivity

// MainActivity는 View & Controller 역할
// View : onCreate 내부에 View에 속한 내용들이 작성
// Controller 역할 : item 뷰에 들어갈 Model(여기서는 Person)을 받아 처리
public class MainActivity extends AppCompatActivity implements MainViewHolder.HolderClickListener {
//    public static final String TAG = MainActivity.class.getSimpleName();
    public final String TAG = this.getClass().getSimpleName();

    RecyclerView recyclerView;
    LinearLayoutManager linearLayoutManager;
    MainAdapter adapter;
    Database database = Database.getInstance();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //리사이클러뷰 선언 & 세팅
        recyclerView = findViewById(R.id.recycler_view);
        linearLayoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(linearLayoutManager);
        adapter = new MainAdapter(this);
        recyclerView.setAdapter(adapter);
        adapter.setItems(database.getPersonList()); // Model
        database.setOnDatabaseListener(new Database.DatabaseListener(){
            @Override
            public void onChanged() {
                adapter.setItems(database.getPersonList());
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add("Add");
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        database.add(new Person(System.currentTimeMillis(), String.format("New Charles %d", new Random().nextInt(1000))));
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onDeleteClick(Person person) {
        database.remove(person);
    }
}

 

MainAdapter

// MainAdapter는 View & Controller & Adapter 역할
// View : 리사이클러뷰에 추가될 item 뷰를 세팅
// Controller 역할 : item 뷰에 들어갈 Model(여기서는 Person)을 받아 처리
// Adapter 역할 : 해당 아이템의 View/Controller를 리사이클러뷰가 셋팅되어있는 View/Controller인 액티비티와 상호 접근이 가능하도록 하는 Adapter
public class MainAdapter extends RecyclerView.Adapter<MainViewHolder> {

    private List<Person> items = new ArrayList<>();
    private MainViewHolder.HolderClickListener holderClickListener;

    public MainAdapter(MainViewHolder.HolderClickListener holderClickListener){
        this.holderClickListener = holderClickListener;
    }
    @NonNull
    @Override
    public MainViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
        View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.view_main, viewGroup, false);
        return new MainViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MainViewHolder mainViewHolder, int position) {
        mainViewHolder.setPerson(items.get(position));
        mainViewHolder.setOnHolderClickListener(holderClickListener);
    }

    public void setItems(List<Person> items) {
        this.items = items;
        notifyDataSetChanged();
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    public List<Person> getItems() {
        return items;
    }
}

 

Database

public class Database {
    private static Database instance;
    private ArrayList<Person> personList = new ArrayList<>();
    private DatabaseListener databaseListener;

    private Database() {
        for (int index = 0; index < 100; index++) {
            personList.add(new Person(index, String.format("StickCode%d", index)));
        }
    }

    public static Database getInstance() {
        if (instance == null) {
            instance = new Database();
        }
        return instance;
    }

    public void add(Person person) {
        personList.add(0,person);
        notifyChange();
    }

    public void remove(Person person) {
        personList.remove(person);
        notifyChange();
    }

    private void notifyChange() {
        if (databaseListener != null) {
            databaseListener.onChanged();
        }
    }

    public void setOnDatabaseListener(DatabaseListener databaseListener) {
        this.databaseListener = databaseListener;
    }

    public ArrayList<Person> getPersonList() {
        return personList;
    }

    public interface DatabaseListener {
        void onChanged();
    }

}

Person

public class Person {
    private long id;
    private String name;

    public Person(long id, String name) {
        this.id = id;
        this.name = name;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

MainViewHolder

public class MainViewHolder extends RecyclerView.ViewHolder {

    private TextView name;
    private Button delete;
    private HolderClickListener listener;
    private Person person;

    public MainViewHolder(@NonNull View itemView) {
        super(itemView);
        name = itemView.findViewById(R.id.name);
        delete = itemView.findViewById(R.id.delete);
        delete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(listener !=null){
                    listener.onDeleteClick(person);
                }
            }
        });
    }

    public void setPerson(Person person){
        this.person = person;
        name.setText(person.getName());
    }

    public void setOnHolderClickListener(HolderClickListener listener) {
        this.listener = listener;
    }

    public interface HolderClickListener{
        void onDeleteClick(Person person);
    }
}

 

리사이클러뷰 기본 틀은 아래 스틱코드를 이용해서 간단히 불러와 썼습니다.

https://stickode.com/detail.html?no=2444 

 

스틱코드

 

stickode.com

 

 

xml 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

 

view_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="50dp">

    <TextView
        android:id="@+id/name"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/delete"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="DELETE"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>