[Object-Oriented + Ada] Bài 4 – Abstraction & Inteface

Tính Trừu Tượng Abstraction được Ada 95 hỗ trợ với khái niệm Interface – được hiểu là giao diện lập trình. Khái niệm này cũng xuất phát từ cuộc sống hàng ngày khi mà chúng ta có các đồ vật có thể được thiết kế với một hoặc nhiều nhóm tính năng. Ví dụ như

Tính Trừu Tượng Abstraction được Ada 95 hỗ trợ với khái niệm Interface – được hiểu là giao diện lập trình. Khái niệm này cũng xuất phát từ cuộc sống hàng ngày khi mà chúng ta có các đồ vật có thể được thiết kế với một hoặc nhiều nhóm tính năng. Ví dụ như một chiếc USB lưu trữ được thiết kế kiêm một chiếc card-wifi thì sẽ có hai giao diện sử dụng là duyệt tệp lưu trữ và bảng điều khiển chức năng kết nối.

Interface

Một object bất kỳ trong môi trường phần mềm cũng có thể được thiết kế như vậy, và nhiều object khác nhau có thể sẽ có cùng giao diện chức năng. Khái niệm Interface được sử dụng để tạo ra một bản khai báo ở dạng thiết kế trừu tượng về một giao diện chức năng. Sau đó, các kiểu dữ liệu khác nhau có thể chọn khai báo để triển khai Interface này bằng một cú pháp lệnh ngắn gọn.

Và kết quả là trình biên dịch sẽ không dừng nhắc nhở cho đến khi các kiểu dữ liệu có khai báo Interface này đã được viết đầy đủ code triển khai chi tiết cho các sub-program có tên được liệt kê trong định nghĩa của Interface.

package Being is
   
   type Entity is interface;
   type Class is access all Entity'Class;

   procedure Meditate (Self: in Entity) is abstract;

end Being;

Ở đây chúng ta đang định nghĩa kiểu Being.Entity là một interface và không có mô tả chi tiết. Vì vậy nên các primitive hay các sub-program khác có sử dụng các tham số kiểu này sẽ không thể viết logic xử lý chi tiêt được và đều phải dừng lại ở cấp độ khai báo trừu tượng is abstract.

Khi chúng ta sử dụng kiểu Being.Entity trong thao tác kế thừa của một kiểu tagged record nào đó thì chúng ta sẽ có thể nhờ trình biên dịch nhắc nhở cho đến khi các sub-program này được triển khai đầy đủ code chi tiết ở kiểu kế thừa.

Bây giờ chúng ta sẽ thử khai báo kiểu Coder có áp dụng interface này và chạy code trước khi bổ sung thêm procedure Meditate.

with Person; use Person;
with Being; use Being;

package Person.Coder is

   -- ...

   type Entity is
      new Person.Entity
      and Being.Entity
   with record
      Intellect : Bit;
   end record;
   
   -- ...

end Person.Coder;
alr run

error: type must be declared abstract or "Meditate" overridden
error: "Meditate" has been inherited from subprogram at being.ads
compilation of main.adb failed

Thông báo này nhắc nhở chúng ta là kiểu Coder.Entity đang kế thừa kiểu Being.Entity là một interface và cần phải override phương thức Meditate, hoặc chuyển thành định nghĩa kiểu trừu tượng abstract.

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

package body Person.Coder is

   -- ...

   overriding
   procedure Meditate (Self: in Entity) is
      -- local
   begin
      Put_Line ("Meditating...");
   end Meditate;
   
end Person.Coder;

Bây giờ chúng ta sẽ thử tham chiếu tới một object Coder.Entity bằng con trỏ Being.Class và truy xuất thuộc tính Name.

with Ada.Text_IO; use Ada.Text_IO;
with Person; use Person;
with Person.Coder; use Person.Coder;
with Being; use Being;

procedure Main is
   Me :  Being.Class;
begin
   Me := new Coder.Entity'
      ( Name => "Semi Dev_   "
      , Intellect => 0 );

   Put_Line ("My name: " & Me.Name);
end Main;
alr run

error: no selector "Name" for type "Entity'Class" defined at being.ads

