author-pic

Ferry S

An ISTJ, Type 5, Engineer, Gamer, and Thriller-Movies-Lover
Prinsip Single Responsibility
Sunday Nov 1st, 2020 11:49 pm5 mins read
Java, Programming Principle, Tips & Tutorial
Prinsip Single Responsibility
Source: Dreamstime.com - Heavy Responsibility Stock Illustrations

Secara definisi:

Single-responsibility principle (SRP) is a computer-programming principle that states that every module, class or function in a computer program should have responsibility over a single part of that program's functionality, which it should encapsulate.

Robert C. Martin

Kalau di-bahasa-indonesia-kan kurang lebih seperti ini: Single Responsibility adalah prinsip yang setiap modul, kelas atau fungsinya hanya bertanggung jawab terhadap satu part fungsionalitas saja yang di-engkapsulasi. Secara definisi memang agak rancu makna responsibility yang dimaksud cukup luas, dan ga hanya gw yang bingung, beberapa pendapat juga bilang begitušŸ¤£. Tapi secara praktiknya yang gw pahami adalah scope dari responsibility-nya tergantung masalah yang akan di-solve pada class. Disini penamaan class cukup penting, itu yang menjadi acuan masalah yang akan di-solve. Tujuannya untuk mengurangi kompleksitas saat terjadi perubahan. Yang penting rancangan class yang dihasilkan saling keterkaitannya sebatas method dan field dalam class itu sendiri (cohesion). Makanya butuh analisa yang cukup mendalam dalam menerapkan prinsip ini. Tulisan tentang Single Responsibility juga sering gw singgung di postingan tentang Mutable Objects dan Global Variables.

Single Responsibility pada Class

Contohnya pada kasus penyimpanan data buku. Dalam sebuah kelas BookService terdapat sebuah API method untuk melakukan saveBook dengan algoritma seperti berikut:

  1. Check apakah Id dari Author tersebut sudah ada:
    • Jika sudah ada, maka ambil Id Author tersebut sebagai Id Author yang akan dipasangkan dengan Book nanti;
    • Jika belum ada, maka lakukan save Author terlebih dahulu dengan nama Author: "unknown" dan ambil Id Author tersebut sebagai Id Author yang akan dipasangkan dengan Book nanti;
  2. Construct objek Book beserta propertinya;
  3. Simpan buku;

Kira-kira code-nya seperti ini:

public class BookService{
	private final BookRepo bookRepo;
	private final AuthorRepo authorRepo;

	public BookService(BookRepo bookRepo, AuthorRepo authorRepo){
		this.bookRepo = bookRepo;
		this.authorRepo = authorRepo;
	}

	public void saveBook(int authorId, String bookName){
		boolean existedAuthor = authorRepo.checkAuthorId(authorId);
		if(!existedAuthor){
			saveAuthor("unknown", authorId);
		}
		Book book = new Book();
		book.setAuthorId(authorId);
		book.setName(bookName);
		bookRepo.save(book);
	}

	private Author saveAuthor(String authorName, int authorId){
		Author author = new Author();
		author.setName(authorName);
    author.setAuthorId(authorId);
		return authorRepo.save(author);
	}
}

Code di atas melanggar Single Responsibility Code karena terdapat dua tanggungjawab yang ditangani oleh BookService, yaitu melakukan penyimpanan buku dan melakukan pengecekan serta penyimpanan author. Sesuai penamaan Class-nya, seharusnya BookService hanya melakukan logika bisnis yang berhubungan dengan buku saja. Solusinya logika tentang pengecekan dan penyimpanan author dikerjakan oleh Class berbeda. BookService menjadi seperti berikut:

public class BookService{
	private final BookRepo bookRepo;
	private final AuthorService authorService;

	public BookService(BookRepo bookRepo, AuthorService authorService){
		this.bookRepo = bookRepo;
		this.authorService = authorService;
	}

	public void saveBook(int authorId, String bookName) throws Exception{
		authorService.saveIfNotExist(authorId);

		Book book = new Book();
		book.setAuthorId(authorId);
		book.setName(bookName);
		bookRepo.save(book);
	}
}

AuthorService menjadi seperti berikut:

public class AuthorService{

	private final AuthorRepo authorRepo;

	public AuthorService(AuthorRepo authorRepo){
		this.authorRepo = authorRepo;
	}

	public void saveIfNotExist(int authorId){
		boolean existedAuthor = authorRepo.checkAuthorId(authorId);
		if(!existedAuthor){
			Author author = new Author();
			author.setName("unknown");
      author.setAuthorId(authorId);
			authorRepo.save(author);
		}
	}
}

Dengan begitu, setiap ada perubahan logic pada Author hanya dilakukan pada kelas AuthorService. Kelas Book hanya tinggal menggunakan AuthorService secara composition tanpa harus tahu kompleksitas dibalik logic AuthorService tersebut.

Single Responsibility pada Method

Single Responsibility tidak hanya diperuntukkan pada Class saja. Method juga berlaku hal yang sama. Masih menggunakan contoh kasus yang sama, misalkan ada tambahan validasi sebagai berikut:

  1. Jika nama bukunya null, maka buku tidak jadi disimpan;
  2. Jika buku dengan authorId dan nama buku yang sama sudah ada, maka buku tidak jadi disimpan;

Code-nya jadi seperti berikut:

public class BookService{
	private final BookRepo bookRepo;
	private final AuthorService authorService;

	public BookService(BookRepo bookRepo, AuthorService authorService){
		this.bookRepo = bookRepo;
		this.authorService = authorService;
	}

	public void saveBook(int authorId, String bookName) throws Exception{
		if(bookName == null) throw new Exception("Book Name is null");
		Book bookByAuthorIdAndBookName = bookRepo.findByAuthorIdAndBookName(authorId, bookName);
		if(bookByAuthorIdAndBookName != null){
			throw new Exception("Duplicate Book");
		}
		authorService.saveIfNotExist(authorId);

		Book book = new Book();
		book.setAuthorId(authorId);
		book.setName(bookName);
		bookRepo.save(book);
	}
}

Code di atas melanggar Single Responsibility karena di dalam method saveBook terdapat dua responsibility, yaitu logic pengecekan buku dan penyimpanan buku. Oleh karena method pengecekan buku perlu dipisah menjadi seperti berikut:

public class BookService{
	private final BookRepo bookRepo;
	private final AuthorService authorService;

	public BookService(BookRepo bookRepo, AuthorService authorService){
		this.bookRepo = bookRepo;
		this.authorService = authorService;
	}

	public void saveBook(int authorId, String bookName) throws Exception{
		validateBook(authorId, bookName);
		authorService.saveIfNotExist(authorId);

		Book book = new Book();
		book.setAuthorId(authorId);
		book.setName(bookName);
		bookRepo.save(book);
	}

	private void validateBook(int authorId, String bookName) throws Exception{
		if(bookName == null) throw new Exception("Book Name is null");
		Book bookByAuthorIdAndBookName = bookRepo.findByAuthorIdAndBookName(authorId, bookName);
		if(bookByAuthorIdAndBookName != null){
			throw new Exception("Duplicate Book");
		}
	}
}

Dengan begini masing-masing method hanya akan bertanggungjawab pada masing-masing tugasnya. Ga ada lagi code campur sari di dalamnya.

Kesimpulan

Benefit dari Single Responsibility ini adalah code jadi lebih rapi. Class atau method hanya bertanggungjawab pada masing-masing tugasnya sesuai penamaannya. Masing-masing class dan method mempunyai tugas yang lebih spesifik. Jumlah line pada method yang menerapkan Single Responsibility akan berkurang dan cenderung membuat code lebih enak di-maintain dan dibaca. Walaupun Class dan Method jadi lebih banyak, tapi sebanding dengan kemudahan maintain-nya. Secara teori banyak Class dan banyak Method memang berpengaruh terhadap performance. Tapi perbandingannya hanya nanoseconds. Siapa yang peduli performance yang hanya cepat sekian nanoseconds? Lagian jaman sekarang resource seperti Memory dan Hard disk makin murah dengan ukuran yang gede. Lebih worth it maintain banyak Class atau Method dengan jumlah line yang sedikit daripada sedikit Class atau Method dengan jumlah line yang panjang banget.

Prinsip SOLID lainnya: