[Object-Oriented + Ada] Bài 1 – Object-Oriented Aspects

Ở thời điểm hiện tại – 12/1/2023 – thì mình đã sắp xếp lại các bài viết của Sub-Series Procedural Programming và lược bỏ bớt những bài viết về các công cụ liên quan tới Lập Trình Hướng Đối Tượng OOP để dành cho Sub-Series mới này. Mục đích là để khoanh vùng định nghĩa

Ở thời điểm hiện tại – 12/1/2023 – thì mình đã sắp xếp lại các bài viết của Sub-Series Procedural Programming và lược bỏ bớt những bài viết về các công cụ liên quan tới Lập Trình Hướng Đối Tượng OOP để dành cho Sub-Series mới này. Mục đích là để khoanh vùng định nghĩa của các mô hình lập trình được giới thiệu trong Series tổng, cũng giống như việc để dành các yếu tố Functional của Elm cho Sub-Series riêng về Funtional Programming.

Về việc lựa chọn ngôn ngữ để tìm hiểu về mô hình lập trình Hướng Đối Tượng OOP - Object-Oriented Programming thì mình đã chọn Java để học và chia sẻ kiến thức qua các bài viết trong Sub-Series [Object-Oriented + Java] tiếp theo. Còn Sub-Series [Object-Oriented + Ada] này chỉ là một phần bổ sung để hoàn thiện cho việc giới thiệu ngôn ngữ Ada.

OOP in Ada

Câu chuyện OOP của Ada cũng bắt đầu cùng khoảng thời gian mà Java xuất hiện năm 1995. Ở thời điểm đó thì các kỹ sư thiết kế phần cứng đã triển khai thành công các kiến trúc vi xử lý lúc bấy giờ để hỗ trợ việc mô phỏng các Đối Tượng object trong bộ nhớ đệm. Điều này đã tạo điều kiện cho việc áp dụng tư duy lập trình Hướng Đối Tượng Object-Oriented đã được thảo luận rất nhiều tại MIT từ những năm 1950, và sau đó đã được Sir Alan Kay giới thiệu lại vào năm 1966 để rồi trở thành một trong số thuật ngữ lập trình được nhắc đến nhiều nhất.

Mặc dù AdaJava có cú pháp sử dụng khác nhau rất nhiều, tuy nhiên các yếu tố thuộc về OOP nói chung thì không bị ràng buộc bởi bất kỳ ngôn ngữ nào bởi tất cả đều xuất phát từ các trang tài liệu trong phòng Lab của MIT, và sau thời điểm được Sir Alan Kay giới thiệu lại thì OOP cũng lại càng có thêm được nhiều sự đồng thuận của cộng đồng về những khái niệm trọng tâm thiết yếu.

