Design pattern - Proxy Pattern (P1)

Proxy Pattern – Kiểm soát truy cập đối tượng (P1)

Proxy Pattern - Kiểm soát truy cập đối tượng

Proxy Pattern – Kiểm soát truy cập

Proxy Pattern – Kiểm soát truy cập đối tượng

Bạn đã bao giờ chơi trò “Good cop, bad cop” (cảnh sát tốt, cảnh sát xấu) chưa? Bạn là một “cảnh sát tốt” và bạn cung cấp tất cả các “dịch vụ” của mình một cách thân thiện và tốt đẹp, nhưng bạn không muốn mọi người yêu cầu về các dịch vụ mà họ không được phép, vì vậy bạn có một “cảnh sát xấu” để kiểm soát các yêu cầu. Đó là những gì Proxy pattern làm: kiểm soát và quản lý truy cập. Như bạn sẽ thấy, có rất nhiều cách để Proxy thay thế cho các đối tượng mà chúng ủy quyền thực hiện. Proxy được biết là thứ chuyển toàn bộ các cuộc gọi phương thức qua Internet đến các đối tượng được ủy quyền; Chúng cũng được biết là kiên nhẫn ở một chỗ cho một số đối tượng “lười biếng” (lazy objects).

Cảnh báo: Chương này nặng lý thuyết về lập trình Network Java RMI (Remote Method Invocation), hãy đọc chương này khi bạn đã biết về Java RMI hoặc khi bạn đang tập trung hết sức. Có thể bỏ qua và xem đến chương tiếp theo, chúng không ảnh hưởng!

Note của người dịch: hãy đọc hết chương này và quay lại đây để hiểu tôi đang nói gì!!! 😉

“Good cop, bad cop” ( “Cảnh sát tốt/Cảnh sát xấu” hoặc “Chính diện/Phản diện” hoặc “Mặt xanh/Mặt đỏ“) là một kỹ thuật đàm phán hoặc thẩm vấn. “Cảnh sát xấu” có lập trường mạnh mẽ, tiêu cực đối với đối tượng, đưa ra những lời buộc tội trắng trợn, bình luận xúc phạm, đe dọa và nói chung tạo ra ác cảm giữa tội phạm và chính họ. Điều này tạo tiền đề cho “ Cảnh sát tốt” hành động thông cảm, tỏ ra ủng hộ và thấu hiểu và nói chung thể hiện sự đồng cảm với đối tượng. Đối tượng có thể cảm thấy họ có thể hợp tác với “cảnh sát tốt” hoặc không tin tưởng hoặc sợ cảnh sát xấu. Sau đó, họ có thể tìm kiếm sự bảo vệ và tin tưởng cảnh sát tốt và cung cấp thông tin mà các thẩm vấn viên đang tìm kiếm.

CEO của Might Gumball

Nghe thật dễ dàng. Nếu bạn còn nhớ, chúng tôi đã có các phương thức trong code Gumball Machine để lấy số lượng kẹo cao su (getCount()) và lấy trạng thái hiện tại của máy (getState()).

Tất cả những gì chúng ta cần làm là tạo một báo cáo có thể được in ra và gửi lại cho CEO. Hmmm, có lẽ chúng ta cũng nên thêm một trường “vị trí đặt máy” (location) vào mỗi Gumball machine; Bằng cách đó, CEO có thể biết những cái máy bắn kẹo hiện đang ở đâu.

Hãy ngồi vào và viết đoạn code này. Chúng tôi sẽ gây ấn tượng với CEO bằng một bước ngoặc rất lớn.

Note của người dịch: Trước khi đọc tiếp, hãy đảm bảo bạn biết điều này:
– MonitorMachine – là Client (Máy khách) – Là màn hình giám sát của CEO.
– GumballMachine – là Server (Máy chủ) – Là máy bắn kẹo cao su, ở một nơi xa CEO.

Coding cho Monitor (Máy giám sát của CEO)

Hãy bắt đầu bằng cách thêm hỗ trợ cho lớp GumballMachine để nó có thể xử lý các location:

Class GumballMachine

Bây giờ, hãy để cho tạo một lớp khác, GumballMonitor, lấy ra vị trí của máy, số lượng kẹo cao su, trạng thái máy hiện tại và in chúng trong một báo cáo:

Class GumballMonitor

Testing cho Monitor

Chúng tôi thực hiện điều đó rất rất nhanh. Giám đốc điều hành sẽ vui mừng và ngạc nhiên trước các kỹ năng lập trình của chúng tôi.

