DesignPattern

Swift 디자인패턴 | 옵저버 패턴(Observer Pattern)

ziziDev 2024. 6. 10. 09:08
반응형

옵저버 패턴(Observer Pattern)

|

정의

객체 간의 일대다(one-to-many) 의존성을 정의합니다

옵저버 패턴을 사용하면 하나의 객체 상태가 변경될 때 그와 의존 관계에 있는 여러 객체들이 자동으로 알림(통지)되고 갱신이 됩니다

옵저버 패턴은 주로 이벤트 핸들링 시스템이나 모델 뷰 컨트롤러(MVC)아키텍처에서 많이 사용됩니다

옵저버 뜻

옵저버 뜻은 감시자 / 관찰자라는 뜻입니다

일상 예를 들자면 중간에서 관리자 역할을 하는겁니다

예를들어 우리가 이력서를 올리게되면 헤드헌터가 중간에서 관리해주는 경우도 있습니다

중간에서 관리해준다라고 생각하신다면 옵저버 패턴을 편하게 이해하실것 같습니다

예시

보통 개인 정보 창이나 알림이왔을 때 옵저버 패턴으로 사용하여

UI변경으로 옵저버 패턴을 많이 사용할것 같습니다

저는 개인적으로 ssg(emart)를 많이 사용하는데요

 

여기서 만약 옵저버 패턴을 사용하게된다면

로그인 화면이라던가

현재 내가 배송보낼 배송지라던가

장바구니에 담겨 있는 갯수 현황에 대해서 옵저버 패턴을 사용했을것 같다고 추측을 하고 있습니다

class CartItem {
    let id: String
    let name: String
    let description: String
    var price: Double
    var quantity: Int
    var discountRate: Double
    let imageUrl: URL?
    
    init(id: String, name: String, description: String, price: Double, quantity: Int, discountRate: Double = 0.0, imageUrl: URL? = nil) {
        self.id = id
        self.name = name
        self.description = description
        self.price = price
        self.quantity = quantity
        self.discountRate = discountRate
        self.imageUrl = imageUrl
    }
    
    var totalPrice: Double {
        return price * Double(quantity) * (1 - discountRate)
    }
}

protocol Cart: AnyObject {
    func cartUpdate()
}

protocol CartItemCellDelegate: AnyObject {
    func increaseQuantity(for item: CartItem)
    func decreaseQuantity(for item: CartItem)
}

class CartItemCell: UITableViewCell {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    @IBOutlet weak var quantityLabel: UILabel!
    
    weak var delegate: CartItemCellDelegate?
    var item: CartItem?
    
    func configure(item: CartItem) {
        self.item = item
        nameLabel.text = item.name
        priceLabel.text = "\(item.totalPrice)"
        quantityLabel.text = "\(item.quantity)"
    }
    
    @IBAction func increaseQuantityButtonPressed(_ sender: UIButton) {
        guard let item = item else { return }
        delegate?.increaseQuantity(for: item)
    }
    
    @IBAction func decreaseQuantityButtonPressed(_ sender: UIButton) {
        guard let item = item else { return }
        delegate?.decreaseQuantity(for: item)
    }
}

class CartManager {
    static let instance = CartManager()
    private init() {}
    
    private var container = NSHashTable<AnyObject>.weakObjects()
    private var items = [CartItem]()
    
    
    func addContainer(_ ob: Cart) {
        container.add(ob)
    }
    
    func removeContainer(_ ob: Cart) {
        container.remove(ob)
    }
    
    func addItem(_ item: CartItem) {
        items.append(item)
        cartUpdateView()
    }
    
    
    func removeItem(_ item: CartItem) {
        if let index = items.firstIndex(where: { $0.id == item.id }) {
            items.remove(at: index)
            cartUpdateView()
        }
    }
    
    func updateItemQuantity(id: String, quantity: Int) {
        if let item = items.first(where: { $0.id == id }) {
            item.quantity = quantity
            cartUpdateView()
        }
    }
    
    func getItems() -> [CartItem] {
        return items
    }
    
    private func cartUpdateView() {
        container.allObjects.compactMap { $0 as? Cart }.forEach { $0.cartUpdate() }
    }
}

class CartViewController: UIViewController, UITableViewDataSource, Cart, CartItemCellDelegate {
    
    @IBOutlet weak var tableView: UITableView!
    var items = [CartItem]()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        CartManager.instance.addContainer(self)
        cartUpdate()
        tableView.dataSource = self
    }
    
    func cartUpdate() {
        let items = CartManager.instance.getItems()
        tableView.reloadData()
    }
    
    deinit {
        CartManager.instance.removeContainer(self)
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "CartItemCell", for: indexPath) as! CartItemCell
        let item = items[indexPath.row]
        
        cell.configure(item: item)
        cell.delegate = self
        return cell
        
    }
    
    func increaseQuantity(for item: CartItem) {
        item.quantity += 1
        CartManager.instance.updateItemQuantity(id: item.id, quantity: item.quantity)
        cartUpdate()
    }
    
    func decreaseQuantity(for item: CartItem) {
        if item.quantity > 1 {
            item.quantity -= 1
            CartManager.instance.updateItemQuantity(id: item.id, quantity: item.quantity)
            cartUpdate()
        } else {
            CartManager.instance.removeItem(item)
            cartUpdate()
        }
    }
}

 

더보기

제가 이전 게임에서 옵저버를 사용했었던건

캐릭터의 체력상태라던가 적의 상태표시에 관해서 사용했었습니다

장점

관찰 대상과 관찰자가 느슨하게 결합되어 있기 때문에 상호 의존성이 줄어들게 됩니다

새로운 관찰자를 쉽게 추가할 수 있습니다

관찰자와 관찰 대상을 독립적으로 재사용 할 수 있습니다

단점

객체간의 관계가 늘어날 수록 시스템의 복잡성이 증가할 수 있습니다

만약 잘못구현하게 된다면 순환 참조로 인하여 메모리 누수가 발생할 수 있습니다

 

한마디

상태변화로 인하여 UI변화가 필요하다?
관리감독에 적합한 옵저버 당장 사용하자 

 

 

혹시 잘못된 부분이 있다면 꼭 알려주세요 감사합니다❤️

반응형

'DesignPattern' 카테고리의 다른 글

Swift 디자인패턴 | 전략 패턴(Strategy Pattern)  (0) 2024.06.09