Bạn có thể đã được nghe về functional programming (FP) và nó thật là tuyệt vời để giảm số dòng code và giúp cho source code dễ đọc hơn. Nhưng ý nghĩa thực sự của functional programming là gì? và khác biệt so với OOP là gì?
Sau đây là một vài khái niệm cơ bản về Functional programming (FP) mà ai cũng phải nên biết
Tất cả các biến là final
// Hãy cùng xem hàm dùng để chào user mới. public String greet(List<String> names) { String greeting = "Welcome "; for(String name : names) { greeting += name + " "; } greeting += "!"; return greeting; }
Hàm bên trên là hoàn hảo để tạo một String chào mừng người mới
Nhưng đối với FP thì việc sử dụng như thế này là không hiệu quả. Chúng ta đang thay đổi state của greeting
, đây là điều không được khuyến khích trong FP. Nhưng nếu bạn thay đổi biến greeting
thành final thì sẽ gặp một lỗi xảy ra. Bởi vì code dang sử dụng nối String bằng dấu +
nghĩa là đang thay đổi state của biến.
Một cách đơn giản trong FP là nối tất cả các names
trong một dòng code như sau
public String greet(List<String> names) { final String reducedString = "Welcome " + names.get(0) + " " + names.get(1) + " " + ... + names.get(names.size()-1) + " "; return reducedString + "!"; }
Đoạn code trên vô cùng rườm rà và bẩn thỉu đúng không? Chúng ta có thể sử dụng function để làm cho nó đẹp hơn
Hãy cùng xem đoạn code bên dưới đây, sử dụng hàm FP để viết lại đoạn code trên
public String greet(List<String> names) { String greeting = names .stream() .map(name -> name + " ") .reduce("Welcome ", (acc, name) -> acc + name); return greeting + "!"; }
Một ưu điểm khi chúng ta sử dụng các biến final là giá trị của chúng luôn luôn giống nhau. Nó giúp chúng ta debug và test dễ hơn
Không sử dụng biến global, tránh side effect
Hãy cùng xem ví dụ sau đấy về biến global time
. Chúng ta sẽ sử dụng một hàm static, mà trả về thời gian hiện tại dạng String
public class Utils { private static Time time; public static String currTime() { return time.now().toString(); } }
Nếu gọi hàm currTime()
2 lần thì nó sẽ trả về 2 giá trị khác nhau bởi vì thời gian tại mỗi thời điểm là khác nhau. Mặc dù đầu vào như nhau (không truyền tham số), nhưng kết quả trả về của cả hai lần là khác nhau.
Điều này là không nên trong FP. Tất cả các phương thức nên dựa trên tham số của nó và không có gì khác. Để thực hiện việc này time
nên là một tham số của hàm currTime
public class Utils { public static String currTime(Time time) { return time.now().toString(); } }
Điều này có vẻ hơi kỳ lạ trong thế giới OO (Object-oriented), nhưng nó có một số lợi ích
Mặt khác nó giúp cho code dễ đọc hơn. Nếu bạn biết rằng phương thức dựa trên tham số truyền vào của nó, bạn sẽ không phải lo các biến Global làm ảnh hưởng đến phương thức. Mặt khác khi thực hiện test cũng dễ hơn! Khi chúng ta thực hiện test hàm currTime
bạn có thể thực hiện mock Time Object. Trong OO việc mock hàm static là vô cùng khó khăn.
Sử dụng hàm như tham số truyền vào
Trong FP, các hàm có thể được sử dụng như tham số của hàm khác. Chúng ta cùng viết hàm cộng thêm 1 vào tất cả các số trong List<Integer>
. Trong OO chúng ta sẽ xử lý như sau
public List<Integer> addOne(List<Integer> numbers) { List<Integer> plusOne = new LinkedList<>(); for(Integer number : numbers) { plusOne.add(number + 1); } return plusOne; }
Như trên thì chúng ta cần xử lý với 2 List. Điều này có thể gây khó hiểu và dẫn đến một lỗi xảy ra. Cũng có những thay đổi làm thay đổi numbers
. Điều này có thể gây lỗi trong các phần sau của chương trình
Trong FP, chúng ta có hàm map
, giúp duyệt tất cả các phần tử trong List. Điều này nghĩa rằng bạn muốn “map” number + 1
cho tất cả các phần tử trong List và lưu vào trong List mới
public List<Integer> addOne(List<Integer> numbers) { return numbers .stream() .map(number -> number + 1) .collect(Collectors.toList()); }
Điều này giúp làm giảm số lượng các biến và do đó là các nơi có thể gây ra lỗi. Ở đây chúng ta đã tạo một List mới và để numbers như nó là.