Do chúng ta đang sử dụng một con trỏ kiểu access all Being.Entity'Class, lúc này tất cả những gì mà con trỏ này có thể nhìn thấy là các thành phần trong giao diện interface Being. Tất cả các khía cạnh khác của object Coder.Entity vẫn ở đó nhưng chỉ đơn giản là chúng ta không thể nhìn thấy từ góc nhìn của Being.Entity.

Ở đây chúng ta lưu ý là Ada chỉ hỗ trợ kế thừa từ duy nhất một kiểu record có định nghĩa chi tiết, và cho phép áp dụng nhiều interface bằng các từ khóa and.

type Coder is
   new Person
   and Being
   and Living
   and Thinking
   and ...
with record
   -- fields ...
end record;

Tính năng này cũng hoàn toàn tương tự với các ngôn ngữ lập trình OOP phổ biến hiện tại.

Abstract record

Oh.. như vậy là chúng ta còn có thêm một yếu tố nữa xuất hiện trong thông báo lỗi của trình biên dịch; Đó là chúng ta có thể định nghĩa một kiểu record trừu tượng abstract. Khái niệm này tương đương với abstract class trong các ngôn ngữ lập trình OOP phổ biến.

Định nghĩa abstract record có thể có code triển khai chi tiết, tuy nhiên chúng ta sẽ không thể tạo ra object từ cú pháp new như với các kiểu record thông thường. Thêm vào đó là bởi vì các abstract record có thể được định nghĩa chi tiết hoặc chỉ khai báo đơn giản giống như interface nên các sub-program liên quan cũng có thể có code triển khai chi tiết hoặc là phải khai báo abstract.

Trong trường hợp cần tạo ra một bản mẫu cho nhiều kiểu record mô phỏng các thực thể có nhiều điểm chung, chúng ta có thể nghĩ tới việc sử dụng abstract record để giảm thiểu lượng code định nghĩa lặp, và hiển nhiên là cả các sub-program xoay quanh abstract record cũng vậy.

Bây giờ chúng ta sẽ chuyển kiểu Person.Entity thành abstract record và khai báo thêm một procedure Learn ở dạng abstract chưa có code triển khai.

package Person is

   type Entity is abstract tagged record
      Name : String (1 .. 12);
   end record;

   procedure Learn (Self: in Entity) is abstract;

end Person;

Code sử dụng tại main sẽ chỉ đơn giản là cố gắng tạo ra object từ abstract record này.

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

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

   Put_Line ("My name: " & Me.Name);
end Main;
alr run

main.adb: error: cannot allocate abstract object
person-coder.ads: error: type must be declared abstract or "Learn" overridden
person-coder.ads: error: "Learn" has been inherited from subprogram at person.ads

Thông báo lỗi đầu tiên là ở code sử dụng tại main, thao tác tạo object từ kiểu abstract là không hợp lệ; Và các thông báo lỗi tiếp theo là ở package Person.Coder chúng ta đang có kiểu Coder.Entity kế thừa từ abstract Person.Entity nhưng chưa triển khai đủ các sub-program abstract liên quan, cụ thể là procedure Learn vừa định nghĩa.

Bổ sung khai báo procedure Learn và code triển khai cho package Person.Coder.

with Person; use Person;
with Being; use Being;

package Person.Coder is

   -- ...

   type Entity is
      new Person.Entity
      and Being.Entity
   with record
      Intellect : Bit;
   end record;

   overriding
   procedure Learn (Self: in Entity);
   
   -- ...
   
end Person.Coder;
with Ada.Text_IO; use Ada.Text_IO;
with Person; use Person;

package body Person.Coder is

   -- ...

   overriding
   procedure Learn (Self: in Entity) is
      -- local
   begin
      Put_Line ("Learning...");
   end Learn;

end Person.Coder;

Ở code sử dụng tại main chúng ta sẽ sửa lại thao tác khởi tạo object từ kiểu Coder.Entity và gọi phương thức Learn vừa định nghĩa bổ sung.

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

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

   Me.Learn;
end Main;
alr run

Learning...

[Object-Oriented + Ada] Bài 5 – Encapsulation & Package Privacy

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 đầ