0
0
Lập trình
NM

Tạo và Phá Hủy Đối Tượng với Java và Kotlin - Hiệu Quả

Đăng vào 2 tuần trước

• 13 phút đọc

Giới thiệu về Chuỗi Bài Viết

"Effective Java" của Joshua Bloch đã giúp tôi viết mã Java vững chắc, hiệu quả và dễ bảo trì. Gần đây, tôi đã bắt đầu khám phá Kotlin, và tôi rất hào hứng tìm hiểu cách các nguyên tắc và phương pháp tốt nhất trong cuốn sách này có thể áp dụng cho Kotlin. Trong chuỗi bài viết này, tôi sẽ tóm tắt từng chương và từng mục, cung cấp các triển khai bằng Java và Kotlin khi có thể, nhằm củng cố hiểu biết của tôi về cả hai ngôn ngữ.

Chương 2: Tạo và Phá Hủy Đối Tượng

  • Mục 1: Phương thức Nhà máy Tĩnh: Cung cấp tính linh hoạt và khả năng đọc tốt hơn so với các bộ tạo.
  • Mục 2: Mẫu Kiến trúc Builder: Hữu ích khi đối mặt với nhiều tham số trong bộ tạo.
  • Mục 3: Thuộc tính Singleton: Cách triển khai singleton bằng cách sử dụng bộ tạo riêng tư hoặc kiểu enum.
  • Mục 4: Không thể khởi tạo: Thực thi không thể khởi tạo bằng cách sử dụng bộ tạo riêng tư.
  • Mục 5: Tiêm phụ thuộc: Ưu tiên tiêm phụ thuộc thay vì kết nối cứng các tài nguyên.

Mục 1: Cân Nhắc Sử Dụng Phương Thức Nhà Máy Tĩnh Thay Vì Bộ Tạo

Tóm tắt

Phương thức nhà máy tĩnh cung cấp một sự thay thế cho bộ tạo để tạo ra các đối tượng. Chúng mang lại nhiều lợi ích, bao gồm tên phương thức có ý nghĩa, tính linh hoạt và khả năng trả về đối tượng của bất kỳ lớp con nào.

Triển Khai Java

java Copy
public class Color {
    private final int value;

    private Color(int value) {
        this.value = value;
    }

    public static Color ofValue(int value) {
        return new Color(value);
    }

    public static Color ofRGB(int r, int g, int b) {
        return new Color((r << 16) | (g << 8) | b);
    }
}

Triển Khai Kotlin

kotlin Copy
class Color private constructor(val value: Int) {
    companion object {
        fun ofValue(value: Int) = Color(value)
        fun ofRGB(r: Int, g: Int, b: Int) = Color((r shl 16) or (g shl 8) or b)
    }
}

Trong Kotlin, companion object được sử dụng để định nghĩa các thành viên hoặc hàm tĩnh thuộc về chính lớp, thay vì các thể hiện của lớp. Nó tương tự như các thành viên tĩnh của Java, nhưng với nhiều tính linh hoạt hơn. Trong ví dụ này, companion object cho phép chúng ta định nghĩa các phương thức nhà máy ofValueofRGB có thể được gọi mà không cần tạo một thể hiện của lớp Color. Bằng cách sử dụng một companion object, chúng ta có thể đạt được chức năng tương tự như các phương thức nhà máy tĩnh của Java, đồng thời tận dụng cú pháp và tính năng ngắn gọn của Kotlin.

Mục 2: Cân Nhắc Sử Dụng Mẫu Builder Khi Đối Mặt Với Nhiều Tham Số Bộ Tạo

Tóm tắt

Khi một lớp có nhiều tham số bộ tạo, việc viết và đọc mã của khách hàng có thể trở nên khó khăn. Mẫu Builder cung cấp một sự thay thế linh hoạt và dễ đọc hơn.

Lợi ích

  • Khả năng đọc: Mã của khách hàng dễ đọc hơn, vì mục đích của mỗi tham số rõ ràng.
  • Tính linh hoạt: Các tham số tùy chọn có thể được bỏ qua hoặc chỉ định theo bất kỳ thứ tự nào.
  • Tính bất biến: Đối tượng được tạo có thể là bất biến, cung cấp lợi ích an toàn cho luồng.

Triển Khai Java

java Copy
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder {
        private final int servingSize;
        private final int servings;
        private int calories = 0;
        private int fat = 0;
        private int sodium = 0;
        private int carbohydrate = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }

        public Builder calories(int val) {
            calories = val;
            return this;
        }

        public Builder fat(int val) {
            fat = val;
            return this;
        }

        public Builder sodium(int val) {
            sodium = val;
            return this;
        }

        public Builder carbohydrate(int val) {
            carbohydrate = val;
            return this;
        }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

// mã khách hàng
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
        .calories(100)
        .sodium(35)
        .carbohydrate(27)
        .build();

Triển Khai Kotlin

kotlin Copy
class NutritionFacts(
    val servingSize: Int,
    val servings: Int,
    val calories: Int = 0,
    val fat: Int = 0,
    val sodium: Int = 0,
    val carbohydrate: Int = 0
)