Và ở đây, tại Sub-Series này thì chúng ta sẽ chỉ đề cập tới những khía cạnh đặc trưng thuộc về OOP và những công cụ mà Ada cung cấp để triển khai những tính năng này trong code. Bao gồm:

  • Interaction – Tính Tương Tác là thể hiện cơ bản nhất của OOP trong khuynh hướng mô phỏng các đối tượng logic như các thực thể sống, với khả năng biểu thị sự tương tác hay phương thức hành động qua các sub-program với cú pháp subject.do_something_to(another); Được Ada 95 hỗ trợ với khái niệm Phép Thực Thi Nguyên Bản Primitive Operation, hoặc gọi ngắn gọn là Primitive.

  • Inheritance – Sự Kế Thừa là thể hiện thiết yếu tiếp theo giúp giảm thiểu việc phải viết lặp lại các yếu tố trong định nghĩa của các đối tượng logic có liên quan. Ví dụ các đối tượng dữ liệu mô phỏng người Person hiển nhiên sẽ có những yếu tố được sử dụng chung với các đối tượng dữ liệu mô phỏng lập trình viên Coder theo mô tuýp Coder được mở rộng từ định nghĩa của Person; Được Ada 95 hỗ trợ với khái niệm Tagged Record.

  • Polymorphism – Tính Đa Hình, mà chính xác hơn thì là Sub-typing Polymorphism, cùng với Inheritance ở trên giúp giảm thiểu thao tác viết lặp lại các định nghĩa sub-program để tiếp nhận vào các đối tượng logic trong cùng cây phả hệ family tree hay hệ thống kế thừa inheritance hierachy. Ví dụ một sub-program tác động lên một trường dữ liệu trong Person hiển nhiên sẽ có thể hoạt động tốt với Coder mà không cần phải viết định nghĩa lại với các mỗi định kiểu tham số đầu vào là PersonCoder; Được Ada 95 hỗ trợ với khái niệm Type'Class, hay còn được gọi là Classwide. Các kiểu Polymorphism còn lại thì không thuộc về OOP nói riêng và chúng ta đã nói đến trong Sub-Series Procedural trước đó.

  • Abstraction – Tính Trừu Tượng được thể hiện trong giai đoạn thiết kế tổng quan phần mềm, khi mà các định nghĩa được viết ở dạng chỉ khai báo các tên định danh của kiểu dữ liệu và các sub-program nhưng không có code mô tả chi tiết; Được Ada 95 hỗ trợ với khái niệm Interface.

  • Encapsulation – Tính Năng Đóng Gói được sử dụng để điều hướng việc tương tác với giao diện lập trình mà một object cung cấp ra bên ngoài. Thay vì code tham chiếu từ bên ngoài có thể trực tiếp chỉnh sửa một trường dữ liệu của một object thì sẽ phải gửi yêu cầu cập nhật trường dữ liệu đó qua một phương thức có dạng như object.set(property, value). Như vậy dữ liệu có thể được kiểm tra và sàng lọc bớt những trường hợp không phù hợp với logic quản lý mà chúng ta muốn xây dựng trong code. Tính năng này rất quan trọng đối với các ngôn ngữ OOP phổ biến nhưng lại không mấy quan trọng đối với các ngôn ngữ có hỗ trợ Design by Contract như Ada; Tuy nhiên vẫn được hỗ trợ rất đầy đủ với khái niệm Package PrivacyPrivate Definition.

Và trong bài viết mở đầu này chúng ta sẽ nói về Interaction với sự hỗ trợ của khái niệm Primitive Operation.

Environment Setup

Sau khi thực hiện xong cái mini project về Tic-Tac-Toe cũng tiêu tốn kha khá thời gian thì mình đã suýt quên mất cách sử dụng Alire để khởi tạo project mới và một số tùy chỉnh cần thiết cho các tệp quản lý project. Vì vậy nên ở đây mình sử dụng một phần của bài viết này để ghi chú lại cách sử dụng căn bản và lúc nào đó nhỡ có quên thì cũng có thể tìm lại ở các bài viết mở đầu project tic-tac-toe trong Sub-Series Procedural trước đó hoặc tại đây.

cd Documents
alr init --bin learn_ada

Success: learn_ada initialized successfully.

Sau khi thư mục learn_ada được Alire khởi tạo xong thì chúng ta cần chỉnh sửa lại tệp quản lý của gprbuild đầu tiên để đổi tên tệp khởi chạy project về main.adb cho quen thuộc. Thêm vào đó là ở phần chỉ định các switch sử dụng kèm lệnh gprbuild chúng ta chỉ cần "-gnat2020", "-gnatX""-gnata" để kích hoạt những tính năng mới nhất của ngôn ngữ. Còn cái danh sách tham số trong tệp cấu hình bổ sung của Alire thì hơi thừa đối với người mới sử dụng Ada.

with "config/learn_ada_config.gpr";

project Learn_Ada is

   for Source_Dirs use ("src/", "src/**", "config/");
   -- for ...
   for Main use ("main.adb");

   package Compiler is
      for Default_Switches ("Ada") use ("-gnat2020", "-gnatX", "-gnata");
   end Compiler;

   -- package ...

end Learn_Ada;

