Prinsip Law of Demeter
Sun. Jun 16th, 2024 04:01 PM7 mins read
Prinsip Law of Demeter
Source: HuggingFace@stabilityai - Law of Demeter

Ini juga merupakan salah satu judul yang paling lama berada di daftar antrian tulisan gw karena selalu ketendang tiap mau nulis😅. Law of Demeter juga disebut dengan Principle of Least Knowledge, yaitu sebuah prinsip pada OOP yang meminimalkan akses terhadap dependensi suatu unit dengan internal unit lain. Sebuah unit hanya boleh berinteraksi dengan unit terdekatnya. Definisi “unit” di sini bisa berupa method, class, ataupun modul. Keywordnya adalah “Don’t talk to strangers”. Sebuah unit dilarang berinteraksi secara langsung dengan internal dari unit lain karena itu akan melanggar konsep enkapsulasi pada OOP.

Pelanggaran Law of Demeter

Contoh pelanggarannya seperti pada use case sistem pembukuan berikut. Kita menggunakan BookService sebagai business logic. BookService memiliki dependensi terhadap BookGateway yang juga memiliki dependensi dengan BookRepository dan AuthorRepository.

Entity Book
public class Book{
	private String id;
	private String title;
	private String authorId;

	public String getId(){
		return id;
	}

	public void setId(String id){
		this.id = id;
	}

	public String getTitle(){
		return title;
	}

	public void setTitle(String title){
		this.title = title;
	}

	public String getAuthorId(){
		return authorId;
	}

	public void setAuthorId(String authorId){
		this.authorId = authorId;
	}
}
Entity Author
public class Author{
	private String id;
	private String name;

	public String getId(){
		return id;
	}

	public void setId(String id){
		this.id = id;
	}

	public String getName(){
		return name;
	}

	public void setName(String name){
		this.name = name;
	}
}
Interface BookRepository
public interface BookRepository{
	Book findById(String id);
}
Interface AuthorRepository
public interface AuthorRepository{
	Author findById(String authorId);
}
Class BookGateway
public class BookGateway{
	private BookRepository bookRepository;
	private AuthorRepository authorRepository;

	public BookRepository getBookRepository(){
		return bookRepository;
	}

	public AuthorRepository getAuthorRepository(){
		return authorRepository;
	}

	public Book getBookById(String id){
		return bookRepository.findById(id);
	}

	public Author getAuthorById(String id){
		return authorRepository.findById(id);
	}

}
Class BookService
public class BookService{
	private final BookGateway bookGateway;

	public BookService(BookGateway bookGateway){
		this.bookGateway = bookGateway;
	}

	public String getAuthorNameByBook(){
		BookRepository bookRepository = bookGateway.getBookRepository();
		Book book = bookRepository.findById("xyz");
		AuthorRepository authorRepository = bookGateway.getAuthorRepository();
		Author author = authorRepository.findById(book.getAuthorId());
		return author.getName();
	}
}

Di method getAuthorNameByBook(), BookService mengakses objek BookRepository dan AuthorRepository lewat BookGateway, lalu keduanya memanggil findById() dari repository untuk mendapatkan data. Method dari BookRepository dan AuthorRepository ini adalah internal unit dari BookGateway sehingga code di atas menyalahi Law of Demeter. BookService hanya boleh berinteraksi dengan method dari objek terdekatnya, dalam hal ini adalah method BookGateway saja karena memiliki dependensi langsung dengan objek BookService. Sedangkan method dari BookRepository dan AuthorRepository tidak boleh diakses secara langsung di dalam BookService karena keduanya bukan objek terdekatnya, melainkan didapat dari method BookGateway. Inilah yang dilarang pada Law of Demeter.

Minimalkan akses terhadap internal unit

Sebisa mungkin kita harus meminimalkan akses terhadap internal unit. Pada kasus ini BookService hanya memiliki dependensi dengan BookGateway saja, bukan dengan BookRepository maupun AuthorRepository. Oleh karena itu, kita bisa menghapus getter terhadap repository di dalam class BookGateway agar internal unit dari BookGateway ga bisa diakses oleh objek lain yang memiliki dependensi dengan BookGateway

Class BookGateway
public class BookGateway{
	private BookRepository bookRepository;
	private AuthorRepository authorRepository;

	public Book getBookById(String id){
		return bookRepository.findById(id);
	}

	public Author getAuthorById(String id){
		return authorRepository.findById(id);
	}

}

