본문 바로가기
Study/디자인 패턴

[C#/Unity][디자인패턴] 프록시 패턴(Proxy Pattern)

by 스테디코디스트 2023. 12. 4.
반응형

1. 프록시 패턴이란?

- 어떤 객체를 사용하고자 할 때, 객체를 직접적으로 참조 하는것이 아니라, 해당 객체를 대행(대리, proxy)하는 객체를 통해 대상 객체에 접근하는 방식을 사용하는 패턴.

- 해당 객체가 메모리에 존재하지 않아도 기본적인 정보를 참조하거나 설정할 수 있고, 실제 객체의 기능이 반드시 필요한 시점까지 객체의 생성을 미룰 수 있다.

- 원래 객체에 대한 접근을 제어할 수 있어, 요청이 들어와 원래 객체에 전달되기 전이나 후에 무언가를 수행할 수 있다.

- 대리자로써 작업 수행을 요청하면 처리 후 그 결과를 알려주는 패턴.

- 특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 패턴.

- 상속받은 인터페이스의 클래스 내부에서 같은 인터페이스를 상속받은 인스턴스를 생성하는 패턴.

 

2. 프록시 패턴을 사용하는 경우

- 원본 객체를 수정할 수 없는 상황을 극복하기 위해 사용

 

cf) 프록시 종류

1) 가상 프록시

- 꼭 필요로 하는 시점까지 객체의 생성을 연기하고, 해당 객체가 생성된 것처럼 동작하도록 만들고 싶을 때 사용하는 패턴.

- 프록시 클래스에서 자잘한 작업들을 처리하고, 리소스가 많이 요구되는 작업들이 필요할 때만 주체 클래스를 사용하도록 구현한다.

- ex) 해상도가 높은 이미지를 텍스트와 분산하여 처리하는 과정

 

2) 원격 프록시

- 원격 객체에 대한 제어가 로컬 환경에 존재하며, 원격 객체에 대한 프록시 객체가 서로 다른 주소 공간에 있는 객체에 대해 마치 같은 주소 공간에 있는 것처럼 동작하게 만드는 패턴.

- ex) Google Docs

 

3) 보호 프록시

- 주체 클래스에 대한 접근을 제어하기 위한 경우, 객체에 대한 접근 권한을 제어하거나 객체마다 접근 권한을 달리하고 싶을 때 사용하는 패턴

 

4) 로깅 프록시

- 대상 객체에 대한 로깅을 추가하려는 경우

- 프록시는 서비스 메서드를 실행하고 전달하기 전에, 로깅을 하는 기능을 추가하여 재정의한다.

 

5) 캐싱 프록시

- 데이터가 큰 경우 캐싱하여 재사용을 유도

- 클라이언트 요청의 결과를 캐시하고, 이 캐시의 수명 주기를 관리

 

3. 프록시 패턴의 장점

1) 사이즈가 큰 객체가 로딩되기 전에 프록시를 통해 참조할 수 있다.

2) 실체 객체의 public, protected 메소드들을 숨기고 인터페이스를 통해 노출시킬 수 있다.

3) 로컬에 있지 않고 떨어져 있는 객체를 사용할 수 있다.

4) 원래 객체의 접근에 대해서 사전처리를 할 수 있다.

 

4. 프록시 패턴의 단점

1) 객체를 생성할 때 한 단계를 더 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있다.

2) 프록시 내부에서 객체 생성을 위해 스레드가 생성, 동기화가 구현되야 하는 경우 성능이 저하될 수 있다.

3) 로직이 난해해져 가독성이 떨어질 수 있다.

 

5. 코드 구현

1) 기본형 프록시

using System;
using System.Collections.Generic;

namespace StudyCSharp
{
    interface ISubject
    {
        void action();
    }

    class RealSubject : ISubject
    {
        public void action() 
        {
            Console.WriteLine("원본 객체");
        }
    }

    // 기본형 프록시
    class Proxy : ISubject
    {
        private RealSubject _subject; // 대상 객체

        public Proxy(RealSubject subject) 
        {
            this._subject = subject;
        }

