JavaRush /Blog Java /Random-VI /Thiết kế các lớp và giao diện (Bản dịch bài viết)
fatesha
Mức độ

Thiết kế các lớp và giao diện (Bản dịch bài viết)

Xuất bản trong nhóm
Thiết kế các lớp và giao diện (Bản dịch bài viết) - 1

Nội dung

  1. Giới thiệu
  2. Giao diện
  3. Điểm đánh dấu giao diện
  4. Giao diện chức năng, phương thức tĩnh và phương thức mặc định
  5. Các lớp trừu tượng
  6. Các lớp học bất biến (vĩnh viễn)
  7. Lớp ẩn danh
  8. Hiển thị
  9. Di sản
  10. Đa kế thừa
  11. Kế thừa và thành phần
  12. Đóng gói
  13. Các lớp và phương pháp cuối cùng
  14. Cái gì tiếp theo
  15. Tải mã nguồn xuống

1. GIỚI THIỆU

Cho dù bạn sử dụng ngôn ngữ lập trình nào (và Java cũng không ngoại lệ), việc tuân theo các nguyên tắc thiết kế tốt là chìa khóa để viết mã rõ ràng, dễ hiểu và có thể kiểm chứng được; đồng thời làm cho nó tồn tại lâu dài và dễ dàng hỗ trợ giải quyết vấn đề. Trong phần hướng dẫn này, chúng ta sẽ thảo luận về các khối xây dựng cơ bản mà ngôn ngữ Java cung cấp và giới thiệu một số nguyên tắc thiết kế nhằm nỗ lực giúp bạn đưa ra quyết định thiết kế tốt hơn. Cụ thể hơn, chúng ta sẽ thảo luận về các giao diện và giao diện sử dụng các phương thức mặc định (một tính năng mới trong Java 8), các lớp trừu tượng và cuối cùng, các lớp bất biến, tính kế thừa, thành phần và xem lại các quy tắc về khả năng hiển thị (hoặc khả năng truy cập) mà chúng ta đã đề cập ngắn gọn trong phần Phần 1 bài học “Cách tạo và hủy đồ vật” .

2. GIAO DIỆN

Trong lập trình hướng đối tượng , khái niệm giao diện tạo cơ sở cho việc phát triển các hợp đồng . Tóm lại, các giao diện xác định một tập hợp các phương thức (hợp đồng) và mỗi lớp yêu cầu hỗ trợ cho giao diện cụ thể đó phải cung cấp cách triển khai các phương thức đó: một ý tưởng khá đơn giản nhưng mạnh mẽ. Nhiều ngôn ngữ lập trình có giao diện ở dạng này hay dạng khác, nhưng Java nói riêng cung cấp hỗ trợ ngôn ngữ cho việc này. Chúng ta hãy xem một định nghĩa giao diện đơn giản trong Java.
package com.javacodegeeks.advanced.design;

public interface SimpleInterface {
void performAction();
}
Trong đoạn mã trên, giao diện mà chúng tôi gọi là SimpleInterface, chỉ khai báo một phương thức được gọi là performAction. Sự khác biệt chính giữa các giao diện và các lớp là các giao diện phác thảo nội dung liên hệ (chúng khai báo một phương thức), nhưng không cung cấp cách triển khai chúng. Tuy nhiên, các giao diện trong Java có thể phức tạp hơn: chúng có thể bao gồm các giao diện, lớp, số đếm, chú thích và hằng số lồng nhau. Ví dụ:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefinitions {
    String CONSTANT = "CONSTANT";

    enum InnerEnum {
        E1, E2;
    }

    class InnerClass {
    }

    interface InnerInterface {
        void performInnerAction();
    }

    void performAction();
}
Trong ví dụ phức tạp hơn này, có một số hạn chế mà các giao diện áp đặt vô điều kiện lên các cấu trúc lồng nhau và khai báo phương thức, và những hạn chế này được thực thi bởi trình biên dịch Java. Trước hết, ngay cả khi không được khai báo rõ ràng, mọi khai báo phương thức trong giao diện đều là công khai (và chỉ có thể là công khai). Do đó các khai báo phương thức sau đây là tương đương:
public void performAction();
void performAction();
Điều đáng nói là mọi phương thức trong một giao diện đều được khai báo ngầm abstract và thậm chí các khai báo phương thức này cũng tương đương:
public abstract void performAction();
public void performAction();
void performAction();
Đối với các trường không đổi được khai báo, ngoài việc là public , chúng còn được ngầm định là static và được đánh dấu là Final . Vì vậy các khai báo sau đây cũng tương đương:
String CONSTANT = "CONSTANT";
public static final String CONSTANT = "CONSTANT";
Cuối cùng, các lớp, giao diện hoặc số lượng lồng nhau, ngoài việc là public , còn được khai báo ngầm là static . Ví dụ: các khai báo này cũng tương đương với:
class InnerClass {
}

