일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- android retrofit login
- 안드로이드 유닛테스트란
- android ar 개발
- 큐 자바 코드
- jvm이란
- 서비스 쓰레드 차이
- 안드로이드 os 구조
- 플러터 설치 2022
- jvm 작동 원리
- rxjava hot observable
- 2022 플러터 설치
- rxjava cold observable
- 안드로이드 유닛 테스트 예시
- 스택 큐 차이
- 안드로이드 라이선스
- ANR이란
- 서비스 vs 쓰레드
- 객체
- 클래스
- 자바 다형성
- 2022 플러터 안드로이드 스튜디오
- ar vr 차이
- 안드로이드 유닛 테스트
- Rxjava Observable
- rxjava disposable
- 멤버변수
- 안드로이드 라이선스 종류
- 안드로이드 레트로핏 crud
- 안드로이드 레트로핏 사용법
- 스택 자바 코드
- Today
- Total
나만을 위한 블로그
프록시 패턴(Proxy pattern)이란? 본문
예전에 포스팅했던 ContextWrapper는 프록시 패턴을 사용해서 구현돼 있다. ContextImpl은 ContextWrapper에 의해서 래핑된 상태고 내가 Context 기능을 호출해야 할 땐 ContextImpl을 직접 건드리지 않고 ContextWrapper의 함수를 호출하는 것으로 Context의 기능을 호출할 수 있다.
프록시 패턴을 몰라서 ContextWrapper가 어떻게 작동하는 건지 정확히 이해하지 못했는데 이 포스팅에서 정리하면서 이해하려고 한다.
위키백과에서 말하는 프록시 패턴은 아래와 같다.
https://en.wikipedia.org/wiki/Proxy_pattern
프록시 패턴에서 가장 일반적인 형태의 프록시는 다른 항목에 대한 인터페이스 역할을 하는 클래스다. 프록시는 네트워크 연결, 메모리의 큰 객체, 파일 또는 비용이 많이 들거나 복제할 수 없는 기타 리소스와 같은 모든 것에 인터페이스할 수 있다. 즉 프록시는 배후에서 실제로 제공되는 객체에 접근하기 위해 클라이언트가 호출하는 래퍼 또는 에이전트 객체다. 프록시에서 추가 기능을 제공할 수 있다. 예를 들어 실제 객체에 대한 작업이 리소스 집약적인 경우 캐싱하거나 실제 객체에 대한 작업이 호출되기 전에 전제조건을 확인한다. 클라이언트의 경우 둘 다 동일한 인터페이스를 구현하기 때문에 프록시 객체를 쓰는 건 실제 객체를 쓰는 것과 유사하다
< 프록시 패턴은 어떤 문제를 해결할 수 있는가? >
- 객체에 대한 접근을 제어해야 할 때
- 객체에 접근할 때 추가 기능을 제공해야 할 때
민감한 객체에 접근할 때, 클라이언트에 필요한 접근 권한이 있는지 확인할 수 있어야 한다
https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%ED%8C%A8%ED%84%B4
일반적으로 프록시는 다른 무언가와 이어지는 인터페이스 역할을 하는 클래스다. 프록시는 어떤 것(네트워크 연결, 메모리 안의 큰 객체, 파일, 복제할 수 없거나 수요가 많은 리소스)과도 인터페이스의 역할을 수행할 수 있다. 프록시 패턴의 잘 알려진 예는 참조 횟수 스마트 포인터 객체다. 복합적인 오브젝트들의 다수의 복사본이 존재해야 하는 상황에서 프록시 패턴은 앱의 메모리 사용량을 줄이기 위해서 플라이웨이트 패턴과 결합된 형태로 나올 수 있다
객체 접근을 제어해야 하거나 객체에 접근할 때 추가 기능을 제공해야 한다면 고려해볼 수 있는 디자인 패턴이란 것 같다.
다른 곳에선 프록시 패턴을 어떻게 설명하는지 확인해 봤다.
https://coding-factory.tistory.com/711
프록시는 대리인이라는 뜻으로 뭘 대신 처리하는 의미다. 일종의 비서다. 사장한테 사소한 질문을 하기보다 비서한테 먼저 물어보는 개념이라고 생각할 수 있다. 이렇게 어떤 객체를 쓰고자 할 때 객체를 직접 참조하는 게 아닌 해당 객체를 대리하는 객체(프록시)를 통해 대상 객체에 접근하는 방식을 사용하면 해당 객체에 메모리가 없어도 기본 정보를 참조하거나 설정할 수 있고, 실제 객체의 기능이 반드시 필요한 시점까지 객체 생성을 미룰 수 있다
용량이 큰 이미지와 글이 같이 있는 문서를 모니터에 띄울 때 이미지 파일은 용량이 크고 텍스트는 용량이 작아서 텍스트는 빠르게 나타나지만 그림은 조금 느리게 로딩되는 걸 봤을 것이다. 만약 이렇게 처리되지 않고 이미지, 텍스트 모두 로딩돼야 화면이 나온다면 사용자는 페이지가 로딩될때까지 기다려야 한다. 그래서 먼저 로딩되는 텍스트라도 먼저 나오는 게 좋다. 이런 방식을 취하려면 텍스트 처리용, 그림 처리용 프로세스를 별도로 운영하면 된다. 이런 구조를 갖게 설계하는 것이 프록시 패턴이다. 일반적으로 프록시는 다른 뭔가와 이어지는 인터페이스 역할을 하는 클래스를 의미한다
장점
- 사이즈가 큰 객체(이미지 등)가 로딩되기 전에도 프록시를 통해 참조할 수 있다
- 실제 객체의 public, protected 메서드들을 숨기고 인터페이스를 통해 노출할 수 있다
- 로컬에 없고 떨어져 있는 상태의 객체를 쓸 수 있다
- 원래 객체의 접근에 대해 사전처리를 할 수 있다
단점
- 객체 생성 시 한 단계를 거치게 되서 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있다
- 프록시 내부에서 객체 생성을 위해 쓰레드가 생성, 동기화가 구현되야 하는 경우 성능이 저하될 수 있다
- 로직이 어려워져 가독성이 떨어질 수 있다
https://refactoring.guru/ko/design-patterns/proxy
프록시 패턴은 다른 객체에 대한 대체 또는 자리표시자를 제공하는 구조 디자인 패턴이다. 프록시는 원래 객체에 대한 접근을 제어하므로 내 요청이 원래 객체에 전달되기 전 or 후에 뭔가를 수행할 수 있게 한다
객체에 대한 접근을 제한하는 이유는 무엇인가? 방대한 양의 시스템 자원을 소비하는 큰 객체가 있다고 가정한다. 이 객체는 필요할 때가 있긴 하지만 항상 필요한 건 아니다(DB 쿼리 등)
실제로 필요할 때만 이 객체를 만들어서 지연 초기화를 구현할 수 있다. 그럼 객체의 모든 클라이언트들은 어떤 지연 초기화된 코드를 실행해야 하는데 이건 아마 많은 코드 중복을 초래할 것이다. 이상적인 상황에선 이 코드를 객체의 클래스에 직접 넣을 수 있겠지만 그게 항상 가능한 건 아니다. 그 클래스가 폐쇄된 타사 라이브러리의 일부일 수도 있다
프록시 패턴은 원래(original) 서비스 객체와 같은 인터페이스로 새 프록시 클래스를 생성하라고 제안한다. 그럼 프록시 객체를 원래 객체의 모든 클라이언트들에 전달하도록 앱을 업데이트할 수 있다. 클라이언트한테 요청을 받으면 이 프록시는 실제 서비스 객체를 생성하고 모든 작업을 이 객체에 위임한다
내가 클래스의 메인 로직 이전이나 이후에 뭔가를 실행해야 하는 경우 프록시는 해당 클래스를 바꾸지 않고도 이 "뭔가"를 실행할 수 있게 한다. 프록시는 원래 클래스와 같은 인터페이스를 구현하므로 실제 서비스 객체를 기대하는 모든 클라이언트에 전달될 수 있다
신용 카드는 은행 계좌의 프록시고, 은행 계좌는 현금의 프록시다. 둘 다 같은 인터페이스를 구현하며 둘 다 결제에 사용할 수 있다. 신용 카드를 사용하는 소비자는 많은 현금을 갖고 다닐 필요가 없다. 또한 상점 주인은 거래 수입을 은행에 가져가는 길에 강도를 당하거나 잃어버릴 위험 없이 계좌에 전자적으로 입금되기 때문에 좋다
정리하면 프록시 패턴은 어떤 객체를 사용하려는데 그 객체를 직접적으로 참조하지 않고, 내가 사용하려는 객체에 대응하는 객체를 통해 대상 객체에 접근하는 방식이다.
예제 코드를 확인해 보자. 아래 예제코드는 위키백과에 있는 자바 코드를 코틀린으로 바꾼 형태다.
interface Image {
fun displayImage()
}
실제 이미지를 사용하는 건 아니고 println()으로 이미지 로딩, 이미지 표시라고 출력할 건데, 이미지 표시를 출력하기 위해 사용할 함수를 인터페이스에 정의한다.
class RealImage(private var fileName: String): Image {
init {
loadImageFromDisk()
}
private fun loadImageFromDisk() = println("Loading $fileName...")
override fun displayImage() = println("Displaying $fileName...")
}
class ProxyImage(private var fileName: String): Image {
private var image: Image? = null
override fun displayImage() {
if (image == null) {
image = RealImage(fileName)
}
image?.displayImage()
}
}
fun main() {
val image1 = ProxyImage("고해상도 10MB 이미지 1번")
val image2 = ProxyImage("고해상도 10MB 이미지 2번")
image1.displayImage()
image2.displayImage()
}
// Loading 고해상도 10MB 이미지 1번...
// Displaying 고해상도 10MB 이미지 1번...
// Loading 고해상도 10MB 이미지 2번...
// Displaying 고해상도 10MB 이미지 2번...
메인 함수에서 displayImage()를 구현한 ProxyImage를 통해 인터페이스에 정의된 displayImage()를 호출한다. ProxyImage는 Image 인터페이스를 구현하고 있어서 displayImage()를 호출하면 Loading을 출력하는 함수를 먼저 호출하고, 그 후 Displaying을 출력하는 함수를 호출한다. 그래서 ProxyImage 객체에 각각 displayImage()를 호출하면 Loading, Displaying 순으로 출력된다.
'개인 공부 > 디자인 패턴' 카테고리의 다른 글
Repository 패턴이란? (0) | 2023.07.17 |
---|---|
컴포지트 패턴(Composite pattern)이란? (0) | 2022.06.12 |
데코레이터(Decorator) 패턴이란? (0) | 2022.01.16 |
빌더(Builder) 패턴이란? (0) | 2022.01.02 |
팩토리 패턴이란? (0) | 2021.10.05 |