Bây giờ chúng ta chỉ cần khởi tạo GumballMonitor (máy giám sát) và cung cấp cho nó một đối tượng GumballMachine (máy bắn kẹo) để theo dõi:

Class GumballMachineTestDrive

Joe: Một cái gì xa?

Frank: Proxy từ xa. Hãy suy nghĩ về nó: chúng ta đã có code cho màn hình giám sát (monitor) được viết, phải không? Chúng ta cung cấp cho GumballMonitor một tham chiếu tới một cái máy bắn kẹo và nó sẽ cung cấp cho chúng ta một báo cáo. Vấn đề là màn hình giám sát với máy bắn kẹo cao su chạy trong cùng một JVM nhưng CEO muốn ngồi vào bàn của mình và giám sát máy từ xa (màn hình và máy bắn kẹo 2 nơi khác nhau)! Vậy điều gì sẽ xảy ra nếu chúng ta không còn ở lớp GumballMonitor, nhưng chúng ta đã đưa cho nó cho một đối tượng từ xa?

Joe: Tôi không chắc chắn chúng ta có được nó.

Jim: Tôi cũng vậy.

Frank: Hãy bắt đầu ngay từ đầu … một proxy là một thay thế cho một đối tượng thực sự. Trong trường hợp này, proxy hoạt động giống như nó là một đối tượng GumballMachine, nhưng đằng sau đó, nó đang liên lạc qua network để nói chuyện với GumballMachine thực sự, từ xa.

Jim: Vì vậy, bạn nói rằng chúng ta giữ nguyên code của mình và chúng ta sẽ cung cấp cho Monitor một tham chiếu đến phiên bản proxy của GumballMachine

Joe: Và proxy này giả vờ nó là đối tượng thật, nhưng nó thực sự chỉ là giao tiếp qua mạng với đối tượng thật.

Frank: Vâng, nó có khá nhiều việc.

Joe: Nghe có vẻ như nói dễ hơn làm.

Frank: Có lẽ, nhưng tôi không nghĩ rằng nó sẽ tệ đến thế. Chúng tôi phải đảm bảo rằng máy bắn kẹo cao su có thể hoạt động như một dịch vụ và chấp nhận các yêu cầu qua mạng; chúng ta cũng cần cung cấp cho MonitorMachine của mình một cách để có được một tham chiếu đến một đối tượng proxy, nhưng chúng ta đã có một số công cụ tuyệt vời được tích hợp sẵn trong Java để giúp chúng ta. Trước tiên, hãy nói thêm một chút về remote proxy…

Vai trò của “remote proxy”

Một “remote proxy” hoạt động như một đối tượng đại diện (ở cục bộ) (local representative) cho một đối tượng từ xa (remote object). 

Cái gì là “remote object”? Nó là một một đối tượng tồn tại trong vùng nhớ heap của một Java Virtual Machine khác (hay nói chung hơn là một remote object đang chạy trong một không gian địa chỉ khác).

Cái gì là một “local representative”? Nó là một đối tượng mà bạn có thể gọi các phương thức ở cục bộ và chuyển tiếp chúng đến remote object.

Đối tượng khách hàng (MornitorMachine) của bạn hoạt động giống như nó thực hiện các cuộc gọi phương thức từ xa. Nhưng những gì nó thực sự làm là gọi các phương thức trên một đối tượng proxy cục bộ xử lý tất cả các chi tiết cấp thấp của giao tiếp mạng.

Sức mạnh bộ não

Trước khi đi xa hơn, hãy suy nghĩ về cách bạn thiết kế một hệ thống để cho phép gọi phương thức từ xa. Làm thế nào bạn có thể làm cho nó dễ dàng với nhà phát triển để có thể viết càng ít code càng tốt? Làm thế nào bạn sẽ làm cho các cuộc gọi từ xa trông liền mạch?

Sức mạnh bộ não 2

Có nên thực hiện các cuộc gọi từ xa hoàn toàn minh bạch? Đó có phải là một ý tưởng tốt? Điều gì có thể là một vấn đề với cách tiếp cận đó?

Thêm remote proxy vào Gumball Machine monitoring code

Trên giấy tờ, điều này có vẻ tốt, nhưng làm thế nào để chúng ta tạo một proxy biết cách gọi một phương thức trên một đối tượng tồn tại trong một JVM khác?

Hừm. Chà, bạn không thể có được một tham chiếu cho một cái gì đó trên một vùng nhớ heap khác, phải không? Nói cách khác, bạn không thể làm điều này:

