2009-03-09 6 views
5

Bạn có thể cho tôi một sự hiểu biết gần như đơn giản về lớp trừu tượng so với sử dụng thừa kế và giúp tôi để tôi có thể thực sự hiểu khái niệm và cách triển khai không? Tôi có một dự án mà tôi đang cố gắng hoàn thành và bị mất về cách thực hiện. Tôi đã trò chuyện với giáo sư của mình và bị nói khá nhiều, nói rằng nếu tôi không thể đoán ra được, có lẽ tôi chưa sẵn sàng cho khóa học. Tôi đã TĂNG LƯỢNG các khóa học tiên quyết và vẫn gặp khó khăn khi hiểu các khái niệm này.Bạn có thể giúp tôi hiểu trong một ví dụ thực tế về các lớp trừu tượng sử dụng so với các giao diện không?

Để làm rõ, dự án như tôi đã thực hiện cho đến thời điểm này là dưới đây. Tôi chưa có lớp học chó/mèo vv. Bạn có thể cho tôi một con trỏ không. Tôi không yêu cầu ai cho tôi câu trả lời. Tôi chỉ mất đi nơi để đi với điều này. Tôi tham gia các khóa học trực tuyến và những nỗ lực giao tiếp của tôi với tôi đã gây rắc rối. Tôi vừa học xong 4.0 với tất cả các khóa học khác của mình, vì vậy tôi sẵn sàng bỏ công sức vào, nhưng tôi đã mất đi sự hiểu biết về các khái niệm này và cách THỰC HIỆN áp dụng chúng.

Bất kỳ nhận xét hoặc trợ giúp nào sẽ giúp tôi tiến xa hơn trong dự án này?

Các mô tả về những gì tôi đang thực hiện như sau:

Tổng quan:

Mục đích của bài tập này là chứng minh việc sử dụng các giao diện, thừa kế, Các lớp trừu tượng, và Đa hình. Nhiệm vụ của bạn là lấy vỏ chương trình được cung cấp và THÊM các lớp thích hợp thích hợp và lớp/phương pháp tương ứng để có được chương trình hoạt động chính xác này. Bạn có thể không thực hiện thay đổi đối với bất kỳ mã nào được cung cấp , bạn chỉ có thể thêm các lớp học bạn viết. Mặc dù có nhiều cách để làm cho chương trình hoạt động, bạn phải sử dụng các kỹ thuật mà chứng minh việc sử dụng Giao diện,
Thừa kế, Lớp trừu tượng và Đa hình. Một lần nữa, để làm rõ, bạn có thể thêm vào mã được cung cấp nhưng bạn không thể thay đổi hoặc xóa bất kỳ mã nào trong số nó. Mã được cung cấp sẽ hoạt động với rất ít mã bổ sung và sẽ đáp ứng các yêu cầu của bài tập.

Nếu bạn hoàn thành công việc chuyển nhượng , chương trình của bạn nên đầu ra các báo cáo sau khi chạy:

Tên tôi là Spot, I am a Dog

Tên tôi là Felix, tôi là một Cát

Yêu cầu:

1) Bạn phải có một lớp trừu tượng cơ sở gọi là 'động vật' mà từ đó các Chó và Mèo c lasses lấy được.

2) Lớp Cơ sở động vật phải lấy được từ Giao diện 'IAnimal', là lớp duy nhất có nguồn gốc từ IAnimal.

3) Vì tất cả các động vật có một tên và một tên không phải là một thuộc tính đó là cụ thể cho một con chó hoặc một con mèo, các động vật

lớp cơ sở nên có nơi tên được lưu trữ và nơi WhatIsMyName get-tài sản được thực hiện.

4) Bạn sẽ cần phải tạo một Dog và một lớp học Cat sẽ chỉ lấy được từ lớp cơ sở Thú.

5) Lớp Dog và Cat nên triển khai thuộc tính getAmI và trả lại giá trị chuỗi thích hợp.

Mã bạn không thể thay đổi:

using System; 

namespace IT274_U2 
{ 
    public interface IAnimal 
    { 
     string WhatAmI { get; } 
     string WhatIsMyName { get; } 
    } 

    public class TesterClass 
    { 
     public static void DescribeAnimal(IAnimal animal) 
     { 
      Console.WriteLine("My name is {0}, I am a {1}", animal.WhatIsMyName, animal.WhatAmI); 
     } 