Rules Law of Demeter

Untuk memenuhi Law of Demeter ada 5 rules yang harus dipenuhi. Suatu objek hanya bisa mengakses unit dari:

  • Objek itu sendiri;
  • Objek yang menjadi komposisi objek tersebut;
  • Parameter sebuah method;
  • Objek baru yang dibuat di dalam objek tersebut;
  • Static unit;

Kita langsung praktekkan saja contohnya😎.

Akses dari objek itu sendiri

Class BookService
public class BookService{
	public String getAuthorNameByBook(){
		Book book = getBook("xyz");
		Author author = getAuthor(book.getAuthorId());
		return author.getName();
	}

	private Book getBook(String id){
		Book book = new Book();
		book.setId(id);
		book.setAuthorId("abc");
		book.setTitle("dilan 1430");
		return book;
	}

	private Author getAuthor(String id){
		Author author = new Author();
		author.setId(id);
		author.setName("kojek");
		return author;
	}

}

Method getAuthorNameByBook() di class BookService mengakses method getBook() dan getAuthor. Ini tidak menyalahi Law of Demeter karena kedua method tersebut berada di class yang sama dengan getAuthorNameByBook().

Akses dari objek yang menjadi komposisi objek tersebut

Class BookService
public class BookService{
	private final BookGateway bookGateway;

	public BookService(BookGateway bookGateway){
		this.bookGateway = bookGateway;
	}

	public String getAuthorNameByBook(){
		Book book = bookGateway.getBookById("xyz");
		Author author = bookGateway.getAuthorById(book.getAuthorId());
		return author.getName();
	}
}

Pada code di atas, BookGateway menjadi komposisi dari BookService sehingga method getAuthorNameByBook() tidak menyalahi Law of Demeter. Method dari BookGateway tersebut meneruskan akses ke BookRepository dan AuthorRepository. Jadi, BookRepository dan AuthorRepository ga diakses langsung oleh BookService. Melainkan cukup sediakan akses dari BookGateway untuk mendapatkan data dari repository sehingga code di BookService lebih simple dan ga muter-muter penggunaannya. Pendekatan ini adalah yang paling dianjurkan pada OOP karena menggunakan Dependency Injection, lebih bersih, gampang dibikin unit test-nya, bisa diakses method lain di dalam class, dan maintainable😎.

Akses dari parameter sebuah method

Class BookService
public class BookService{

	public String getAuthorNameByBook(BookGateway bookGateway){
		Book book = bookGateway.getBookById("xyz");
		Author author = bookGateway.getAuthorById(book.getAuthorId());
		return author.getName();
	}
}

Method getAuthorNameByBook() di atas boleh mengakses method dari parameter bookGateway. Ini tidak menyalahi Law of Demeter karena BookService hanya mengakses method dari parameter, bukan lewat internal unit dari BookGateway yang di-ekspose.

Akses dari objek baru yang dibuat di dalam objek tersebut

Class BookService
public class BookService{

	public String getAuthorNameByBook(){
		BookGateway bookGateway = new BookGateway();
		Book book = bookGateway.getBookById("xyz");
		Author author = bookGateway.getAuthorById(book.getAuthorId());
		return author.getName();
	}
}

Method getAuthorNameByBook() seperti di atas juga tidak menyalahi Law of Demeter, karena diakses dengan cara membuat instance BookGateway di dalam method tersebut.

Akses dari static unit

Interface AuthorRepository
public interface AuthorRepository{
	static Author findAuthor(String authorId){
		Author author = new Author();
		author.setId(id);
		author.setName("kojek");
		return author;
	}
}
Class BookService
public class BookService{

	public String getAuthorNameByBook_static(){
		Book book = bookGateway.getBookById("xyz");
		Author author = AuthorRepository.findAuthor(book.getAuthorId());
		return author.getName();
	}
}

Kita membuat static method pada AuthorRepository dan mengakses data Author lewat static method tersebut. Secara aturan itu juga ga melanggar Law of Demeter karena diakses lewat static method yang bisa diakses secara global. Kita tidak mengakses Repository lewat internal unit BookGateway. Meskipun begitu pendekatan ini ga dianjurkan pada OOP karena static method sulit di-test dan tidak sesuai dengan konsep objek pada OOP🤦.

Catatan tambahan

Ada juga beberapa orang yang gw liat di internet salah memahami Law of Demeter. Diantaranya terkait hal-hal berikut:

Builder Design Pattern

Beberapa orang menganggap Builder Pattern melanggar Law of Demeter karena kita mengakses objek Builder yang dianggap sebagai internal unit sebuah objek. Sebenarnya itu tergantung gimana builder itu dibuat. Misalnya begini:

Class Genre
public final class Genre{
	private final int id;
	private final String name;

	Genre(int id, String name){
		this.id = id;
		this.name = name;
	}

	public static GenreBuilder builder(){
		return new GenreBuilder();
	}

	public int getId(){
		return this.id;
	}

	public String getName(){
		return this.name;
	}

	public static class GenreBuilder{
		private int id;
		private String name;

		GenreBuilder(){
		}

		public GenreBuilder id(int id){
			this.id = id;
			return this;
		}

		public GenreBuilder name(String name){
			this.name = name;
			return this;
		}

		public Genre build(){
			return new Genre(this.id, this.name);
		}

	}
}
Contoh penggunaan
public static void main(String[] args){
	Genre genre = Genre.builder()
			.id(2)
			.name("komedi")
			.build();
}

Objek genre dibuat menggunakan builder. Pada code di atas, kita menggunakan method builder() pada objek Genre untuk membuat objek builder yang baru. Ini tidak melanggar Law of Demeter karena sesuai salah satu rule: “suatu objek boleh mengakses unit dari objek baru yang dibuat di dalam objek tersebut”. Dikatakan melanggar apabila method class builder menjadi komposisi class Genre dan method builder() meng-ekspose komposisi tersebut sehingga menyalahi enkapsulasi.

Fluent API atau Method Chain

Fluent API atau Method Chain adalah pemanggilan method secara berantai karena method tersebut mengembalikan return value. Ada juga yang beranggapan semua method chaining melanggar Law of Demeter. Ini juga salah kaprah, karena poin dari Law of Demeter itu adalah terkait enkapsulasi yang disalahgunakan, bukan melarang Fluent API atau Method Chain. Selama return valuenya bukan internal unit suatu objek dan itu tidak menyalahi 5 rules di atas ya boleh-boleh saja. Contohnya pada Builder Design Pattern yang tentu saja bisa dieksekusi secara berantai.

Data Class

Untuk kasus di atas kita memang harus menghapus getter di class BookGateway karena akses untuk BookRepository dan AuthorRepository lewat class BookGateway emang ga diperlukan. Tapi bukan berarti kita harus menghapus semua accessor di semua class. Contohnya pada data class yang emang butuh di-ekspose datanya ke luar class. Kalaupun accessor tersebut juga memiliki nested object yang datanya dapat di-ekspose maka perlu pertimbangkan juga untuk menyediakan method untuk mengakses data dari nested object tersebut tanpa meng-ekspose internal unitnya. Contohnya seperti berikut:

Class Contact
public class Contact{
	String email;
	String phone;

	public String getEmail(){
		return email;
	}

	public void setEmail(String email){
		this.email = email;
	}

	public String getPhone(){
		return phone;
	}

	public void setPhone(String phone){
		this.phone = phone;
	}
}
Class Publisher
public class Publisher{
	String name;
	Contact contact;

	public String getName(){
		return name;
	}

	public void setName(String name){
		this.name = name;
	}

	public String getEmail(){
		return contact != null ? contact.getEmail() : null;
	}

	public String getPhone(){
		return contact != null ? contact.getPhone() : null;
	}

}

Kita bisa menyediakan method getEmail() dan getPhone() pada class Publisher tanpa meng-ekspose class Contact yang menjadi internal unit class Publisher. Jadi, saat mau mengakses email kita ga perlu panggil getContact() terlebih dahulu, melainkan langsung panggil getEmail() aja.

Verdict

Dengan Law of Demeter, kita dapat menyederhanakan code agar lebih mudah dibaca. Saat development kita hanya perlu eksekusi lewat method dependensi saja. Kita ga perlu repot-repot mengakses internal unit dari suatu dependensi. Melainkan cukup sediakan method yang diperlukan saja dari dependensi sehingga code lebih readable. Untuk data structure seperti Array, Collection, dan Map, ini dikecualikan dari Law of Demeter karena fungsi dari data structure tersebut memang untuk meng-ekspose data. Justru malah ribet kalau kita bikin semua implementasi method dari data structure tersebut🤯.

© 2024 · Ferry Suhandri