static class InnerClass {
}
Kiểu bạn chọn là sở thích cá nhân, nhưng việc biết những đặc tính đơn giản này của giao diện có thể giúp bạn tránh khỏi việc gõ phím không cần thiết.

3. Điểm đánh dấu giao diện

Giao diện điểm đánh dấu là một loại giao diện đặc biệt không có phương thức hoặc cấu trúc lồng nhau khác. Thư viện Java định nghĩa nó như thế nào:
public interface Cloneable {
}
Các điểm đánh dấu giao diện bản chất không phải là hợp đồng mà là một kỹ thuật hữu ích để "gắn" hoặc "liên kết" một số đặc điểm cụ thể với một lớp. Ví dụ: đối với Cloneable , lớp được đánh dấu là có thể nhân bản được, nhưng cách thực hiện điều này có thể hoặc nên thực hiện không phải là một phần của giao diện. Một ví dụ rất nổi tiếng và được sử dụng rộng rãi khác về điểm đánh dấu giao diện là Serializable:
public interface Serializable {
}
Giao diện này đánh dấu lớp này là phù hợp cho việc tuần tự hóa và giải tuần tự hóa, và một lần nữa, nó không chỉ rõ cách thức thực hiện điều này. Các điểm đánh dấu giao diện có vị trí của chúng trong lập trình hướng đối tượng, mặc dù chúng không đáp ứng được mục đích chính của giao diện là một hợp đồng. 

4. GIAO DIỆN CHỨC NĂNG, PHƯƠNG PHÁP MẶC ĐỊNH VÀ PHƯƠNG PHÁP TĨNH

Kể từ khi phát hành Java 8, các giao diện đã có được một số tính năng mới rất thú vị: phương thức tĩnh, phương thức mặc định và chuyển đổi tự động từ lambdas (giao diện chức năng). Trong phần giao diện, chúng tôi đã nhấn mạnh thực tế là các giao diện trong Java chỉ có thể khai báo các phương thức chứ không cung cấp cách triển khai chúng. Với một phương thức mặc định, mọi thứ lại khác: một giao diện có thể đánh dấu một phương thức bằng từ khóa mặc định và cung cấp cách triển khai cho nó. Ví dụ:
package com.javacodegeeks.advanced.design;

public interface InterfaceWithDefaultMethods {
    void performAction();