Bất cứ biến d nào được tham chiếu đều phải nằm trong cùng một vùng heap giống như code đang chạy câu lệnh. Vậy làm thế nào để chúng ta tiếp cận điều này? Chà, đó là nơi mà Remote Method Invocation xuất hiện… RMI cho chúng ta một cách để tìm các đối tượng trong một JVM từ xa và cho phép chúng ta gọi các phương thức của chúng.

Bạn có thể đã gặp RMI trong cuốn Head First Java; nếu chưa, chúng ta sẽ đi đường vòng nhẹ và tăng tốc độ trên RMI trước khi thêm proxy helper vào code của Gumball Machine.

Vì vậy, đây là những gì chúng ta sẽ làm:

  1. Đầu tiên, chúng tôi sẽ xem qua RMI Detour và kiểm tra RMI. Ngay cả khi bạn đã quen thuộc với RMI, bạn có thể muốn nhìn lại một chút.
  2. Sau đó, chúng tôi sẽ lấy GumballMachine của chúng tôi và biến nó thành một dịch vụ từ xa cung cấp một tập hợp các phương thức có thể được gọi từ xa.
  3. Sau đó, chúng tôi sẽ tạo một proxy có thể giao tiếp với GumballMachine từ xa, một lần nữa sử dụng RMI và đặt hệ thống giám sát MornitorMachine lại với nhau để CEO có thể giám sát bất kỳ số lượng máy từ xa nào.

Remote methods 101  

Hãy nói rằng chúng ta muốn thiết kế một hệ thống cho phép chúng ta gọi một đối tượng cục bộ (local object) chuyển tiếp từng yêu cầu đến một đối tượng ở xa (remote object). Chúng ta sẽ thiết kế nó như thế nào? Chúng ta cần một vài đối tượng trợ giúp (helper objects) thực sự giao tiếp với đối tượng ở xa.

Các trình trợ giúp (helper) làm cho Client có thể hành động như thể nó gọi một phương thức ở xa, trên một đối tượng cục bộ (thực tế là như vậy). Client gọi một phương thức trên client helper, như thể client helper là dịch vụ thực. Client helper sau đó sẽ đảm nhiệm việc chuyển tiếp yêu cầu đó giúp chúng ta.

Nói cách khác, đối tượng client nghĩ rằng nó gọi một phương thức trên dịch vụ từ xa, bởi vì client helper đang giả vờ là đối tượng dịch vụ. Giả vờ trở thành thứ có phương thức mà client muốn gọi.

Nhưng client helper không thực sự là dịch vụ từ xa. Mặc dù client helper hoạt động như vậy (vì nó có cùng phương thức mà dịch vụ từ xa), nhưng client helper không có bất kỳ logic thực tế nào mà Client đang mong đợi. Thay vào đó, client helper liên lạc với máy chủ, chuyển thông tin về lệnh gọi phương thức (ví dụ: tên của phương thức, đối số, v.v.) và chờ return từ máy chủ.

Về phía máy chủ, trình trợ giúp dịch vụ (service helper) nhận yêu cầu từ client helper (thông qua kết nối Socket), giải nén thông tin về cuộc gọi và sau đó gọi phương thức thực trên đối tượng dịch vụ thực. Vì vậy, đối tượng dịch vụ, cuộc gọi là cục bộ. Nó đến từ người trợ giúp dịch vụ (service helper), không phải khách hàng ở xa (remote client). (Note: Chưa hiểu thì có thể xem hình dưới và đọc lại đoạn này.)

Service helper nhận giá trị trả về từ service, đóng gói và gửi lại (qua Socket output stream) cho client helper. Client helper giải nén thông tin và trả về giá trị cho client object.

Cách gọi phương thức xảy ra

  1. Client object gọi tới phương thức doBigThing() trên client helper object.

2. Client helper gói thông tin về cuộc gọi (đối số, tên phương thức, v.v.) và gửi nó qua mạng cho Service helper.

3. Service helper giải nén thông tin từ Client helper, tìm ra phương thức nào để gọi (và trên đối tượng nào) và gọi phương thức thực (real method) trên đối tượng real service.

4. Phương thức được gọi trên service object, trả về một số kết quả cho service helper.

5. Service helper đóng gói thông tin trả về từ cuộc gọi và gửi lại qua mạng cho client helper.

6. Client helper giải nén các giá trị được trả về và trả chúng về client object. Đối với client object (MonitorMachine), tất cả đều trong suốt (transparent – ý đoạn này là MonitorMachine sẽ không cần biết sự hiện diện của đối tượng ở xa, hay đối tượng ở xa sẽ trong suốt trước MonitorMachine).

Java RMI, bức tranh lớn

