L-j-h-c / TIL

CS, Swift, Java, C++, 개발 관련 공부한 내용 정리
11 stars 0 forks source link

[Design Pattern] Flyweight Pattern #37

Closed L-j-h-c closed 2 years ago

L-j-h-c commented 2 years ago

Flyweight Pattern이란?

플라이웨이트 패턴이란 다수의 인스턴스를 생성할 때에 메모리를 절약하기 위한 구조 패턴 중의 하나이다. 모든 객체의 데이터를 유지하는 대신에, 이미 존재하는 객체는 더이상 생성하지 않고 기존에 있는 객체를 return한다.

이렇게 공유되는 자원을 flyweight라 하고, 이를 참조하는 여러 객체들을 client라고 한다. 아래에서 살펴볼 Log를 찍는 Logger를 예로 들면, 모든 Logger가 몇 가지 정해진 Printer를 공유한다. Logger를 생성할 때마다 새로운 Printer를 만들 필요가 없기 때문에 메모리를 더 효율적으로 사용할 수 있다.

플라이웨이트 패턴을 사용하기 위해서 가장 핵심적인 부분은 이미 만들어진 flyweight들을 관리할 로직이 존재해야 한다는 것이다. static 메서드 또는 프로퍼티를 사용하여 이미 존재하는 인스턴스인지 판별하고 적절한 인스턴스를 return하거나 새로 만들어 줄 수 있다.

샘플코드

Logging : Logger Class가 채택할 프로토콜 및 열거형

public protocol Logging {
    var subsystem: String {get}
    var category: String {get}

    init(subsystem: String, category: String, printer: PrinterType)

    func log(_ message: String,
             type: LogType,
             file: String,
             function: String,
             line: Int)
}

public enum LogType {
    case info
    case warning
    case error
}

extension LogType: CustomStringConvertible {
    public var description: String {
        switch(self) {
        case .info:
            return "INFO"
        case .warning:
            return "WARNING"
        case .error:
            return "ERROR"
        }
    }
}

Logger Class : printer protocol에 의존하여 log 기능을 사용할 수 있다.

public class Logger: Logging {
    public let subsystem: String
    public let category: String

    let printer: Printer

    required public init(subsystem: String, category: String, printer: PrinterType) {
        self.printer = PrinterFactory.printer(for: subsystem, category: category, type: printer)

        self.subsystem = subsystem
        self.category = category
    }

    public func log(_ message: String,
                    type: LogType,
                    file: String = #file,
                    function: String = #function,
                    line: Int = #line) {
        printer.output(message, type: type, file: file, function: function, line: line)
    }
}

Logger 클래스는 Logging 프로토콜을 채택하여 구현한다. 또한 PrinterType, SubSytem, Category를 인자로 받는 PrinterFactory에서 생산된 printer에 의존하여 printer.output을 통해 Log를 출력하는 것을 볼 수 있다.

Printer 프로토콜과 이를 채택한 구체 Printer들

public enum PrinterType: String {
    case console
    case file
}

protocol Printer: AnyObject {
    func output(_ message: String,
                type: LogType,
                file: String,
                function: String,
                line: Int)
}

class ConsolePrinter: Printer {
    private let syncQueue = DispatchQueue(label: "consolePrinterSyncQueue")

    func output(_ message: String,
                type: LogType,
                file: String,
                function: String,
                line: Int) {
        syncQueue.sync {
            let message = "\(type) [\(file) \(function) line#\(line)] \(message)"
            print(message)
        }
    }
}

class FilePrinter: Printer {
    let logFileURL: URL?
    private let syncQueue = DispatchQueue(label: "filePrinterSyncQueue")

    init() {
        guard let documentsURL = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {
            logFileURL = nil
            return
        }

        // create the file URL
        let logFile = "logs.txt"
        logFileURL = documentsURL.appendingPathComponent(logFile)
    }

    func output(_ message: String,
                type: LogType,
                file: String,
                function: String,
                line: Int) {
        guard let fileURL = logFileURL else {
            return
        }
        syncQueue.sync {
            let message = "\(type) [\(file) \(function) line#\(line)] \(message)"
            try? message.write(to: fileURL, atomically: false, encoding: .utf8)
        }
    }
}

PrinterType에는 ConsolePrinter와 FilePrinter가 있는데, 이들은 Printer 프로토콜을 채택하였기 때문에 output의 기능을 가진다.

PrinterFactory : Dictionary 타입을 통해 static하게 고유 키를 가진 프린터들을 저장하고 있다.

struct PrinterFactory {
    private static var printersByID = Dictionary<String, Printer>()

    static func printer(for subsystem: String, category: String, type: PrinterType) -> Printer {
        let result: Printer
        let key = subsystem+category+type.rawValue

        if let printer = printersByID[key] {
            result = printer
        } else {
            switch type {
            case .console:
                result = ConsolePrinter()
            case .file:
                result = FilePrinter()
            }

            printersByID[key] = result
        }
        return result
    }
}

Dictionary와 static variable을 통해서 이미 생성된 Printer들의 key를 저장하고 있다.

L-j-h-c commented 2 years ago

참고 레포 https://readystory.tistory.com/137 핑구