// Mã khách hàng
val cocaCola = NutritionFacts(
    servingSize = 240,
    servings = 8,
    calories = 100,
    sodium = 35,
    carbohydrate = 27
)

Trong Kotlin, chúng ta có thể tận dụng giá trị tham số mặc định để đơn giản hóa việc tạo đối tượng với nhiều tham số. Cách tiếp cận này loại bỏ nhu cầu phải có một lớp builder riêng biệt.

Mục 3: Thực Thi Thuộc Tính Singleton Với Bộ Tạo Riêng Tư Hoặc Kiểu Enum

Tóm tắt

Một singleton là một lớp được thiết kế để chỉ có một thể hiện duy nhất trên mỗi JVM, cung cấp một điểm truy cập toàn cầu duy nhất. Thực thi điều này một cách chính xác tránh được các vấn đề như các thể hiện trùng lặp với trạng thái không nhất quán.

Cách Tiếp Cận Bộ Tạo Riêng Tư

java Copy
public class DatabaseConnection {
    public static final DatabaseConnection INSTANCE = new DatabaseConnection();

    private DatabaseConnection() {}

    public void connect() {
        System.out.println("Kết nối đến cơ sở dữ liệu...");
    }
}

Trong cách tiếp cận này, bộ tạo riêng tư đảm bảo rằng lớp không thể được khởi tạo từ bên ngoài. Trường INSTANCE cung cấp quyền truy cập toàn cầu vào thể hiện singleton.

Cách Tiếp Cận Enum

java Copy
public enum DatabaseConnection {
    INSTANCE;

    public void connected() {
        System.out.println("Kết nối đến cơ sở dữ liệu...");
    }
}

Trong cách tiếp cận này, kiểu enum đảm bảo rằng chỉ có một thể hiện được tạo ra. Trường INSTANCE là công khai ngầm định và cung cấp quyền truy cập toàn cầu vào thể hiện singleton.

Lợi ích

Cả hai cách tiếp cận đều cung cấp một số lợi ích, bao gồm:

  • An toàn với luồng: Cả hai cách tiếp cận đều đảm bảo rằng thể hiện singleton là an toàn với luồng.
  • An toàn với tuần tự hóa: Cách tiếp cận enum cung cấp an toàn với tuần tự hóa, đảm bảo rằng thuộc tính singleton được bảo tồn ngay cả sau khi tuần tự hóa và giải tuần tự hóa.

Mã Khách Hàng

java Copy
public class Main {
    public static void main(String[] args) {
        DatabaseConnection dbConn = DatabaseConnection.INSTANCE;
        dbConn.connect();
    }
}

Trong ví dụ này, chúng ta truy cập vào thể hiện singleton bằng cách sử dụng trường INSTANCE và gọi phương thức connect().

Triển Khai Kotlin

kotlin Copy
object DatabaseConnection {
    fun connect() {
        println("Kết nối đến cơ sở dữ liệu...")
    }
}

// Mã khách hàng
fun main() {
    DatabaseConnection.connect()
}

Trong Kotlin, chúng ta có thể sử dụng từ khóa object để định nghĩa một singleton. Khai báo object đảm bảo rằng chỉ có một thể hiện được tạo ra và cung cấp quyền truy cập toàn cầu vào thể hiện singleton. Bằng cách sử dụng bộ tạo riêng tư hoặc kiểu enum (hoặc khai báo object trong Kotlin), bạn có thể thực thi thuộc tính singleton và đảm bảo rằng lớp của bạn chỉ được khởi tạo một lần duy nhất.

Mục 4: Thực Thi Không Thể Khởi Tạo Với Bộ Tạo Riêng Tư

Tóm tắt

Một số lớp, chẳng hạn như các lớp tiện ích, được thiết kế để sử dụng tĩnh và không nên được khởi tạo. Để thực thi không thể khởi tạo, bạn có thể sử dụng một bộ tạo riêng tư.

Ví dụ

java Copy
public class UtilityClass {
    // Ngăn chặn bộ tạo mặc định cho việc không thể khởi tạo
    private UtilityClass() {
        throw new AssertionError();
    }

    public static void utilityMethod() {
        System.out.println("Phương thức tiện ích...");
    }
}

Trong ví dụ này, bộ tạo riêng tư đảm bảo rằng lớp không thể được khởi tạo từ bên ngoài. AssertionError được ném ra trong bộ tạo làm rõ rằng việc khởi tạo không được phép.

Lợi ích

Sử dụng bộ tạo riêng tư để thực thi không thể khởi tạo cung cấp một số lợi ích, bao gồm:

  • Ý định rõ ràng: Bộ tạo riêng tư truyền đạt rõ ràng rằng lớp không được dự định để khởi tạo.
  • Ngăn chặn khởi tạo: Bộ tạo riêng tư ngăn chặn việc khởi tạo lớp một cách tình cờ.

Thực Hành Tốt Nhất

