Lebih Dekat dengan Visitor Pattern
Tags: indonesia, go, designpatterns
Published: 2023-06-28 08:21:18

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/orfvbwez3w35gov9x9r2.png)



### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rjhtxs9x99ed0nar9su2.png)

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)

	}
}
```
**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)
}
```
**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)
}
```
**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)
}
```

**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
}
```



### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jk5top2t4w4wn7kvlt8u.png)

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)

}
```

### Epilog
Seluruh _sources code_ yang berkaitan dengan artikel ini dapat dilihat pada repository berikut https://github.com/Mhakimamransyah/practice-design-pattern.

**Bacaan Lanjutan**
- [Decorator Pattern](https://dev.to/mhakimamransyah/bersolek-ria-dengan-decorator-pattern-2j7h)

**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