     static void Main(string[] args) 
     { 
      Dog mydog = new Dog("Spot"); 
      Cat mycat = new Cat("Felix"); 
      DescribeAnimal(mydog); 
      DescribeAnimal(mycat); 
     } 
    } 
} 

///////////////////////

Mã tôi' đã viết cho đến thời điểm này:

using System; 


namespace IT274_U2 
{ 
    public interface IAnimal 
    { 
     string WhatAmI { get; } 
     string WhatIsMyName { get; } 
    } 


    public class Dog 
    { 
     public abstract string WhatAmI 
     { 
      get; 
      set; 
     } 
    }//end public class Dog 

    public class Cat 
    { 
    public abstract string WhatIsMyName 
    { 
     get; 
     set; 
    } 
    }//end public class Cat 

    public abstract class Animal : IAnimal 
    { 
    // fields 
    protected string Dog; 
    protected string Cat; 

        // implement WhatIsMyName 

    //properties 
    public abstract String Dog 
    { 
     get; 
     set; 
    } 
    public abstract String Cat 
    { 
     get; 
     set; 
    } 
    public abstract string WhatIsMyName(); 

    } //end public abstract class Animal 


    public class TesterClass 
    { 
     public static void DescribeAnimal(IAnimal animal) 
     { 
      Console.WriteLine("My name is {0}, I am a {1}", animal.WhatIsMyName, animal.WhatAmI); 
     } 

     static void Main(string[] args) 
     { 

      Dog mydog = new Dog("Spot"); 
      Cat mycat = new Cat("Felix"); 
      DescribeAnimal(mydog); 
      DescribeAnimal(mycat); 
     } 
    } 
} 
+0

Mẫu mã trong bài đăng của bạn là mã mà bạn không được phép sửa đổi? –

+0

@Mike - chỉ là những gì tôi sắp hỏi :) –

+0

cập nhật câu hỏi ở trên với mã do giáo sư đưa ra và mã tôi đã viết. Cảm ơn vì điều đó, tôi thậm chí còn không cân nhắc. Anh ấy đã nói với tôi rằng tôi đã có đề cương chung ok, nhưng một lần nữa anh ấy đã đẩy tôi ra khỏi phiên khá nhanh, vì vậy chỉ nhìn qua một thời gian ngắn. – SheldonH

Trả lời

6

EDIT:

tôi đã đưa ra những cơ thể của mã cho mỗi lớp ra - Nếu bạn muốn xem câu trả lời của tôi, có một cái nhìn tại các biên tập sửa đổi :)

Trước hết chúng ta định nghĩa interface

public interface IAnimal 
{ 
    string WhatAmI { get; } 
    string WhatIsMyName { get; } 
} 

Bất kỳ lớp nào triển khai giao diện này phải triển khai các thuộc tính này. Một giao diện giống như một hợp đồng; một lớp thực hiện một giao diện đồng ý cung cấp việc thực hiện các phương thức của giao diện, các sự kiện thuộc tính hoặc các bộ chỉ mục.

Tiếp theo, chúng ta cần phải xác định abstract class Animal bạn

public abstract class Animal : IAnimal 
{ 
    //Removed for Training, See Edit for the code 
} 

Thực tế là lớp là abstract chỉ ra rằng lớp chỉ nhằm mục đích là một lớp cơ sở cho các lớp khác. Chúng tôi đã thực hiện cả hai thuộc tính của giao diện và cũng có một trường riêng để lưu tên động vật. Thêm vào đó, chúng ta đã tạo ra một đối tượng truy cập đặc tính của WhatAmI để chúng ta có thể thực hiện logic truy cập đặc tính riêng của chúng ta trong mỗi lớp dẫn xuất và cũng đã định nghĩa một hàm tạo chấp nhận đối số chuỗi và gán giá trị cho trường riêng tư _name.

Bây giờ, chúng ta hãy xác định CatDog của chúng tôi lớp

public class Dog : Animal 
{ 
    //Removed for Training, See Edit for the code 
} 

public class Cat : Animal 
{ 
    //Removed for Training, See Edit for the code 
} 

