DEV Community

Cover image for Lebih Dekat dengan Visitor Pattern
M.Hakim Amransyah
M.Hakim Amransyah

Posted on • Updated on

Lebih Dekat dengan Visitor Pattern

Prolog

Alkisah anda sebagai seorang backend engineer dihadapkan kepada sebuah tugas untuk menambahkan fitur notifikasi peringatan kepada seluruh level pegawai dengan ketentuan yang bervariasi. Ketentuan yang dimaksud antara lain;

  • Staf
    • Staf yang memiliki memiliki KPI dibawah 30% akan menerima  notifikasi "peringatan-keras".
    • Staf yang memiliki KPI dibawah 67% dan diatas 30% akan menerima notifikasi "peringatan".
  • Supervisor
    • Supervisor yang memiliki memiliki KPI dibawah 30% akan menerima notifikasi "peringatan-keras".
    • Supervisor yang memiliki masa kerja di atas 10 tahun dan memiliki KPI diatas 30% dan dibawah 60% akan menerima notifikasi "peringatan".
    • Supervisor memiliki masa kerja di bawah 10 tahun dan memiliki KPI diatas 30% dan dibawah 70% akan menerima notifikasi "peringatan".
  • Manajer
    • Manajer yang memiliki memiliki KPI dibawah 40% akan menerima  notifikasi "peringatan-keras".
    • Manajer yang memiliki usia di atas 40 tahun dan memiliki KPI diatas 40% dan dibawah 60% akan menerima notifikasi "peringatan".
    • Manajer yang memiliki usia di bawah 40 tahun dan memiliki KPI diatas 40% dan dibawah 70% akan menerima notifikasi "peringatan".

Aplikasi anda saat ini dibuat menggunakan bahasa pemrograman go dan hanya memiliki 4 kelas yang membedakan setiap level pegawai yang ada. Tugas anda adalah menambahkan fungsionalitas sesuai dengan ketentuan tersebut ke sources code yang ada.

Image description

Ide yang Tertolak

Anda berdiskusi dengan atasan anda dan menyampaikan ide bahwa anda akan menambahkan sebuah fungsi sendWarningNotification pada objek Staf, Supervisor dan Manager. Atasan anda mengerenyutkan dahi dan bertanya kepada anda.

Apakah pegawai di perusahaan kita sekarang berperan sebagai seorang pemberi peringatan ?

Diskusi berlanjut dan atasan anda menolak ide anda karena dianggap menambahkan sifat "alien" ke ketiga objek tersebut. Atasan anda juga men-changelle bagaimana ide tersebut nantinya akan mampu mengakomodasi apabila ada penambahan behaviour lagi terhadap ketiga objek ini.

Visitor Pattern

Pada akhirnya anda dan atasan memutuskan untuk menggunakan visitor pattern untuk menangani kasus ini.

Image description

Seluruh logika pengiriman notifikasi peringatan akan menjadi tanggung jawab dari objek PerformanceWarningVisitor. Setiap objek turunan employee(Staff, Supervisor dan Manager) tidak perlu tahu definisi dan implementasi pengiriman notifikasi peringatan. Objek Staff, Supervisor dan Manager hanya perlu memanggil method Accept dengan paramater PerformanceWarningVisitor untuk mengirimkan notifikasi peringatan kepada seluruh level pegawai.

main.go

func main() {

    employees := make([]entities.Employee, 0)

    performanceNotification := visitor.PerformanceWarningVisitor{}

    employees = append(employees,
        entities.Staff{
            Id:                    12343,
            Name:                  "Pedro Pacquita",
            JoinDate:              time.Date(2011, time.February, 10, 0, 0, 0, 0, time.UTC),
            BirthDate:             time.Date(1990, time.January, 07, 0, 0, 0, 0, time.UTC),
            PerformancePercentage: 47.5,
        },
        entities.Supervisor{
            Id:                    12342,
            Name:                  "Omar Maulo Atarez",
            JoinDate:              time.Date(2005, time.December, 15, 0, 0, 0, 0, time.UTC),
            BirthDate:             time.Date(1987, time.December, 02, 0, 0, 0, 0, time.UTC),
            PerformancePercentage: 75,
        },
        entities.Manager{
            Id:                    12312,
            Name:                  "Zakaria Owele",
            JoinDate:              time.Date(1993, time.December, 04, 0, 0, 0, 0, time.UTC),
            BirthDate:             time.Date(1971, time.July, 15, 0, 0, 0, 0, time.UTC),
            PerformancePercentage: 50,
        },
    )

    for _, employee := range employees {

        // send performance notification
        employee.Accept(performanceNotification)

    }
}
Enter fullscreen mode Exit fullscreen mode

staff.go

type Staff struct {
    Id                    int
    Name                  string
    JoinDate              time.Time
    BirthDate             time.Time
    PerformancePercentage float32
}

