DesignPattern

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

ziziDev 2024. 6. 9. 14:18
반응형

 

전략 패턴(Strategy Pattern)

|

정의

알고리즘군을 정의하고 각각 캡슐화하여 교환해서 사용할 수 있도록 만들고 전략패턴을 활용하게되면 알고리즘을 사용하는

클라이언트와 독립적으로 알고리즘을 변경할 수 있다

 

사용하면 어떤 부분에서 좋을까?

상속으로 해결될 수 없는 코드 중복이나 객체의 실시간 알고리즘의 변경시에 유용합니다

추상객체(프로토콜-protocol / 익스텐션-extension)를 상속해서 알고리즘을 추가하여 확장이 가능합니다

그리고 알고리즘이 변경될 때 해당 알고리즘만 변경하면 되기 때문에 유지보수 측면에서도 용이하다고 볼 수 있습니다

 

예시

온라인 스토어 같은 경우 요즘 새벽배송 / 직진배송 / 일반배송 여러가지 배송이 있는데요

각 객체에서 공통된 역할이 존재합니다

배송과 시간이라는 점이죠

만약 배송 클래스에서 만약 새벽배송에 필요한 구문과 직진배송에 필요한 메서드가 들어가있다면

일반 배송에서는 의미없는 메서드로 변질될것이고

 

배송에서 만약 새로운 기능이 생성되었을 때 상속받는 객체들을 모두 수정해줘야하는 유지보수에 어려움이

발생하게 됩니다

장점

전략패턴을 사용하게되면 불필요한 코드의 작성을 남발하지 않아도 되기 때문에 코드의 반복사용성을 줄일 수 있어

재사용성이 용이하고 기능확장에 유연하게 대처할 수 있습니다

 

그리고 그 배송상품이 할인된 상품인지 아닌지 결제 또한 어떤방식으로 사용해서 결제할 건지 복합적으로 생각해본다면

각각 세부적으로 나눠서 관리하는게 용이하다라고 생각이 들죠?

 

우선 단순하게 배송관련 코드를 작성해보고자 합니다

 

더보기

예전에 어떤식으로 사용했냐?

게임적으로 설명하자면

 

각 몬스터나 캐릭터도 특징이 많습니다

움직이며 공격만 하는 캐릭터, 공중을 날라다니며 공격하는 캐릭터,

힐러 탱커  등등 아주 많은 특성을 가지고 있죠?

그걸 한 클래스에 넣어버리면 상상만해도 끔찍하겠죠?..

그래서 추상화나 인터페이스를 사용하여 (protocol, extension)

사용하게됩니다

 

//⭐️온라인 쇼핑몰을 생각한다면 아주 많은 기능이 있다
//✏️할인, 배송, 결제 등등

protocol Discount {
    func applyDiscount(to price: Double) -> Double
}


enum DeliveryType {
    case standard
    case overnight
    case sameday
    case discussWithDriver
    
    var description: String {
        switch self {
        case .standard:
            return "보통 2-3일 그 이상 소요될 수 있습니다"
        case .overnight:
            return "다음 날 배송됩니다"
        case .sameday:
            return "당일 배송됩니다"
        case .discussWithDriver:
            return "기사님과 협의해야합니다"
        }
    }
}

protocol Shipping {
    var calculateShippingCost: Double { get }
    var deliveryType: DeliveryType { get }
}

class CODShipping: Shipping {
    var calculateShippingCost: Double { return 30000 }
    var deliveryType: DeliveryType { return .discussWithDriver }
}

class Freeshipping: Shipping {
    var calculateShippingCost: Double { return 0.0 }
    var deliveryType: DeliveryType { return .standard }
}


class Overnightshipping: Shipping {
    var calculateShippingCost: Double { return 10000 }
    var deliveryType: DeliveryType { return .overnight }
}

 

간단하게 이렇게 작성할 수도 있겠지만

조금만 응용을 하자면

주문 클래스를 적용할 수 있습니다

 

주문자 or 판매자가 적용한 배송방법을 보고

주문할 때 적용하는 방법입니다

 

class Product {
    var name: String
    var price: Double
    
    init(name: String, price: Double) {
        self.name = name
        self.price = price
    }
}

class Order {
    var products: [Product]
    var shipping: Shipping
    
    init(products: [Product], shipping: Shipping) {
        self.products = products
        self.shipping = shipping
    }
    
    func calculatorCost() -> Double {
        var cost = products.reduce(0.0) { $0 + $1.price }
        cost += shipping.calculateShippingCost
        return cost
    }
}

 