        public void action()
        {
            _subject.action();  // 위임 -> RealSubject의 함수를 대신하여 호출

            Console.WriteLine("프록시 객체");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ISubject sub = new Proxy(new RealSubject());
            sub.action();
        }
    }
}

 

- 실행 결과

 

2) 가상 프록시

using System;
using System.Collections.Generic;

namespace StudyCSharp
{
    interface ISubject
    {
        void action();
    }

    class RealSubject : ISubject
    {
        public void action()
        {
            Console.WriteLine("원본 객체");
        }
    }

    // 가상 프록시
    class Proxy : ISubject
    {
        private RealSubject _subject; // 대상 객체

        public Proxy() { }

        public void action()
        {
            if(_subject == null)
            {
                this._subject = new RealSubject();
            }

            _subject.action();  // 위임 -> RealSubject의 함수를 대신하여 호출

            Console.WriteLine("가상 프록시 객체");

        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ISubject sub = new Proxy();
            sub.action();
        }
    }
}

 

- 실행 결과

 

3) 보호 프록시

using System;
using System.Collections.Generic;

namespace StudyCSharp
{
    interface ISubject
    {
        void action();
    }

    class RealSubject : ISubject
    {
        public void action() 
        {
            Console.WriteLine("원본 객체");
        }
    }

    // 보호 프록시
    class Proxy : ISubject
    {
        private RealSubject _subject; // 대상 객체
        private bool _access;

        public Proxy(RealSubject subject, bool access) 
        {
            this._subject = subject;
            this._access = access;
        }

        public void action()
        {
            if(_access )
            {
                _subject.action();  // 위임 -> RealSubject의 함수를 대신하여 호출
            }            

            Console.WriteLine("프록시 객체");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ISubject sub = new Proxy(new RealSubject(), true);
            sub.action();

            Console.WriteLine("---------------------------------");

            ISubject sub2 = new Proxy(new RealSubject(), false);
            sub2.action();
        }
    }
}

 

- 실행 결과

 

4) 로깅 프록시

using System;
using System.Collections.Generic;

namespace StudyCSharp
{
    interface ISubject
    {
        void action();
    }

    class RealSubject : ISubject
    {
        public void action() 
        {
            Console.WriteLine("원본 객체");
        }
    }

    // 로깅 프록시
    class Proxy : ISubject
    {
        private RealSubject _subject; // 대상 객체

        public Proxy(RealSubject subject) 
        {
            this._subject = subject;
        }

        public void action()
        {
            Console.WriteLine("로깅...............");

            _subject.action();  // 위임 -> RealSubject의 함수를 대신하여 호출

            Console.WriteLine("프록시 객체");

            Console.WriteLine("로깅...............");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ISubject sub = new Proxy(new RealSubject());
            sub.action();
        }
    }
}

 

- 실행결과

 

5) 가상 프록시 적용 전 후 비교

- 적용 전

using System;
using System.Collections.Generic;
using System.Threading;

namespace StudyCSharp
{
    class HighResolutionImage
    {
        string img;

        public HighResolutionImage(string path)
        {
            loadImage(path);
        }

        private void loadImage(string path)
        {
            // 이미지를 디스크에서 불러와 메모리에 적재(작업 자체가 무겁고 많은 자원을 필요로 함)
            try
            {
                Thread.Sleep(1000);
                img = path;
            }
            catch (ThreadInterruptedException e)
            {
                // 에러의 경로를 찾음
                Console.WriteLine(e.StackTrace.ToUpper());
            }

            Console.WriteLine("{0}에 있는 이미지 로딩 완료", path);
        }

        public void showImage()
        {
            // 이미지를 화면에 렌더링
            Console.WriteLine("{0} 이미지 출력", img);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            HighResolutionImage highResolutionImage1 = new HighResolutionImage("./img/고해상도 이미지_1");
            HighResolutionImage highResolutionImage2 = new HighResolutionImage("./img/고해상도 이미지_2");
            HighResolutionImage highResolutionImage3 = new HighResolutionImage("./img/고해상도 이미지_3");

            highResolutionImage2.showImage();
        }
    }
}

 

- 실행 결과

 : 이미지여서 볼 순 없지만, 실행해보면 약간의 텀을 두고 이미지 1,2,3이 차례로 로딩된 후 이미지 2가 출력되는 것을 알 수 있다.

 

- 적용 후

using System;
using System.Collections.Generic;
using System.Threading;

namespace StudyCSharp
{
    interface IImage
    {
        void showImage();
    }

    class HighResolutionImage : IImage
    {
        string img;

        public HighResolutionImage(string path)
        {
            loadImage(path);
        }

        private void loadImage(string path)
        {
            // 이미지를 디스크에서 불러와 메모리에 적재(작업 자체가 무겁고 많은 자원을 필요로 함)
            try
            {
                Thread.Sleep(1000);
                img = path;
            }
            catch(ThreadInterruptedException e)
            {
                // 에러의 경로를 찾음
                Console.WriteLine(e.StackTrace.ToUpper());
            }

            Console.WriteLine("{0}에 있는 이미지 로딩 완료", path);
        }

        // Override
        public void showImage()
        {
            // 이미지를 화면에 렌더링
            Console.WriteLine("{0} 이미지 출력", img);
        }
    }

    class ImageProxy : IImage
    {
        private IImage proxyImage;
        private string _path;

        public ImageProxy(string path)
        {
            this._path = path;
        }

        // Override
        public void showImage()
        {
            // 고해상도 이미지 로딩
            proxyImage = new HighResolutionImage(this._path);
            proxyImage.showImage();
        }   
    }

    class Program
    {
        static void Main(string[] args)
        {
            IImage highResolutionImage1 = new ImageProxy("./img/고해상도 이미지_1");
            IImage highResolutionImage2 = new ImageProxy("./img/고해상도 이미지_2");
            IImage highResolutionImage3 = new ImageProxy("./img/고해상도 이미지_3");

            highResolutionImage2.showImage();
        }
    }
}

 

- 실행 결과

: 이미지여서 볼 순 없지만, 실행 후 약간의 시간이 지난 뒤 두 출력 메세지가 동시에 뜬다.


<참고 사이트>

 

 

💠 프록시(Proxy) 패턴 - 완벽 마스터하기

Proxy Pattern 프록시 패턴(Proxy Pattern)은 대상 원본 객체를 대리하여 대신 처리하게 함으로써 로직의 흐름을 제어하는 행동 패턴이다. 프록시(Proxy)의 사전적인 의미는 '대리인'이라는 뜻이다. 즉, 누

inpa.tistory.com

 

 

[Design pattern] 2-6. 프록시 패턴 (Proxy pattern)

안녕하세요. 명월입니다. 이 글은 디자인 패턴의 프록시 패턴(Proxy pattern)에 대한 글입니다. 프록시 패턴은 어떻게 보면 데코레이터 패턴과 유사한 구조를 구성하고 있습니다만, 데코레이터 패턴

nowonbun.tistory.com

 

 

구조 관련 디자인 패턴 > 프록시 패턴(Proxy Pattern)

https://inf.run/6Shr 코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의 디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한,

myoung-min.tistory.com

 

 

[Design Pattern] 프록시 패턴(Proxy Pattern)에 대하여

프록시 패턴이란? 프록시는 대리인이라는 뜻으로, 무엇인가를 대신 처리하는 의미입니다. 일종의 비서라고 생각하시면 됩니다. 사장님한테 사소한 질문을 하기보다는 비서한테 먼저 물어보는

coding-factory.tistory.com

 

 

프록시 패턴

/ 디자인 패턴들 / 구조 패턴 프록시 패턴 다음 이름으로도 불립니다: Proxy 의도 프록시는 다른 객체에 대한 대체 또는 자리표시자를 제공할 수 있는 구조 디자인 패턴입니다. 프록시는 원래 객체

refactoring.guru

 

 

[C# 디자인패턴] 제12강 프록시 패턴

예제코드 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks; namespace CSharp_DesignPattern{ abstract c

codepump.tistory.com