Được rồi, bạn đã khái niệm về cách thức hoạt động của các phương thức từ xa; bây giờ bạn chỉ cần hiểu cách sử dụng RMI để cho phép gọi phương thức từ xa.

Những gì RMI làm cho bạn là xây dựng các đối tượng client helper và service helper, ngay lập tức để tạo một đối tượng client helper với các phương thức tương tự như remote service (service helper). Điều thú vị về RMI là bạn không phải tự viết bất kỳ đoạn code network hoặc code I/O nào. Với client của bạn, bạn gọi các phương thức từ xa (tức là, các phương thức mà Real Service có) giống như các cuộc gọi phương thức thông thường trên các đối tượng đang chạy trong JVM của Client.

RMI cũng cung cấp tất cả các cơ sở hạ tầng trong runtime để làm cho tất cả công việc, bao gồm cả dịch vụ tra cứu mà khách hàng có thể sử dụng để tìm và truy cập các đối tượng từ xa.

Có một sự khác biệt giữa các cuộc gọi RMI và các cuộc gọi phương thức cục bộ. Hãy nhớ rằng mặc dù với máy khách, có vẻ như cuộc gọi phương thức là cục bộ, client helper sẽ gửi cuộc gọi phương thức qua mạng. Vì vậy, có mạng và I/O. Và chúng ta biết gì về các phương thức kết nối mạng và I/O?

Chúng rất mạo hiểm! Chúng có thể thất bại! Và vì vậy, chúng ném exception khắp nơi. Kết quả là, client phải chấp nhận rủi ro. Chúng ta sẽ xem làm thế nào trong một vài trang tiếp.

Tên gọi trong RMI: trong RMI, client helper là một “stub” và service helper là một “skeleton”.

Bây giờ, hãy xem qua tất cả các bước cần thiết để biến một đối tượng thành một service có thể chấp nhận các cuộc gọi từ xa và cả các bước cần thiết để cho phép Client thực hiện các cuộc gọi từ xa.

Bạn có thể muốn đảm bảo dây an toàn của bạn đã được thắt chặt; có rất nhiều bước và một vài va chạm và đường cong – nhưng không có gì phải quá lo lắng.

Tạo Remote service

Đây là tổng quan về các bước để tạo dịch vụ từ xa (remote service). Nói cách khác, các bước cần thiết để lấy một đối tượng bình thường và siêu nạp (supercharge) nó để nó có thể được gọi bởi một remote client. Chúng ta sẽ làm điều này sau đó với GumballMachine. Bây giờ, hãy xem tiếp và sau đó chúng tôi sẽ giải thích chi tiết.

BƯỚC MỘT: TẠO MỘT REMOTE INTERFACE  

Remote interface xác định các phương thức mà client có thể gọi từ xa. Nó có những gì mà client sẽ sử dụng làm loại lớp cho service của bạn. Cả Stub và service thực tế sẽ implement interface này!

BƯỚC HAI: TẠO MỘT REMOTE IMPLEMENTATION

Đây là lớp thực sự làm việc. Nó có triển khai thực sự của các remote method được định nghĩa trong remote interface. Nó yêu cầu đối tượng mà Client muốn gọi các phương thức trên (ví dụ: GumballMachine của chúng ta!).

BƯỚC BA: TẠO “STUB” VÀ “SKELETON” SỬ DỤNG RMIC  

Đây là client và server “helpers”. Bạn không cần phải tạo các lớp này hoặc xem source code tạo ra chúng. Tất cả đều xử lý tự động khi bạn chạy công cụ rmic đi kèm với bộ công cụ phát triển Java (JDK) của bạn.

BƯỚC BỐN: RUN RMIREGISTRY (RMIREGISTRY)  

Các rmiregology giống như các trang trắng (white pages) của một danh bạ điện thoại. Nó ở nơi Client đi lấy proxy (client stub/helper object).

BƯỚC NĂM: BẮT ĐẦU REMOTE SERVICE  

Bạn phải đưa đối tượng dịch vụ lên và chạy. Lớp triển khai dịch vụ của bạn khởi tạo một thể hiện của dịch vụ và đăng ký nó với RMI registry. Đăng ký nó làm cho dịch vụ có sẵn cho khách hàng.

Bước một: Tạo một Remote Interface  

1. Extend java.rmi.Remote  

Remote là một ‘marker’ interface, có nghĩa là nó không có phương thức. Tuy nhiên, nó có ý nghĩa đặc biệt đối với RMI, vì vậy bạn phải tuân theo quy tắc này. Lưu ý rằng chúng tôi nói ‘extends’ ở đây. Một giao diện được phép mở rộng giao diện khác.

2. Mô tả tất cả phương thức ném ra một RemoteException

Remote interface là giao diện mà Client sử dụng như là một loại cho service. Nói cách khác, Client gọi các phương thức trên một cái gì đó – thứ sẽ implement remote interface. Tất nhiên, cái gì đó là stub, và vì stub đang kết nối mạng và I/O, tất cả các loại “Điều xấu” đều có thể xảy ra. Client phải thừa nhận rủi ro bằng cách xử lý hoặc khai báo các exception từ xa. Nếu các phương thức trong Interface khai báo các exception, bất kỳ phương thức gọi code nào trên tham chiếu của loại đó (loại interface) cũng phải xử lý hoặc khai báo các exception.

Mỗi cuộc gọi phương thức từ xa được coi là “rủi ro”. Khai báo RemoteException trên mọi phương thức buộc client phải chú ý và thừa nhận rằng mọi thứ có thể không hoạt động.

3. Hãy chắc chắn các đối số và giá trị trả về là primitive (kiểu nguyên thủy: int, float,double…) hoặc Serializable

Các đối số và giá trị trả về của một remote method phải là hoặc primitive hoặc Serializable. Hãy suy nghĩ về nó. Bất kỳ đối số nào đối với một remote method đều phải được đóng gói và vận chuyển qua mạng và điều đó được thực hiện thông qua Serialization. Cũng tương tự với giá trị trả về. Nếu bạn sử dụng primitives, Strings và phần lớn các loại trong API (bao gồm cả arrays và collection), bạn sẽ vẫn ổn.

Nếu bạn đang chuyển qua các kiểu của riêng mình, chỉ cần chắc chắn rằng các lớp của bạn implement Serializable.

(Hãy xem Head First Java nếu bạn cần nhớ lại kiến thức của mình về serializable)

Giá trị trả về này sẽ được chuyển qua mạng từ server trở lại client, vì vậy nó cũng phải được Serializable. Đó là cách thức args và return values được đóng gói và gửi đi.

Bước hai: Tạo một Remote Implementation

1. Implement the Remote interface  

Dịch vụ của bạn phải implement remote interface, một trong những phương thức mà client của bạn sẽ gọi.

2. Extend UnicastRemoteObject  

Để làm việc như một đối tượng remote service, đối tượng của bạn cần một số chức năng liên quan đến ‘being remote’. Cách đơn giản nhất là extend  UnicastRemoteObject (từ gói java.rmi.server) và để lớp đó (superclass của bạn) thực hiện công việc cho bạn.

3. Viết một hàm khởi tạo không có đối số mô tả RemoteException

Superclass mới của bạn, UnicastRemoteObject, có một vấn đề nhỏ, constructor của nó ném ra RemoteException. Cách duy nhất để giải quyết vấn đề này là khai báo constructor cho việc triển khai từ xa của bạn, để bạn có một nơi để khai báo RemoteException. Hãy nhớ rằng, khi một lớp được khởi tạo, superclass constructor của nó luôn được gọi. Nếu hàm tạo của lớp cha của bạn quăng ra một ngoại lệ, bạn không có lựa chọn nào khác ngoài việc khai báo rằng constructor của bạn cũng ném một ngoại lệ.

Bạn không phải đặt bất cứ thứ gì vào hàm khởi tạo. Bạn chỉ cần một cách để khai báo rằng hàm constructor của siêu lớp của bạn ném một ngoại lệ.

4. Đăng ký dịch vụ với RMI registry

Bây giờ bạn đã có một dịch vụ từ xa, bạn phải cung cấp nó cho các remote client. Bạn làm điều này bằng cách khởi tạo nó và đưa nó vào RMI registry (phải thực hiện điều này hoặc code của bạn bị lỗi). Khi bạn đăng ký đối tượng implementation, hệ thống RMI thực sự đặt stub trong registry, vì đó là những gì client thực sự cần. Đăng ký dịch vụ của bạn bằng phương thức static rebind() của lớp java.rmi.Naming.

Đặt tên cho dịch vụ của bạn (thứ mà client có thể sử dụng để tra cứu nó trong registry) và đăng ký nó với RMI registry. Khi bạn liên kết (bind) đối tượng dịch vụ (service object), RMI hoán đổi dịch vụ cho stub và đặt stub trong registry.

Bước ba: Tạo “stub” và “skeleton” sử dụng rmic

1. Chạy rmic trên lớp remote implementation class (không phải remote interface)

