@manhng

Welcome to my blog!

Design Pattern

May 29, 2021 16:43

Design Pattern (edit)

Observer

Design Pattern – Observer | Nixforest (wordpress.com)

Giới thiệu:

Xét mối quan hệ 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 thì tất cả các đối tượng phụ thuộc vào nó được thông báo và cập nhật một cách tự động.

Đặc điểm:

– Một đối tượng được gọi là chủ thể (Subject), nó không cần biết đối tượng quan sát là gì và làm những gì.

– Các đối tượng quan sát (Observer) được cập nhật một cách tự động mà không phải thực thi cập nhật cho từng đối tượng riêng rẽ.

– Thay đổi một trong 2 chủ thể sẽ không ảnh hưởng gì đến nhau.

Ví dụ:

Bài toán: Ta có một Form với 3 radio color button, 1 label text, 1 show button.

  • Khi chech vào một trong 3 radio color button thì label text sẽ đổi màu.
  • Khi click vào show button, một Form mới sẽ được tạo ra chứa 1 textbox. Khi check vào một trong 3 radio color button thì backcolor trong textbox của Form vừa tạo sẽ đổi màu.

Giải quyết: ta tạo 2 đối tượng để giải quyết bài toán:

  • Observer: người quan sát, chờ đợi và xử lý các thông tin được cập nhật từ Sub, một ứng dụng phải đăng ký với Sub để trở thành Obs.
  • Subject: chủ thể chính, được khởi tạo cùng với ứng dụng. Dùng để quản lý, thông báo mọi sự thay đổi của ứng dụng đó đến các Obs đã đăng ký.

So sánh với Mediator:

Như bài viết đã nói trước đây, bạn có thể nghiệm ra rằng Mediator tương tự một chương trình chat, một Mediator chứa các đối tượng Colleagues ngang hàng nhau, nó xử lý thông điệp từ một hay nhiều Colleages này và truyền cho tất cả các Colleages còn lại. Còn Observer thì hơi khác một chút, nó lại tương đồng với một “trung tâm dự báo thời tiết”, khi có thông tin bất thường về khí hậu, nó lập tức gửi tín hiệu thay đổi về cho các trung tâm thông tin nhận tín hiệu. Tín hiệu ở mẫu Observer là hoàn toàn một chiều, trong khi tín hiệu của mẫu Mediator là có thể 2 chiều.

Decorator

Design Pattern – Decorator | Nixforest (wordpress.com)

Giới thiệu:

Mẫu Decorator cung cấp cho chúng ta phương pháp điều chỉnh cách xử lý của các object riêng lẻ mà không cần phải xây dựng thêm class kế thừa từ lớp có sẵn.

Ví dụ ta muốn vẽ viền đặc biệt xung quanh một vài nút trên thanh công cụ. Nếu ta tạo một lớp con mới, các nút tạo từ lớp con này sẽ đều có viền bao đặc biệt; như vậy đôi khi không phù hợp với ý định của ta.

Thay vì làm vậy, ta tạo một class Decorator để trang trí các nút. Từ class Decorator này, ta có thể xây dựng nhiều class con làm một công việc trang trí cụ thể. Để trang trí nút, Decorator phải được kế thừa từ một class đồ họa thông thường nào đó, nhờ đó mà nó có thể nhận lời gọi hàm paint và gửi lời gọi hàm đồ họa khác cho object mà nó trang trí.

Ví dụ:

Các ứng dụng Windows hiện nay như Internet Explorer hay Netscape Navigator thường có một dãy các nút dẹp, không viền và được highlight bằng viền khi bạn di chuyển chuột về phía chúng. Vài người lập trình Windows gọi thanh công cụ như vậy là CoolBar và các nút như vậy là CoolButton.

Ví dụ sau, ta vẽ viền đen và viền trắng để highlight nút, hoặc vẽ viền xám khi không được highlight.

– Xem xét cách xây dựng Decorator, ta nên xây dựng Decorator là một class con của một class chung thông thường nào đó và như vậy tất cả các thông điệp (message) gửi cho nút sẽ được gửi cho Decorator. Cách làm như vậy không thiết thực lắm trong C#, nhưng nếu ta dùng decorator làm vật chứa, tất cả các sự kiện đều được gửi tới bộ điều khiển bên trong.

– Các class như Decorator nên là class ảo và các class decorator làm việc thật sự (ConcreteDecorator) là class kế thừa từ class Decorator ảo trên. Trong ví dụ dưới, ta xây dựng một interface tên là Decorator chặn sự kiện chuột và vẽ.

Sử dụng nhiều Decorator cùng lúc:

– Ta đã thấy cách thức hoạt động của một Decorator, còn nhiều Decorator thì sao? Chẳng hạn ta muốn trang trí CoolButton với hiệu khác như một đường chéo màu đỏ. Việc này hơi phức tạp một chút, vì ta cần bao bọc CoolDecorator trong một decorator panel khác. Sự khác biệt ở đây là ta sử dụng cả thể hiện (object) của panel được bọc bên trong(panel vẽ đường viền) và object trung tâm (là nút bấm), vì ta muốn phương thức vẽ của object bên trong có thêm cả 2 thủ tục vẽ mới. Do đó, constructor của decorator phải nhận cả panel được bọc bên trong và nút với kiểu Control.

Decorator không đồ họa:

Decorator không chỉ gói gọn trong phạm vi trang trí đồ họa. Ta có thể thêm hay tùy chỉnh hoạt động của bất kỳ object với cách làm tương tự. Trên thực tế, object không đồ họa dễ sử dụng decorator hơn vì ít các yêu cầu cần chặn và gửi đi hơn. Khi ta đặt một thể hiện của một class trong một class khác và cho class bên ngoài thao tác lên class bên trong, có nghĩa là ta đang “trang trí” class bên trong. Đây là công cụ lập trình thường sử dụng trong Visual Studio.NET.

Decorator, Adapter, và Composite:

Ta thấy có sự tương đồng giữa 3 mẫu này. Adapter cũng “trang trí” một class có sẵn. Nhưng nó thay đổi interface của một hay nhiều class thành một interface chung có lợi cho chương trình. Decorator chỉ thêm chức năng cho một vài object của class hơn là toàn bộ class đó. Composite giống như một decorator duy nhất, nhưng với mục đích khác.

Hệ quả của mẫu Decorator:

 Decorator cho ta phương pháp thêm chức năng cho class thay vì dùng kế thừa, và chức năng được thêm vào một vài object được lựa chọn chứ không phải tất cả.

– Nhược điểm:

  • Decorator và object nó bao bên trong không giống nhau => các kiểm tra về kiểu của object sẽ thất bại.
  • Decorator làm cho hệ thống đầy các object giống nhau, khó khăn cho công việc bảo trì.

Iterator

Design Pattern – Iterator | Nixforest (wordpress.com)

Giới thiệu:

Ta muốn truy cập tới từng phần tử của một object tổ hợp gồm nhiều phần tử nhỏ gộp lại (aggregate) mà không làm lộ các biểu diễn bên dưới.

– Ý tưởng chính của mẫu này là đưa nhiệm vụ truy cập và duyệt ra khỏi list và cho vào một object Iterator. Class Iterator định nghĩa một interface cho việc truy cập đến phần tử của list. Object Iterator có nhiệm vụ theo dõi phần tử nào đang được duyệt và biết được phần tử nào đã được duyệt rồi.

Ví dụ: class List gọi ListIterator, giữa chúng có quan hệ như sau:

image

Ứng dụng và cấu trúc:

+ Truy cập nội dung một object tổ hợp mà không làm lộ biểu diễn bên trong.

+ Hỗ trợ nhiều phương pháp duyệt cho object tổ hợp.

+ Cung cấp interface thống nhất cho việc duyệt các cấu trúc tổ hợp khác nhau (hỗ trợ duyệt đa hình).

– Cấu trúc:

image

– Thành phần tham gia:

  • Iterator: định nghĩa interface cho việc truy cập và duyệt phần tử.
  • ConcreteIterator: thi hành interface của Iterator, theo dõi vị trí hiện tại của quá trình duyệt tổ hợp.
  • Aggregate: xây dựng interface cho việc tạo object Iterator.
  • ConcreteAggregate: thi hành interface của Aggregate và trả về object của ConcreteIterator thích hợp. ConcreteIterator theo dõi vị trí đang duyệt của tổ hợp và tính toán được điểm tiếp theo của quá trình duyệt.

Hệ quả:

– Hỗ trợ sự đa dạng trong việc duyệt một tổ hợp: Tổ hợp phức tạp có thể được duyệt bằng nhiều cách. Iterator giúp việc thay đổi thuật toán duyệt dễ dàng. Chỉ cần thay object Iterator này bằng object Iterator khác. Xây dựng tiếp được các class Iterator hỗ trợ các phương thức duyệt mới.

 Iterator rút gọn interface của Aggregate: interface của Iterator bỏ bớt yêu cầu phải có interface tương tự cho Aggregate, do đó làm interface của Aggregate đơn giản hơn.

– Nhiều phương pháp duyệt cùng trong quá trình đang giải quyết trên cùng một tổ hợp: Iterator tự quản lý tráng thái quá trình duyệt của nó. Do vậy, nhiều phương thức duyệt có thể xử lý cùng lúc được.

Implementation:

Iterator có nhiều cách cài đặt. Việc cân nhắc nên lựa chọn cách nào tùy thuộc vào khả năng hỗ trợ của ngôn ngữ ta dùng. Một số ngôn ngữ (như CLU) hỗ trợ trực tiếp mẫu này.

a/ Đối tượng nào điều khiển sự duyệt: iterator hay tổ hợp sử dụng iterator điều khiển sự duyệt ? Khi tổ hợp nắm quyền điều khiển sự duyệt, iterator được gọi là iterator bên ngoài (external iterator); khi iterator giữ quyền điều khiển, iterator được gọi là iterator bên trong (internal iterator).

Iterator bên ngoài linh động hơn iterator bên trong.

b/ Đối tượng nào định nghĩa thuật toán duyệt: Iterator không phải là nơi duy nhất ta có thể định nghĩa thuật toán duyệt. Tổ hợp có thể tự định nghĩa thuật toán duyệt và iterator chỉ làm nhiệm vụ lưu giữ trạng thái quá trình duyệt. Iterator như vậy được gọi là con trỏ (cursor).

Nếu thuật toán duyệt nằm trên iterator, sẽ đơn giản khi dùng nhiều thuật toán trên cùng một tổ hợp, hoặc một thuật toán trên nhiều tổ hợp khác nhau.

c/ Iterator mạnh mẽ (robust) tới mức nào: Thay đổi tổ hợp khi ta đang duyệt nó rất nguy hiểm. Một phương pháp đơn giản là tạo một bản sao của tổ hợp và duyệt bản sao đó, nhưng nói chung cách này rất tốn chi phí.

Một iterator mạnh mẽ (robust iterator) đảm bảo việc thêm hay bớt không ảnh hưởng tới quá trình duyệt mà không cần phải sao chép tổ hợp. Có nhiều cách cài đặt iterator mạnh mẽ. Cách được sử dụng nhiều là đăng ký iterator với tổ hợp.

d/ Hàm phụ trong Iterator: Interface của iterator ít nhất có các hàm First, Next, CurrentItem và IsDone. Các hàm phụ thêm vào phục vụ nhiều lợi ích khác.

e/ Iterator đa hình trong C++: Iterator đa hình có mức tổn phí của nó.

Iterator đa hình có nhược điểm khác: người dùng có trách nhiệm xóa nó.

Mẫu Proxy cung cấp phương pháp giải quyết vấn đề này. Proxy bảo đảm việc dọn dẹp dữ liệu được trọn vẹn, ngay cả khi xảy ra ngoại lệ. Đây là ứng dụng của một kỹ thuật nổi tiếng trong C++ “phân bổ tài nguyên là sự khởi tạo”.

f/ Iterator có thể có quyền truy cập ưu tiên: Iterator được xem như phần mở rộng của tổ hợp tạo ra nó. Iterator và tổ hợp được ghép đôi khăng khít. Diễn tả mối quan hệ này trong C++ bằng việc cho iterator là friend của tổ hợp.

Tuy nhiên, quyền truy cập ưu tiên như vậy làm việc xây dựng các cơ chế duyệt mới khó khăn, vì đòi hỏi phải thay đổi interface của tổ hợp để thêm friend mới.

g/ Iterator dành cho composite: Khó khăn khi cài đặt iterator bên ngoài cho các cấu trúc tổ hợp phức tạp đệ quy thuộc mẫu Composite, do cấu trúc như vậy có thể mở rộng xuống nhiều tầng.

Nếu một node trong Composite có một interface cho việc di chuyển sang các node ngang hàng, node cha, hoặc node con của nó, thì sử dụng iterator kiểu con trỏ hiệu quả hơn.