    default void performDefaulAction() {
        // Implementation here
    }
}
Ở cấp phiên bản, các phương thức mặc định có thể bị ghi đè bởi mỗi lần triển khai giao diện, nhưng các giao diện giờ đây cũng có thể bao gồm các phương thức tĩnh , ví dụ: package com.javacodegeeks.advanced.design;
public interface InterfaceWithDefaultMethods {
    static void createAction() {
        // Implementation here
    }
}
Có thể nói rằng việc cung cấp việc triển khai trong giao diện sẽ phá hủy toàn bộ mục đích của việc lập trình hợp đồng. Nhưng có nhiều lý do tại sao những tính năng này được đưa vào ngôn ngữ Java và cho dù chúng hữu ích hay khó hiểu đến đâu thì chúng vẫn luôn sẵn sàng phục vụ bạn và việc sử dụng của bạn. Giao diện chức năng là một câu chuyện hoàn toàn khác và đã được chứng minh là những bổ sung rất hữu ích cho ngôn ngữ. Về cơ bản, một giao diện chức năng là một giao diện chỉ có một phương thức trừu tượng được khai báo trên đó. RunnableGiao diện thư viện chuẩn là một ví dụ rất hay về khái niệm này.
@FunctionalInterface
public interface Runnable {
    void run();
}
Trình biên dịch Java xử lý các giao diện chức năng theo cách khác nhau và có thể biến hàm lambda thành một triển khai giao diện chức năng ở nơi hợp lý. Hãy xem xét mô tả chức năng sau: 
public void runMe( final Runnable r ) {
    r.run();
}
Để gọi hàm này trong Java 7 trở xuống, phải cung cấp cách triển khai giao diện Runnable(ví dụ: sử dụng các lớp ẩn danh), nhưng trong Java 8, chỉ cần cung cấp cách triển khai phương thức run() bằng cú pháp lambda là đủ:
runMe( () -> System.out.println( "Run!" ) );
Ngoài ra, chú thích @FunctionalInterface (các chú thích sẽ được đề cập chi tiết trong Phần 5 của hướng dẫn) gợi ý rằng trình biên dịch có thể kiểm tra xem một giao diện có chỉ chứa một phương thức trừu tượng hay không, do đó mọi thay đổi được thực hiện đối với giao diện trong tương lai sẽ không vi phạm giả định này .

5. LỚP TÓM TẮT

Một khái niệm thú vị khác được ngôn ngữ Java hỗ trợ là khái niệm về các lớp trừu tượng. Các lớp trừu tượng có phần giống với các giao diện trong Java 7 và rất gần với giao diện phương thức mặc định trong Java 8. Không giống như các lớp thông thường, một lớp trừu tượng không thể được khởi tạo nhưng nó có thể được phân lớp (tham khảo phần Kế thừa để biết thêm chi tiết). Quan trọng hơn, các lớp trừu tượng có thể chứa các phương thức trừu tượng: một loại phương thức đặc biệt không cần triển khai, giống như một giao diện. Ví dụ:
package com.javacodegeeks.advanced.design;

public abstract class SimpleAbstractClass {
    public void performAction() {
        // Implementation here
    }

    public abstract void performAnotherAction();
}
Trong ví dụ này, lớp SimpleAbstractClassđược khai báo là trừu tượng và chứa một phương thức trừu tượng được khai báo. Các lớp trừu tượng rất hữu ích; hầu hết hoặc thậm chí một số phần của chi tiết triển khai có thể được chia sẻ giữa nhiều lớp con. Dù vậy, chúng vẫn để ngỏ cánh cửa và cho phép bạn tùy chỉnh hành vi vốn có trong mỗi lớp con bằng các phương thức trừu tượng. Điều đáng nói là không giống như các giao diện chỉ có thể chứa các khai báo công khai , các lớp trừu tượng có thể sử dụng toàn bộ sức mạnh của các quy tắc trợ năng để kiểm soát khả năng hiển thị của một phương thức trừu tượng.

6. LỚP NGAY LẬP TỨC

Tính bất biến ngày càng trở nên quan trọng trong phát triển phần mềm ngày nay. Sự nổi lên của các hệ thống đa lõi đã đặt ra nhiều vấn đề liên quan đến việc chia sẻ dữ liệu và tính song song. Nhưng một vấn đề chắc chắn đã nảy sinh: việc có ít (hoặc thậm chí không có) trạng thái có thể thay đổi sẽ dẫn đến khả năng mở rộng (khả năng mở rộng) tốt hơn và lập luận dễ dàng hơn về hệ thống. Thật không may, ngôn ngữ Java không cung cấp sự hỗ trợ tốt cho tính bất biến của lớp. Tuy nhiên, bằng cách sử dụng kết hợp các kỹ thuật, có thể thiết kế các lớp không thay đổi được. Trước hết, tất cả các trường của lớp phải là cuối cùng (được đánh dấu là cuối cùng ). Đây là một khởi đầu tốt nhưng không có gì đảm bảo. 
package com.javacodegeeks.advanced.design;