Công cụ rmic, đi kèm với bộ công cụ phát triển phần mềm Java (JDK), lấy service implementation và tạo hai lớp mới, stub và skeleton. Nó sử dụng quy ước đặt tên là tên của việc triển khai từ xa của bạn + “_Stub” hoặc “_Skel” được thêm vào cuối. Có các tùy chọn khác với rmic, bao gồm không tạo skeletons, xem source code của các lớp này trông như thế nào và thậm chí sử dụng IIOP làm giao thức (protocol). Cách chúng tôi đang làm ở đây là cách mà bạn sẽ thường làm. Các lớp sẽ đặt trong thư mục hiện tại (tức là bạn sẽ phải cd tới các lớp). Hãy nhớ rằng, rmic phải nhìn thấy lớp triển khai của bạn, vì vậy bạn có thể chạy rmic từ thư mục nơi triển khai từ xa của bạn. (Chúng tôi cố tình không sử dụng các packages ở đây, để làm cho nó đơn giản hơn. Trong thế giới thực, bạn sẽ cần tính đến các cấu trúc thư mục packages và tên đủ điều kiện).

Bước bốn: run rmiregistry (rmiregistry)  

1. Mở terminal và bắt đầu rmiregistry.  

Hãy chắc chắn rằng bạn bắt đầu nó từ một thư mục có quyền truy cập vào các lớp của bạn. Cách đơn giản nhất là khởi động nó từ thư mục ‘classes’.

Bước năm: Bắt đầu service  

1. Đưa lên một terminal khác và bắt đầu service của bạn

Điều này có thể là từ một phương thức main() trong remote implementation class của bạn hoặc từ một lớp launcher riêng biệt.

Trong ví dụ đơn giản này, chúng tôi đặt code khởi động (starter code) trong lớp triển khai, trong một phương thức main để khởi tạo đối tượng và đăng ký nó với RMI registry.

Hoàn thành code cho server side  

Remote interface:  

Remote service (implementation):  

Làm thế nào để client có được stub object?

Client phải lấy stub object (proxy của chúng ta), vì đó là điều mà client sẽ gọi các phương thức trên. Và đó là nơi mà RMI registry xuất hiện. Client thực hiện tra cứu, giống như đi đến các trang trắng của một danh bạ điện thoại, và về cơ bản, Đây là tra cứu một cái tên, và stub sẽ gắn liền với cái tên đó.

Chúng ta hãy xem code chúng ta cần lookup và truy xuất một đối tượng stub.

CÁCH NÓ LÀM VIỆC

1. Client thực hiện tra cứu trên sổ RMI registry  

Naming.lookup("rmi://127.0.0.1/RemoteHello");

2. RMI registry return một stub object  

(như giá trị trả về của phương thức lookup) và RMI tự động deserializes stub. Bạn PHẢI có lớp stub (rmic tạo cho bạn) trên client ngược lại stub sẽ không được deserialized.

3. Client gọi một phương thức trên stub, như thể stub đó là real service.

HOÀN THÀNH CLIENT CODE  

Làm thế nào để client có được stub class?

Bây giờ chúng ta đến với một câu hỏi thú vị. Làm thế nào, hay bằng cách nào đó, client phải có stub class (thứ bạn đã tạo trước đó bằng cách sử dụng rmic) tại thời điểm client thực hiện lookup, nếu không thì stub sẽ không được deserialized trên client và mọi thứ tiếp theo sẽ không được thực hiện. Client cũng cần các lớp cho bất kỳ serialized object nào được trả về bởi các phương thức gọi đến remote object. Trong một hệ thống đơn giản, bạn có thể chỉ cần cung cấp các lớp này cho client.

Có một cách hay hơn nhiều, mặc dù nó vượt quá phạm vi của cuốn sách này. Nhưng trong trường hợp bạn có hứng thú, cách hay hơn được gọi là “dynamic class downloading”. Với việc tải xuống động, các đối tượng được serialized (như stub) được đóng dấu với một URL thông báo cho hệ thống RMI trên client để tìm tệp lớp cho đối tượng đó. Sau đó, trong quá trình deserializing một đối tượng, nếu RMI không thể tìm thấy lớp cục bộ, nó sẽ sử dụng URL đó để thực hiện HTTP Get để lấy class file. Vì vậy, bạn có thể cần một máy chủ web đơn giản để phục vụ các class file và bạn cũng cần thay đổi một số tham số bảo mật trên client. Có một vài vấn đề phức tạp khác với “dynamic class downloading”, nhưng đó là tổng quan.

Đối với stub object cụ thể, có một cách khác để client có thể nhận được lớp. Điều này chỉ có trong Java 5. Chúng tôi sẽ nói ngắn gọn về điều này ở gần cuối chương.

Tiếp tục xem