Composite cần nhiều cách duyệt khác nhau: preorder, inorder, postorder hay breadth-first thường được sử dụng. Ta hỗ trợ mỗi kiểu duyệt bằng một class iterator riêng biệt.

h/ Null iterator: NullIterator là một iterator suy giảm để xử lý các điều kiện giới hạn. Trên định nghĩa, NullIterator luôn hoàn thành việc duyệt, nghĩa là hàm IsDone của nó luôn trả về giá trị true.

NullIterator làm việc duyệt cấu trúc giống cấu trúc cây (như Composite) đơn giản hơn.

Ứng dụng thực tế:

 Iterator được dùng rất nhiều trong các hệ thống object-oriented. Phần lớn các thư viện class đều hỗ trợ iterator dưới dạng này hoặc dạng khác.

Ví dụ từ Booch component, một tập hợp thư viện class nổi tiếng. Nó hỗ trợ queue kích thước cố định (fixed size) và cả tăng dần động (dynamically growing). Interface của queue được định nghĩa trong class Queue ảo. Queue Iterator được cài đặt trong sự tương quan với class Queue ảo để hỗ trợ duyệt đa hình.

– Duyệt đa hình và dùng proxy dọn dẹp bộ nhớ được cung cấp bởi class container của ET++. Framework chỉnh sửa đồ họa Unidraw dùng iterator kiểu con trỏ.

– ObjectWindows 2.0 cung cấp bảng phân cấp class cho iterator của các containers. Có duyệt các kiểu container khác nhau bằng cùng một cách. Cú pháp của ObjectWindow dựa trên việc overload toán tử ++ để tiến triển việc duyệt.

Mẫu liên quan:

 Composite: iterator thường được sử dụng cho cấu trúc đệ quy như Composite.

 Factory Method: duyệt đa hình dựa vào Factory Method để khởi tạo iterator thích hợp.

 Memento: thường được dùng kết hợp với mẫu Iterator. Iterator dùng Memento để lưu trạng thái quá trình duyệt. Iterator lưu trữ memento bên trong nó.

Mediator

Design Pattern – Mediator | Nixforest (wordpress.com)

Giới thiệu:

Trong thiết kế hướng đối tượng, ta có thể có rất nhiều đối tượng. Nhưng chúng ta thường chỉ chú trọng vào cách thiết kế cấu trúc lớp mà hay quên đi cách các lớp tương tác với nhau. Và các đối tượng không những chỉ giao tiếp 2 chiều với nhau mà còn có các loại giao tiếp nhiều chiều khác.

Mediator pattern cơ bản gồm 2 đối tượng chính, đó là Mediator làm nhiệm vụ điều phối thông tin và các lớp giao tiếp với Mediator gọi chung là Colleagues. Các Colleagues chỉ cần biết đến Mediator nên nó sẽ chứa Mediator chứ không quan tâm đến các Colleagues khác. Mediator, đương nhiên, sẽ định nghĩa một giao thức bắt buộc các Colleagues phải tuân theo. Số lượng Colleagues là tùy ý.

Đặc điểm của Mediator Pattern:

– Ưu điểm

  • Đơn giản hóa quá trình giao tiếp : các đối tượng Tom, Bill, John không hề biết về cấu trúc của nhau nhưng vẫn giao tiếp với nhau được.
  • Có thể xử lý riêng tại Mediator : Như trong ví dụ trên, đối tượng Yahoo có thể xử lý các thông điệp trước khi gửi đến các đối tượng khác. Ví dụ ta Tom, Bill, John chat với nhau nhưng ta muốn đối tượng Yahoo lọc từ ngữ, bất cứ thông điệp nào gửi đến mà có chứa từ “UIT” thì loại bỏ, không gửi đến các Colleagues khác thì có thể làm như sau:
class Mediator {
        public delegate void ProcessMessage(String name,
                                 String message);
        public ProcessMessage callback;

        public void Handle(String name, String message) {
           if(message.Contains("UIT") == false)
                callback(name, message);
        }
    }
  • Có thể tạo ra bao nhiêu Colleagues tùy thích, miễn nó chấp nhận giao thức mà Mediator định nghĩa, trong ví dụ này là delegate ProcessMessage.

– Khuyết điểm

  • Xử lý ở Mediator trở nên nặng nề hơn : do tất cả việc xử lý đều nằm ở Mediator nên nó sẽ phình ra khi các yêu cầu bài toán ngày càng tăng, 1 trong những vấn đề sẽ gặp là “nghẽn cổ chai”.
  • Đối với những ngôn ngữ không hỗ trợ delegate hay callback function thì mediator khá khó thiết kế.

Strategy Pattern

Design Pattern – Strategy | Nixforest (wordpress.com)

Giới thiệu:

Strategy Pattern định nghĩa và đóng gói các thuật toán có cùng mục đích trong những lớp có giao diện chung. Làm cho sự thay đổi thuật toán trở lên linh động và độc lập với client.

Interpreter

Design Pattern – Interpreter | Nixforest (wordpress.com)

Ý nghĩa:

– Xây dựng một kiến trúc ngữ pháp cho một ngôn ngữ.

– Dựa vào kiến trúc này, một chương trình thông dịch có thể hiểu được ý nghĩa của những câu lệnh được viết bằng ngôn ngữ đó.

Các tình huống áp dụng:

Khi vấn đề giải quyết lặp lại tương đối nhiều lần và ta có thể biểu diễn vấn đề bằng một ngôn ngữ đặc tả tương đối đơn giản (ngôn ngữ đặc tả này do ta tự thiết kế và xây dựng hoặc là những quy tắc đã có sẵn).

Bài toán thực tế:

Viết một chương trình cho phép người dùng nhập vào dòng lệnh (command) theo một cấu trúc xác định do ta quy định sẵn, chương trình sẽ nhận dạng Command dựa vào cấu trúc của nó và trả về kết quả phù hợp.

