Design pattern: Decorator Pattern

Design pattern - Decorater pattern

Để tiếp tục với series học và tìm hiểu Design Pattern trong lập trình IOS, hôm nay mình sẽ trình bày những hiểu biết mình đã tìm hiểu và nghiên cứu được về Decorator pattern - Đây là một design pattern thuộc nhóm structural và có ứng dụng nhiều trong không chỉ trong IOS mà còn trong các ngôn ngữ lập trình khác nữa.

Bài này vẫn như các bài trước mình vẫn đi theo bố cục 3 phần:

  • Tổng quan về Decorater pattern
  • Cách sử dụng
  • Ứng dụng của Decorater pattern trong lập trình IOS

Tổng quan về Decorater pattern

Như đã giới thiệu bên trên thì Decorator pattern là 1 pattern thuộc nhóm structural pattern. Decorator sinh ra để cho phép người dùng thêm các tính năng mới vào một đối tượng đã có mà không làm thay đổi cấu trúc lớp của nó.

Đặc điểm của Decorator:

  • Bổ sung linh động những đặc tính mới cho 1 object.
  • Object gốc không thay đổi cũng như không biết gì về những thứ được bổ sung.
  • Không cần phải xây dựng một class khổng lồ với mọi thứ bên trong bởi đối tượng gốc sẽ được wrapper trong các đối tượng thành phần.
  • Các object là độc lập và có thể tổ hợp lẫn nhau nên phù hợp với hệ thống mở rộng liên tục.

Có thể nói pattern này khá gần gũi với kế thừa nhưng việc thực hiện sẽ ở mức độ object thay vì class. Việc mở rộng bằng decorator là mở rộng một cách linh động thay vì mở rộng theo hướng tĩnh như kế thừa.

  • Mở rộng theo hướng linh động: ta sẽ cung cấp một cơ cấu cho phép chúng ta thay đổi một đối tượng đã tồn tại nhưng không làm ảnh hưởng đến các đối tượng khác cùng lớp đó.
  • Mở rộng theo hướng tĩnh: tức là khi ta mở rộng phương thức thì chúng ta sẽ triển khai áp dụng sự thay đổi lên tất cả các đối tượng thuộc lớp này.

Nếu 1 class đánh dấu là final thì không thể kế thừa từ class đó. Muốn mở rộng chúng ta bắt buộc phải dùng Decorator

Các thành phần của Decorator được mô tả trong lược đồ UML sau.

trong đó thì:

  • Component: Protocol (interface) chung sẽ được triển khai của các đối tượng cần thêm chức năng trong quá trình runtime.
  • ConcreteComponent: Định nghĩa một đối tượng cần thêm các chức năng trong quá trình chạy.
  • Decorator: Một lớp chứa duy trì một tham chiếu của đối tượng thành phần và đồng thời cài đặt các thành phần của Component.
  • ConcreteDecorator: Một cài đặt của Decorator, nó cài đặt thêm các thành phần vào đầu của các đối tượng thành phần.

Cách sử dụng

Chúng ta cùng đi vào 1 ví dụ gọi là ShapeDecorator để hiểu rõ hơn về cách implement và hoạt động của pattern này. ví dụ này đơn giản là tạo ra 1 object gốc là shape và sau đó tiến hành sử dụng decorator để trang trí và custom cho object đó.

Để bắt tay vào ví dụ thì đầu tiên chúng ta phải tạo playground dạng singleView và chạy thử ta nhận được như sau.

Theo đúng như các thành phần của Decorator thì chúng ta sẽ tạo Shape đóng vai trò là component. Shape là protocol chứa chức năng chung sẽ được triển khai mà cụ thể ở đây là draw() và trả về một CAShapeLayer .

protocol Shape {
   func draw() -> CAShapeLayer
}

tiếp theo chúng ta tiến hành tạo tới các ConcreteComponent cụ thể là CircleRectangle.

Circle sẽ cần thêm 2 thuộc tính để định nghĩa đó arcCenterradius còn Rectangle sẽ cần định nghĩa Frame đầu vào. Sau đó triển khai hàm draw() của từng shape.

class Rectangle: Shape {
    var frame: CGRect
    
    init(frame: CGRect) {
        self.frame = frame
    }
    
    func draw() -> CAShapeLayer {
        let drect = CGRect(x: (frame.width * 0.25),
                           y: (frame.height * 0.25),
                           width: (frame.width * 0.5),
                           height: (frame.height * 0.5))
        let bpath: UIBezierPath = UIBezierPath(rect: drect)
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = bpath.cgPath
        shapeLayer.fillColor = UIColor.yellow.cgColor
        return shapeLayer
    }
}

class Circle: Shape {
    var arcCenter: CGPoint
    var radius: CGFloat
    
    init(arcCenter: CGPoint, radius: CGFloat) {
        self.arcCenter = arcCenter
        self.radius = radius
    }
    
    func draw() -> CAShapeLayer {
        let circlePath = UIBezierPath(arcCenter: arcCenter, radius: radius, startAngle: CGFloat(0), endAngle: CGFloat(Double.pi * 2), clockwise: true)
        let shapeLayer = CAShapeLayer()
        shapeLayer.path = circlePath.cgPath
        shapeLayer.fillColor = UIColor.blue.cgColor
        return shapeLayer
    }
}

Vậy là đã hoàn thành xong ConcreteComponentConcreteComponent. Bước tiếp theo chúng ta đi định nghĩa DecoratorConcreateDecorator.

Decorator chúng ta định nghĩa là ShapeDecorator là một protocol implement Component và chứa một tham chiếu đến Component.

protocol ShapeDecorator: Shape {
    var shape: Shape { get set }
    
    func draw() -> CAShapeLayer
}

Để trang trí hay custom cho Object gốc chúng ta cần tạo các ConcreteDecorator. Cụ thể ở đây là

  • AddBorderDecorator để thêm vào border
  • AddShadowDecorator để thêm shadow
  • AddConnerRadiusDecorator để thêm connerRadius.

Các ConcreteDecorator này đều implement ShapeDecorator ngoài ra còn có các phương thức và thuộc tính cần thiết cho mỗi loại cụ thể như sau:

class AddBorderDecorator: ShapeDecorator {
    var shape: Shape
    
    init(shape: Shape) {
        self.shape = shape
    }
    
    func draw() -> CAShapeLayer {
        let view = shape.draw()
        view.strokeColor = UIColor.black.cgColor
        view.lineWidth = 1
        return view
    }
}

class AddShadowDecorator: ShapeDecorator {
    var shape: Shape
    
    var radius: CGFloat
    
    init(shape: Shape, radius: CGFloat) {
        self.shape = shape
        self.radius  = radius
    }
    
    func draw() -> CAShapeLayer {
        let view = shape.draw()
        view.shadowRadius = radius
        view.shadowOpacity = 0.8
        view.shadowColor = UIColor.black.cgColor
        view.shadowOffset = CGSize(width: 0, height: radius / 2)
        view.masksToBounds = false
        return view
    }
}

class AddConnerRadiusDecorator: ShapeDecorator {
    var shape: Shape
    
    var radius: CGFloat
    
    init(shape: Shape, radius: CGFloat) {
        self.shape = shape
        self.radius  = radius
    }
    
    func draw() -> CAShapeLayer {
        let view = shape.draw()
        view.cornerRadius = radius
        return view
    }
}

Vậy là chúng ta đã xong các thành phần của Decorator. Giờ tiến hành chạy và xem kết quả. Khi tạo PlayGround có viết sẵn class MyViewController. Trong hàm loadView() chúng ta tiến hành khởi tạo các object và tiến hành chạy:

class MyViewController : UIViewController {
    override func loadView() {
        let view = UIView()
        view.backgroundColor = .white

        let rectagle = Rectangle(frame: CGRect(x: 200, y: 100, width: 200, height: 100))
        let addBorderDecoratorRectangle = AddBorderDecorator(shape: rectagle)
        let addShadowDecoratorRectangle = AddShadowDecorator(shape: addBorderDecoratorRectangle, radius: 10)
        
        let circle = Circle(arcCenter: CGPoint(x: 200, y: 300), radius: 100)
        let addShadowDecoratorCircle = AddShadowDecorator(shape: circle, radius: 10)
        
        
        
        view.layer.addSublayer(addShadowDecoratorRectangle.draw())
        
        view.layer.addSublayer(addShadowDecoratorCircle.draw())
        
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

trong đoạn code trên mình có tạo 2 đối tượng là hình tròn và hình chữ nhật sau đó tiến hành thêm border và shadow cho hình chữ nhật. Hình tròn thì chỉ thêm shadow. Sau khi chạy ta nhận được:

Thông qua ví dụ này các bạn sẽ hiểu được Decorator và cách để sử dụng nó trong lập trình ios. Cũng khá đơn giản phải k nào?

Tiếp theo mình sẽ đi vào ứng dụng trong thực tế Decorator được ứng dụng nhiều như thế nào.

Ứng dụng của Decorater pattern trong lập trình IOS

Trong lập trình IOS mình thấy Decorator được ứng dụng khá nhiều cụ thể như sau:

Trong Cocoa có thiết lập với một số object khá thông dụng mà lập trình viên ios nào cũng biết đó là

  • NSAttributedString: là một decorator của NSString với các thuộc tính như font, color hay underline…

  • NSScrollView: Thay vì thực hiện lại tất cả các khả năng scroll trong mọi đối tượng thì Cocoa cung cấp scroll bằng cách decorating các đối tương bằng 1 đối tương NSClipView và sau đó lần lượt trang trí bởi 1 đối tương NSScrollView. NsClipView che giấu các phần của khung nhìn mà nó decorate.

  • UIDatePicker: thực tế là môt decorator của UIpickerView, chúng ta có thể từ UipickerView thêm các thuộc tính khác nhau để tạo ra được 1 loại Uidatepicker hoàn toàn mới.

Ngoài ra thì Category, Delegation trong Objective-C và Extention, Delegation trong Swift cũng đều là các thể hiện của Decorator tuy nhiên không gói gọn một thể hiện của lớp được mở rộng.

All Rights Reserved