프록시 패턴에는 여러 가지 종류가 있으며, 그 종류는 다음과 같다.
가상 프록시 적용 예시
protocol SubjectProtocol {
func action()
}
class RealSubject: SubjectProtocol {
func action() {
print("무거운 로직들이 가득 담겨 있음")
}
}
// 프록시 객체에 해당하는 클래스
class Proxy: SubjectProtocol {
// 대상 객체를 옵셔널 타입으로 선언한다. nil 값을 할당할 수 있게끔 하기 위해서이다.
private var realSubject: RealSubject?
init() {
}
func action() {
// 프록시 객체는 Client측에서 action 메소드를 호출할 때에만
// 실제 객체를 생성하는 방식으로 구현.
if realSubject == nil {
realSubject = RealSubject()
}
realSubject?.action() // 위임. 옵셔널 체이닝 사용
}
}
// 클라이언트 코드
class Client {
static func main() {
let sub: SubjectProtocol = Proxy()
// Client 측에서 action 메소드를 실행할 경우에만 RealSubject 객체를 찍어낸다.
sub.action()
}
}
// 메인 함수 실행
Client.main()
protocol SubjectProtocol {
func action()
}
// 실제 대상 객체
class RealSubject: SubjectProtocol {
func action() {
print("실제 객체 액션")
}
}
// 프록시 객체
class Proxy: SubjectProtocol {
private var realSubject: RealSubject
private var access: Bool // 접근 권한을 제어할 Bool 타입
init(realSubject: RealSubject, access: Bool) {
self.realSubject = realSubject
self.access = access
}
func action() {
// true일 경우 RealSubject의 action 메소드에 접근할 수 있다.
if access {
realSubject.action() // 위임
print("추가적인 작업 실행")
} else {
// false일 경우 RealSubject에 접근할 수 없음.
print("접근 권한 없음")
}
}
}
// 클라이언트 사용 예
class Client {
static func main() {
// Client에서 false를 적용하였으므로 실제 객체에 접근할 수 없음
// 실제 코드에서는 이렇게 정적으로 처리하기 보다는 동적으로 처리하는 게 좋음
let sub: SubjectProtocol = Proxy(realSubject: RealSubject(), access: false)
sub.action()
}
}
// 메인 실행
Client.main()
protocol SubjectProtocol {
func action()
}
// 실제 대상 객체
class RealSubject: SubjectProtocol {
func action() {
print("실제 객체 액션")
}
}
// 프록시 객체
class Proxy: SubjectProtocol {
private var realSubject: RealSubject
init(realSubject: RealSubject) {
self.realSubject = realSubject
}
func action() {
print("로그 메세지 작성")
realSubject.action() // 위임
// 추가 작업 수행
print("프록시 객체 액션 추가")
print("로그 메세지 작성")
}
}
// 클라이언트 사용 예
class Client {
static func main() {
let sub: SubjectProtocol = Proxy(realSubject: RealSubject())
sub.action()
}
}
// 메인 실행
Client4.main()
protocol WeatherService {
func fetchWeather(city: String, api: String, completion: @escaping (Weather?, Error?) -> Void)
}
struct Weather: Codable {
var currentWeather: String
var city: String
}
class RealWeatherService: WeatherService {
func fetchWeather(city: String, api: String, completion: @escaping (Weather?, Error?) -> Void) {
let url = URL(string: "https://weather.com/api/users\(api)")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(nil, error)
return
}
guard let data = data, let user = try? JSONDecoder().decode(Weather.self, from: data) else {
completion(nil, NSError(domain: "", code: 0, userInfo: nil))
return
}
completion(user, nil)
}
task.resume()
}
}
class WeatherServiceProxy: WeatherService {
private let realWeatherService: RealWeatherService
init(realWeatherService: RealWeatherService) {
self.realWeatherService = realWeatherService
}
func fetchWeather(city: String, api: String, completion: @escaping (Weather?, Error?) -> Void) {
// 요청 검증 로직 (예: 도시 이름이 유효한지 확인)
guard city.count > 0 else {
print("오류: 유효하지 않은 도시 이름입니다.")
return
}
// 보안 조치 (예: API 키 검증, 요청 빈도 제한 확인 등)
guard api == "올바른_API_키" else {
print("오류: 잘못된 API 입니다")
return
}
// 실제 서비스에 요청 전달
return realWeatherService.fetchWeather(city: city, api: api) { weather, error in
completion(weather, error)
}
}
}
class Client {
static let apiKey = "올바른_API_키"
static func main() {
let proxy = WeatherServiceProxy(realWeatherService: RealWeatherService())
proxy.fetchWeather(city: "서울", api: apiKey) { weather, error in
print(weather)
}
}
}
Client.main()
// 네트워크 서비스를 프로토콜로 처리
// 실제 객체와 프록시 객체가 모두 해당 프로토콜을 채택할 것이다.
protocol UserService {
func fetchUser(id: Int, completion: @escaping (User?, Error?) -> Void)
}
struct User: Decodable {
var id: Int
var name: String
}
// 실제 원격 서비스
class RemoteUserService: UserService {
func fetchUser(id: Int, completion: @escaping (User?, Error?) -> Void) {
let url = URL(string: "https://example.com/api/users/\(id)")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(nil, error)
return
}
guard let data = data, let user = try? JSONDecoder().decode(User.self, from: data) else {
completion(nil, NSError(domain: "", code: 0, userInfo: nil))
return
}
completion(user, nil)
}
task.resume()
}
}
// 원격 프록시
class UserProxy: UserService {
private let remoteUserService: RemoteUserService
private var cache: [Int: User] = [:]
init(remoteUserService: RemoteUserService) {
self.remoteUserService = remoteUserService
}
func fetchUser(id: Int, completion: @escaping (User?, Error?) -> Void) {
// 캐시에 값이 있을 경우, 네트워크 처리를 하지 않고 값을 캐시로부터 가져옴. ⭐️
if let cachedUser = cache[id] {
print("저장되어 있는 캐싱 데이터 반환")
completion(cachedUser, nil)
return
}
// 저장되어 있는 캐시가 없을 경우 외부에서 네트워킹 처리를 직접 하여 값을 가져온다. ⭐️
remoteUserService.fetchUser(id: id) { [weak self] user, error in
guard let self = self, let user = user else {
completion(nil, error)
return
}
// 네트워크 처리를 한 후에 캐시에 저장.
self.cache[id] = user
print("외부 데이터 반환")
completion(user, nil)
}
}
}
class Client {
static func main() {
// 실제 원격 객체 서비스를 프록시에 할당
let userProxy = UserProxy(remoteUserService: RemoteUserService())
// 캐시값을 이용해 네트워크 처리 실행
// 단, 호출할 때마다 네트워크의 값이 변경될 경우 캐시를 사용하는 것은 비추천한다. ⭐️
// 네트워크 값이 고정되어 나타날 경우에만 캐시를 사용하는 것을 권장.
userProxy.fetchUser(id: 1) { user, error in
if let user = user {
print("사용자 이름: \(user.name)")
} else if let error = error {
print("오류 발생: \(error.localizedDescription)")
}
}
}
}
Client.main()
객체지향 프로그래밍, 프로토콜지향 프로그래밍은 원리는 단순한데 적용이 어려운 것 같아요
잘 적용하려면 그만큼의 경험과 지식이 필요한 것 같습니다
만약 RealSubject 객체가 기존 코드로 있고 SubjectProtocol을 채택하지 않은 상황이라면
Proxy 클래스를 추가할 때는 SubjectProtocol을 생성해서
RealSubject가 SubjectProtocol를 채택하도록 한 뒤에 Proxy 클래스를 만들어야 할까요?