Ví dụ:

 Vd1: Ta viết chương trình giống DOS cho phép người dùng nhập vào các câu lệnh, chương trình sẽ đọc và chuyển những câu lệnh này thành những câu lệnh cấp thấp để máy tính thực hiện.

 Vd2: Viết chương trình phân tích biểu thức toán học như cộng, trừ, nhân, chia, dấu ngoặc… và trả về kết quả cho người dùng. Lúc này ta có thể áp dụng mẫu Interpreter để thực hiện việc chuyển các biểu thức của người dùng thành các lệnh để yêu cầu máy tính thực hiện.

 Vd3: Ngôn ngữ HTML dùng để đặc tả việc hiển thị một trang văn bản có “cấu trúc ngữ pháp” xác định. Trình duyệt Web (IE, FireFox …) hiểu rõ “cấu trúc ngữ pháp” này. Do đó khi trình duyệt nhận dữ liệu từ Web Server dưới dạng HTML text, nó sẽ phân tích và hiển thị lên trang Web theo đúng thiết kế ban đầu dựa trên “cấu trúc ngữ pháp” mà nó đã được cung cấp.

 Vd4: Trong một số ứng dụng thương mại như Word, Excel… chương trình cho phép người dùng xây dựng các “macro” để thực hiện một công việc nào đó lặp lại nhiều lần. Macro này có thể xem như một chỉ thị lệnh của người dùng được đặc tả theo cấu trúc ngữ pháp xác định của chương trình và chương trình có thể “thông dịch” và thực thi được.

Thuận lợi và hạn chế:

– Thuận lợi: Một số ứng dụng đặc thù sẽ trở nên đơn giản khi cài đặt nếu ta xây dựng một bộ cấu trúc ngữ pháp để đặc tả các tác vụ mà nó sẽ thực hiện thành các “lệnh”. Sau đó, tạo một thông dịch viên có thể phân tích các lệnh này và thực thi nó.

– Hạn chế: Ngôn ngữ đặc tả được xây dựng đòi hỏi phải có cấu trúc ngữ pháp đơn giản.

Bridge

Design Pattern – Bridge | Nixforest (wordpress.com)

Tại sao phải sử dụng Bridge pattern?

Một thành phần trong OOP thường có 2 phần:

– Phần ảo: định nghĩa các chức năng.

– Phần thực thi: thực thi các chức năng được định nghĩa trong phần ảo.

– Hai phần này liên hệ với nhau thông qua quan hệ kế thừa. Những thay đổi trong phần ảo dẫn đến các thay đổi trong phần thực thi.

Mẫu Bridge được sử dụng để tách thành phần ảo và thành phần thực thi riêng biệt.

– Do đó, các thành phần này có thể thay đổi độc lập và linh động.

– Thay vì liên hệ với nhau bằng quan hệ kế thừa, hai thành phần này liên hệ với nhau thông qua quan hệ “chứa trong”.

Ưu nhược điểm:

– Lợi ích:

Tính kế thừa trong hướng đối tượng thường gắn chặt abstraction và implementation. Sử dụng thiết kế Bridge sẽ giúp chúng ta giảm sự phụ thuộc giữa abstraction và implementation.

Giảm số lượng những lớp con không cần thiết. Một số trường hợp sử dụng tính inheritance sẽ tăng số lượng subclass vô tội vạ.

– Nhược điểm (hạn chế):

Khi sử dụng Bridge Pattern, chúng ta đã tăng số lần gọi gián tiếp lên hai.

Prototype

Design Pattern – Prototype | Nixforest (wordpress.com)

Giới thiệu:

Hệ thống các mẫu Design Pattern được chia thành 3 nhóm: nhóm Creational, nhóm Structural và nhóm Behavioral.

Prototype thuộc nhóm Creational (nhóm cấu thành).

Prototype quy định loại của các đối tượng cần tạo bằng cách dùng một đối tượng mẫu, tạo mới nhờ vào sao chép đối tượng mẫu này.

Sử dụng:

Prototype được sử dụng trong trường hợp:

  • Tránh việc tạo nhiều lớp con cho mỗi đối tượng tạo như của Abstract Factory Pattern.
  • Giảm chi phí để tạo ra một đối tượng mới theo “chuẩn”, tức là việc này tăng Performance so với việc sử dụng từ khóa new để tạo đối tượng mới.

Để cài đặt Prototype Pattern, tạo ra một clone() Method ở lớp cha, và triển khai ở các lớp con.

– Cách nhân bản trong Prototype có 2 kiểu:

  • Shallow copy: chỉ nhân bản được value type.
  • Deep copy: nhân bản được value type và reference type.

– Ví dụ: Có một đối tượng tên là X, có liên quan đến đối tượng A và B. Tạo X2 là một Shallow copy của X thì X2 vẫn còn liên quan đến đối tượng A và B. Còn nếu X2 là một Deep copy của X thì X2 liên quan đến đối tượng A2 và B2 là những copy của A và B.

Cài đặt Demo:

Ta có một ví dụ như sau: Khi ta lập hồ sơ lý lịch, về mặt gia đình, những thành viên trong gia đình sẽ có chung một số thông tin như Địa chỉ nhà, Số điện thoại liên lạc, Con thì cùng Họ với Bố, Con trai thì cùng giới tính với Bố… Sử dụng Prototype giúp cho công việc khai báo lý lịch trở nên nhẹ nhàng hơn.

State

Design Pattern – State | Nixforest (wordpress.com)

Định nghĩa:

State là mẫu thiết kế cho phép ta thay đổi các hành vi khi các trạng thái bên trong nó thay đổi.

Các trường hợp sử dụng State:

– Mỗi hành vi của đối tượng phụ thuộc vào trạng thái, khi trạng thái thay đổi thì hành vi thay đổi.

– Trong môi trường ứng dụng lớn, có nhiều trạng thái và nhiều qui định cho mỗi trạng thái thì ta thường viết mỗi trạng thái rời ra thành từng lớp riêng biệt.

Các mẫu liên quan:

– Mẫu Flyweight: giải thích khi nào các đối tượng State có thể được phân tách và được phân tách như thế nào.

– Các đối tượng State thường là các Singleton.

Demo:

– Ở đây chúng ta mô phỏng một màn game sử dụng State:

Adapter

Design Pattern – Adapter | Nixforest (wordpress.com)

Đặt vấn đề:

Một trong những lợi ích quan trọng nhất của lập trình hướng đối tượng là tái sử dụng mã nguồn. Bạn có thể sử dụng một hay một nhóm các đối tượng đã được phát triển từ trước vào trong dự án của mình mà để tận dụng những chức năng sẵn có mà tốn rất ít công sức.

Tuy nhiên rất ít ai có khả năng dự đoán tương lai. Họ có thể hoàn thành một số chức năng nào đó nhưng lại không dự đoán được chúng sẽ đi về đâu và thay đổi ra sao, những yêu cầu nào có thể nảy sinh. Vì thế chúng ta không thể biết được làm cách nào để thiết kế các lớp đối tượng một cách tối ưu nhất để có thể tái sử dụng lại sau này.