Cả lớp kế thừa từ Animal và từng có một nhà xây dựng định nghĩa một đối số chuỗi và truyền rằng lý lẽ như một tham số để các nhà xây dựng cơ bản. Ngoài ra, mỗi lớp thực hiện accessor thuộc tính riêng của nó cho WhatAmI, trả về một chuỗi kiểu của chúng tương ứng.

Đối với phần còn lại của mã

public class Program 
{ 
    public static void DescribeAnimal(IAnimal animal) 
    { 
     Console.WriteLine("My name is {0}, I am a {1}", animal.WhatIsMyName, animal.WhatAmI); 
    } 

    static void Main(string[] args) 
    { 
     Dog mydog = new Dog("Spot"); 
     Cat mycat = new Cat("Felix"); 
     DescribeAnimal(mydog); 
     DescribeAnimal(mycat); 
     Console.ReadKey(); 
    } 
} 

phương pháp tĩnh DescribeAnimal chấp nhận một IAnimal như một cuộc tranh cãi và viết ra các giá trị được trả về bởi các accessors tài sản WhatIsMyNameWhatAmI cho thông qua trong IAnimal.

Kể từ Animal thực hiện IAnimal và cả DogCat kế thừa từ Animal, bất kỳ Cat hoặc Dog đối tượng có thể được thông qua như một tham số cho phương thức DescribeAnimal.

Tôi hy vọng rằng tôi đã giải thích rõ ràng điều này, Nếu bất cứ ai cảm thấy sự lựa chọn của tôi về từ cần thắt chặt, xin vui lòng bình luận và tôi sẽ rất vui khi chỉnh sửa câu trả lời của tôi.

+0

một cách trung thực, tôi đã tránh đọc hết tất cả bình luận của bạn vì tôi không thích "đọc" câu trả lời cho dự án của tôi, nhưng fyi liên kết bạn đã cung cấp cho MSDN đã cung cấp mã "mẫu" đã giúp tôi di chuyển dự án của mình cảm ơn vì sự giúp đỡ đó! – SheldonH

+1

những thứ tốt :) Hãy cho tôi biết nếu bạn muốn tôi xóa câu trả lời của tôi, hoặc để nó trở lại khi bạn đã có giải pháp của mình –

+0

không, tôi đánh giá cao sự trợ giúp, tôi sẽ xem xét sau khi tôi đã hoàn thành bên của tôi để xem tôi có ý tưởng đúng không. điều này thực sự khó khăn hơn nhiều so với hầu hết các khái niệm ... sách giáo khoa thực sự hút, tôi có trình độ đọc cao, nhưng rất khô, tôi nhận được 1-2 đoạn trước khi mất tiêu điểm – SheldonH

6

Sự khác biệt chính giữa giao diện và lớp trừu tượng là trong giao diện bạn chỉ xác định phương thức công khai và thuộc tính của đối tượng thực hiện giao diện này e. Một lớp trừu tượng cung cấp một số cơ sở thực hiện, nhưng có một số "khoảng trống" - phương pháp trừu tượng mà người thừa kế cần phải thực hiện.

Tôi sẽ không làm bài tập về nhà cho bạn, nhưng một gợi ý: lớp học động vật không nên chứa bất cứ điều gì cụ thể cho chó và mèo.

0

Lớp trừu tượng: Thiết lập cơ sở cho các lớp học có nguồn gốc mà chúng cung cấp hợp đồng cho tất cả các lớp có nguồn gốc. Nó thực thi heirarchies

Giao diện:

Một giao diện không phải là một lớp học, một định nghĩa của phương pháp.

Một lớp có thể hít nhiều giao diện nhưng chỉ có một lớp trừu tượng.

Tôi hy vọng rằng sẽ giúp

+0

"Một giao diện không phải là một lớp học, một định nghĩa của phương pháp" OH. .. tôi nghĩ đó là một loại của một lớp học ?? bạn đang nói nó không phải là? Điều đó giúp ích rất nhiều! – SheldonH

+0

"một giao diện là một loại đặc biệt của lớp C# có hỗ trợ stustituion polymorphic và latebinding mà không thừa kế." Thiết kế phần mềm hiện đại - RIchard Weiner Điều này được cho là mâu thuẫn với tuyên bố rằng một giao diện không phải là một lớp, bất kỳ bình luận nào? – SheldonH

+0

@Sheldon Tôi đứng bởi nhận xét của tôi rằng một giao diện không phải là một lớp học nó là một hợp đồng – CodeMonkey