여기서 개발이 완료후 퀵배송도 생각할 수 있고 온라인 문자로 전달 같은 부분도 기능을 추가한다고 했을때도 고려할 수 있고

심지어 배송전 배송 타입을 변경하고 싶을때 자유롭게 변경이 가능한 점도 있습니다

어떻게 보면 런타임에 자유롭게 변경이 가능한 점도 장점이라고 할 수 있습니다

 

import UIKit

class Product {
    var name: String
    var price: Double
    var shippingCost: Double 
    
    init(name: String, price: Double, shippingCost: Double) {
        self.name = name
        self.price = price
        self.shippingCost = shippingCost
    }
}


protocol ShippingStrategy {
    func calculateShippingCost(for products: [Product]) -> Double
}


class VariableShippingStrategy: ShippingStrategy {
    func calculateShippingCost(for products: [Product]) -> Double {

        let totalShippingCost = products.reduce(0.0) { $0 + $1.shippingCost }
        return totalShippingCost
    }
}


class Order {
    var products: [Product]
    var shipping: ShippingStrategy
    
    init(products: [Product], shipping: ShippingStrategy) {
        self.products = products
        self.shipping = shipping
    }
    
    func calculateCost() -> Double {
        let shippingCost = shipping.calculateShippingCost(for: products)
        let productCost = products.reduce(0.0) { $0 + $1.price }
        return shippingCost + productCost
    }
}


class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var totalPriceLabel: UILabel!
    
    var products: [Product] = []
    var order: Order?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        loadProducts()
    }
    
    func setupUI() {
        tableView.dataSource = self
        tableView.delegate = self
    }
    
    func loadProducts() {
        products = [
            Product(name: "Product 1", price: 10.0, shippingCost: 5.0),
            Product(name: "Product 2", price: 15.0, shippingCost: 7.0),
            Product(name: "Product 3", price: 20.0, shippingCost: 6.0)
        ]
        tableView.reloadData()
    }
    
    @IBAction func calculateCostButtonTapped(_ sender: Any) {
        calculateCost()
    }
    
    func calculateCost() {
        let shippingStrategy = VariableShippingStrategy()
        order = Order(products: products, shipping: shippingStrategy)
        let totalCost = order?.calculateCost() ?? 0.0
        totalPriceLabel.text = "Total Cost: $\(totalCost)"
    }
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return products.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell", for: indexPath)
        let product = products[indexPath.row]
        cell.textLabel?.text = product.name
        cell.detailTextLabel?.text = "$\(product.price)"
        return cell
    }
}

 

그리고 조건문을 사용하지 않고 사용할 수 있는것이 장점입니다

그리고 새로운 기능이 생긴다고해서 자식 클래스에서 추가하는것이 아닌 프로토콜을 하나 생성해서 필요한 부분에만 추가하는게

유지보수 측면이나 확장성을 생각했을때 훨씬 더 유연한 구조라고 생각할 수 있습니다

 

여기서 또 조금 자세히 생각해 볼 수 있는건

상품의 카테고리 / 상품 할인 등등 여러요건을 생각해 볼 수 있습니다

그리고 판매수량과 무관하게 계속 판매할 수 있는 경우도 있지만 미리 예약받은 한정 수량만 판매하는 경우는

미리 기재하면 좋겠죠? 공동구매라던가?.. 그런식으로 여러개 생각하며 작성하게된다면 전략패턴이

유용하다는걸 생각할 수 있을것 같습니다

 

예시2

만약 한 제품에서 다양한 옵션이 들어간 제품을 만들게 된다면??

슬라임도 요즘 여러가지 만들잖아요

 

비즈가 들어가는 슬라임

색깔이 투명인 슬라임 / 초코색인 슬라임 등등 여러개를 만들 수 있잖아요?

이런식으로 만들게 되면 프로토콜을 사용할 수 밖에 없는 상황이 옵니다

 

그렇게 되면 슬라임 하나를 생성한다고 할 때

투명색 / 비즈가 들어가고 / 촉감도 프로토콜로 만들어서 관리할 수 있겠죠?

 

변경하고 싶다면 실시간으로 변경도 가능하게 끔 메서드나 아님 계산 프로퍼티로 사용하여도 가능하구용

 

단점

클래스 증가가 됩니다 각 알고리즘마다 별도의 클래스를 만들어야하기 때문입니다

알고리즘 선택을 위해 추가적인 코드가 필요하기 때문에 코드의 복잡성이 증가할 수 있습니다

 

한마디

상속보다 유연하게 사용할 수 있는 방법은 많다

 

끝입니다

 

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

반응형

'DesignPattern' 카테고리의 다른 글

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