Những Design Pattern thường dùng trong Android
Trong quá trình thực hiện các dự án, ngoài việc làm thoả mãn yêu cầu của khách hàng, việc viết code một cách rõ ràng, sạch sẽ (clean code) là một điều vô cùng quan trọng. Có thể trong tương lai bạn sẽ phải phát triển một chức năng mà kế thừa lại những code cũ củabạn, hoặc chí ít là trong quá ...
Trong quá trình thực hiện các dự án, ngoài việc làm thoả mãn yêu cầu của khách hàng, việc viết code một cách rõ ràng, sạch sẽ (clean code) là một điều vô cùng quan trọng.
Có thể trong tương lai bạn sẽ phải phát triển một chức năng mà kế thừa lại những code cũ củabạn, hoặc chí ít là trong quá trình maintain phải đọc lại code thì việc viết code một cách “sạch sẽ” giúp quá những công việc sau này trở nên dễ dàng và ít phát sinh bug hơn.
Design patterns là các giải pháp đã được tối ưu hóa, được tái sử dụng cho các vấn đề lập trình mà chúng ta gặp phải hàng ngày. Nó là một khuôn mẫu đã được suy nghĩ, giải quyết trong tình huống cụ thể rồi.
Các vấn đề mà bạn gặp phải có thể bạn sẽ tự nghĩ ra cách giải quyết nhưng có thể nó chưa phải là tối ưu. Design Pattern giúp bạn giải quyết vấn đề một cách tối ưu nhất, cung cấp cho bạn các giải pháp trong lập trình OOP.
Nó không phải là ngôn ngữ cụ thể nào cả. Design patterns có thể thực hiện được ở phần lớn các ngôn ngữ lập trình. Ta thường gặp nó nhất trong lập trình OOP.
Trong bài viết này tôi xin giới thiệu một số design pattern thường sử dụng trong Android.
Phân loại design pattern
Có 3 nhóm chính sau:
Creational Pattern (nhóm khởi tạo) gồm: Builder, Dependency Injection, Singleton. Nó sẽ giúp bạn trong việc khởi tạo đối tượng.
Structural Pattern (nhóm cấu trúc) gồm: Adapter, Facade. Nó dùng để thiết lập, định nghĩa quan hệ giữa các đối tượng.
Behavioral Pattern (nhóm hành vi) gồm: Command, Observer, Model View Controller, Model View View Model. Nhóm này dùng trong thực hiện các hành vi của đối tượng.
Ta sẽ đi tìm hiểu cụ thể từng loại design pattern.
Creational Pattern
Builder
Builder design pattern phân chia việc khởi tạo(construction) tổng thể một đối tượng phức tạp ra làm nhiều phần nhỏ. Tức làthay vì tạo construction cho cả một đối tượng phức tạp thì nó sẽ tạoconstruction cho từng phần đặc trưng của đối tượng đó.
Trong Android, Builder design pattern xuất hiện khi sử dụngnhững đối tượng như AlertDialog.Builder.
new AlertDialog.Builder(this) .setTitle("Metaphorical Sandwich Dialog") .setMessage("Metaphorical message to please usethe spicy mustard.") .setNegativeButton("No thanks", newDialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { // "No thanks" button wasclicked } }) .setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int i) { // "OK" button was clicked } }) .show();
Cách dùngnày sẽ thực hiện từng bước một, mỗi bước giúp ta khởi tạo được một phần cụ thểcủa đối tượng AlertDialog.
Đoạn code trên sẽ tạo ra một dialog như hình dưới:
Dependency Injection
Dependency Injection giống như việc di chuyển vào một ngôinhà đã có đầy đủ đồ đạc nội thất. Ta không cần chuẩn bị mà chỉ việc dùng luônnhững gì cái sẵn có.
Nó cung cấp một đối tượng mà bạn yêu cầu khi bạn muốn khởi tạonhanh một đối tượng mới, các đối tượng mới không cần xây dựng hay tuỳ chỉnh nhữngthành phần con của nó.
Trong Android, đôi khi ta cần truy cập đến một đối tượng phứctạp từ nhiều điểm trong ứng dụng, ví dụ như network client, image loader, SharedPreferenceshay local storage. Ta có thể inject những đối tượng đó vào Activity hoặc Fragmentvà truy cập chúng ngay lập tức khi cần.
Dagger 2 là một open-source dependency injection frameworkthường được sử dụng nhất trong Android, được phát triển bởi sự cộng tác giữaGoogle và Square. Với Dagger 2, bạn có thể chú thích đơn giản một class vớ[email protected] annotation và dần bổ sung các method với @Provides annotation như ví dụdưới đây:
public class AppModule { @Provides SharedPreferences provideSharedPreferences(Application app) { return app.getSharedPreferences("prefs", Context.MODE_PRIVATE); } }
Các module bên trên sẽ tạo và cấu hình tất cả các đối tượngcần thiết. Nó sẽ giúp ích trong quá trình xây dựng các ứng dụng lớn, ta có thểtạo các module tuỳ theo từng chức năng.
Sau đó, ta tạo Componentinterface để liệt kê các module và class mà ta sẽ inject.
@Component(modules = AppModule.class) interface AppComponent { ... }
Cuối cùng, ta sử dụng @Injectannotation để request đến các dependency mỗi khi cần đến nó.
@Inject SharedPreferences sharedPreferences;
Như ví dụ trên, cách tiếp cận này trong Activity sẽ giúp đơngiản việc sử dụng SharedPreferences để lưu trữ cục bộ, mà Activity không cần biếtSharedPreference sẽ được khởi tạo và chuyển đến như thế nào.
Bạn có thể tìm hiểu thêm về Dagger theo link: https://google.github.io/dagger/
Singleton
Singleton Design Patter chỉ định rằng sẽ chỉ tồn tại mộtinstance duy nhất của một class nào đó trong toàn bộ chương trình. Nó được dùngkhi ta muốn mô hình hoá một đối tượng cụ thể trong thế giới thực và chỉ có một instance duy nhất.
public class ExampleSingleton { private static ExampleSingleton instance = null; private ExampleSingleton() { // customize if needed } public static ExampleSingleton getInstance() { if (instance == null) { instance = new ExampleSingleton(); } return instance; } }
Trong đoạn code trên, ở method getInstance sẽ đảm bảo rằng bạnchỉ khởi tạo đối tượng của class một lần duy nhất. Để truy cập đến singleton bạnchỉ cận gọi:
ExampleSingleton.getInstance();
Structural Patterns
Adapter
Adapter design pattern cho phép 2 lớp không tương thích làmviệc được cùng nhau bằng cách chuyển đổi giao diện (interface) của một lớp sanggiao diện khác phù hợp với yêu cầu của khách hàng.
Ví dụ khi ta muốn hiển thị một danh sách các đối tượng cho tương thích với view.
public class TribbleAdapter extends RecyclerView.Adapter { private List mTribbles; public TribbleAdapter(List tribbles) { this.mTribbles = tribbles; } @Override public TribbleViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext()); View view = inflater.inflate(R.layout.row_tribble, viewGroup, false); return new TribbleViewHolder(view); } @Override public void onBindViewHolder(TribbleViewHolder viewHolder, int i) { viewHolder.configure(mTribbles.get(i)); } @Override public int getItemCount() { return mTribbles.size(); } }
RecyclerView sẽ không biết Tribble là gì, vì nó không bao giờthấy cụ thể, đó là công việc của Adapter. Adapter sẽ có nhiệm vụ xử lý dữ liệutrong Tribble và gửi cấu hình chính xác đến ViewHolder.
Façade
Façade design pattern cung cấp các high level interface để các interface khác dễ sử dụng hơn.
Nếu Activity của bạn cần một danh sách Book, nó có thể yêu cầu một đối tượng duy nhất cho danh sách đó mà không hiểu các hoạt động bên trong như lưu trữ cục bộ, bộ nhớ cache và các API. Nó sẽ giữ cho các đoạn mã được rõ ràng, sạch sẽ mà không làm thay đổi hoạt động trong Activity. Sau này khi cần, ta có thể thay đổi các API mà không làm ảnh hưởng đến các tác động mà nó thựchiện.
Retrofit là một Open Source của Square sẽ giúp ta thực thi Façade design pattern.
Client chỉ đơn giản gọi listBook() method để nhận được danh sách các Book trong callback.
Điều này cho phép ta thực hiện các điều chỉnh bên dưới mà không ảnh hưởng đến client.
Ví dụ: bạn có thể chỉ định deserializer JSON tùy chỉnh:
RestAdapter restAdapter = new RestAdapter.Builder() .setConverter(new MyCustomGsonConverter(new Gson())) .setEndpoint("http://www.myexampleurl.com") .build(); return restAdapter.create(BooksApi.class);
Mỗi đốitượng càng ít biết về những gì diễn ra sau đó (ít phụ thuộc lẫn nhau) thì ta sẽcàng dễ trong việc sửa và điều chỉnh ứng dụng sau này.
Behavioral Patterns
Command
Commanddesign pattern giúp ta phát hành đi các request mà không cần biết người nhận.Ta đóng gói các yêu cầu như là các đối tượng và gửi đi. Việc thực hiện yêu cầu như thế nào sẽ ở một cơ chế khác và không quan tâm ở đây.
EventBus của Greenrobot là một thư viện opensource phổ biến hỗ trợ design pattern này.
Một Event là một đối tượng kiểu lệnh (command-style) được kích hoạt bởi user input, server data hay bất cứ thành phần nào trong ứng dụng. Ta có thể tạo các lớp controng đó mang dữ liệu:
public class MySpecificEvent { /* Additional fields if needed */ }
Sau khi định nghĩa sự kiện, ta sẽ có được một instance của EventBus và đăng ký một đối tượng như một subscriber.
eventBus.register(this);
Giờ đây đối tượng đã là một subscriber, ta sẽ nói cho nó biết loại của event và định nghĩa những hành động khi nhận được event đó.
public void onEvent(MySpecificEvent event) {/* Do something */};
Cuối cùng, tạo và đăng một trong các sự kiện đó dựa trên tiêu chí của mình.
eventBus.post(event);
Observer
Observer design pattern xác định một phụ thuộc một - nhiều giữa các đối tượng. Khi một đối tượng thay đổi trạng thái, các đối tượng phụ thuộc nó sẽ được thông báo và tự động cập nhật.
Đây là một mô hình linh hoạt, ta có thể sử dụng nó cho các hoạt động có thời gian không xác định, ví dụ như gọi API. Ta cũng có thể sử dụng nó để phản hồi với input từ người dùng.
RxAndroid Framework sẽ dẫn ta thực hiện pattern này xuyên suốt ứng dụng.
apiService.getData(someData) .observeOn(AndroidSchedulers.mainThread()) .subscribe (/* an Observer */);
Trong một thời gian ngắn, ta xác định các đối tượng Observable sẽ phát giá trị (thông báo). Các giá trị có thể phát cùng lúc, như một luồng liên tục, ở bất cứ tốc độ và thời gian nào.
Các đối tượng Subscriber sẽ lắng nghe những giá trị trên và phán ứng khi chúng đến. Ví dụ, bạn có thể mở đăng ký (subscribe) khi gọi API, lắng nghe response từ server và thực hiện hành vi tương ứng.
Model View Controller
MVC đề cập đến mô hình kiến trúc đang thống trị trên nhiều nền tảng. Nó đề cập đến sự phân chia các lớp thành 3 loại:
- Model: Các lớp đại diện cho dữ liệu. nó là những mô hình cho thế giới thật.
- View: Các lớp trực quan, đang đương trong việc hiển thị cho người dùng.
- Controller: Là trung gian của 2 loại trên. Nó cập nhật View, lấy input từ người dùng và thực hiện những thay đổi trong Model.
Model View View Model
Một kiến trúc khá giống với MVC. Hai thành phần Model và View giống với MVC. Thành phần ViewModel là trung gian giữa View và Model, nhưng hoạt động hơi khác với Controller. Thay vào đó nó sẽ cung cấp lệnh cho View và bind chúng với Model. Khi Model cập nhật, các View tương ứng cũng sẽ được cập nhật từ các ràng buộc dữ liệu với Model. Tương tự khi người dùng tương tác với View, các ràng buộc sẽ hoạt động theo chiều hướng ngược lại và sẽ cập nhật lên Model. Với mô hình này ta sẽ loại bỏ được khá nhiều code trung gian để kết nối giữa Model và View.
Trên đây là một số Design Pattern thường được sử dụng trong Android mà tôi biết. Nếu bạn có bất kỳ thắc mắc hoặc ý kiến gì vui lòng like và comment bên dưới để chúng ta cùng trao đổi.