Hun's Blog

[Android] 디자인패턴 1 - 디자인패턴이란? 본문

Android

[Android] 디자인패턴 1 - 디자인패턴이란?

jhk-im 2020. 3. 22. 09:51

디자인 패턴


mvc - mvp - mvvm을 사용해보기위해 공부해보니 안드로이드 개발 시 사용하는 디자인패턴이라는 것을 알게되었다.  그렇다면 먼저 디자인 패턴에 대해 공부를 해봐야 할 것 같아서 찾아보기 시작했는데 공부해야 할 것이 너무 많다... 앞으로 꾸준히 해야 한다고 생각하고 여기서는 간단하게 개념만 정리한 후 예제를 만들고 실제 프로젝트에 적용해보면서 공부해 나가도록 하겠다. 



디자인패턴이란 무엇인가?

- 여기서 다루는 디자인패턴이란 '소프트웨어 공학'의 개념이다.

- 프로그래밍할 때 다양한 문제 상황에 대한 재사용 가능한 해결책이다.
  *일반적인 문제를 해결하기 위해 최선의 방법을 공식화 혹은 정의 하는 것  

- 패러다임과 알고리즘과는 다르다.
  * 객체지향 패러다임이든 함수형 프로그래밍 패러다임이든 문제상황은 일관되기에 패러다임과 디자인패턴은 동의어가 될 수 없다. 또한 어떠한 알고리즘이라도 마찬가지로 문제상황은 일관되기 때문에 알고리즘과도 동의어가 될 수 없다. 디자인패턴은 일반화된 해결책이다. 

- 디자인 패턴은 객체지향 패러다임의 개념이 아니다.
  * 디자인 패턴에 대한 내용을 보면 객체지향 패러다임에 국한된 내용들이 많은데 엄밀히 말하면 아니다. 디자인 패턴이 반드시 객체지향에 적합해야 할 필요는 없다. 다만 객체지향이 굉장히 유용한 패러다임이기 때문에 이에 대한 연구가 더 많이 진행되었던 것이다. 

- Creational(창조), Structural(구조), Behavioral(행동)으로 구분한다.
 * 클래스와 인스턴스에 관한 생성, 구조화, 행동과 관련있다. 여기서 디자인 패턴이 객체지향에 초점을 맞추고 있음을 알 수 있다.

 

Creational patterns


- 'Creat'이라는 어미에 맞게 클래스의 인스턴스를 만드는 것과 관련있다.
- Class creation patterns : 상속을 효과적으로 사용하는데 집중
- Object creation patterns : 인스턴스를 효과적으로 생성하는데 집중 (Delegation 활용)
- 대표적으로 Singleton 패턴이 있다.
  *싱글톤은 하나의 클래스에 단 하나의 인스턴스를 허용하는 패턴이다. 자주 사용되므로 이 예제를 구현해보면서 이해해보자. 

SingletonSample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SingletonSample {
    private static SingletonSample instance = null;
 
    private SingletonSample(){}
    public static SingletonSample getInstance(){
        if(instance == null){
            instance = new SingletonSample();
        }
        return instance;
    }
    private Activity activity;
 
    public Activity getActivity(){
        return activity;
    }
    public void setActivity(Activity activity) {
        this.activity = activity;
    }
}
 
 


Singleton
하나의 클래스에 대해 어플리케이션이 시작될 때 최초 한번만 메모리를 할당하고 그 메모리에 인스턴스를 생성한다. 즉, 인스턴스를 단 하나만 생성한다.

장점
- 고정된 메모리영역을 얻어 하나의 인스턴스만 생성하기 때문에 메모리 낭비를 방지한다.
- 인스턴스가 전역적으로 사용될 수 있기에 다른 클래스의 인스턴스들이 데이터를 공유하고 변경할 수 있다.

단점
- 싱글톤 인스턴스에게 많은일을 위임하거나 데이터를 공유시킬 경우 다른 클래스의 인스턴스간에 결합도가 높아져 개방폐쇄원칙에 위배된다.
- 멀티스레드 환경에서 데이터 동기화 문제가 발생할 수 있음 (Synchronized 키워드 활용)
- 너무 많이 사용하지 않도록 한다.


private static SingletonSample instance = null;

static으로 인스턴스의 메모리 할당

private SingletonSample(){}

생성자 앞에 private으로 선언
-> 다른 클래스에서 new 키워드로 해당 인스턴스를 생성할 수 없음

 