Ba điều hàng đầu mà các lập trình viên làm sai với RMI là:

1) Quên start rmiregistry trước khi start remote service (khi service được đăng ký Naming.rebind(), rmiregistry phải được chạy!)

2) Quên tạo các đối số và trả về các kiểu serializable (bạn sẽ không biết cho đến khi runtime; trình biên dịch sẽ không phát hiện.)

3) Quên cung cấp stub class cho client.

Quay lại với GumballMachine remote proxy của chúng ta

Được rồi, bây giờ bạn đã có kiến thức cơ bản về RMI, bạn đã có các công cụ cần thiết để thực hiện GumballMachine remote proxy. Hãy xem GumballMachine phù hợp với framework này như thế nào:

Chuẩn bị GumballMachine trở thành một dịch vụ từ xa

Bước đầu tiên trong việc chuyển đổi code của chúng tôi để sử dụng Remote proxy là cho phép GumballMachine phục vụ các yêu cầu từ xa từ client. Nói cách khác, chúng tôi sẽ biến nó thành một dịch vụ. Để làm điều đó, chúng ta cần phải:

1) Tạo giao diện từ xa cho GumballMachine. Điều này sẽ cung cấp một tập hợp các phương thức có thể được gọi từ xa.

2) Đảm bảo tất cả các kiểu trả về trong giao diện được serializable.

3) Thực hiện giao diện trong một lớp cụ thể. Chúng tôi sẽ bắt đầu với remote interface:

Chúng ta có một kiểu trả về không Serializable: lớp State. Hãy sửa nó lại…

Trên thực tế, chúng tôi chưa hoàn thành được với serializable; chúng tôi có một vấn đề với State. Bạn có thể nhớ, mỗi đối tượng State có một tham chiếu đến Gumball machine để nó có thể gọi phương thức của Gumball machine và thay đổi trạng thái của nó. Chúng tôi không muốn cho toàn bộ gumball machine được serialized và chuyển giao với State object. Có một cách dễ dàng để khắc phục điều này:

Chúng tôi đã triển khai GumballMachine, nhưng chúng tôi cần đảm bảo rằng nó có thể hoạt động như một dịch vụ và xử lý các yêu cầu đến từ mạng. Để làm điều đó, chúng tôi phải đảm bảo GumballMachine đang làm mọi thứ cần thiết để implements giao diện GumballMachineRemote.

Như bạn đã thấy trong phần “RMI detour”, điều này khá đơn giản, tất cả những gì chúng ta cần làm là thêm một vài thứ…

Đăng ký với RMI registry…

Điều đó sẽ hoàn thành gumball machine service. Bây giờ chúng tôi chỉ cần kích hoạt nó để nó có thể nhận được yêu cầu. Trước tiên, chúng tôi cần đảm bảo rằng chúng tôi đăng ký nó với RMI registry để client có thể xác định vị trí của nó.

Chúng tôi sẽ thêm một ít code để xử lý việc này:

Hãy xem kết quả:

Bây giờ là GumballMonitor client  

Nhớ GumballMonitor không? Chúng tôi muốn sử dụng lại nó mà không cần viết lại để hoạt động qua mạng. Chúng tôi sẽ làm điều đó, nhưng chúng tôi cần phải thực hiện một vài thay đổi.

Viết Monitor test drive

Bây giờ chúng tôi đã có tất cả các phần chúng tôi cần. Chúng ta chỉ cần viết một số code để CEO có thể giám sát một loạt các máy bắn kẹo cao su:

Demo khác cho CEO của Mighty Gumball…

Được rồi, đã đến lúc kết hợp tất cả công việc này lại với nhau và đưa ra một bản demo khác. Trước tiên, hãy để chắc chắn rằng một vài Gumball machine đang chạy code mới:

Và bây giờ hãy đặt màn hình theo dõi cho CEO. Hy vọng lần này anh ấy sẽ thích nó:

Bằng cách gọi các phương thức trên proxy, một cuộc gọi từ xa được thực hiện trên một biến String, một số int và một State object. Bởi vì chúng tôi đang sử dụng proxy, GumballMonitor không biết, hoặc cần không quan tâm, các cuộc gọi đó là từ xa (trừ việc phải lo lắng về các exception từ xa được ném ra).

1. CEO chạy màn hình, đầu tiên lấy proxy của máy bắn kẹo cao su từ xa và sau đó gọi getState() trên mỗi máy (cùng với getCount() và getLocation()).

2. getState() được gọi trên proxy, chuyển cuộc gọi đến remote service. Skeleton nhận được yêu cầu và sau đó chuyển nó đến máy bắn kẹo cao su.