3

Bạn thân thiết, nhưng việc này khó khăn hơn mức cần thiết.

Tôi không muốn cung cấp cho bạn câu trả lời;) nhưng đây là một vài gợi ý.

Trước tiên, bạn đang tạo 3 lớp và 1 giao diện. Tuy nhiên, một điều tôi tin rằng bạn có thể bị thiếu là bạn cần 3 loại đối tượng khác nhau ở đây (từ "ít được xác định" đến "được xác định nhiều nhất"):

1) Giao diện
Đây là IAnimal - và có thể được thực hiện bởi bất kỳ thứ gì có thể hoạt động giống như một con vật

2) Lớp cơ sở trừu tượng Đây là calss động vật - bất kỳ thứ gì LÀ động vật đều lấy được từ Động vật nhưng không thể tạo trực tiếp.Nếu bạn giả vờ là Thượng đế, bạn không tạo ra Động vật, bạn tạo ra Chó, Mèo, Sóc hoặc FuzzyBunny

3) Thực hiện Bê tông Động vật Đây là những lớp thực tế. Đây là những gì bạn tạo ra. Chó hoặc Mèo trong trường hợp của bạn. Bí quyết ở đây là bạn chỉ có thể tạo các lớp cụ thể, nhưng bạn có thể sử dụng IAnimal hoặc Animal (giao diện hoặc lớp cơ sở trừu tượng) để thao tác và làm việc với bất kỳ động vật nào (hoặc trong trường hợp giao diện) một động vật)

+0

cảm ơn rất nhiều cho câu trả lời kỹ lưỡng. Tôi cần các con trỏ, không phải là câu trả lời. Tôi chỉ bối rối, vì vậy loại trợ giúp này sẽ giúp tôi tăng tốc nhanh chóng thay vì bỏ cuộc! Ăn trưa lần đến và tôi sẽ đọc tất cả các câu trả lời và làm việc thông qua dự án của tôi để hoàn thành ngày hôm nay. REALLY đánh giá cao sự giúp đỡ – SheldonH

+0

Đọc lại những gì tôi đã đăng và xem các lớp Dog và Cat của bạn - Bất kỳ việc sử dụng "trừu tượng" nào trên một thuộc tính sẽ làm cho lớp trở thành một lớp trừu tượng, vì vậy bạn buộc Dog và Cat trở thành thể loại của tôi 2. Hãy xem từ khóa "ghi đè" trong C# - nó có thể giúp bạn hiểu thêm một chút. –

1
  1. Giao diện là hợp đồng. Đây là nơi bạn muốn mô tả các chức năng mà bạn sẽ cung cấp, mà không có bất kỳ chi tiết triển khai nào

  2. Lớp trừu tượng là một lớp có mục đích chia sẻ chi tiết triển khai giữa các lớp con của nó. Vì nó chỉ ở đây vì mục đích chia sẻ/nhân tố mã, nó không thể được khởi tạo

  3. lớp thực tế của bạn sẽ kế thừa từ lớp trừu tượng của bạn và triển khai các chức năng của lớp cụ thể trong khi sử dụng mã được chia sẻ trong lớp trừu tượng nếu cần.

0

Về cơ bản, một giao diện xác định 'hợp đồng' (nghĩa là một tập hợp các hoạt động/thuộc tính) mà tất cả người triển khai phải cung cấp. Trong trường hợp này giao diện IAnimal yêu cầu các thuộc tính WhatAmI và WhatIsMyName. Các lớp trừu tượng có thể cung cấp một số chức năng nhất định, nhưng cũng sẽ để lại một số hoạt động phải được thực hiện bởi các lớp con của chúng.

Trong trường hợp ví dụ của bạn, lớp Cơ sở động vật có thể cung cấp chức năng WhatIsMyName vì 'Tên' là thuộc tính của tất cả các động vật. Tuy nhiên, nó không thể cung cấp thuộc tính 'WhatAmI', vì chỉ có các lớp con cụ thể mới biết chúng là gì.

Vấn đề với mã mẫu mà bạn đăng, là thét lớp Thú đã biết đó là lớp con, mà nó không nên có

+0