Ví dụ: Bây giờ bạn đang thực hiện một dự án và bạn muốn hợp tác với một người bạn nào đó để cùng làm. Nhưng các class mà người bạn của bạn viết có thể sẽ không tương thích với các class của dự án hiện tại của bạn.

Tất nhiên bạn có thể viết lại tất cả các class của dự án mình sao cho tương thích với những class mà người bạn kia cung cấp. Nhưng tại sao ta không chọn một cách giải quyết tốt hơn.

===> Giải pháp:

Bạn cần một thành phần trung gian, có nhiệm vụ làm cho class của bạn và class người bạn kia cung cấp hiểu được lẫn nhau. Khi bạn thực hiện lời gọi hàm thành phần trung gian sẽ biết cách chuyển lời gọi hàm đó đến các lớp đối tượng kia và yêu cầu chúng thực thi một cách dễ hiểu nhất. Và khi các class thay đổi thì cái mà bạn phải thay đổi chỉ là thành phần trung gian chứ không cần phải viết lại cả chương trình.

Thành phần trung gian mà tôi muốn nói ở đây chính là Adapter. Một mẫu Design Pattern có cơ chế như một file converter giúp để chuyển đổi kiểu dữ liệu thành một kiểu dữ liệu tương thích.

Định nghĩa:

Vai trò của Adapter là tạo một giao diện (interface) trung gian để gắn kết một đối tượng nào đó vào hệ thống của bạn mà bạn mong muốn.

Thuận lợi và hạn chế:

– Thuận lợi: tăng khả năng tái sử dụng các thành phần của hệ thống. Cho phép 2 hay nhiều đối tượng tương tác với nhau dù chúng không tương thích với nhau (về kiểu/loại đối tượng).

– Hạn chế: việc lạm dụng Adapter có thể sẽ dẫn tới sự không tương thích trong hệ thống do đó cần có chiến lược để quản lý đảm bảo chức năng của mỗi lời gọi hàm bên trong. Hơn nữa các tham số của các Adapter và hệ thống (FrameWork) của chúng ta không phải lúc nào cũng tương thích.

Command

Design Pattern – Command | Nixforest (wordpress.com)

Command Pattern là một Design Pattern trong đó nó được dùng để đóng gói các yêu cầu khác nhau thành từng đối tượng riêng biệt, qua đó cho phép ta tham chiếu đến client với những yêu cầu khác nhau.

Mẫu Command đóng gói yêu cầu như một đối tượng, làm cho nó có thể được truyền như 1 tham số, và được lưu trữ lại theo những cách thức khác nhau.

Trong Command Pattern có 3 lớp luôn gắn liền với nhau, đó là Client, Invoker  Receiver:

  • Client tạo đối tượng Command và cung cấp các thông tin cần thiết để gọi các yêu cầu tại một thời gian sau đó.
  • Invoker quyết định lúc nào các yêu cầu này được gọi.
  • Receiver chứa các yêu cầu cần được thực hiện.

Tình huống áp dụng:

Dùng Command pattern khi:

  • Tham chiếu đến một object.
  • Xác định và thực hiện những yêu cầu tại những thời điểm khác nhau.
  • Cần thực hiện thao tác Undo.
  • Cần thực hiện thao tác Logging changes (trong trường hợp hệ thống bị treo).
  • Cấu trúc hệ thống có dạng: điều khiển cấp cao được xây dựng trên những điều khiển nền tảng.

Ưu điểm và hạn chế của Command Pattern:

Mô hình Command có những ưu điểm sau:

  • Command Pattern tách riêng đối tượng và các điều khiển của đối tượng mà vẫn biết cách đáp ứng các request lên đối tượng đó.
  • Các command là những lớp đối tượng cơ bản. Chúng có thể được vận dụng và mở rộng giống như bất kỳ đối tượng nào khác.
  • Các Command có thể được tập hợp lại thành một Composite Command.
  • Dễ dàng thêm vào một Command mới vì không cần phải chỉnh sửa lại các class sẵn có.

Demo:

Chúng ta sẽ xây dựng một ứng dụng nhỏ thể hiện các thao tác Undo và Redo. Ta nhập một text, Add nó vào một vùng hiển thị nào đó. Ta có thể sử dụng thao tác Undo để trở về bước kế trước hay Redo để trở về bước kế sau.

Proxy

Design Pattern – Proxy | Nixforest (wordpress.com)

Định nghĩa:

Proxy Pattern là mẫu thiết kế mà ở đó tất cả các truy cập trực tiếp một đối tượng nào đó sẽ được chuyển hướng vào một đối tượng trung gian (Proxy Class).

Nếu như Factory Pattern giúp quản lý đối tượng tốt hơn thì Proxy Pattern có nhiệm vụ bảo vệ việc truy cập một đối tượng thông qua Proxy, hay còn gọi là truy cập gián tiếp.

Proxy Pattern được sử dụng khi bạn muốn đơn giản một đối tượng phức tạp. Proxy được ủy quyền về phía ứng dụng khách cho phép tương tác với đối tượng đích theo những cách khác nhau, như gửi yêu cầu một dịch vụ nào đó, theo dõi trạng thái và vòng đời đối tượng, xây dựng lớp vỏ bảo vệ đối tượng…

Bài toán thực tế:

Ví dụ: Chương trình hiển thị các hình ảnh từ đĩa cứng hoặc các hình ảnh được download từ Internet.

Phân loại:

Proxy Pattern có những đặc điểm chung sau đây:

  • Cung cấp mức truy cập gián tiếp vào một đối tượng.
  • Tham chiếu vào đối tượng đích và chuyển tiếp các yêu cầu đến đối tượng đó.
  • Cả Proxy và đối tượng đích đều kế thừa hoặc thực thi chung một lớp giao diện. Mã máy dịch cho lớp giao diện thường “nhẹ” hơn các lớp cụ thể và do đó có thể giảm được thời gian tải dữ liệu giữa server và client.

Phân loại:

 Remote Proxy: Client truy cập qua Remote Proxy để chiếu tới một đối tượng được bảo về nằm bên ngoài ứng dụng (trên cùng máy hoặc máy khác).

 Virtual Proxy: Virtual Proxy tạo ra một đối tượng trung gian mỗi khi có yêu cầu tại thời điểm thực thi ứng dụng, nhờ đó làm tăng hiệu suất của ứng dụng.

 Monitor Proxy: loại này sẽ thiết lập các bảo mật trên đối tượng cần bảo vệ, ngăn không cho client truy cập một số trường quan trọng của đối tượng.

 Protection Proxy: phạm vi truy cập của các client khác nhau sẽ khác nhau. Protection proxy sẽ kiểm tra các quyền truy cập của client khi có một dịch vụ được yêu cầu.

 Firewall Proxy: bảo vệ đối tượng từ chối các yêu cầu xuất xứ từ các client không tín nhiệm.

 Cache Proxy: cung cấp không gian lưu trữ tạm thời cho các kết quả trả về từ đối tượng nào đó, kết quả này sẽ được tái sử dụng cho các client chia sẻ chung một yêu cầu gửi đến.

 Smart Reference Proxy: là nơi kiểm soát các hoạt động bổ sung mỗi khi đối tượng được tham chiếu.

 Synchronization Proxy: đảm bảo nhiều client có thể truy cập vào cùng một đối tượng mà không gây ra xung đột.

* Khi một client nào đó chiếm dụng khóa khá lâu khiến cho số lượng các client trong danh sách hàng đợi cứ tăng lên, và do đó hoạt động của hệ thống bị ngừng trệ, có thể dẫn đến hiện tượng “tắc nghẽn”.

 Copy-On-Write Proxy: loại này đảm bảo rằng sẽ không có client nào phải chờ vô thời hạn.

* Copy-On-Write Proxy là một thiết kế rất phức tạp.

Cấu trúc mẫu:

 Service: là lớp đối tượng thuần ảo hay interface. Lớp này được thực thi bởi Proxy và các đối tượng khác.

 ServiceImpl: kế thừa từ Service, thực hiện đầy đủ hoặc mở rộng thêm các chức năng được định nghĩa trước tại Service.

 ServiceProxy: Là một Proxy được kế thừa từ Service. Nhiệm vụ của nó là chuyển lời gọi hàm từ Service đến ServiceImpl mỗi khi thích hợp.

Áp dụng:

– Nếu có nhiều đối tượng, tốn quá nhiều chi phí để nạp hoặc khởi tạo.

– Nếu đối tượng cần gọi, nằm ở một Remote Machine và việc nạp đối tượng này qua mạng có thể rất chậm, nhất là giờ cao điểm.

– Khi đối tượng bị giới hạn quyền truy cập hay truy xuất tài nguyên.

 Client gửi đến một yêu cầu và đòi trả về một thể hiện của một lớp đối tượng.

Abstract Factory

Design Pattern – Abstract Factory | Nixforest (wordpress.com)

Định nghĩa:

Mẫu Abstract Factory là một mẫu thiết kế cung cấp cho trình khách một giao diện cho một họ hoặc một tập các đối tượng thuộc các lớp khác nhau nhưng cùng có chung giao diện với nhau mà không phải trực tiếp làm việc với từng lớp con cụ thể.

Tình huống áp dụng:

– Hệ thống không phụ thuộc vào những sản phẩm của nó được tạo ra như thế nào.

– Hệ thống được cấu hình với một hoặc nhiều họ sản phẩm.

– Các đối tượng được tạo ra như một tập hợp có thể tương thích, sử dụng cùng nhau.

– Chúng ta muốn cung cấp một tập các lớp và chúng ta muốn thể hiện các ràng buộc, các mối quan hệ giữa chúng mà không phải là các thực thi của chúng.

Lợi ích và hạn chế:

a/ Ưu điểm:

– Phát triển thực thi một cách độc lập với các phần còn lại của ứng dụng thông qua giao diện chung.

– Thúc đẩy sự thống nhất giữa các “sản phẩm”.

b/ Hạn chế:

– Khó dự đoán những vấn đề phát sinh trong tương lai.

– Nếu AbstractProduct không đúng thì sẽ sinh ra các ConcreteProduct không theo ý muốn.

– Rất khó để hỗ trợ một loại “sản phẩm” mới.

Các Pattern liên quan:

 Factory Method: sử dụng để thi hành (implement) AbstractFactory, nhưng chúng cũng có thể được thi hành bằng cách sử dụng Prototype.

 Singleton: sử dụng trong ConcreteFactory.

Factory Method

Design Pattern – Factory Method | Nixforest (wordpress.com)

Ví dụ:

Ta muốn tạo một list để quản lý các loại sách, ban đầu có 3 loại:

  • Toán
  • Hóa

– Bây giờ vấn đề là ta muốn tạo thêm một vài loại sách nữa mà “không thay đổi hàm Main” (nói cách khác là làm sao cho phần mềm độc lập với những lớp cụ thể).

===> Vấn đề sẽ được giải quyết với Factory Method.

Khi nhắc đến lợi ích của hướng đối tượng thì chắc chắn không thể không nhắc đến 2 tính kế thừa và đa hình. Ngoài việc không viết lại code, chúng ta có thể linh hoạt trong việc tạo và quản lý ra các đối tượng của các lớp con từ 1 con trỏ của lớp cơ sở.

Factory Method định nghĩa một phương thức cho việc tạo đối tượng, và các lớp con thừa kế có thể quyết định đối tượng nào sẽ được tạo.

Đặc điểm:

Ưu điểm:

– Tạo sự linh hoạt trong việc sử dụng lại code bằng cách loại bỏ việc tạo ra các lớp ứng dụng cụ thể, chỉ thao tác với interface Product và có thể làm việc với mọi ConcreteProduct hỗ trợ interface này.

– Client thao tác dựa trên interface mà không quan tâm sản phẩm gì được trả về, quá trình tạo sản phẩm được che dấu hoàn toàn.

– Dễ dàng cập nhật phần mềm.

Khuyết điểm:

– Hạn chế có thể thấy ngay là một khi cần tạo một loại đối tượng mới, ta phải thừa kế lại lớp Creator và cài đặt phương thức khởi tạo.

– Khi thêm một sản phẩm mới, ta phải sửa đổi và bổ sung mã chương trình khá nhiều (thêm hàm sản xuất, lớp sản xuất…) điều này làm gia tăng độ phức tạp của hệ thống.

* Bạn nên xem xét sử dụng mẫu Factory Method trong các trường hợp sau:

  • Một lớp không thể biết trước được nó sẽ tạo ra những loại lớp đối tượng nào.
  • Một lớp chỉ sử dụng các lớp con của nó để xác định đối tượng mà nó tạo ra.
  • Bạn muốn định ra những gì mà lớp sẽ được tạo.

Composite

Design Pattern – Composite | Nixforest (wordpress.com)

Trong OOP, một đối tượng Composite được tạo thành từ một hay nhiều đối tượng tương tự nhau (hoặc có một số chức năng tương tự nhau). Ý tưởng ở đây là có thể thao tác trên một nhóm đối tượng theo cách như thao tác trên một đối tượng duy nhất. Các đối tượng của nhóm phải có các thao tác chung, hay còn gọi là mẫu số chung nhỏ nhất.

Composite có thể được gọi là “Đối tượng đa hợp”.

  • Composite là mẫu thiết kế dùng để tạo ra các đối tượng trong các cấu trúc cây để biểu diễn hệ thống phân lớp: bộ phận – toàn bộ.
  • Composite cho phép các client tác động đến từng đối tượng và các thành phần của đối tượng một cách thống nhất.

Ứng dụng:

Sử dụng các mẫu Composite để:

  • Miêu tả cho toàn bộ hệ thống của các đối tượng.
  • Có thể bỏ qua sự khác biệt giữa thành phần của các đối tượng và các đối tượng cá nhân.
  • Xử lý tất cả các đối tượng trong cơ cấu Composite chung.

Đặc điểm:

Ưu điểm:

  • Làm cho việc thêm các thành phần trong một cấu trúc tương đồng trở nên dễ dàng.
  • Làm cho các client đơn giản hơn, vì không cần phải biết là đang làm việc trên một leaf hoặc một thành phần của Composite.

Mẫu Composite này rất “lợi hại”, nó chuyên trị những bài có dạng đệ qui, nó làm việc trên các đối tượng abstract, không làm việc với đối tượng cụ thể nên khả năng mở rộng cũng rất cao

Nhược điểm:

Khó khăn trong việc hạn chế các loại thành phần trong một Composite.

Tổng kết:

Composite Pattern: Tổ chức các đối tượng theo cấu trúc phân cấp dạng cây. Tất cả các đối tượng trong cấu trúc được thao tác một cách thuần nhất như nhau.

Builder

Design Pattern – Builder | Nixforest (wordpress.com)

Giới thiệu:

– Trong những ứng dụng lớn, với nhiều chức năng phức tạp, mô hình giao diện đồ sộ, việc khởi tạo ứng dụng gặp nhiều khó khăn. Chúng ta không thể dồn các công việc khởi tạo lại thành một hàm khởi tạo. Vì như vậy sẽ rất khó kiểm soát và không phải lúc nào các thành phần của ứng dụng cũng được đồng bộ. Có thành phần tạo ra vào lúc dịch chương trình nhưng cũng có thành phần tạo ra tùy theo yêu cầu của người dùng (tùy vào hoàn cảnh mà nó sử dụng).

=> Do vậy ta sẽ giao công việc này cho một đối tượng chịu trách nhiệm khởi tạo để từ đó có thể tạo những ứng dụng riêng rẽ khác nhau.

Định nghĩa:

 Builder là mẫu thiết kế hướng đối tượng, được tạo ra để chia một công việc khởi tạo phức tạp của từng đối tượng riêng rẽ, từ đó có thể tiến hành khởi tạo đối tượng ở các hoàn cảnh khác nhau.

Ví dụ: Xây dựng lớp Animals từ đó xây dựng thêm lớp Cat, Dog…

Dễ thấy rằng, việc chia công việc khởi tạo của từng đối tượng sẽ giúp tiết kiệm được tài nguyên. Việc xử lý cũng nhẹ nhàng hơn rất nhiều.

Fly weight

Design Pattern – Fly weight | Nixforest (wordpress.com)

Flyweight là một mẫu thiết kế phần mềm. Khi nhiều đối tượng phải được xử lý mà chương trình không thể chịu nổi một lượng dữ liệu khổng lồ, thì cần dùng Flyweight.

– Ý nghĩa: Làm phương tiện dùng chung để quản lý một số lượng lớn các đối tượng nhỏ có các đặc điểm chung, mà các đối tượng nhỏ này lại được sử dụng tùy thuộc vào hoàn cảnh, điều kiện ngoài.

– Đối với ngôn ngữ C++, trong mẫu Flyweight, dữ liệu không có các con trỏ (pointer) trỏ đến các phương thức của kiểu dữ liệu đó, vì như thế sẽ tốn rất nhiều bộ nhớ. Thay vào đó các chương trình con (subroutine) sẽ được gọi trực tiếp. Trong một vài trường hợp, Flyweight inheritance được thực hiện bằng cách “shift in” và “shift out” các data markers dưới dạng các chu trình tác vụ ở mức cao (higher level operation cycles) thông qua một mảng các Flyweight data.

Trường hợp sử dụng:

  • Ứng dụng sử dụng nhiều đối tượng giống hoặc gần giống nhau.
  • Với các đối tượng gần giống nhau, những phần không giống nhau có thể tách rời với các phần giống nhau để cho phép các phần giống nhau có thể chia sẻ.
  • Nhóm các đối tượng gần giống nhau có thể được thay thế bởi một đối tượng chia sẻ mà các phần không giống nhau đã được loại bỏ.
  • Nếu ứng dụng cần phân biệt các đối tượng gần giống nhau trong trạng thái gốc của chúng.

Chain of Responsibility

Design Pattern – Chain of Responsibility | Nixforest (wordpress.com)

Chain of Responsibility là một mẫu cho phép một class nào đó thực hiện yêu cầu mà không cần biết về khả năng của các class khác. Nó tạo ra những liên kết không chặt chẽ giữa các class, và yêu cầu chỉ được chuyển qua các class thông qua các đường dẫn chung (common link). Yêu cầu sẽ được chuyển cho đến khi có 1 class thực hiện yêu cầu đó.

Ví dụ như “Hệ thống trợ giúp” trong một ứng dụng.

  • Vấn đề: đối tượng cung cấp “Help” không biết đối tượng phát sinh yêu cầu.
  • Cần làm: tách biệt đối tượng phát sinh yêu cầu với đối tượng cung cấp thông tin “Help”.

-> Ý tưởng:

  • Tổ chức hệ thống Help thành một chuỗi mắt xích từ cụ thể đến tổng quát.
  • Mỗi đối tượng trong mắt xích có một phương thức để “nắm giữ” và “xứ lý” yêu cầu.
  • Đối tượng gửi yêu cầu không biết đối tượng nhận yêu cầu.

Hoàn toàn không dùng một cấu trúc if-else hay switch nào cả. Mệnh lệnh được di chuyển qua các đối tượng xử lý một cách tự nhiên. Đây chính là mục đích của mẫu Chain of Responsibility.

Ứng dụng:

– Lợi ích khi sử dụng Chain of Responsibility là gì?

Bạn sẽ thấy được sự hữu ích của nó trong các trường hợp:

  • Chain of Responsibility là một loại mẫu giúp cho các đối tượng khác nhau hoạt động riêng rẽ trong một chương trình.
  • Mặt khác nó hạn chế sự liên kết giữa 2 đối tượng nên chúng hành động một cách độc lập.
  • Có nhiều đối tượng với nhiều phương thức khác nhau thích hợp với hành động mà chương trình đang yêu cầu. Nó sẽ giúp chọn cái thích hợp hơn những cái còn lại để bạn xây dựng quyết định này vào trong code.
  • Một đối tượng có thể thích hợp nhất nhưng bạn không muốn xây dựng nó trong chuỗi if-else hay switch mà chọn một đối tượng đặc biệt.
  • Có thể có những đối tượng mới muốn thêm vào danh sách thích hợp của lựa chọn xử lý khi chương trình đang được chạy.
  • Có thể có những trường hợp khi có nhiều hơn một đối tượng sẽ hành động tương ứng với một yêu cầu và bạn không muốn có xung đột xảy ra trong chương trình được gọi.

– Lưu ý: có 2 điểm quan trọng ta cần lưu ý:

  • Thứ nhất: chuỗi (the chain) được tổ chức từ riêng biệt nhất đến tổng quát nhất.
  • Thứ 2: không có gì đảm bảo rằng yêu cầu gửi đến sẽ được phản hồi trong mọi trường hợp.

– Ý nghĩa:

  • Mẫu Chain of Resposibility dùng để tránh sự liên kết trực tiếp giữa đối tượng gửi yêu cầu và đối tượng nhận yêu cầu khi yêu cầu có thể được xử lý bởi hơn một đối tượng.
  • Móc nối các đối tượng nhận yêu cầu thành một chuỗi và gửi yêu cầu theo chuỗi đó cho đến khi có một đối tượng xử lý nó.

Nguyên lý thiết kế SOLID

Nguyên lý Open – Closed

Vào năm 1988, Bertrand Meyer, cha đẻ của ngôn ngữ lập trình hướng đối tượng Eiffel, trong quyển Object Oriented Software Construction của mình, đã phát biểu một câu mà bây giờ trở thành nguyên lý Open – Closed nổi tiếng, xin được trích nguyên văn:

“Software entities (classes, modules, functions, etc) should be open for extension, but closed for modification”.

Thoạt nghe thì câu này có vẻ mâu thuẫn. Cách thông thường để mở rộng một chương trình là sửa đổi nó. Như vậy việc thiết kế một chương trình “closed for modification” thì làm sao có thể mở rộng nó!

Một chương trình bên cạnh tính độc lập tương đối của mình còn có quan hệ với rất nhiều chương trình khác. Việc sửa đổi nó có thể dẫn đến việc sửa đổi tất cả những chương trình liên quan. Và trong một hệ thống lớn thì việc này quả là một thảm họa. Nâng cấp một phần của hệ thống kéo theo phải nâng cấp toàn bộ hệ thống! Như vậy làm thế nào để xây dựng một chương trình chạy ổn định, thích ứng được với những thay đổi trong tương lai? Xem xét kỹ nguyên lý Open – Closed ta thấy rằng việc mở rộng chương trình chỉ nên là bổ sung thêm những cái mới chứ không nên sửa đổi những gì đã có.

Xét chương trình sau:

const int LINE = 0;
const int RECTANGLE = 1;
void DrawLine()
{
    //draw line
}
void DrawRectangle()
{
    //draw rectangle
}
void DrawShape(int iType)
{
    switch(iType)
    {
        case LINE: DrawLine();
        break;
        case RECTANGLE: DrawRectangle();
        break;
    }
}

 

Đoạn chương trình giúp ta vẽ đường thẳng và hình chữ nhật. Nếu muốn thêm vào vẽ hình tròn, ta phải thêm “case CIRCLE: DrawCircle();break;”. Như vậy khi muốn vẽ thêm một hình mới, ta lại phải sửa đổi lại hàm DrawShape. Nhưng bạn hãy thử tưởng tượng chương trình của chúng ta có đến vài chục hoặc vài trăm hàm liên quan đến việc vẽ hình? Lúc đó ta phải thực hiện việc sửa đổi trên rất nhiều hàm của chương trình! Một chương trình được thiết kế như vậy không tuân thủ nguyên lý Open – Closed. Để thỏa mãn nguyên lý Open – Closed, ta sửa lại như sau:

class Shape
{
    public:
        virtual void Draw() = 0;
};
class Line : public Shape
{
    //line data
    private:
        void Draw()
        {
            //draw line
        }
};
class Rectangle : public Shape
{
    //rectangle data
    private:
        void Draw()
        {
            //draw rectangle
        }
};

void DrawShape(Shape* obj)
{
    obj->Draw();
}

 

Trong đoạn chương trình trên, khi muốn vẽ thêm hình tròn, ta chỉ việc thêm class Circle kế thừa từ Shape mà không cần sửa đổi chương trình hiện có. Cốt lõi vấn đề ở đây chính là lớp Shape. Nó là abstraction của các đối tượng hình.

Có thể nói nguyên lý Open – Closed là nguyên lý cơ bản nhất của lập trình hướng đối tượng, bên cạnh 3 nguyên lý khác là: nguyên lý Thay thế Liskov, nguyên lý Nghịch đảo phụ thuộc, và nguyên lý Phân tách Interface. Nó là nền tảng của mọi phân tích thiết kế hướng đối tượng (Object Oriented Analysis and Design). Nó giúp chương trình dễ dàng tái sử dụng và mở rộng, nâng cao tính trong sáng và có một chút gì đó rất “lịch lãm” !!!

Kinh nghiệm ôn thi chứng chỉ ở cấp đại học

Tạo ứng dụng Command Line bằng C#

Command-line Building With csc.exe (Part 1) | Nixforest (wordpress.com)

Command-line Building With csc.exe (Part 2) | Nixforest (wordpress.com)

Ôn thi FE | Nixforest (wordpress.com)

Kinh nghiệm ôn thi FE (phần 2) | Nixforest (wordpress.com)

Kinh nghiệm ôn thi FE (phần 3) | Nixforest (wordpress.com)

Ôn tập OOP & OS | Nixforest (wordpress.com)

Ôn bài tập lớn môn HĐH OS | Nixforest (wordpress.com)

Nên chọn công nghệ Java hay .NET ? | Nixforest (wordpress.com)

Categories

Recent posts