import java.util.Collection;

public class ImmutableClass {
    private final long id;
    private final String[] arrayOfStrings;
    private final Collection<String> collectionOfString;
}
Thứ hai, hãy đảm bảo khởi tạo đúng cách: nếu một trường là tham chiếu đến một tập hợp hoặc mảng, thì đừng gán các trường đó trực tiếp từ các đối số của hàm tạo, thay vào đó hãy tạo các bản sao. Điều này sẽ đảm bảo rằng trạng thái của bộ sưu tập hoặc mảng không bị sửa đổi bên ngoài nó.
public ImmutableClass( final long id, final String[] arrayOfStrings,
        final Collection<String> collectionOfString) {
    this.id = id;
    this.arrayOfStrings = Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
    this.collectionOfString = new ArrayList<>( collectionOfString );
}
Và cuối cùng, đảm bảo quyền truy cập thích hợp (getters). Đối với các bộ sưu tập, tính bất biến phải được cung cấp dưới dạng trình bao bọc  Collections.unmodifiableXxx: Với mảng, cách duy nhất để cung cấp tính bất biến thực sự là cung cấp một bản sao thay vì trả về một tham chiếu đến mảng. Điều này có thể không được chấp nhận từ quan điểm thực tế, vì nó phụ thuộc rất nhiều vào kích thước của mảng và có thể gây áp lực rất lớn lên bộ thu gom rác.
public String[] getArrayOfStrings() {
    return Arrays.copyOf( arrayOfStrings, arrayOfStrings.length );
}
Ngay cả ví dụ nhỏ này cũng đưa ra một ý tưởng hay rằng tính bất biến vẫn chưa phải là công dân hạng nhất trong Java. Mọi thứ có thể trở nên phức tạp nếu một lớp bất biến có một trường tham chiếu đến một đối tượng của lớp khác. Những lớp đó cũng phải bất biến, nhưng không có cách nào để đảm bảo điều này. Có một số trình phân tích mã nguồn Java tốt, như FindBugs và PMD, có thể giúp ích rất nhiều bằng cách kiểm tra mã của bạn và chỉ ra các lỗi lập trình Java phổ biến. Những công cụ này là người bạn tuyệt vời của bất kỳ nhà phát triển Java nào.

7. LỚP ẨN DANH

Trong thời kỳ tiền Java 8, các lớp ẩn danh là cách duy nhất để đảm bảo các lớp được xác định nhanh chóng và được khởi tạo ngay lập tức. Mục đích của các lớp ẩn danh là giảm bớt bản soạn sẵn và cung cấp một cách ngắn gọn và dễ dàng để biểu diễn các lớp dưới dạng bản ghi. Chúng ta hãy xem cách truyền thống điển hình để tạo ra một luồng mới trong Java:
package com.javacodegeeks.advanced.design;

public class AnonymousClass {
    public static void main( String[] args ) {
        new Thread(
            // Example of creating anonymous class which implements
            // Runnable interface
            new Runnable() {
                @Override
                public void run() {
                    // Implementation here
                }
            }
        ).start();
    }
}
Trong ví dụ này, việc triển khai Runnablegiao diện được cung cấp ngay lập tức dưới dạng lớp ẩn danh. Mặc dù có một số hạn chế liên quan đến các lớp ẩn danh, nhược điểm chính của việc sử dụng chúng là cú pháp xây dựng dài dòng mà Java với tư cách là một ngôn ngữ bắt buộc phải tuân theo. Ngay cả chỉ một lớp ẩn danh không làm gì cũng cần ít nhất 5 dòng mã mỗi lần được viết.
new Runnable() {
   @Override
   public void run() {
   }
}
May mắn thay, với Java 8, lambda và các giao diện chức năng, tất cả những khuôn mẫu này sẽ sớm biến mất, cuối cùng việc viết mã Java sẽ trông thực sự ngắn gọn.
package com.javacodegeeks.advanced.design;

public class AnonymousClass {
    public static void main( String[] args ) {
        new Thread( () -> { /* Implementation here */ } ).start();
    }
}

8. TẦM NHÌN

Chúng ta đã nói một chút về các quy tắc hiển thị và khả năng truy cập trong Java trong Phần 1 của hướng dẫn. Trong phần này, chúng ta sẽ quay lại chủ đề này một lần nữa, nhưng trong bối cảnh phân lớp con. Thiết kế các lớp và giao diện (Bản dịch bài viết) - 2Khả năng hiển thị ở các cấp độ khác nhau cho phép hoặc ngăn các lớp nhìn thấy các lớp hoặc giao diện khác (ví dụ: nếu chúng ở trong các gói khác nhau hoặc được lồng trong nhau) hoặc các lớp con nhìn thấy và truy cập các phương thức, hàm tạo và trường của cha mẹ chúng. Trong phần tiếp theo, tính kế thừa, chúng ta sẽ thấy điều này được thực hiện.

9. THỪA KẾ

Kế thừa là một trong những khái niệm chính của lập trình hướng đối tượng, làm cơ sở để xây dựng một lớp các mối quan hệ. Kết hợp với các quy tắc về khả năng hiển thị và khả năng truy cập, tính kế thừa cho phép các lớp được thiết kế thành một hệ thống phân cấp có thể được mở rộng và duy trì. Ở cấp độ khái niệm, tính kế thừa trong Java được triển khai bằng cách sử dụng phân lớp và từ khóa mở rộng , cùng với lớp cha. Một lớp con kế thừa tất cả các phần tử public và protected của lớp cha. Ngoài ra, một lớp con kế thừa các phần tử gói-riêng tư của lớp cha nếu cả hai (lớp con và lớp) đều nằm trong cùng một gói. Nói như vậy, điều rất quan trọng là, bất kể bạn đang cố gắng thiết kế gì, hãy tuân thủ tập hợp phương thức tối thiểu mà một lớp hiển thị công khai hoặc với các lớp con của nó. Ví dụ: chúng ta hãy xem lớp Parentvà lớp con của nó Childđể chứng minh sự khác biệt về mức độ hiển thị và tác dụng của chúng.
package com.javacodegeeks.advanced.design;

public class Parent {
    // Everyone can see it
    public static final String CONSTANT = "Constant";

    // No one can access it
    private String privateField;
    // Only subclasses can access it
    protected String protectedField;

    // No one can see it
    private class PrivateClass {
    }

    // Only visible to subclasses
    protected interface ProtectedInterface {
    }

    // Everyone can call it
    public void publicAction() {
    }

    // Only subclass can call it
    protected void protectedAction() {
    }

    // No one can call it
    private void privateAction() {
    }

    // Only subclasses in the same package can call it
    void packageAction() {
    }
}
package com.javacodegeeks.advanced.design;

// Resides in the same package as parent class
public class Child extends Parent implements Parent.ProtectedInterface {
    @Override
    protected void protectedAction() {
        // Calls parent's method implementation
        super.protectedAction();
    }

    @Override
    void packageAction() {
        // Do nothing, no call to parent's method implementation
    }

    public void childAction() {
        this.protectedField = "value";
    }
}
Kế thừa bản thân nó là một chủ đề rất rộng lớn, với rất nhiều chi tiết cụ thể về Java. Tuy nhiên, có một số quy tắc dễ tuân theo và có thể giúp ích rất nhiều trong việc duy trì sự ngắn gọn của hệ thống phân cấp lớp. Trong Java, mỗi lớp con có thể ghi đè bất kỳ phương thức kế thừa nào của lớp cha trừ khi nó được khai báo là cuối cùng. Tuy nhiên, không có cú pháp hoặc từ khóa đặc biệt nào để đánh dấu một phương thức là bị ghi đè, điều này có thể dẫn đến nhầm lẫn. Đây là lý do tại sao chú thích @Override được giới thiệu : bất cứ khi nào mục tiêu của bạn là ghi đè một phương thức được kế thừa, vui lòng sử dụng chú thích @Override để chỉ ra một cách ngắn gọn về nó. Một vấn đề nan giải khác mà các nhà phát triển Java thường xuyên gặp phải trong thiết kế là việc xây dựng hệ thống phân cấp lớp (với các lớp cụ thể hoặc trừu tượng) so với việc triển khai các giao diện. Chúng tôi thực sự khuyên bạn nên ưu tiên giao diện hơn các lớp hoặc lớp trừu tượng bất cứ khi nào có thể. Các giao diện nhẹ hơn, dễ kiểm tra và bảo trì hơn, đồng thời cũng giảm thiểu tác dụng phụ của những thay đổi triển khai. Nhiều kỹ thuật lập trình nâng cao, chẳng hạn như tạo các lớp proxy trong thư viện chuẩn Java, phụ thuộc rất nhiều vào giao diện.

10. THIẾT KẾ NHIỀU

Không giống như C++ và một số ngôn ngữ khác, Java không hỗ trợ đa kế thừa: trong Java, mỗi lớp chỉ có thể có một lớp cha trực tiếp (với lớp Objectở trên cùng của hệ thống phân cấp). Tuy nhiên, một lớp có thể triển khai nhiều giao diện và do đó, xếp chồng giao diện là cách duy nhất để đạt được (hoặc mô phỏng) tính đa kế thừa trong Java.
package com.javacodegeeks.advanced.design;

public class MultipleInterfaces implements Runnable, AutoCloseable {
    @Override
    public void run() {
        // Some implementation here
    }

    @Override
    public void close() throws Exception {
       // Some implementation here
    }
}
Việc triển khai nhiều giao diện thực sự khá mạnh mẽ, nhưng thường thì nhu cầu sử dụng triển khai nhiều lần sẽ dẫn đến hệ thống phân cấp lớp sâu như một cách khắc phục việc Java thiếu hỗ trợ cho đa kế thừa. 
public class A implements Runnable {
    @Override
    public void run() {
        // Some implementation here
    }
}
// Class B wants to inherit the implementation of run() method from class A.
public class B extends A implements AutoCloseable {
    @Override
    public void close() throws Exception {
       // Some implementation here
    }
}
// Class C wants to inherit the implementation of run() method from class A
// and the implementation of close() method from class B.
public class C extends B implements Readable {
    @Override
    public int read(java.nio.CharBuffer cb) throws IOException {
       // Some implementation here
    }
}
Và vân vân... Bản phát hành gần đây của Java 8 phần nào giải quyết được vấn đề với việc chèn phương thức mặc định. Do các phương thức mặc định, các giao diện thực sự không chỉ cung cấp một hợp đồng mà còn cung cấp một bản triển khai. Do đó, các lớp triển khai các giao diện này cũng sẽ tự động kế thừa các phương thức được triển khai này. Ví dụ:
package com.javacodegeeks.advanced.design;

public interface DefaultMethods extends Runnable, AutoCloseable {
    @Override
    default void run() {
        // Some implementation here
    }

    @Override
    default void close() throws Exception {
       // Some implementation here
    }
}