1
2
3
4
5
6
public static SingletonSample getInstance(){
    if(instance == null){
        instance = new SingletonSample();
    }
    return instance;
}
 
 


return instance;}getInstace()를 통해 다른 클래스에서 해당 인스턴스에 접근할 수 있다.

*위 싱글톤 예제는 해당 인스턴스를 통해 Activity를 얻어온다. 
싱글톤 클래스의 인스턴스를 생성한 Activity의 생명주기에따라 영향을 받을 수 있게하기 위함이다. 만약 Activity 생명주기에 영향을 받지 않게 하기 위해선 Application 레벨의 Context를 받는 것으로 대체할 수 있다. 

참고
https://limkydev.tistory.com/37


 

Structural patterns


- 클래스나 인스턴스들의 관계와 관련있다.
- 프로그램의 여러 기능들 간에 인터페이스를 명확히한다.
- 클래스나 인스턴스의 관계를 조정하고 구조를 짜맞추는 패턴들이 포함된다.
- 대표적으로 Adapter 패턴이 있다.
  * 외부와 접촉하는 인터페이스가 불일치해 상호 접근이 불가한 객체들 사이에서 연결점이되어 두 객체를 연결해주는 역할을 한다. 마찬가지로 예제를 통해 알아보도록 하자. 

안드로이드에서 대표적으로 Adapter를 사용하는 것이 리사이클러뷰다.
리사이클러뷰에 대한 글이 아니기때문에 Adapter위주로 자세히 살펴보도록 하자.

리사이클러뷰 구성요소

 

 

리사이클러뷰는 데이터 목록을 아이템 단위의 뷰로 구성하여 화면에 표시하기 위해 Adapter를 사용한다. 또한 아이템뷰가 나열되는 형태를 관리하기 위한 요소를 제공하며 이를 레이아웃 매니저라고 한다. 레이아웃 매니저가 제공하는 레이아웃의 형태로 어댑터를 통해 만들어진 각 아이템뷰는 뷰홀더 객체에 저장되어 화면에 표시되고 필요에 따라 생성 또는 재활용(Recycle)된다.

Adapter
- 리사이클러뷰에 표시될 아이템뷰를 생성하는 역할
- 사용자 데이터 리스트로부터 아이템뷰를 생성

레이아웃 매니저
- 어떤 형태로 아이템뷰를 배치할 것인지를 결정

뷰홀더
- 화면에 표시될 아이템뷰를 저장하는 객체

실행화면

 


AdapterSample

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class AdapterSample extends RecyclerView.Adapter<AdapterSample.ViewHolder> {
    private ArrayList<String> mData = null;
    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView tv_main;
        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tv_main = itemView.findViewById(R.id.tv_main);
        }
    }
    public AdapterSample(ArrayList<String> list){
        mData = list;
    }
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        Context context = parent.getContext();
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(
                Context.LAYOUT_INFLATER_SERVICE);
        View view = inflater.inflate(R.layout.rv_item_main, parent, false);
        AdapterSample.ViewHolder vh = new AdapterSample.ViewHolder(view);
        return vh;
    }
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        String text = mData.get(position);
        holder.tv_main.setText(text);
    }
    @Override
    public int getItemCount() {
        return mData.size();
    }
}
 
 


ViewHolder 클래스
-> 아이템 뷰를 저장한다.
-> 뷰 객체에 대한 참조를 한다.

onCreateViewHolder()
-> 아이템 뷰를 위한 뷰홀더 객체를 생성하여 리턴한다.

onBindViewHolder()
-> position에 해당하는 데이터를 뷰홀더의 아이템뷰에 표시한다.

getItemCount()
-:> 전체 데이터 갯수를 리턴한다.

* 어댑터 내에 뷰홀더를 위한 클래스를 구현한것을 확인할 수 있다.
뷰홀더는 RecyclerView.ViewHolder를 상속받아 구현한다. 해당 뷰홀더는 아이템에 표시될 텍스트뷰에 대한 참조를 가진다.
이렇게 작성한 뷰홀더는 어댑터로부터 onCreateViewHolder()와 onBindViewHolder() 메소드를 오버라이드하여 각각 생성과 데이터 표시를 하여 화면에 출력한다.


MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ArrayList<String> list = new ArrayList<>();
        for (int i=0; i<100; i++) {
            list.add(String.format("TEXT %d", i)) ;
        }
        RecyclerView recyclerView = findViewById(R.id.rv_main) ;
        recyclerView.setLayoutManager(new LinearLayoutManager(this)) ;
        AdapterSample adapter = new AdapterSample(list) ;
        recyclerView.setAdapter(adapter) ;
    }
}
 
 

메인 액티비티에서 위에서 구현한 리사이클러뷰를 출력한다.

여기서 다시한번 Adapter의 특징을 생각해보자. 
  * 외부와 접촉하는 인터페이스가 불일치해 상호 접근이 불가한 객체들 사이에서 연결점이되어 두 객체를 연결해주는 역할을 한다. 

메인액티비티의 activity_main.xml 에 리사이클러뷰가 셋팅되어있고 해당 리사이클러뷰에는 rv_tiem_main.xml을 사용하는 아이템뷰들이 표시된다. 해당 아이템뷰들은 ViewHolder에서 저장하고 있다.   

즉, 메인액티비티의 리사이클러뷰와 뷰홀더의 아이템뷰는 상호 접근이 불가한 객체인 것이다. 이 사이에서 AdpaterSample 클래스에서 Adapter 패턴을 구현해 두 객체를 연결해주는 역할을 하고있다. 

참고
https://recipes4dev.tistory.com/154



Behavioral patterns



- 클래스와 인스턴스가 동작하는 방식이나 소통하는 방식과 관련있다.
- 객체 속 작업이 진행되는 작업 흐름을 정의하고 따라간다.
- 알고리즘이나 기능들이 어떻게 흐르는지, 어떤 순서로 소통하는 지에 대해 정의한다.
- 대표적으로 Template Method 패턴이 있다.
  * 어떤 동작의 알고리즘을 단위 기능 모듈로 분류하고 이들간의 동작 순서를 정의한다. 그리고 나서 단위 기능을 바로 구현하는 것이 아니라 몇몇은 그 클래스를 상속할 자식 클래스에 위임한다.  예제를 통해 알아보자. 


Template Method 예제

AutoCar

1
2
3
4
5
6
7
8
9
public class AutoCar {
    public void playWithOwenr(){
        Log.e("on","시동켜기");
        Log.e("side_break","해제");
        Log.e("start","D");
        Log.e("operation","자동");
        Log.e("break","브레이크");
    }
}
 
 

Manual Car