Khi thực thi không thể khởi tạo, hãy xem xét các thực hành tốt nhất sau:

  • Đảm bảo bộ tạo là riêng tư: Đảm bảo rằng bộ tạo là riêng tư để ngăn chặn việc khởi tạo.
  • Ném một AssertionError: Ném một AssertionError trong bộ tạo làm rõ rằng việc khởi tạo không được dự định.

Triển Khai Kotlin

kotlin Copy
class UtilityClass private constructor() {
    companion object {
        fun utilityMethod() {
            println("Phương thức tiện ích...")
        }
    }
}

// hoặc

object UtilityClass {
    fun utilityMethod() {
        println("Phương thức tiện ích...")
    }
}

Trong Kotlin, bạn có thể thực thi không thể khởi tạo của một lớp bằng cách khai báo một bộ tạo riêng tư hoặc sử dụng một khai báo đối tượng. Bộ tạo riêng tư ngăn cản mã bên ngoài tạo ra các thể hiện của lớp, điều này rất hữu ích cho các lớp tiện ích hoặc bất kỳ lớp nào không nên được khởi tạo. Cách tiếp cận này chỉ ra rõ ràng rằng việc khởi tạo không được phép. Ngược lại, một khai báo đối tượng định nghĩa một lớp singleton ở cấp độ ngôn ngữ, đảm bảo một thể hiện duy nhất mà không cho phép bất kỳ cuộc gọi bộ tạo nào. Đây là cách tiếp cận chuẩn mực trong Kotlin để thực thi một thể hiện duy nhất trong khi ngăn chặn bất kỳ việc khởi tạo nào. Cả hai cách tiếp cận đều truyền đạt ý định rõ ràng và đảm bảo quyền truy cập có kiểm soát vào các thể hiện của lớp, với khai báo đối tượng cung cấp an toàn với luồng và cú pháp đơn giản hơn.

Mục 5: Ưu Tiên Tiêm Phụ Thuộc Hơn Là Kết Nối Cứng Các Tài Nguyên

Tóm tắt

Tiêm phụ thuộc là một mẫu thiết kế cho phép các thành phần được liên kết lỏng lẻo, giúp dễ dàng kiểm tra, bảo trì và mở rộng hệ thống. Thay vì kết nối cứng các tài nguyên, hãy ưu tiên tiêm phụ thuộc để cung cấp các phụ thuộc cho một lớp.

Kết Nối Cứng Các Tài Nguyên

java Copy
public class Lexicon {
    private static final Dictionary dictionary = new MerriamWebster();

    public Lexicon() {}

    public void lookup(String word) {
        dictionary.define(word);
    }
}

Trong ví dụ này, lớp Lexicon bị liên kết chặt chẽ với từ điển MerriamWebster. Điều này làm cho việc kiểm tra hoặc sử dụng lớp Lexicon với một từ điển khác trở nên khó khăn.

Tiêm Phụ Thuộc

java Copy
public class Lexicon {
    private final Dictionary dictionary;

    public Lexicon(Dictionary dictionary) {
        this.dictionary = Objects.requireNonNull(dictionary);
    }

    public void lookup(String word) {
        dictionary.define(word);
    }
}

Trong ví dụ này, lớp Lexicon được tách rời khỏi việc triển khai từ điển cụ thể. Thay vào đó, nó phụ thuộc vào giao diện Dictionary, có thể được triển khai bởi các từ điển khác nhau.

Lợi ích

Tiêm phụ thuộc cung cấp một số lợi ích, bao gồm:

  • Liên kết lỏng lẻo: Các thành phần được tách rời, giúp dễ dàng kiểm tra, bảo trì và mở rộng hệ thống.
  • Khả năng kiểm tra: Các thành phần có thể được kiểm tra một cách độc lập bằng cách sử dụng các phụ thuộc giả lập.
  • Tính linh hoạt: Các thành phần có thể được sử dụng với các phụ thuộc khác nhau, giúp dễ dàng thích ứng với các yêu cầu thay đổi.

Thực Hành Tốt Nhất

Khi sử dụng tiêm phụ thuộc, hãy xem xét các thực hành tốt nhất sau:

  • Sử dụng giao diện: Định nghĩa các giao diện cho các phụ thuộc để tách rời các thành phần khỏi các triển khai cụ thể.
  • Sử dụng tiêm qua bộ tạo: Tiêm các phụ thuộc thông qua bộ tạo để đảm bảo rằng các thành phần được khởi tạo đúng cách.

Triển Khai Kotlin

kotlin Copy
class Lexicon(private val dictionary: Dictionary) {
    fun lookup(word: String) {
        dictionary.define(word)
    }
}

interface Dictionary {
    fun define(word: String)
}

class MerriamWebster : Dictionary {
    override fun define(word: String) {
        println("Định nghĩa $word...")
    }
}

Trong Kotlin, bạn có thể sử dụng tiêm phụ thuộc tương tự như trong Java.
Bằng cách ưu tiên tiêm phụ thuộc hơn là kết nối cứng các tài nguyên, bạn có thể viết mã linh hoạt, dễ kiểm tra và dễ bảo trì hơn.

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào