OOP - Lập trình mô phỏng thế giới quan con người
Nếu bạn chưa biết về OOP, bạn có thể lên google và tìm kiếm “OOP là gì”, có rất nhiều bài viết giới thiệu về OOP và các nguyên lý của nó. Nhưng với mình, OOP chỉ đơn giản là sự mô phỏng lại một cách tự nhiên nhất các vấn đề từ thế giới thực mang vào trong lập trình. Vấn đề ví dụ: ...
Nếu bạn chưa biết về OOP, bạn có thể lên google và tìm kiếm “OOP là gì”, có rất nhiều bài viết giới thiệu về OOP và các nguyên lý của nó. Nhưng với mình, OOP chỉ đơn giản là sự mô phỏng lại một cách tự nhiên nhất các vấn đề từ thế giới thực mang vào trong lập trình.
Vấn đề ví dụ:
Mình xin bắt đầu bằng ví dụ implement bài toán các con vịt trong cuốn “Head first design patterns”. Ở đây tác giả đã trình bày các bước giúp implement source code một cách hợp lý, giúp tránh được việc dupplicate source code, maintaince code, tạo thêm các loại con vịt mới một cách dễ dàng và hạn chế việc phải sửa lại code cũ. Đây đúng là một bài viết hay, nó mang lại cho chúng ta nhiều ý tưởng mới mẻ và thú vị về thiết kế trong lập trình.
Vậy vấn đề ở đây là gì, chúng ta muốn cái gì. Cái chúng ta cần chỉ đơn giản là thiết kế các class để mô tả về 4 con vịt, chúng ta có:
- Vịt trời (MallardDuck), vịt đầu đỏ (RedheadDuck) cả 2 loại trên đều có thể bơi - swim, bay - fly và kêu - quack như thường.
- Với vịt nhựa (RubberDuck), chỉ có thể bơi, không thể bay và kêu.
- Vịt mồi (DecoyDuck) không thể làm gì cả.
Ngay khi đọc đề bài, trong đầu chúng ta đã có một mô hình liên hệ, và công việc của chúng ta chỉ là chuyển đổi các mô hình đó ra ngôn ngữ lập trình.
Thế giới quan của chúng ta:
Ở đây chúng ta có, đối tượng quan tâm đến tất cả đều là Vịt, theo bài toán, thì các con vịt đều cần có một cái tên - dùng cho method display(), ta có cần một chức năng giới thiệu tên vịt.
-> Chuyển qua lập trình, ta có một đối tượng Vịt, có một phương thức để lấy tên của nó. Và 4 loại vịt kia đều sẽ kế thừa từ đối tượng Vịt cơ bản này. (trong đầu: vịt nào mà chả là vịt)
Vịt có các hành động cần quan tâm là: bơi, bay và kêu. -> ta có ngay thêm một loại các Object mới được xác định nữa là hành động, bơi, bay và kêu.
Tại sao ta lại có thêm hành động ở đây, vì sau khi đọc đề bài xong, chúng ta sẽ tự động gộp 3 yêu cầu về bơi, bay và kêu lại thành một nhóm, có thể một số người sẽ nghĩ nó là kĩ năng, một số người nghĩ nó là khả năng. Nhưng tựu chung lại sẽ đều tự động gộp lại thành một loại gì đấy.
-> Chuyển qua lập trình, ta có đối tượng gốc được tổng quát ở đây là Hành động (Action), cả bơi, kêu và bay đều là hành động. Một con vịt có thể có ít hoặc nhiều hoặc không có hành động nào cả. -> Trong lớp vịt ta có danh sách quản lý các hành động -> List<Action>.
Trong lớp Vịt, ta cần method nữa là biểu diễn hành động, perform(Action).
Chuyển thành code:
Chúng ta có sơ đồ quan hệ các class được nhắc đến:
- Duck được kế thừa bởi MallarDuck, RedHeadDuck, RubberDuck, DecoyDuck.
- Action được kế thừa bởi FlyAction, SwimAction, MakeNoiseAction.
Code:
abstract class Action { String name; Action(String name) { this.name = name; } public String getName() { return name; } public abstract String perform(); @Override public boolean equals(Object obj) { return obj instanceof Action && ((Action) obj).getName().equals(name); } } class FlyAction extends Action { public FlyAction() { super("fly"); } @Override public String perform() { return "flying"; } } class SwimAction extends Action { public SwimAction() { super("swim"); } @Override public String perform() { return "swimming"; } } class makeNoiseAction extends Action { public makeNoiseAction() { super("make noise"); } @Override public String perform() { return "making noise"; } }
Các loại Vịt được define như sau:
class Duck { public Duck(String name) { this.name = name; } String name; List<Action> actions; public void perform(String action) { String result = actions.stream().filter(s -> s.getName().equals(action)) .map(s -> s.perform()).findAny() .orElse(name + " can't " + action); System.out.println(result); } public void introduce() { String result = "this is a " + name; System.out.println(result); } } class MallarDuck extends Duck { public MallarDuck() { super("Mallar Duck"); actions = Arrays.asList(new FlyAction(), new SwimAction(), new makeNoiseAction()); } } class RedHeadDuck extends Duck { public RedHeadDuck() { super("Red Head Duck"); actions = Arrays.asList(new FlyAction(), new SwimAction(), new makeNoiseAction()); } } class RubberDuck extends Duck { public RubberDuck() { super("Rubber Duck"); actions = Arrays.asList(new makeNoiseAction()); } } class DecoyDuck extends Duck { public DecoyDuck() { super("Decoy Duck"); actions = Arrays.asList(); } }
Simulator:
public class Simular { public static void main(String[] args) { Duck mallar = new MallarDuck(); mallar.introduce(); mallar.perform("fly"); System.out.println("--------------------------"); Duck decoy = new DecoyDuck(); decoy.introduce(); decoy.perform("fly"); } }
Kết quả:
this is a Mallar Duck flying -------------------------- this is a Decoy Duck Decoy Duck can't fly
Tham khảo với phương án tác giả đưa ra:
Chốt lại:
Qua ví dụ trên có thể thấy về cơ bản chính suy nghĩ, tư duy của chúng ta về thế giới quan, cách nhìn nhận các vấn đề bản thân nó đã là bậc thầy về OOP, nhiệm vụ của chúng ta chỉ đơn giản là làm theo chính những gì chúng ta suy nghĩ về bài tuấn, mô hình hóa các đối tượng. Có thể vấn đề ví dụ được đưa ra ở trên còn quá đơn giản, nhưng các bạn đừng lo lắng, bản thân chính suy nghĩ của chúng ta cũng phải đối mặt với các vấn đề mà bài toán đưa ra. Chừng nào chúng ta còn có thể tự mình suy nghĩ rành mạch và rõ ràng về bài toán thì chừng đấy lối suy nghĩ và làm việc theo hướng tự nhiên vẫn còn giúp chúng ta có được một thiết kế rõ ràng và dùng ổn. Còn nếu như chính bản thân chúng ta còn chưa suy nghĩ rõ ràng được thì cần phải xem lại bài toán một lần nữa. Thank you.