// Class C inherits the implementation of run() and close() methods from the
// DefaultMethods interface.
public class C implements DefaultMethods, Readable {
    @Override
    public int read(java.nio.CharBuffer cb) throws IOException {
       // Some implementation here
    }
}
Hãy nhớ rằng đa kế thừa là một công cụ rất mạnh mẽ nhưng đồng thời cũng nguy hiểm. Vấn đề Diamond of Death nổi tiếng thường được coi là một lỗ hổng lớn trong việc thực hiện đa kế thừa, buộc các nhà phát triển phải thiết kế hệ thống phân cấp lớp rất cẩn thận. Thật không may, các giao diện Java 8 với các phương thức mặc định cũng trở thành nạn nhân của những lỗi này.
interface A {
    default void performAction() {
    }
}

interface B extends A {
    @Override
    default void performAction() {
    }
}

interface C extends A {
    @Override
    default void performAction() {
    }
}
Ví dụ: đoạn mã sau sẽ không biên dịch được:
// E is not compilable unless it overrides performAction() as well
interface E extends B, C {
}
Tại thời điểm này, thật công bằng khi nói rằng Java với tư cách là một ngôn ngữ luôn cố gắng tránh các trường hợp góc cạnh của lập trình hướng đối tượng, nhưng khi ngôn ngữ phát triển, một số trường hợp đó đột nhiên bắt đầu xuất hiện. 

11. THIẾU THẾ VÀ THÀNH PHẦN

May mắn thay, kế thừa không phải là cách duy nhất để thiết kế lớp của bạn. Một giải pháp thay thế khác mà nhiều nhà phát triển tin rằng tốt hơn nhiều so với tính kế thừa là tính kết hợp. Ý tưởng rất đơn giản: thay vì tạo ra một hệ thống phân cấp các lớp, chúng cần bao gồm các lớp khác. Hãy xem ví dụ này:
// E is not compilable unless it overrides performAction() as well
interface E extends B, C {
}
Lớp này Vehiclebao gồm một động cơ và bánh xe (cộng với nhiều bộ phận khác được bỏ qua để đơn giản). Tuy nhiên, có thể nói rằng một lớp Vehiclecũng là một công cụ nên nó có thể được thiết kế bằng tính kế thừa. 
public class Vehicle extends Engine {
    private Wheels[] wheels;
    // ...
}
Giải pháp thiết kế nào sẽ đúng? Các nguyên tắc cốt lõi chung được gọi là nguyên tắc IS-A (là) và HAS-A (chứa). IS-A là một mối quan hệ kế thừa: một lớp con cũng thỏa mãn đặc tả lớp của lớp cha và một biến thể của lớp cha (lớp con) mở rộng lớp cha của nó. Nếu bạn muốn biết liệu một thực thể có mở rộng một thực thể khác hay không, hãy thực hiện kiểm tra so khớp - IS -A (is)") Do đó, HAS-A là một mối quan hệ thành phần: một lớp sở hữu (hoặc chứa) một đối tượng mà Trong hầu hết các trường hợp, nguyên tắc HAS-A hoạt động tốt hơn IS-A vì một số lý do: 
  • Thiết kế linh hoạt hơn;
  • Mô hình ổn định hơn vì sự thay đổi không lan truyền qua hệ thống phân cấp lớp;
  • Một lớp và thành phần của nó được liên kết lỏng lẻo so với thành phần, liên kết chặt chẽ với lớp cha và lớp con của nó.
  • Quá trình suy nghĩ logic trong một lớp đơn giản hơn vì tất cả các phần phụ thuộc của nó đều được đưa vào đó, ở một nơi. 
Dù sao đi nữa, tính kế thừa có vai trò của nó và giải quyết một số vấn đề thiết kế hiện có theo nhiều cách khác nhau, vì vậy không nên bỏ qua nó. Hãy ghi nhớ hai lựa chọn thay thế này khi thiết kế mô hình hướng đối tượng của bạn.

12. ĐÓNG GÓI.

Khái niệm đóng gói trong lập trình hướng đối tượng là ẩn tất cả các chi tiết triển khai (như chế độ vận hành, phương thức bên trong, v.v.) với thế giới bên ngoài. Lợi ích của việc đóng gói là khả năng bảo trì và dễ thay đổi. Việc triển khai nội bộ của lớp bị ẩn, việc làm việc với dữ liệu lớp chỉ diễn ra thông qua các phương thức công khai của lớp (một vấn đề thực sự nếu bạn đang phát triển một thư viện hoặc khung công tác được nhiều người sử dụng). Việc đóng gói trong Java đạt được thông qua các quy tắc về khả năng hiển thị và khả năng truy cập. Trong Java, cách tốt nhất được coi là không bao giờ hiển thị trực tiếp các trường mà chỉ thông qua getters và setters (trừ khi các trường được đánh dấu là cuối cùng). Ví dụ:
package com.javacodegeeks.advanced.design;

public class Encapsulation {
    private final String email;
    private String address;

    public Encapsulation( final String email ) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getEmail() {
        return email;
    }
}
Ví dụ này gợi nhớ đến cái được gọi là JavaBeans trong ngôn ngữ Java: các lớp Java tiêu chuẩn được viết theo một tập hợp các quy ước, một trong số đó cho phép các trường chỉ được truy cập bằng cách sử dụng các phương thức getter và setter. Như chúng tôi đã nhấn mạnh trong phần kế thừa, vui lòng luôn tuân thủ hợp đồng công khai tối thiểu trong một lớp, sử dụng các nguyên tắc đóng gói. Mọi thứ không nên công khai sẽ trở thành riêng tư (hoặc được bảo vệ/gói riêng tư, tùy thuộc vào vấn đề bạn đang giải quyết). Điều này sẽ mang lại lợi ích về lâu dài bằng cách cho phép bạn tự do thiết kế mà không thực hiện những thay đổi đột phá (hoặc ít nhất là giảm thiểu chúng). 

13. CÁC LỚP VÀ PHƯƠNG PHÁP CUỐI CÙNG

Trong Java, có một cách để ngăn một lớp trở thành lớp con của lớp khác: lớp kia phải được khai báo là lớp cuối cùng. 
package com.javacodegeeks.advanced.design;

public final class FinalClass {
}
Từ khóa cuối cùng tương tự trong khai báo phương thức sẽ ngăn các lớp con ghi đè phương thức. 
package com.javacodegeeks.advanced.design;

public class FinalMethod {
    public final void performAction() {
    }
}
Không có quy tắc chung nào để quyết định liệu một lớp hoặc các phương thức có phải là cuối cùng hay không. Các lớp và phương thức cuối cùng hạn chế khả năng mở rộng và rất khó để suy nghĩ trước liệu một lớp có nên được kế thừa hay không, hoặc liệu một phương thức có nên hay không nên bị ghi đè trong tương lai. Điều này đặc biệt quan trọng đối với các nhà phát triển thư viện, vì những quyết định thiết kế như thế này có thể hạn chế đáng kể khả năng ứng dụng của thư viện. Thư viện chuẩn Java có một số ví dụ về các lớp cuối cùng, nổi tiếng nhất là lớp String. Ở giai đoạn đầu, quyết định này được đưa ra nhằm ngăn chặn mọi nỗ lực của các nhà phát triển nhằm đưa ra giải pháp “tốt hơn” của riêng họ để triển khai chuỗi. 

14. TIẾP THEO LÀ GÌ

Trong phần này của bài học, chúng ta đã đề cập đến các khái niệm về lập trình hướng đối tượng trong Java. Chúng tôi cũng xem nhanh về lập trình hợp đồng, đề cập đến một số khái niệm chức năng và xem ngôn ngữ đã phát triển như thế nào theo thời gian. Trong phần tiếp theo của bài học, chúng ta sẽ tìm hiểu về các khái niệm chung và cách chúng thay đổi cách chúng ta tiếp cận vấn đề an toàn kiểu trong lập trình. 

15. MÃ NGUỒN TẢI XUỐNG

Bạn có thể tải xuống nguồn tại đây - Advanced-Java-part-3 Nguồn: Cách thiết kế các lớp và
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION