└── readme.md /readme.md: -------------------------------------------------------------------------------- 1 | # Design pattern 2 | 3 | ### References: 4 | - [GitHub - tmrts/go-patterns: Curated list of Go design patterns, recipes and idioms](https://github.com/tmrts/go-patterns) 5 | - [How important are Design Patterns really? - Stack Overflow](https://stackoverflow.com/a/978509) 6 | - [Evaluating the GO Programming Language with Design Patterns PDF](https://ecs.victoria.ac.nz/foswiki/pub/Main/TechnicalReportSeries/ECSTR11-01.pdf) 7 | - [Tutorials · Software adventures and thoughts](http://blog.ralch.com/tutorial/) 8 | - [Software design pattern - Wikipedia](https://en.wikipedia.org/wiki/Software_design_pattern) 9 | - [Guru design pattern](https://refactoring.guru/design-patterns) 10 | 11 | ### Design pattern là gì: 12 | - **Định nghĩa**: Là giải pháp tổng quát cho những vấn đề mà lập trình viên hay gặp phải. Design pattern không thể được chuyển đổi trực tiếp thành code mà nó chỉ là một khuôn mẫu cho một vấn đề cần được giải quyết 13 | 14 | - **Ưu điểm**: 15 | - **Code readability**: giúp chúng ta viết code dễ hiểu hơn bằng việc sử dụng những tên biến liên quan đến những gì chúng ta đang thực hiện 16 | - **Communication**: giúp cho các lập trình viên có thể dễ dàng tiếp cận trao đổi về giái pháp cho một vấn đề bằng việc sử dụng một design pattern bất kỳ 17 | - **Code reuse**: Code được viết theo design pattern có thể được sử dụng lại nhiều lần 18 | 19 | - **Nhược điểm**: 20 | - **More complexity**: Làm phức tạp hóa quá trình code, khiến lập trình viên vất vả hơn khi phải suy nghĩ nên dùng mấu thiết kế nào để giải quyết vấn đề của mình khi mà anh ta chưa thực sự viết được 1 dòng code nào trong khi deadline đang cận kề 21 | - **Different variants**: Có nhiều biến thể cho mỗi mấu đối tượng khiến cho việc áp dụng các mẫu thiết kế đôi khi không đông nhất nên gây bất đồng khi làm việc giữa các lập trình viên 22 | 23 | ### Phân loại 24 | - #### Creational pattern 25 | 26 | | Pattern | 27 | |:-------:| 28 | | [Singleton]( #Singleton) | 29 | | [Builder](#Builder) | 30 | | [Factory Method](#Factory-method) | 31 | 32 | - #### Behavioral pattern 33 | | Pattern | 34 | |:-------:| 35 | | [Observer](#Observer) | 36 | | [Strategy](#Strategy-pattern) | 37 | | [Iterator](#Iterator-pattern) | 38 | | [State](#State-pattern) | 39 | 40 | - #### Structural pattern 41 | | Pattern | 42 | |:-------:| 43 | | [Adapter](#Adapter) | 44 | | [Bridge](#Bridge) | 45 | | [Composite](#Composite) | 46 | | [Proxy](#Proxy) | 47 | --- 48 | 49 | ### Singleton 50 | 51 | Chỉ duy nhất một đối tượng của một type bất kì được khởi tạo trong suốt quá trình hoạt động của một chương trình 52 | 53 | - **Các ứng dụng**: 54 | - Chúng ta muốn sử dụng lại kết nối đến database khi truy vấn 55 | - Khi chúng ta mở một kết nối SSH đến một server để thực hiện một số công việc và chúng ta không muốn mở một kết nối khác mà sử dụng lại kết nối trước đó 56 | - Khi chúng ta cần hạn chế sự truy cập đến một vài biến số, chúng ta sử dụng mẫu singleton là trung gian để truy cập biến này 57 | 58 | ```go 59 | package DB 60 | 61 | var ( 62 | once sync.Once 63 | singleton *db.Connection 64 | ) 65 | 66 | func GetDBConnection(config *mysql.Config) *db.Connection { 67 | once.Do(func() { 68 | singleton = mysql.Dial(config) 69 | }) 70 | return singleton 71 | } 72 | ``` 73 | 74 | - **Lưu ý**: Là mấu thiết kế phổ biến nhất nhưng đôi khi bị lạm dụng. Nên tránh việc sử dụng mẫu singleton vì khiến cho việc test cụ thể là việc tạo mock/stub trở nên khó khăn hơn 75 | 76 | ### Builder 77 | - Có thể tạo ra nhiều biến thể khác nhau cho cùng một đối tượng 78 | - Ví dụ: 79 | Có 1 anh chàng nọ đang làm việc trong một team phát triển các hệ thống cho một ngân hàng và anh ta được giao nhiệm vụ cần tạo một kiểu dữ liệu BankAccout để chứa thông tin các khách đã mở tài khoản tại ngân hàng này 80 | ```go 81 | type BankAccount struct { 82 | ownerName string 83 | ownerIdentificationNumber uint64 84 | balance int64 85 | } 86 | 87 | func NewBankAccount(ownerName string, idNo uint64, balance int64) { 88 | return &BankAccount{ownerName, idNo, balance} 89 | } 90 | ``` 91 | Rất dễ dàng để mở một tài khoản 92 | ```go 93 | var account BankAccount = NewBankAccount("Tuan", 123456789, 10000); 94 | ``` 95 | tuy nhiên đời không như mơ, Sếp lớn xỉ vả anh ta vì kiểu BankAccout thiếu chi nhánh ngân hàng và tỉ lệ lãi suất. Thế là anh ta lại hớt hải chỉnh sủa lại 96 | ```go 97 | type BankAccount struct { 98 | ownerName string 99 | ownerIdentificationNumber uint64 100 | balance int64 101 | interestRate float64 // ti suat 102 | branch string // chi nhanh 103 | } 104 | 105 | func NewBankAccount(ownerName string, idNo uint64, balance int64, interestRate float64, branch string) { 106 | return &BankAccount{ownerName, idNo, balance, interestRate, branch} 107 | } 108 | ``` 109 | Một ngừời khác sử dụng code của anh ta viết để thực hiện task của mình và anh ta đã sử dụng như sau 110 | ```go 111 | var account BankAccount = NewBankAccount("Tuan",1000, 123456789, , 0.8, "Sai Gon"); 112 | ``` 113 | anh này đã vô tình đổi số CMND(123456789), ra sau số dư(1000) và khiến ngân hàng thất thoát tiền. anh ta vô tình nhưng compiler không phát hiện ra vì cả 2 đều có kiểu dữ liệu uint64 và int64. Anh này đã đổ lỗi cho cho người đã thiết kế kiểu dữ liệu BankAccount chính là anh chàng trước đó. 114 | 115 | - Cách giải quyết được đặt ra: 116 | - Tạo các setter: Tuy nhiên người dùng đôi khi sẽ quên gọi các setter này 117 | 118 | Anh chàng bị sếp trừ lương và quá uất ức anh sử dụng bí kíp design pattern Builder 119 | ```go 120 | type bankAccount struct { 121 | ownerName string 122 | identificationNumber uint64 123 | branch string 124 | balance int64 125 | } 126 | 127 | type BankAccount interface { 128 | WithDraw(amt uint64) 129 | Deposit(amt uint64) 130 | GetBalance() uint64 131 | } 132 | 133 | type BankAccountBuilder interface { 134 | WithOwnerName(name string) BankAccountBuilder 135 | WithOwnerIdentity(identificationNumber uint64) BankAccountBuilder 136 | AtBranch(branch string) BankAccountBuilder 137 | OpeningBalance(balance uint64) BankAccountBuilder 138 | Build() BankAccount 139 | } 140 | 141 | func (acc *bankAccount) WithDraw(amt uint64) { 142 | 143 | } 144 | 145 | func (acc *bankAccount) Deposit(amt uint64) { 146 | 147 | } 148 | 149 | func (acc *bankAccount) GetBalance() uint64 { 150 | return 0 151 | } 152 | 153 | func (acc *bankAccount) WithOwnerName(name string) BankAccountBuilder { 154 | acc.ownerName = name 155 | return acc 156 | } 157 | 158 | func (acc *bankAccount) WithOwnerIdentity(identificationNumber uint64) BankAccountBuilder { 159 | acc.identificationNumber = identificationNumber 160 | return acc 161 | } 162 | 163 | func (acc *bankAccount) AtBranch(branch string) BankAccountBuilder { 164 | acc.branch = branch 165 | return acc 166 | } 167 | 168 | func (acc *bankAccount) OpeningBalance(balance uint64) BankAccountBuilder { 169 | acc.balance = int64(balance) 170 | return acc 171 | } 172 | 173 | func (acc *bankAccount) Build() BankAccount { 174 | return acc 175 | } 176 | 177 | func NewBankAccountBuilder() BankAccountBuilder { 178 | return &bankAccount{} 179 | } 180 | 181 | func main() { 182 | account := NewBankAccountBuilder(). 183 | WithOwnerName("Tuan"). 184 | WithOwnerIdentity(123456789). 185 | AtBranch("Sai Gon"). 186 | OpeningBalance(1000).Build() 187 | 188 | account.Deposit(10000) 189 | account.WithDraw(50000) 190 | } 191 | ``` 192 | Bằng việt sử dụng builder pattern, code của anh chàng tuy có hơi dai dòng hơn nhưng đã khiến code trở nên dễ đọc hơn rất nhiều 193 | 194 | #### Reference: https://dzone.com/articles/design-patterns-the-builder-pattern 195 | 196 | ### Factory method 197 | - Tạo một đối tượng mà không cần thiết chỉ ra một cách chính xác lớp nào sẽ được tạo bằng cách nhóm các đối tượng liên quan đến nhau và sử dụng 1 đối tượng trung gian để khởi tạo đối tượng cần tạo 198 | - ví dụ: chúng ta sẽ tạo các phương thức thanh toán cho 1 shop bằng factory method, hình thức thanh toán có thể bằng tiền mặt hoặc bằng thẻ debit 199 | ```go 200 | type PaymentMethod interface { 201 | Pay(amount float32) string 202 | } 203 | 204 | type PaymentType int 205 | 206 | const ( 207 | Cash PaymentType = iota 208 | DebitCard 209 | ) 210 | 211 | type CashPM struct {} 212 | type DebitCardPM struct{} 213 | 214 | func (c *CashPM) Pay(amount float32) string { 215 | return "" 216 | } 217 | 218 | func (c *DebitCardPM) Pay(amount float32) string { 219 | return "" 220 | } 221 | 222 | func GetPaymentMethod(t PaymentType) PaymentMethod { 223 | siwtch t { 224 | case Cash: 225 | return new(CashPM) 226 | case DebitCard: 227 | return new(DebitCardPM) 228 | } 229 | } 230 | 231 | // usage 232 | payment := GetPaymentMethod(DebitCard) 233 | payment.Pay(20) 234 | payment := GetPaymentMethod(Cash) 235 | payment.Pay(50) 236 | 237 | ``` 238 | --- 239 | ## Behavioural Patterns 240 | ### Observer 241 | - Tạo mối liên hệ one-to-many giữa subject và các observer với nhau(chẳng hạn 1 subject sẽ có thuộc tính là một mảng bao gồm nhiều observer) nên khi trạng thái của subject thay đổi, tất cả các observer liên kết với subject này sẽ được thông báo và tự động cập nhật 242 | - Gồm 2 thành phần chính là **Subject** và **Observer** 243 | - sơ đồ: 244 | 245 | ![I'm an inline-style link](https://upload.wikimedia.org/wikipedia/commons/thumb/a/a8/Observer_w_update.svg/500px-Observer_w_update.svg.png) 246 | - Ví dụ: Giả sử chúng ta có 1 kênh youtube và mối khi chúng ta release 1 video mới, các subscriber đều được thông báo về thông tin của video mới này. Ta coi youtube channel là 1 subject và những subscriber trong channel này là các observer. Khi các observer nhận được thông tin về video mới, app của các subscriber sẽ chịu trách nhiệm update lại UI để người dùng có thể click vào những video vừa được đăng tải 247 | 248 | - Code: 249 | 250 | ```go 251 | type Observer interface { 252 | update(interface{}) 253 | } 254 | 255 | type Subject interface { 256 | registerObserver(obs Observer) 257 | removeObserver(obs Observer) 258 | notifyObservers() 259 | } 260 | 261 | type Video struct { 262 | title string 263 | } 264 | 265 | // YoutubeChannel is a concrete implementation of Subject interface 266 | type YoutubeChannel struct { 267 | Observers []Observer 268 | NewVideo *Video 269 | } 270 | 271 | func (yt *YoutubeChannel) registerObserver(obs Observer) { 272 | yt.Observers = append(yt.Observers, obs) 273 | } 274 | 275 | func (yt *YoutubeChannel) removeObserver(obs Observer) { 276 | // 277 | } 278 | 279 | // notify to all observers when a new video is released 280 | func (yt *YoutubeChannel) notifyObservers() { 281 | for _, obs := range yt.Observers { 282 | obs.update(yt.NewVideo) 283 | } 284 | } 285 | 286 | func (yt *YoutubeChannel) ReleaseNewVideo(video *Video) { 287 | yt.NewVideo = video 288 | yt.notifyObservers() 289 | } 290 | 291 | // UserInterface is a concrete implementation of Observer interface 292 | type UserInterface struct { 293 | UserName string 294 | Videos []*Video 295 | } 296 | 297 | func (ui *UserInterface) update(video interface{}) { 298 | ui.Videos = append(ui.Videos, video.(*Video)) 299 | for video := range ui.Videos { 300 | View.AddChildNode(NewVideoComponent(video)) 301 | } 302 | fmt.Printf("UI %s - Video: '%s' has just been released\n", ui.UserName, video.(*Video).title) 303 | } 304 | 305 | func NewUserInterface(username string) Observer { 306 | return &UserInterface{UserName: username, Videos: make([]*Video, 0)} 307 | } 308 | 309 | // usage 310 | 311 | func main() { 312 | var ytChannel Subject = &YoutubeChannel{} 313 | ui1 := NewUserInterface("Bob") 314 | ui2 := NewUserInterface("Peter") 315 | ytChannel.registerObserver(ui1) 316 | ytChannel.registerObserver(ui2) 317 | ytChannel.(*YoutubeChannel).ReleaseNewVideo(&Video{title: "Avatar 2 trailer"}) 318 | ytChannel.(*YoutubeChannel).ReleaseNewVideo(&Video{title: "Avengers Endgame trailer"}) 319 | } 320 | 321 | ``` 322 | ### Result 323 | ``` 324 | UI Bob - Video: 'Avatar 2 trailer' has just been released 325 | UI Peter - Video: 'Avatar 2 trailer' has just been released 326 | UI Bob - Video: 'Avengers Endgame trailer' has just been released 327 | UI Peter - Video: 'Avengers Endgame trailer' has just been released 328 | 329 | ``` 330 | ## Strategy pattern 331 | - Là mẫu thiết kế cho phép chọn thuật toán trong 1 nhóm các thuật toán liên quan đến nhau ngay tại lúc chương trình đang chạy(at runtime) để thực hiện một hoạt động nào đó 332 | 333 | - Giả sử: chúng ta cần xây dựng 1 thư viện để mã hóa một đoạn tin bằng các phương pháp asymmetric chúng ta có thể sử dụng 1 trong 2 thuật toán sau: RSA hoặc Elliptic curve 334 | ```go 335 | package encryption 336 | 337 | type AsymEncryptionStrategy interface { 338 | Encrypt(data interface{}) (byte[] cipher, error) 339 | } 340 | 341 | type EllipticCurvestrategy struct {} 342 | type RSA struct {} 343 | 344 | func (strat *EllipticCurvestrategy) Encrypt(data interface{}) (byte[] cipher, error) { 345 | // some complex math 346 | ... 347 | return cipher, err 348 | } 349 | 350 | func (strat *RSAstrategy) Encrypt(data interface{}) (byte[] cipher, error) { 351 | // some complex math 352 | ... 353 | return cipher, err 354 | } 355 | 356 | func encryptMessage(msg string, strat AsymEncryptionStrategy) (byte[] cipher, error) { 357 | return strat.Encrypt(msg) 358 | } 359 | 360 | // usage 361 | msg := "this is a confidential message" 362 | cipher, err := encryptMessage(msg, encryption.EllipticCurvestrategy) 363 | cipher, err := encryptMessage(msg, encryption.RSAstrategy) 364 | ``` 365 | ## Iterator pattern 366 | - Mẫu này được sử dụng để truy cập vào các phần tử của 1 collection(array, map, set) một cách tuần tự mà không cần phải hiểu biết về nó. 367 | - Iterate 1 collection sử dụng callback: 368 | ```go 369 | func iterateEvenNumbers(max int, cb func(n int) error) error { 370 | if max < 0 { 371 | return fmt.Errorf("'max' is %d, must be >= 0", max) 372 | } 373 | for i := 2; i <= max; i += 2 { 374 | err := cb(i) 375 | if err != nil { 376 | return err 377 | } 378 | } 379 | return nil 380 | } 381 | 382 | func printEvenNumbers(max int) { 383 | err := iterateEvenNumbers(max, func(n int) error { 384 | fmt.Printf("%d\n", n) 385 | return nil 386 | }) 387 | if err != nil { 388 | log.Fatalf("error: %s\n", err) 389 | } 390 | } 391 | 392 | printEvenNumbers(10) 393 | ``` 394 | > Pattern này được sử dụng trong go standard library: [filepath.Walk](https://golang.org/pkg/path/filepath/#Walk) 395 | - Iterate với `Next()` 396 | ```go 397 | // EvenNumberIterator generates even numbers 398 | type EvenNumberIterator struct { 399 | max int 400 | currValue int 401 | err error 402 | } 403 | 404 | // NewEvenNumberIterator creates new number iterator 405 | func NewEvenNumberIterator(max int) *EvenNumberIterator { 406 | var err error 407 | if max < 0 { 408 | err = fmt.Errorf("'max' is %d, should be >= 0", max) 409 | } 410 | return &EvenNumberIterator{ 411 | max: max, 412 | currValue: 0, 413 | err: err, 414 | } 415 | } 416 | 417 | // Next advances to next even number. Returns false on end of iteration. 418 | func (i *EvenNumberIterator) Next() bool { 419 | if i.err != nil { 420 | return false 421 | } 422 | i.currValue += 2 423 | return i.currValue <= i.max 424 | } 425 | 426 | // Value returns current even number 427 | func (i *EvenNumberIterator) Value() int { 428 | if i.err != nil || i.currValue > i.max { 429 | panic("Value is not valid after iterator finished") 430 | } 431 | return i.currValue 432 | } 433 | 434 | // Err returns iteration error. 435 | func (i *EvenNumberIterator) Err() error { 436 | return i.err 437 | } 438 | 439 | func printEvenNumbers(max int) { 440 | iter := NewEvenNumberIterator(max) 441 | for iter.Next() { 442 | fmt.Printf("n: %d\n", iter.Value()) 443 | } 444 | if iter.Err() != nil { 445 | log.Fatalf("error: %s\n", iter.Err()) 446 | } 447 | } 448 | 449 | func main() { 450 | fmt.Printf("Even numbers up to 8:\n") 451 | printEvenNumbers(8) 452 | fmt.Printf("Even numbers up to 9:\n") 453 | printEvenNumbers(9) 454 | fmt.Printf("Error: even numbers up to -1:\n") 455 | printEvenNumbers(-1) 456 | } 457 | ``` 458 | - Pattern này được sử dụng nhiều trong go standard library 459 | - [Rows.Next](https://golang.org/pkg/database/sql/#Rows.Next): iterate các kết quả thu được từ SQL SELECT statement 460 | - [Scanner.Scan](https://golang.org/pkg/database/sql/#Rows.Next): iterate text 461 | - [Decoder.Token](https://golang.org/pkg/encoding/xml/#Decoder.Token): XML parsing 462 | - [Reader.Read](https://golang.org/pkg/encoding/csv/#Reader.Read): CSV reader 463 | ## References: https://blog.kowalczyk.info/article/1Bkr/3-ways-to-iterate-in-go.html 464 | 465 | ## State pattern 466 | - Mỗi đối tượng có 1 trạng thái gắn với nó và trạng thái có thể được thay đổi thông qua `SetState` method 467 | - Ví dụ: Giả sử điện thoại có 2 trạng thái nhắc nhở: Im lặng hoặc Rung 468 | - code 469 | ```go 470 | type MobileAlertState interface { 471 | alert() 472 | } 473 | 474 | type AlertStateContext struct { 475 | currentState MobileAlertState 476 | } 477 | 478 | func NewAlertStateContext() *AlertStateContext { 479 | return &AlertStateContext{currentState: &Vibration{}} 480 | } 481 | 482 | func (ctx *AlertStateContext) SetState(state MobileAlertState) { 483 | ctx.currentState = state 484 | } 485 | 486 | func (ctx *AlertStateContext) Alert() { 487 | ctx.currentState.alert() 488 | } 489 | 490 | type Vibration struct{} 491 | 492 | func (v *Vibration) alert() { 493 | fmt.Println("vibrating....") 494 | } 495 | 496 | type Silence struct{} 497 | 498 | func (s *Silence) alert() { 499 | fmt.Println("silent ....") 500 | } 501 | 502 | func main() { 503 | stateContext := NewAlertStateContext() 504 | stateContext.Alert() 505 | stateContext.Alert() 506 | stateContext.Alert() 507 | stateContext.SetState(&Silence{}) 508 | stateContext.Alert() 509 | stateContext.Alert() 510 | stateContext.Alert() 511 | } 512 | 513 | // result 514 | vibrating.... 515 | vibrating.... 516 | vibrating.... 517 | silent .... 518 | silent .... 519 | silent .... 520 | ``` 521 | ### Reference: https://www.geeksforgeeks.org/state-design-pattern/ 522 | --- 523 | ## Structural Patterns 524 | ### Adapter 525 | - Cho phép các interface không liên quan đến nhau có thể làm việc cùng nhau 526 | 527 | - Liên tưởng: Giả sử bạn cần cắm sạc điện thoại vào một ổ cắm điện không tương thích khi đó bạn cần phải dùng một bộ chuyển đổi(adapter) 528 | - Ví dụ: Mèo và cá sấu 529 | 530 | ```go 531 | package animal 532 | 533 | type Animal interface { 534 | Move() 535 | } 536 | 537 | // Cat is a concrete animal since it implements the method Move 538 | type Cat struct {} 539 | 540 | func (c *Cat) Move() {} 541 | 542 | // and somewhere in the code we need to use the crocodile type which is often not our code and this Crocodile type does not implement the Animal interface 543 | // but we need to use a crocodile as an animal 544 | 545 | type Crocodile struct {} 546 | 547 | func (c *this.Crocodile) Slither() {} 548 | 549 | // we create an CrocodileAdapter struct that dapts an embeded crocodile so that it can be usedd as an Animal 550 | 551 | type CrocodileAdapter struct { 552 | *Crocodile 553 | } 554 | 555 | func NeweCrocodile() *CrocodileAdapter { 556 | return &CrocodileAdapter{new(Crocodile)} 557 | } 558 | 559 | func (this *CrocodileAdapter) Move() { 560 | this.Slither() 561 | } 562 | 563 | // usage 564 | import "animal" 565 | 566 | var animals []animal.Animal 567 | animals = append(animals, new(animals.Cat)) 568 | animals = append(animals, new(animals.NewCrocodile())) 569 | 570 | for entity := range animals { 571 | entity.Move() 572 | } 573 | 574 | ``` 575 | Reference: [Design Patterns Explained: Adapter Pattern With Code Examples - DZone Java](https://dzone.com/articles/design-patterns-explained-adapter-pattern-with-cod) 576 | 577 | --- 578 | ### Composite 579 | - cho phép tương tác với tất cả các đối tượng tương tự nhau giống như là 1 đối tượng đơn hoặc 1 nhóm các đối tượng 580 | 581 | - Hình dung: Đối tượng File sẽ là 1 đối tượng đơn nếu bên trong nó không có file nào khác, nhưng đối tượng file sẽ được đối xử giống như 1 collection nếu bên trong nó lại có những File khác. 582 | 583 | - Sơ đồ: 584 | ![332b7bd4.png](https://upload.wikimedia.org/wikipedia/commons/thumb/5/5a/Composite_UML_class_diagram_%28fixed%29.svg/600px-Composite_UML_class_diagram_%28fixed%29.svg.png) 585 | - Cấu trúc: 586 | - **Component** (Thành phần): 587 | - Khai báo interface hoặc abstract chung cho các thành phần đối tượng. 588 | - Chứa các method thao tác chung của các thành phần đối tượng. 589 | - **Leaf** (Lá): 590 | - Biểu diễn các đối tượng lá (ko có con) trong thành phần đối tượng. 591 | - **Composite** (Hỗn hợp): 592 | - Định nghĩa một thao tác cho các thành phần có thành phần con. 593 | - Lưu trữ và quản lý các thành phần con 594 | 595 | - Ví dụ khác: Khi vẽ, chúng ta được cung cấp các đối tượng Square, Circle, các đối tượng này đều là Shape. Giả sử chúng ta muốn vẽ nhiều loại hình cùng 1 lúc chúng ta sẽ tạo một Layer chứa các Shape này và thực hiện vòng lặp để vẽ chúng. Ở đây composite được hiểu là chúng ta có thể sử dụng các đối tượng Square, Circle riêng biệt nhưng khi cần chúng ta có thể gom chúng lại thành 1 nhóm 596 | ```go 597 | // Shape is the component 598 | type Shape interface { 599 | Draw(drawer *Drawer) error 600 | } 601 | 602 | // Square and Circle are leaves 603 | type Square struct { 604 | Location Point 605 | Size float64 606 | } 607 | 608 | func (square *Square) Draw(drawer *Drawer) error { 609 | return drawer.DrawRect(Rect{ 610 | Location: square.Location, 611 | Size: Size{ 612 | Height: square.Side, 613 | Width: square.Side, 614 | }, 615 | }) 616 | } 617 | 618 | type Circle struct { 619 | Center Point 620 | Radius float64 621 | } 622 | 623 | func (circle *Circle) Draw(drawer *Drawer) error { 624 | rect := Rect{ 625 | Location: Point{ 626 | X: circle.Center.X - circle.Radius, 627 | Y: circle.Center.Y - circle.Radius, 628 | }, 629 | Size: Size{ 630 | Width: 2 * circle.Radius, 631 | Height: 2 * circle.Radius, 632 | }, 633 | } 634 | 635 | return drawer.DrawEllipseInRect(rect) 636 | } 637 | 638 | // Layer is the composite 639 | type Layer struct { 640 | Shapes []Shape 641 | } 642 | 643 | func (layer *Layer) Draw(drawer *Drawer) error { 644 | for _, shape := range layer.Shapes { 645 | if err := shape.Draw(drawer); err != nil { 646 | return err 647 | } 648 | fmt.Println() 649 | } 650 | 651 | return nil 652 | } 653 | 654 | // usage 655 | circle := &photoshop.Circle{ 656 | Center: photoshop.Point{X: 100, Y: 100}, 657 | Radius: 50, 658 | } 659 | 660 | square := &photoshop.Square{ 661 | Location: photoshop.Point{X: 50, Y: 50}, 662 | Side: 20, 663 | } 664 | 665 | layer := &photoshop.Layer{ 666 | Elements: []photoshop.Shapes{ 667 | circle, 668 | square, 669 | }, 670 | } 671 | 672 | circle.Draw(&photoshop.Drawer{}) 673 | square.Draw(&photoshop.Drawer{}) 674 | // or 675 | layer.Draw(&photoshop.Drawer{}) 676 | ``` 677 | ### Bridge 678 | - Tách 1 class lớp thành 2 phần interface và reprensentation để 2 phần này không bị phụ thuộc vào nhau và có thể được phát triển song song 679 | - **Vấn đề**: Giả sử chúng ta cần xây dựng 1 package UI hỗ trợ vẽ nhiều hình dạng trên màn hình bằng cả 2 công nghệ rendering direct2d và opengl. Trong trường hợp này là vẽ hình tròn dùng cả 2 công nghệ. Ta tách struct Circle khỏi phần drawing 680 | ```go 681 | type Circle struct { 682 | DrawingContext drawer 683 | Center Point 684 | Radius float64 685 | } 686 | 687 | func (circle *Circle) Draw() error { 688 | rect := Rect{ 689 | Location: Point{ 690 | X: circle.Center.X - circle.Radius, 691 | Y: circle.Center.Y - circle.Radius, 692 | }, 693 | Size: Size{ 694 | Width: 2 * circle.Radius, 695 | Height: 2 * circle.Radius, 696 | } 697 | } 698 | return circle.DrawingContext.DrawEllipseInRect(rect) 699 | } 700 | 701 | type Drawer interface { 702 | DrawEllipseInRect(Rect) error 703 | } 704 | 705 | // OpenGL drawer 706 | type OpenGL struct{} 707 | // DrawEllipseInRect draws an ellipse in rectangle 708 | func (gl *OpenGL) DrawEllipseInRect(r Rect) error { 709 | fmt.Printf("OpenGL is drawing ellipse in rect %v", r) 710 | return nil 711 | } 712 | 713 | // Direct2D drawer 714 | type Direct2D struct{} 715 | 716 | // DrawEllipseInRect draws an ellipse in rectangle 717 | func (d2d *Direct2D) DrawEllipseInRect(r Rect) error { 718 | fmt.Printf("Direct2D is drawing ellipse in rect %v", r) 719 | return nil 720 | } 721 | 722 | // usage 723 | openGL := &uikit.OpenGL{} 724 | direct2D := &uikit.Direct2D{} 725 | 726 | circle := &uikit.Circle{ 727 | Center: uikit.Point{X: 100, Y: 100}, 728 | Radius: 50, 729 | } 730 | 731 | circle.DrawingContext = openGL 732 | circle.Draw() 733 | 734 | circle.DrawingContext = direct2D 735 | circle.Draw() 736 | ``` 737 | 738 | ### Proxy 739 | - Mục đích 740 | - Kiểm soát quyền truy suất các phương thức của đối tượng 741 | - Bổ xung thêm chức năng trước khi thực thi phương thức gốc 742 | - Tạo ra đối tượng mới có chức năng cao hơn đối tượng ban đầu 743 | - Giảm chi phí khi có nhiều truy cập vào đối tượng có chi phí khởi tạo ban đầu lớn 744 | 745 | ```go 746 | type Image interface { 747 | display() 748 | } 749 | 750 | type HighResolutionImage struct { 751 | imageFilePath string 752 | } 753 | 754 | type ImageProxy struct { 755 | imageFilePath string 756 | realImage Image 757 | } 758 | 759 | func (this *HighResolutionImage) loadImage(path string) { 760 | // load image from disk into memory 761 | // this is a heavy and costly operation 762 | fmt.Printf("load image %s from disk\n", path) 763 | } 764 | 765 | func (this *HighResolutionImage) display() { 766 | this.loadImage(this.imageFilePath) 767 | fmt.Printf("display high resolution image\n") 768 | } 769 | 770 | func (this *ImageProxy) display() { 771 | this.realImage = &HighResolutionImage{imageFilePath: this.imageFilePath} 772 | this.realImage.display() 773 | } 774 | 775 | func NewImageProxy(path string) Image { 776 | return &ImageProxy{imageFilePath: path} 777 | } 778 | 779 | // usage 780 | 781 | highResolutionImage := NewImageProxy("sample/img1.png") 782 | // the realImage won't be loaded until user calls display 783 | // later 784 | highResolutionImage.display() 785 | 786 | ``` 787 | #### Reference: https://www.oodesign.com/proxy-pattern.html 788 | 789 | 790 | 791 | 792 | 793 | --------------------------------------------------------------------------------