func (staff Staff) Accept(v Visitor) {
    v.VisitStaff(&staff)
}
Enter fullscreen mode Exit fullscreen mode

supervisor.go

type Supervisor struct {
    Id                    int
    Name                  string
    JoinDate              time.Time
    BirthDate             time.Time
    PerformancePercentage float32
}

func (spv Supervisor) Accept(visitor Visitor) {
    visitor.VisitSupervisor(&spv)
}
Enter fullscreen mode Exit fullscreen mode

manager.go

type Manager struct {
    Id                    int
    Name                  string
    JoinDate              time.Time
    BirthDate             time.Time
    PerformancePercentage float32
}

func (manager Manager) Accept(visitor Visitor) {
    visitor.VisitManager(&manager)
}
Enter fullscreen mode Exit fullscreen mode

performance_warning_visitor.go

type PerformanceWarningVisitor struct{}

func (visitor PerformanceWarningVisitor) VisitStaff(staff *entities.Staff) (interface{}, error) {

    var warningType string

    if staff.PerformancePercentage < 30 {
        warningType = STERN_WARNING
    }

    if staff.PerformancePercentage >= 30 && staff.PerformancePercentage < 67 {
        warningType = WARNING
    }

    if warningType != "" {
        fmt.Printf("Notification %s send to staff : %s \n", warningType, staff.Name)
    }

    return warningType, nil
}

func (visitor PerformanceWarningVisitor) VisitSupervisor(spv *entities.Supervisor) (interface{}, error) {

    var warningType string
    durationInYear := (time.Now()).Sub(spv.JoinDate).Hours() / 8760

    if spv.PerformancePercentage < 30 {
        warningType = STERN_WARNING
    }

    if durationInYear >= 10 && (spv.PerformancePercentage >= 30 && spv.PerformancePercentage < 60) {
        warningType = WARNING
    }

    if durationInYear < 10 && (spv.PerformancePercentage >= 30 && spv.PerformancePercentage < 70) {
        warningType = WARNING
    }

    if warningType != "" {
        fmt.Printf("Notification %s send to supervisor : %s \n", warningType, spv.Name)
    }

    return warningType, nil
}

func (visitor PerformanceWarningVisitor) VisitManager(manager *entities.Manager) (interface{}, error) {

    var warningType string
    Age := (time.Now()).Sub(manager.BirthDate).Hours() / 8760

    if manager.PerformancePercentage < 40 {
        warningType = STERN_WARNING
    }

    if Age >= 40 && (manager.PerformancePercentage >= 40 && manager.PerformancePercentage < 60) {
        warningType = WARNING
    }

    if Age < 40 && (manager.PerformancePercentage >= 40 && manager.PerformancePercentage < 70) {
        warningType = WARNING
    }

    if warningType != "" {
        fmt.Printf("Notification %s send to manager : %s \n", warningType, manager.Name)
    }

    return warningType, nil
}
Enter fullscreen mode Exit fullscreen mode

Perubahan adalah Kepastian

Seiring berjalannya waktu prediksi atasan anda akan adanya penambahan fitur yang melibatkan ketiga objek employee ini ternyata menjadi kenyataan.

Anda kembali diminta untuk menambahkan fitur untuk menghitung bonus pendapatan tambahan dari masing-masing level employee dengan ketentuan sebagai berikut ;

  • Staf
    • Setiap staf akan menerima bonus pendapatan tambahan sebesar 1000000 apabila mempeeoleh KPI di atas 87%
  • Supervisor
    • Setiap supervisor akan menerima bonus pendapatan tambahan sebesar 2000000 apabila mempeeoleh KPI di atas 85%
  • Manajer
    • Setiap manajer akan menerima bonus pendapatan tambahan sebesar 5000000 apabila mempeeoleh KPI di atas 80%

Anda dan atasan tahu bahwa sources code yang ada bisa di expand dengan mengikuti pola visitor pattern yang sudah ada.

Image description

Anda hanya perlu memanggil kembali method Accept akan tetapi kali ini gunakan BonusIncomeVisitor sebagai parameter nya

for _, employee := range employees {

  // send performance notification
  employee.Accept(performanceNotification)

  // calculate bonus income
  employee.Accept(bonusIncome)

}
Enter fullscreen mode Exit fullscreen mode

Epilog

Seluruh sources code yang berkaitan dengan artikel ini dapat dilihat pada repository berikut https://github.com/Mhakimamransyah/practice-design-pattern.

Bacaan Lanjutan

Referensi Bacaan
https://refactoring.guru/design-patterns/visitor
https://refactoring.guru/design-patterns/visitor/go/example
https://refactoring.guru/design-patterns/visitor-double-dispatch

Author
https://github.com/Mhakimamransyah
https://www.linkedin.com/in/hakim-amr/
mailto: m.hakim.amransyah.hakim@gmail.com

Top comments (0)