3. GumballMachine return trạng thái cho skeleton, serialize nó và chuyển nó trở lại proxy. Proxy deserialize nó và return nó như một object cho monitor.

Định nghĩa Proxy Pattern

Chúng ta đã có nhiều trang ở trên để giải thích thêm cho định nghĩa này; như bạn có thể thấy, việc giải thích Remote Proxy khá liên quan. Mặc dù vậy, bạn sẽ thấy rằng định nghĩa và sơ đồ lớp cho Proxy Pattern thực sự khá đơn giản. Lưu ý rằng Remote Proxy là một triển khai của Proxy Pattern cơ bản; thực tế có khá nhiều biến thể của mẫu này và chúng ta sẽ nói về chúng sau. Còn bây giờ, hãy xem chi tiết của mẫu Proxy Pattern cơ bản.

Ở đây, định nghĩa Proxy pattern:

Định nghĩa Proxy Pattern

(Proxy Pattern cung cấp một “đại diện thay thế” hoặc “giữ chỗ” cho một đối tượng khác để kiểm soát quyền truy cập vào nó)

Sử dụng Proxy Pattern để tạo một đối tượng đại diện kiểm soát quyền truy cập vào một đối tượng khác, có thể là điều khiển từ xa, tốn kém để tạo hoặc cần bảo mật.

Chà, chúng ta đã thấy cách Proxy Pattern cung cấp thay thế hoặc giữ chỗ cho một đối tượng khác. Chúng ta cũng đã mô tả proxy như là một “đại diện” cho một đối tượng khác.

Nhưng proxy pattern kiểm soát truy cập như thế nào? Nghe có vẻ hơi lạ. Đừng lo lắng. Trong trường hợp của gumball machine, chỉ cần nghĩ đến proxy kiểm soát truy cập vào đối tượng từ xa. Proxy pattern cần thiết để kiểm soát quyền truy cập vì client của chúng ta, monitor, không biết cách nói chuyện với một đối tượng ở xa. Vì vậy, trong một số trường hợp, remote proxy kiểm soát truy cập để nó có thể xử lý các chi tiết mạng cho chúng ta. Như chúng ta vừa thảo luận, có rất nhiều biến thể của Proxy Pattern và các biến thể thường xoay quanh cách proxy kiểm soát truy cập. Sau đó, chúng ta sẽ nói thêm về điều này sau (ở phần 2), nhưng bây giờ đây là một vài cách kiểm soát truy cập của proxy:

  • Như chúng ta biết và thảo luận bên trên, một Remote proxy kiểm soát truy cập vào một remote object.
  • Một Virtual proxy (proxy ảo) kiểm soát truy cập vào một tài nguyên, thứ sẽ rất tốn kém để tạo ra.
  • Protection proxy kiểm soát quyền truy cập vào tài nguyên dựa trên quyền truy cập

Bây giờ bạn đã biết những khái niệm chính của mẫu Proxy pattern, hãy xem sơ đồ lớp…

Sơ đồ lớp Proxy Pattern

Sơ đồ lớp Proxy Pattern

Hãy xem qua sơ đồ Proxy Pattern…

Đầu tiên chúng ta có một Subject, cung cấp giao diện cho RealSubject và Proxy.

Bằng cách triển khai cùng một giao diện, Proxy có thể được thay thế cho RealSubject ở bất cứ nơi nào nó xuất hiện.

RealSubject là đối tượng thực hiện công việc thực sự. Nó yêu thích đối tượng mà Proxy đại diện và kiểm soát quyền truy cập.

Proxy giữ một tham chiếu đến RealSubject. Trong một số trường hợp, Proxy có thể chịu trách nhiệm tạo và hủy RealSubject. Client tương tác với RealSubject thông qua Proxy. Vì Proxy và RealSubject implements cùng một giao diện (Subject), Proxy có thể được thay thế ở bất kỳ nơi nào có thể sử dụng subject. Proxy cũng kiểm soát quyền truy cập vào RealSubject; điều khiển này có thể cần thiết nếu Subject đang chạy trên một máy từ xa, nếu Subject tốn kém để tạo theo một cách nào đó hoặc nếu quyền truy cập vào Subject cần được bảo vệ theo một cách nào đó.

Bây giờ bạn đã hiểu mẫu cơ bản, hãy có một cái nhìn khác về cách sử dụng proxy ngoài Remote Proxy…

(Đón xem...Proxy Pattern – Kiểm soát truy cập đối tượng phần 2).

Link đính kèm bản gốc của quyển sách: Head First Design Patterns.
Link đính kèm sourcecode của sách: Tải SourceCode.