Tôi không muốn downvote vì đây là câu trả lời đúng về mặt kỹ thuật, nhưng vì rõ ràng là poster đang cố hiểu điều này cho bài tập về nhà, tôi thấy thực tế là bạn đã cho anh ta câu trả lời đáng thất vọng nhất. –

+0

điểm tốt ... tôi không phải là một để sao chép và dán lạm dụng mặc dù, vì vậy nó không phải là một vấn đề. Tôi cảm thấy như tôi đã có thể một chút TOO giúp đỡ nhiều mặc dù. Tôi sẽ lấy những gì tôi có thể nhận được mặc dù, tôi đã dành gần 2 tuần bị mắc kẹt không hiểu biết. – SheldonH

0

Một giả thuyết khác - (hơi off topic, nhưng vẫn có liên quan)

tôi đề nghị, cho mục đích học tập, để tránh các thuộc tính tự động. Nó sẽ giúp bạn hiểu những gì đang xảy ra nếu bạn thực hiện chúng một cách rõ ràng.

Ví dụ, thay vì thực hiện:

class SomeClass 
{ 
    public string MyProperty 
    { 
     get; 
     set; 
    } 
} 

Cố gắng thực hiện điều này bản thân:

class SomeClass 
{ 
    public string MyProperty 
    { 
     get 
     { 
      return "MyValue"; // Probably a private field 
     } 
     set 
     { 
      // myField = value; or something like that 
     } 
} 

Tôi đề cập đến điều này bởi vì nó sẽ giúp bạn trong trường hợp cụ thể này. Vì bạn đang sử dụng các thuộc tính tự động, trình biên dịch là "điền vào chỗ trống" cho bạn, và trong trường hợp của bạn, tôi nghĩ rằng nó ngăn cản bạn nhận được một số lỗi trình biên dịch rất hữu ích. Khi cố gắng hiểu các khái niệm này hoạt động như thế nào, làm công việc của bản thân thường làm cho nó dễ dàng hơn, không khó hơn.

1

Thành thật mà nói nó làm tôi sợ số lượng người trong ngành không biết điều này bất kể đó là câu hỏi về bài tập về nhà hay không. Do đó tôi sẽ trả lời.

Giao diện triển khai trừu tượng và các lớp trừu tượng cũng vậy. Không có "vs" bởi vì bạn có thể tạo một lớp trừu tượng thực hiện một giao diện. Vì vậy, đừng nghĩ rằng họ đang chiến tranh với nhau.

Do đó, EITHER có thể được sử dụng khi bạn không muốn người tiêu dùng biết quá nhiều về việc triển khai.Một giao diện tốt hơn một chút ở công việc này bởi vì nó không có triển khai thực hiện, nó chỉ cho biết các nút mà người tiêu dùng có thể nhấn các giá trị mà họ nhận được và gửi đến nơi lớp trừu tượng có thể có nhiều hơn một chút (hoặc thậm chí nhiều hơn nữa!) . Vì vậy, nếu bạn chỉ mất điểm này trên tàu bạn chỉ thực sự cần giao diện. Ergo, điểm hai:

Là lớp trừu tượng được sử dụng khi bạn muốn chia sẻ mã chung giữa hai triển khai khác nhau của một giao diện. Trong kịch bản này, hai triển khai cụ thể đều được kế thừa từ lớp trừu tượng thực hiện giao diện.

Ví dụ đơn giản là IDataStore. Kho dữ liệu SavingToATextFile chỉ là một lớp thực hiện IDataStore. Tuy nhiên MsSqlDataStore và MySqlDataStore sẽ chia sẻ mã chung. Cả hai đều sẽ kế thừa từ lớp trừu tượng SqlDataStore mà triển khai IDataStore.

2

Nói chung:

  • Một giao diện mô tả các phương pháp một đối tượng sẽ trả lời. Nó là một hợp đồng mà đối tượng cam kết thỏa mãn.

  • Lớp trừu tượng mô tả chức năng cơ bản và để chức năng chuyên biệt cho lớp con.

Vì vậy, về cơ bản bạn sử dụng giao diện khi bạn muốn các đối tượng khác nhau về bản chất, phản hồi cùng một phương pháp cụ thể.

Và bạn sử dụng lớp trừu tượng khi bạn cần phải có phiên bản chuyên biệt của một số lớp.

Giả sử bạn muốn tạo một hệ thống mà bất kỳ loại đối tượng nào có thể được nhận diện bởi một id duy nhất và bạn không quan tâm đến lớp mà chúng thuộc về.

Bạn có thể có:

  • vật

  • Giao thông vận tải

  • tiện ích máy tính.

  • Mọi thứ.

Vì họ là những chủ đề không liên quan, bạn có thể chọn để thực hiện và giao diện, chúng ta hãy nói:

public interface IIdentifiable 
{ 

     public long GetUniqueId(); 
} 

Và tất cả các lớp học mà muốn để đáp ứng hợp đồng này sẽ triển khai giao diện đó.

public class IPod: IIdentifiable 
{ 
     public long GetUniqueId() 
     { 
      return this.serialNum + this.otherId; 
     } 
} 

public class Cat: IIdentifiable 
{ 
     public long GetUniqueId() 
     { 
      return this..... 
     } 
} 

Cả hai, và iPod và một Cát, có bản chất rất khác nhau, nhưng cả hai đều có thể đáp ứng với các phương pháp "GetUniqueId()", sẽ được sử dụng trong hệ thống danh mục.

Sau đó, nó có thể được sử dụng như thế này:

... 

    IIdentifiable ipod = new IPod(); 
    IIdentifiable gardfield = new Cat(); 

    store(ipod); 
    store(gardfield); 


    .... 
    public void store(IIdentifiable object) 
    { 

     long uniqueId = object.GetUniqueId(); 
     // save it to db or whatever. 
    } 

Mặt khác, bạn có thể có một lớp trừu tượng xác định tất cả các hành vi phổ biến đối tượng có thể có, và để cho các lớp con xác định phiên bản đặc biệt.

public abstract class Car 
    { 
     // Common attributes between different cars 
     private Tires[] tires; // 4 tires for most of them 
     private Wheel wheel; // 1 wheel for most of them. 

     // this may be different depending on the car implementation. 
     public abstract void move(); 
    } 


    class ElectricCar: Car 
    { 
     public void move() 
     { 
     startElectricEngine(); 
     connectBattery(); 
     deploySolarShields(); 
     trasnformEnertyToMovemetInWheels(); 
     } 
    } 

    class SteamCar: Car 
    {  
     public void move() 
     { 
      fillWithWather(); 
      boilWater(); 
      waitForCorrectTemperature(); 
      keepWaiting(); 
      releasePreasure.... 
     } 
    } 

Ở đây, hai loại ô tô, thực hiện phương pháp "di chuyển" theo những cách khác nhau, chúng vẫn chia sẻ những thứ phổ biến trong lớp cơ sở.

Để làm cho mọi thứ trở nên thú vị hơn, hai chiếc xe này cũng có thể triển khai giao diện de IIdentifiable, nhưng bằng cách đó, họ chỉ cam kết đáp ứng phương pháp GetUniqueId chứ không phải bản chất của xe hơi. Đó là lý do tại sao chiếc xe tự nó có thể không thực hiện giao diện đó. Tất nhiên, nếu việc xác định có thể dựa trên các thuộc tính chung mà những chiếc xe có thể có, thì GetIdentifiableId có thể được thực hiện bởi lớp cơ sở và các lớp con sẽ kế thừa phương thức đó.

// trường hợp 1 ... mỗi lớp con thực hiện các giao diện

public class ElectricCar: Car, IIdentifiable 
    { 
     public void move() 
     { 
     ..... 
     } 
     public long GetUniqueId() 
     { 
     .... 
     } 
    } 

    public class SteamCar: Car, IIdentifiable 
    { 
     public void move() 
     { 
     ..... 
     } 
     public long GetUniqueId() 
     { 
     .... 
     } 
    } 

Trường hợp 2, các lớp cơ sở thực hiện các giao diện và lợi ích subclass từ nó.

public abstract class Car: IIdentifiable 
    { 
     // common attributes here 
     ... 
     ... 
     ... 



     public abstract void move(); 
     public long GetUniqueId() 
     { 
      // compute the tires, wheel, and any other attribute 
      // and generate an unique id here. 
     } 
    } 

    public class ElectricCar: Car 
    { 
     public void move() 
     { 
     ..... 
     } 
    } 

    public class SteamCar: Car 
    { 
     public void move() 
     { 
     ..... 
     } 
    } 

Tôi hy vọng điều này sẽ hữu ích.