Lưu ý thêm là ở phần chỉ định các thư mục chứa các tệp mã nguồn thì mình có thêm pattern"src/**" để trình biên dịch sẽ duyệt thêm tất cả những thư mục con bên trong thư mục src theo dạng đệ quy. Trong trường hợp không có thói quen sắp xếp các module ở dạng các thư mục xếp lớp nested thì không cần thêm bước này.

...

executables = ["main"]

Cuối cùng là trong tệp quản lý project của Alire phần executables cần trỏ tới đúng tên tệp thực thi sau khi biên dịch xong sẽ tương ứng với tên tệp khởi đầu projectmain.

Primitive & OOP Syntax

Khái niệm Primitive Operation hay được gọi ngắn gọn là Primitive do Ada đặt ra – được sử dụng để chỉ những sub-program được định nghĩa trong cùng package với kiểu dữ liệu của tham số đầu tiên mà sub-program đó tiếp nhận.

Ví dụ, chúng ta có package Person đamg chứa định nghĩa record Entity; Nếu như chúng ta định nghĩa thêm procedure Greet (Self : in Entity) ngay trong package Person thì lúc này procedure Greet đang có tham số đầu tiên chính là kiểu Entity được định nghĩa cùng cấp, và như vậy procedure Greet sẽ được gọi là Primitive của Entity.

package Person is
   
   type Entity;
   type Class is access all Entity;
   
   -- type definition & sub-program declarations
   
   type Entity is record
      Name : String (1 .. 12);
   end record;
   
   procedure Greet (Self: in Entity, Other: in Entity);

private
   
   -- nothing here

end Person;

Và khi sử dụng các Primitive như procedure Greet ở đây thì chúng ta sẽ có thể phát động với cú pháp gọi phương thức phổ biến trong OOP có dạng me.greet(someone);.

with Ada.Text_IO; use Ada.Text_IO;

package body Person is

   procedure Greet
      ( Self : in Entity
      ; Other : in Entity )
   is -- local
   begin
      Put_Line ("Hi, " & Other.Name);
      Put_Line ("I'm " & Self.Name);
   end Greet;
   
end Person;

Như vậy là chúng ta đã có thể bắt đầu mô phỏng các object có khả năng tương tác với nhau trong code sử dụng tại main.

with Ada.Text_IO; use Ada.Text_IO;
with Person; use Person;

procedure Main is
   Me, Lucy : Person.Class;
begin
   Me := new Person.Entity'
      (Name => "Semi Dev_   ");

   Lucy := new Person.Entity'
      (Name => "Lucy        ");

   Me.Greet (Lucy.all);
end Main;

Oh.. thế nhưng tại sao khi khởi tạo các object chúng ta nên sử dụng kiểu con trỏ access thay vì kiểu record nguyên bản? Riêng điểm này thì mình đã phải rà lại và cập nhật gọn gàng nhất cho bài viết về con trỏ access trong Sub-Series Procedural tại đây: Recursive Record & Access Pointer. Khi làm việc với các record, chúng ta sẽ sử dụng pattern này và thao tác dereference bằng khóa .all để tạo một giao diện lập trình chung cho mọi trường hợp.

alr run

Hi, Lucy
I'm Semi Dev_

[Object-Oriented + Ada] Bài 2 – Inheritance & Tagged Record

Nguồn: viblo.asia

Bài viết liên quan

WebP là gì? Hướng dẫn cách để chuyển hình ảnh jpg, png qua webp

WebP là gì? WebP là một định dạng ảnh hiện đại, được phát triển bởi Google

Điểm khác biệt giữa IPv4 và IPv6 là gì?

IPv4 và IPv6 là hai phiên bản của hệ thống địa chỉ Giao thức Internet (IP). IP l

Check nameservers của tên miền xem website trỏ đúng chưa

Tìm hiểu cách check nameservers của tên miền để xác định tên miền đó đang dùn

Mình đang dùng Google Domains để check tên miền hàng ngày

Từ khi thông báo dịch vụ Google Domains bỏ mác Beta, mình mới để ý và bắt đầ