1
2
3
4
5
6
7
8
9
public class ManualCar {
    public void playWithOwenr(){
        Log.e("on","시동켜기");
        Log.e("side_break","해제");
        Log.e("start","2단");
        Log.e("operation","수동");
        Log.e("break","브레이크");
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

자동 변속하는 자동차 클래스가있고, 수동 변속하는 자동차 클래스가있다.
둘다 playWithOwner() 메소드를 통해 동일하게 구현되있지만 수동기어인지 자동기어인지에 대해서 사로 다르다.

이 경우 Template Method Pattern 을 사용하여 설계할 수 있다.

*한꺼번에 구체적인 클래스를 구현하지 않고 추상클래스의 템플릿 메소드를 구현하여서 구체적인 클래스가 이를 상속받아 자신이 필요한 메소드를 작성해주는 방식이다.

다음과같이 변경이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Car {
    public void playWithOwner(){
        Log.e("on","시동켜기");
        Log.e("side_break","해제");
        play();
        stopBreak();
    }
    abstract void play();
    void stopBreak() {
        Log.e("break","브레이크");
    }
}
 
 

추상클래스 안에 추상메소드 선언을 해놓고 일반 메소드를 어느정도 구현해 놓았다.
추상클래스를 상속받은 구체적인 클래스는 추상메소드를 강제로 오버라이딩 시켜 재정의해야한다. 일반 메소드는 재정의 해도되고 안해도 된다. 이러한 메소드는 Hook 메소드라고 한다. stopBreak()이 Hook에 해당된다. 다음과 같이 수정해보자.

 

AutoCar

1
2
3
4
5
6
7
8
9
10
11
12
public class AutoCar extends Car{
    @Override
    void play() {
        Log.e("start","2단");
        Log.e("operation","수동");
    }
    @Override
    void stopBreak() {
        super.stopBreak();
        Log.e("break","강력한 브레이크");
    }
}
 
 

ManualCar

1
2
3
4
5
6
7
8
9
10
11
12
public class ManualCar extends Car{
    @Override
    void play() {
        Log.e("start","2단");
        Log.e("operation","수동");
    }
    @Override
    void stopBreak() {
        super.stopBreak();
        Log.e("break","부드러운 브레이크");
    }
}
 
 


*상위 클래스에게 공통적인 로직은 템플릿 메소드로 두고, 구체적인 클래스에서 스타일에 맞게 구현을 강제하기 위해 추상메소드를 사용하고, Hook 메소드를 두는 패턴을 템플릿 메소드 패턴 (Template Method Pattern) 이라고 한다. 


참고
https://limkydev.tistory.com/81





정리 -
mvc - mvp - mvvm에 대해서 구현하기 전에 디자인 패턴에대해서 간단히 정리하고자 글을 작성하였다. 간단한게 하려했는데 이것도 꽤 오래걸린 느낌이다. 어쨋든 ... 

누군가에게 디자인패턴에 대하여 설명한다고 생각하고 정리해 보겠다. 

디자인패턴은 프로그래밍 시 발생하는 다양한 문제상황에 대한 해결책이다. 
또한 객체지향과 밀접하지만 객체지향에 속한 개념은 아니다. 
생성, 구조, 행동으로 구분하여 설명할 수 있다. 

생성이란 클래스의 인스턴스를 만드는것과 관련이있다.
대표적으로 싱글톤 패턴이 이에 속한다. 싱글톤 패턴이란 하나의 클래스에 단 하나의 인스턴스를 허용하는 패턴이다. 

싱글톤 패턴을 사용하면 고정된 메모리영역에 하나의 인스턴스만 생성하기 때문에 메모리 낭비를 방지할 수 있다. 또한 인스턴스가 전역으로 사용될 수 있다는 장점이 있다. 
메모리 영역을 고정한다는 점에서 너무 많이 사용해서는 않된다는 점을 기억해두어야 한다.

구조란 클래스와 인스턴스의 관계를 조정하고 맞춰가는 것이 목적이다. 
대표적으로 어댑터가 이에 속한다. 어댑터는 상호간에 접근이 불가한 객체사이에서 연결해주는 역할을 한다. 

안드로이드에서는 대표적으로 리사이클러뷰에서 어댑터 패턴을 발견할 수 있다. 
특정 액티비티에 속한 리사이클러뷰에 더해질 아이템뷰를 뷰홀더에서 저장하는데 둘사이에 연관이 없어 접근이 불가함으로 어댑터 패턴을 활용하여 두 객체가 연결된다. 

행동이란 클래스와 인스턴스가 동작하는 방식이나 소통하는 방식을 다루는 패턴이다. 
대표적으로 템플릿 메소드가 있다. 템플릿 메소드는 특정 기능들을 모듈로 분류하고 몇몇 기능들은 상속할 자식 클래스가 위임하여 구현한다. 

많이 들어본 단어들도 있고 생소한 단어들도 있어서 뒤죽박죽이라 아직 완벽히 정리가 되지는 않지만 mvc - mvp - mvvm 를 구현하면서 계속해서 디자인 패턴에 대한 이해를 확장해 나가야 할 것 같다. 

디자인패턴에 관련한 내용과 객체지향에 대한 자세한 내용은 아래 링크에서 확인하자.

디자인패턴 
https://ko.wikipedia.org/wiki/%EB%94%94%EC%9E%90%EC%9D%B8_%ED%8C%A8%ED%84%B4_(%EC%B1%85)

객체지향 설계 
https://ko.wikipedia.org/wiki/SOLID_(%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%EC%84%A4%EA%B3%84)



참고
https://shoark7.github.io/programming/knowledge/what-is-design-pattern
https://lktprogrammer.tistory.com/187

 

[Android Studio] 안드로이드 스튜디오 - 싱글톤 패턴 (SingleTon Pattenr) 클래스 자동 생성

디자인 패턴(Design Pattern) 중 싱글톤(Singleton) 패턴은 특정 클래스에 대한 인스턴스를 단 한 번만 Static 메모리 영역에 할당하고 해당 클래스에 대한 생성자를 여러 번 호출하더라도 최초에 생성된 객체를..

lktprogrammer.tistory.com

 

디자인 패턴이란 무엇인가?

디자인 패턴이 무엇인지 알아보고 파이썬으로 실습해보자.

shoark7.github.io