CNDI
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Week 6: 5GC development - 1 ## 課程目標 - Concurrent programming in Go - Design Pattern - NF 專案架構與相關套件 [free5GC Upgrade R17 Suggestion Step](https://hackmd.io/@free5gc-dev/B1HzZ7pBkx) ## Concurrent programming A goroutine is a lightweight thread managed by the Go runtime. ```go= package main import ( "fmt" "time" ) func say(s string) { for i := 0; i < 5; i++ { time.Sleep(100 * time.Millisecond) fmt.Println(s) } } func main() { go say("world") say("hello") } ``` ![image](https://hackmd.io/_uploads/S1u8Vs76ll.png) > *M-P-G Model,圖片來源:https://alanzhan.dev/post/2022-01-24-golang-goroutine/* - **G**oroutine:表示 goroutine,每個 goroutine 都有自已的 stack 與定時器,初始化時 stack 大小在 2KB 左右,其大小是會被 golang runtime 自動縮放的。 - **M**achine:代表 kernel thread,紀錄 thread 對應的 stack 資訊。當 goroutine 被調度到 machine 時會使用 goroutine 本身的 stack。 - **P**rocessor:表示調度器,負責調度 goroutine ,維護一個 local goroutine queue,並且將 queue 與 M 綁定,使 M 能夠從 P 上獲得 goroutine 並執行,同時還負責部分記憶體管理機制。 從 M-P-G 模型可以了解,基本上 golang runtime 就是一個排程器,它為使用者建立的 goroutine 分配背後的 memory 以及 cpu 資源。 同時,golang runtime 還實作了 sleep 機制,避免資源上的浪費(這部分我們在討論 mutex lock 時再談)。 :::info 💡補充: - **GOMAXPROCS** 預設是 CPU 的 CORE 數,這樣的設計讓 golang runtime 盡可能地使用機器上的 CPU 資源。 - Golang 的優勢之一是其內建易於使用的 goroutine,然而,如果你的應用程式部署在 Kubernetes 平台,其實有可能會因為 CPU limit 與 GOMAXPROCS 的設定,導致應用程式因為錯誤的 GOMAXPROCS,在 runtime 嘗試 GC 時觸發系統的限流。 -- [ref](https://go.dev/blog/container-aware-gomaxprocs) - 閥值 = 上次 GC 記憶體分配量 * 記憶體增長量。 -- [ref](https://tip.golang.org/doc/gc-guide) - 看記憶體增長量觸發 GC:由變數 GOGC 控制,預設值為 100,即每當記憶體使用量擴大一倍(100%)時啟動 GC。 - 默認情況下,每兩分鐘觸發一次 GC。 - 使用 `runtime.GC()` 也能觸發 GC ::: ### Mutex ![image](https://hackmd.io/_uploads/ryPbMn7Txg.png) > *Critical Section 示意圖,圖片來源:https://medium.com/@punyatoya213/multithreading-can-be-fun-too-part-2-e27f1841c8ca* Mutex Lock 是常見用來處理 synchronization 的手段之一,Mutext Lock 有以下特性: - 解鎖的人必須是上鎖的人 - 排隊等鎖的人會進入休眠 ```go= package main import ( "fmt" "sync" "time" ) // SafeCounter is safe to use concurrently. type SafeCounter struct { mu sync.Mutex v map[string]int } // Inc increments the counter for the given key. func (c *SafeCounter) Inc(key string) { c.mu.Lock() // Lock so only one goroutine at a time can access the map c.v. c.v[key]++ c.mu.Unlock() } // Value returns the current value of the counter for the given key. func (c *SafeCounter) Value(key string) int { c.mu.Lock() // Lock so only one goroutine at a time can access the map c.v. defer c.mu.Unlock() return c.v[key] } func main() { c := SafeCounter{v: make(map[string]int)} for i := 0; i < 1000; i++ { go c.Inc("somekey") } time.Sleep(time.Second) fmt.Println(c.Value("somekey")) } ``` 休眠這件事情就需要仰賴系統的支援: - 如果在 RTOS 上,那就是由 RTOS 來掌管 Thread 的狀態 - 對於 Golang 來說,使用原生的 Mutext 上鎖的話,其餘等鎖的 goroutine 就會由 Golang runtime 將其放入下圖的 sudog(pseudo-g)之中 ![image](https://hackmd.io/_uploads/rkr-PiXTgg.png) > *M-P-G Model Overview,圖片來源:https://alanzhan.dev/post/2022-01-24-golang-goroutine/* :::info 💡補充: - Golang 沒有提供 SpinLock,如果需要,可使用 atomic operation 實作出類似的效果。 - Golang 提供 `sync.RWMutex`,可以將讀操作與寫操作分開對待,進一步提升效能。 ::: ### Inter-communication methods #### Unbuffered channel :::spoiler 以下內容取自 [A Tour of Go](https://go.dev/tour/concurrency/2): Channels are a typed conduit through which you can send and receive values with the channel operator, <-. ```go= ch <- v // Send v to channel ch. v := <-ch // Receive from ch, and // assign value to v. ``` Like maps and slices, channels must be created before use: ```go= ch := make(chan int) ``` By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables. ::: #### Buffered channel A Tour of Go 對 Unbuffered channel 的形容是這樣的: > By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables. 這意味著,在高度並行的情況下,goroutine 之間使用 Unbuffered channel 會出現阻塞。如果希望 sender 不要因為等待 receiver,那麼我們可以在宣告時為 channel 多分配一些空間: ```go= ch := make(chan int, <SIZE>) ``` #### Semaphore Unbuffer channel 會阻斷 sender 或是 receiver 的執行,所以它一般會被用在流程控制,而不是訊息的傳遞。 實際上,我們也可以用 unbuffered channel 實作出 semaphore: ![image](https://hackmd.io/_uploads/rklDP2m6ll.png) > *Semaphore 示意圖,圖片來源:https://medium.com/happytech/limiting-goroutines-with-semaphore-b8ccc248e5f0* ```go= package semaphore import ( "context" "time" ) type Semaphore struct { sem chan struct{} // use unbuffered channel as semaphore } func New(size int) *Semaphore { return &Semaphore{ sem: make(chan struct{}, size), } } func (s *Semaphore) Acquire(ctx context.Context) error { select { case s.sem <- struct{}{}: return nil case <-ctx.Done(): return ctx.Err() } } func (s *Semaphore) Release() { <-s.sem } ``` ### Workflow control 如果一個應用程式會啟動多個 goroutine,如何保證當系統收到 SIGNAL 時,能夠等待所有 goroutine 都執行完畢才結束 process 的生命週期呢? #### WaitGroup Golang 實作了 WaitGroup,用於保證 goroutine 的結束順序: ```go= package main import ( "fmt" "sync" "time" ) // worker simulates a task performed by a goroutine. func worker(id int, wg *sync.WaitGroup) { defer wg.Done() // Decrement the WaitGroup counter when the goroutine finishes. fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) // Simulate some work fmt.Printf("Worker %d finished\n", id) } func main() { var wg sync.WaitGroup // Declare a WaitGroup. // Launch several goroutines. for i := 1; i <= 3; i++ { wg.Add(1) // Increment the WaitGroup counter for each goroutine. go worker(i, &wg) // Pass a pointer to the WaitGroup to the goroutine. } wg.Wait() // Block until all goroutines have called wg.Done(). fmt.Println("All workers finished") } ``` #### Context 參考 [[開發技巧]在 Golang 中使用 context](https://medium.com/@ianchen0119/%E9%96%8B%E7%99%BC%E6%8A%80%E5%B7%A7-%E5%9C%A8-golang-%E4%B8%AD%E4%BD%BF%E7%94%A8-context-6aa1e3630ce0)。 #### Graceful shutdown 對於一個任意的網路應用程式,在應用程式收到 SIGTERM 以後都應該在寬限期(Graceful Period)內處理完已經接受的客戶端請求,以避免服務品質不佳。 ![image](https://hackmd.io/_uploads/SJuIsn7Tle.png) > *Graceful shutdown in k8s,圖片來源:https://learnk8s.io/graceful-shutdown* 假設一個使用 Golang 開發的網路程式,它將接收封包與處理封包的部分拆開來,使用 goroutine 實現並行化處理,那麼該程式至少會有: - 執行 `main()` 的 goroutine - 處理封包接收的 goroutine(s) - 處理封包的 goroutine(s) 當應用程式進入寬限期時,執行 `main()` 的 goroutine 負責發送 cancel 訊號,並且等待其他 goroutine 完成任務: ```go= // handle SIGTERM cancel() // 發送 cancel 訊號,其他 goroutine 會從 ctx.Done() 收到該事件 wg.Wait() // 等待 n 個 goroutine 執行 wg.Done() log.Println("graceful shutdown") ``` 其他 goroutine: ```go= for { select { case <- ctx.Done(): for _, req := range reqCh { handleReq(req) } wg.Done() return case req := <- reqCh: handleReq(req) // ... } } ``` ## Design Patterns for Concurrency ### Single Threaded Execution Pattern 確保 critical section 在任意時間至多只會有一個 thread 執行: ```go= package main import ( "fmt" "sync" "time" ) var ( counter int mutex sync.Mutex ) func increment() { mutex.Lock() // Acquire the lock defer mutex.Unlock() // Release the lock when done counter++ fmt.Println("Counter:", counter) } func main() { for i := 0; i < 5; i++ { go increment() } time.Sleep(time.Second) // Allow goroutines to complete } ``` ### Read-Write-Lock Pattern 多讀單寫: - 讀操作不會影響結果,所以多個讀操作可以同時進行 - 寫操作會影響結果,所以同時只能有一個寫操作 ```go= package main import ( "fmt" "sync" "time" ) var ( counter int mutex sync.RWMutex ) func increment() { mutex.Lock() // Acquire the lock defer mutex.Unlock() // Release the lock when done counter++ } func read() int { mutex.RLock() // Acquire the read lock defer mutex.RUnlock() // Release the read lock when done fmt.Println("Counter:", counter) } func main() { for i := 0; i < 5; i++ { go increment() go read() } time.Sleep(time.Second) // Allow goroutines to complete } ``` ### 一個案例看多種 design pattern ```go= // ... func newWorker( // ... ) { for { select { case <- ctx.Done(): for req := range reqChan { // Two-phase Termination Pattern handleReq(req) } wg.Done() return case req := <- reqChan // Guarded Suspension Pattern handleReq(req) } } } func main() { var wg sync.WaitGroup signalChannel := make(chan os.Signal, 2) signal.Notify(signalChannel, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) ctx, cancel := context.WithCancel(context.Background()) reqChan := make(chan Req{}, 20) // worker pool pattern for i := 0; i < 5; i++ { // consumer wg.Add(1) go newWorker(ctx, reqChan, wg) } wg.Add(1) go func(){ // producer for { select { case: <- ctx.Done(): wg.Done() return default: // !!!IMPORTANT!!! } // prepare net connection reqChan <- req } }() <- signalChannel cancel() wg.Wait() } ``` - Guarded Suspension Pattern:滿足先決條件,thread 才能向下執行。 - Two-phase Termination Pattern:先結束程式邏輯,再結束 goroutine。 - Producer-Consumer Pattern:將生產者與消費者分開執行。 - Worker Pool Pattern:使用 Worker Pool 避免 goroutine 濫開。 ## Creational Design Pattern 專注於高效的管理物件 ### Simple Factory Pattern Simple Factory Pattern 是一種管理物件創建的模式,隨著輸入的參數不同,Factory 會提供不同的物件,使用者取得物件的時候只要在意傳入的參數,不需要去理解物件本身: ```go= package main import "fmt" // Product Interface type Animal interface { Speak() string } // Concrete Product: Dog type Dog struct{} func (d *Dog) Speak() string { return "Woof!" } // Concrete Product: Cat type Cat struct{} func (c *Cat) Speak() string { return "Meow!" } // Simple Factory func NewAnimal(animalType string) Animal { switch animalType { case "dog": return &Dog{} case "cat": return &Cat{} default: return nil // Or handle error } } func main() { // Client interacts with the factory to get animals dog := NewAnimal("dog") if dog != nil { fmt.Println("Dog says:", dog.Speak()) } cat := NewAnimal("cat") if cat != nil { fmt.Println("Cat says:", cat.Speak()) } unknown := NewAnimal("bird") if unknown == nil { fmt.Println("Unknown animal type.") } } ``` ### Factory Method Pattern 將複雜的生產邏輯再拆分至特定工廠,由使用者來決定使用哪個工廠生產產品: ```go= package main import "fmt" // Product Interface type Vehicle interface { Drive() } // Concrete Products type Car struct{} func (c *Car) Drive() { fmt.Println("Driving a car.") } type Truck struct{} func (t *Truck) Drive() { fmt.Println("Driving a truck.") } // Creator Interface (Factory Method) type VehicleFactory interface { CreateVehicle() Vehicle } // Concrete Creators type CarFactory struct{} func (cf *CarFactory) CreateVehicle() Vehicle { return &Car{} } type TruckFactory struct{} func (tf *TruckFactory) CreateVehicle() Vehicle { return &Truck{} } func main() { var carFactory VehicleFactory = &CarFactory{} car := carFactory.CreateVehicle() car.Drive() var truckFactory VehicleFactory = &TruckFactory{} truck := truckFactory.CreateVehicle() truck.Drive() } ``` ### Abstract Factory Pattern 使用者不需要知道具體要建立哪個產品,只要使用「工廠介面」即可產生對應的一整組物件。 假設我們要開發一個跨平台的應用程式, 會需要根據作業系統(Windows / MacOS)產生不同風格的按鈕(Button)與核取方塊(Checkbox)。我們希望做到: - 當在 Mac 上時 → 產生 Mac 風格的按鈕與核取方塊 - 當在 Windows 上時 → 產生 Windows 風格的按鈕與核取方塊 - 「客戶端程式」不需要知道這些差異 #### 1. 定義抽象產品介面(Abstract Products) ```go= type Button interface { Render() } type Checkbox interface { Check() } ``` #### 2. 實作具體產品(Concrete Products) ```go= import "fmt" // MacOS 實作 type MacButton struct{} func (b *MacButton) Render() { fmt.Println("渲染 MacOS 風格的按鈕") } type MacCheckbox struct{} func (c *MacCheckbox) Check() { fmt.Println("勾選 MacOS 風格的核取方塊") } // Windows 實作 type WinButton struct{} func (b *WinButton) Render() { fmt.Println("渲染 Windows 風格的按鈕") } type WinCheckbox struct{} func (c *WinCheckbox) Check() { fmt.Println("勾選 Windows 風格的核取方塊") } ``` #### 3. 定義抽象工廠介面(Abstract Factory) ```go= type GUIFactory interface { CreateButton() Button CreateCheckbox() Checkbox } ``` #### 4. 實作具體工廠(Concrete Factories) ```go= type MacFactory struct{} func (f *MacFactory) CreateButton() Button { return &MacButton{} } func (f *MacFactory) CreateCheckbox() Checkbox { return &MacCheckbox{} } type WinFactory struct{} func (f *WinFactory) CreateButton() Button { return &WinButton{} } func (f *WinFactory) CreateCheckbox() Checkbox { return &WinCheckbox{} } ``` #### 5. 客戶端程式(Client Code) ```go= func renderUI(factory GUIFactory) { button := factory.CreateButton() checkbox := factory.CreateCheckbox() button.Render() checkbox.Check() } func main() { var factory GUIFactory osType := "mac" // 模擬偵測到系統為 macOS if osType == "mac" { factory = &MacFactory{} } else { factory = &WinFactory{} } renderUI(factory) } ``` ### Singleton Pattern 確保整個程式在執行期間,某個類別只會被建立一個實例(instance),並且提供一個全域存取點(global access point) 讓其他地方能取得該實例。 ```go= package main import ( "fmt" "sync" ) // Config 是我們要共用的單例物件 type Config struct { AppName string Version string } var ( instance *Config once sync.Once // 確保只初始化一次 ) // GetConfig 回傳唯一的 Config 實例 func GetConfig() *Config { once.Do(func() { fmt.Println("🔧 初始化 Config 實例...") instance = &Config{ AppName: "Gthulhu", Version: "1.0.0", } }) return instance } func main() { // 模擬多次呼叫,但只會建立一次 cfg1 := GetConfig() cfg2 := GetConfig() fmt.Println(cfg1.AppName, cfg1.Version) // 驗證兩者是同一個實例 if cfg1 == cfg2 { fmt.Println("✅ cfg1 和 cfg2 是同一個實例") } else { fmt.Println("❌ cfg1 和 cfg2 是不同實例") } } ``` ### Prototype Pattern 透過複製(clone)現有物件,而不是直接建立新的物件,來產生新的實例,避免重複初始化或複雜的建立流程。 #### 1. 定義 Prototype 介面 ```go= package main import "fmt" // Prototype 介面:定義 Clone() 方法 type Prototype interface { Clone() Prototype GetInfo() } ``` #### 2. 定義具體原型(Concrete Prototype) ```go= type Character struct { Name string Class string Attack int Defense int } func (c *Character) Clone() Prototype { // 這裡做淺層複製(Go 的 struct 是值型別,可直接複製) clone := *c return &clone } func (c *Character) GetInfo() { fmt.Printf("角色名稱: %s | 職業: %s | 攻擊: %d | 防禦: %d\n", c.Name, c.Class, c.Attack, c.Defense) } ``` #### 3. 範例 ```go= func main() { // 建立原型角色 baseKnight := &Character{ Name: "Arthur", Class: "Knight", Attack: 80, Defense: 100, } // 使用原型複製出新角色 knight2 := baseKnight.Clone().(*Character) knight2.Name = "Lancelot" // 修改部分屬性 knight3 := baseKnight.Clone().(*Character) knight3.Name = "Gawain" knight3.Attack = 90 // 顯示結果 baseKnight.GetInfo() knight2.GetInfo() knight3.GetInfo() } ``` ## Structural Design Patterns 專注於低耦合物件的設計 ### Adapter Pattern 透過定義統一介面的方式,讓多種物件可以用同樣的方式管理。 舉例:UPF 的 Data Plane 可以用多種方式實作出來,假設我希望實作一個支持多種 Data Plane 實作的 UPF,我可以套用 Adapter Pattern! ```go= type Driver interface { Close() CreatePDR(uint64, *ie.IE) error UpdatePDR(uint64, *ie.IE) error RemovePDR(uint64, *ie.IE) error CreateFAR(uint64, *ie.IE) error UpdateFAR(uint64, *ie.IE) error RemoveFAR(uint64, *ie.IE) error CreateQER(uint64, *ie.IE) error UpdateQER(uint64, *ie.IE) error RemoveQER(uint64, *ie.IE) error CreateURR(uint64, *ie.IE) error UpdateURR(uint64, *ie.IE) ([]report.USAReport, error) RemoveURR(uint64, *ie.IE) ([]report.USAReport, error) QueryURR(uint64, uint32) ([]report.USAReport, error) CreateBAR(uint64, *ie.IE) error UpdateBAR(uint64, *ie.IE) error RemoveBAR(uint64, *ie.IE) error HandleReport(report.Handler) } ``` - Related PR: https://github.com/free5gc/go-upf/compare/main...feat/dummy - 單看程式碼會有點像是 Simple Factory Pattern,我認為 Adapter Pattern 關注的是物件的統一方法,而 Simple Factory Pattern 則是專注在取得物件上。 ### Decorator Pattern 用包裝(wrapping)取代繼承(inheritance)。 假設我們有一個簡單的訊息系統: 原本的功能是:傳送純文字訊息。 現在想動態增加功能: - 可以加上「加密」 - 可以加上「壓縮」 - 可以組合使用(例如先壓縮再加密) 這時候「Decorator Pattern」就派上用場了: #### 1. 定義介面 ```go= package main import "fmt" // Component 介面:所有訊息傳送者都要實作 Send() type Sender interface { Send(msg string) } ``` #### 2. 具體實作 ```go= // BasicSender 是最原始的訊息傳送者 type BasicSender struct{} func (b *BasicSender) Send(msg string) { fmt.Println("傳送訊息:", msg) } ``` #### 3. 定義 Decorator ```go= // BaseDecorator 內部包含一個 Sender,讓子類別能包裝其他 Sender type BaseDecorator struct { wrapped Sender } func (d *BaseDecorator) Send(msg string) { if d.wrapped != nil { d.wrapped.Send(msg) } } ``` #### 4. 實作 Decorator ```go= // EncryptionDecorator:為訊息加密 type EncryptionDecorator struct { BaseDecorator } func (e *EncryptionDecorator) Send(msg string) { encrypted := "[加密]" + msg + "[/加密]" fmt.Println("加密處理中...") e.wrapped.Send(encrypted) } // CompressionDecorator:為訊息壓縮 type CompressionDecorator struct { BaseDecorator } func (c *CompressionDecorator) Send(msg string) { compressed := "[壓縮]" + msg + "[/壓縮]" fmt.Println("壓縮處理中...") c.wrapped.Send(compressed) } ``` #### 5. 使用範例 ```go= func main() { // 建立基本 sender var sender Sender = &BasicSender{} // 加上壓縮功能 sender = &CompressionDecorator{BaseDecorator{sender}} // 再加上加密功能 sender = &EncryptionDecorator{BaseDecorator{sender}} // 傳送訊息(順序:先壓縮,再加密) sender.Send("Hello Decorator Pattern") } ``` :::info 文章推薦:https://blog.messfar.com/page/design-pattern/ :::

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully