Sunday, December 27, 2020

Các dạng giải thuật cơ bản

https://www.youtube.com/watch?v=COtr_e5zqBA 

<script type="text/javascript">

//<![CDATA[
document.write('<iframe width="560" height="315" 
src="https://www.youtube.com/embed/COtr_e5zqBA" frameborder="0" 
allow="accelerometer; autoplay; clipboard-write; encrypted-media; 
gyroscope; picture-in-picture" allowfullscreen></iframe>');
//]]>
</script> 

Sunday, March 8, 2020

Java Design Pattern – Memento

Đôi khi chúng ta cần phải ghi lại trạng thái bên trong của một đối tượng. Điều này là bắt buộc khi thực hiện tại các điểm kiểm tra và cung cấp cơ chế hoàn tác cho phép người dùng có thể khôi phục từ các lỗi. Chúng ta phải lưu thông tin trạng thái ở đâu đó để có thể khôi phục các đối tượng về trạng thái trước đó của chúng. Nhưng các đối tượng thường đóng gói một phần hoặc tất cả trạng thái của chúng, khiến nó không thể truy cập được vào các đối tượng khác và không thể lưu ở bên ngoài. Public các trạng thái này sẽ vi phạm nguyên tắc đóng gói, có thể làm giảm độ tin cậy và khả năng mở rộng của ứng dụng. Trong những trường hợp như vậy chúng ta có thể nghĩ đến Memento Pattern, nó sẽ giúp chúng ta giải quyết vấn đề này.

Memento Pattern là gì?

Without violating encapsulation, capture and externalize an object’s internal state so that the object can be returned to this state later.
Memento là một trong những Pattern thuộc nhóm hành vi (Behavior Pattern). Memento là mẫu thiết kế có thể lưu lại trạng thái của một đối tượng để khôi phục lại sau này mà không vi phạm nguyên tắc đóng gói.
Dữ liệu trạng thái đã lưu trong đối tượng memento không thể truy cập bên ngoài đối tượng được lưu và khôi phục. Điều này bảo vệ tính toàn vẹn của dữ liệu trạng thái đã lưu.
Hoàn tác (Undo) hoặc ctrl + z là một trong những thao tác được sử dụng nhiều nhất trong trình soạn thảo văn bản (editor). Mẫu thiết kế Memento được sử dụng để thực hiện thao tác Undo. Điều này được thực hiện bằng cách lưu trạng thái hiện tại của đối tượng mỗi khi nó thay đổi trạng thái, từ đó chúng ta có thể khôi phục nó trong mọi trường hợp có lỗi. 
Cài đặt Memento Pattern như thế nào?

Các thành phần tham gia mẫu Memento:
  • Originator : đại diện cho đối tượng mà chúng ta muốn lưu. Nó sử dụng memento để lưu và khôi phục trạng thái bên trong của nó.
  • Caretaker : Nó không bao giờ thực hiện các thao tác trên nội dung của memento và thậm chí nó không kiểm tra nội dung. Nó giữ đối tượng memento và chịu trách nhiệm bảo vệ an toàn cho các đối tượng. Để khôi phục trạng thái trước đó, nó trả về đối tượng memento cho Originator.
  • Memento : đại diện cho một đối tượng để lưu trữ trạng thái của Originator. Nó bảo vệ chống lại sự truy cập của các đối tượng khác ngoài Originator.
    • Lớp Memento cung cấp 2 interfaces: 1 interface cho Caretaker và 1 cho Originator. Interface Caretaker không được cho phép bất kỳ hoạt động hoặc bất kỳ quyền truy cập vào trạng thái nội bộ được lưu trữ bởi memento và do đó đảm bảo nguyên tắc đóng gói. Interface Originator cho phép nó truy cập bất kỳ biến trạng thái nào cần thiết để có thể khôi phục trạng thái trước đó.
    • Lớp Memento thường là một lớp bên trong của Originator. Vì vậy, originator có quyền truy cập vào các trường của memento, nhưng các lớp bên ngoài không có quyền truy cập vào các trường này.

Ví dụ Memento Pattern quản lý trạng thái của một đối tượng

Ví dụ đơn giản bên dưới cho phép chúng ta lưu trữ trạng thái của một đối tượng và có thể phục hồi lại trạng thái của nó tại một thời điểm đã được lưu trữ.

package com.gpcoder.patterns.behavioral.memento.state;
import java.util.ArrayList;
import java.util.List;
class Originator {
    private String state;
    public void set(String state) {
        System.out.println("Originator: Setting state to " + state);
        this.state = state;
    }
    public Memento saveToMemento() {
        System.out.println("Originator: Saving to Memento.");
        return new Memento(this.state);
    }
    public void restoreFromMemento(Memento memento) {
        this.state = memento.getSavedState();
        System.out.println("Originator: State after restoring from Memento: " + state);
    }
    public static class Memento {
        private final String state;
        public Memento(String stateToSave) {
            state = stateToSave;
        }
        public String getSavedState() {
            return state;
        }
    }
}
class MementoExample {
    public static void main(String[] args) {
        List<Originator.Memento> savedStates = new ArrayList<>(); // caretaker
        Originator originator = new Originator();
        originator.set("State #1");
        originator.set("State #2");
        savedStates.add(originator.saveToMemento());
        originator.set("State #3");
        savedStates.add(originator.saveToMemento());
        originator.set("State #4");
        originator.restoreFromMemento(savedStates.get(1)); // This point need roll back
    }
}
Output của chương trình:

Originator: Setting state to State #1
Originator: Setting state to State #2
Originator: Saving to Memento.
Originator: Setting state to State #3
Originator: Saving to Memento.
Originator: Setting state to State #4
Originator: State after restoring from Memento: State #3

Ví dụ Memento Pattern với ứng dụng quản lý tọa độ các điểm ảnh

Trong ví dụ bên dưới chúng ta sẽ tách biệt các thành phần của Memento Pattern ra từng class riêng lẻ để tiện quản lý. Chương trình cho phép chúng ta có thể khôi phục lại dữ liệu tại một thời điểm đã lưu trữ trước đó.
Originator.java

package com.gpcoder.patterns.behavioral.memento.point;
public class Originator {
    private double x;
    private double y;
    public Originator(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public double getX() {
        return x;
    }
    public double getY() {
        return y;
    }
    public void setX(double x) {
        this.x = x;
    }
    public void setY(double y) {
        this.y = y;
    }
    public Memento save() {
        return new Memento(this.x, this.y);
    }
    public void undo(Memento mem) {
        this.x = mem.getX();
        this.y = mem.getY();
    }
    @Override
    public String toString() {
        return "X: " + x + ", Y: " + y;
    }
}
Memento.java

package com.gpcoder.patterns.behavioral.memento.point;
public class Memento {
    private double x;
    private double y;
    public Memento(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public double getX() {
        return x;
    }
    public double getY() {
        return y;
    }
}
CareTaker.java

package com.gpcoder.patterns.behavioral.memento.point;
import java.util.HashMap;
import java.util.Map;
public class CareTaker {
    private final Map<String, Memento> savepointStorage = new HashMap<>();
    public void saveMemento(Memento memento, String savedPointName) {
        System.out.println("Saving state..." + savedPointName);
        savepointStorage.put(savedPointName, memento);
    }
    public Memento getMemento(String savedPointName) {
        System.out.println("Undo at ..." + savedPointName);
        return savepointStorage.get(savedPointName);
    }
    public void clearSavepoints() {
        System.out.println("Clearing all save points...");
        savepointStorage.clear();
    }
}
MementoPatternExample.java

package com.gpcoder.patterns.behavioral.memento.point;
public class MementoPatternExample {
    public static void main(String[] args) {
        CareTaker careTaker = new CareTaker();
        Originator originator = new Originator(5, 10);
        originator.setX(originator.getY() * 51);
        System.out.println("State initial: " + originator);
         
        careTaker.saveMemento(originator.save(), "SAVE #1");
        originator.setY(originator.getX() / 22);
        System.out.println("State changed: " + originator);
         
        originator.undo(careTaker.getMemento("SAVE #1"));
        System.out.println("State after undo: " + originator);
        originator.setX(Math.pow(originator.getX(), 3));
        careTaker.saveMemento(originator.save(), "SAVE #2");
        System.out.println("State changed: " + originator);
         
        originator.setY(originator.getX() - 30);
        careTaker.saveMemento(originator.save(), "SAVE #3");
        System.out.println("State saved #3: " + originator);
         
        originator.setY(originator.getX() / 22);
        careTaker.saveMemento(originator.save(), "SAVE #4");
        System.out.println("State saved #4: " + originator);
        originator.undo(careTaker.getMemento("SAVE #2"));
        System.out.println("Retrieving at saved #2: " + originator);
    }  
}
Output của chương trình:

State initial: X: 510.0, Y: 10.0
Saving state...SAVE #1
State changed: X: 510.0, Y: 23.181818181818183
Undo at ...SAVE #1
State after undo: X: 510.0, Y: 10.0
Saving state...SAVE #2
State changed: X: 1.32651E8, Y: 10.0
Saving state...SAVE #3
State saved #3: X: 1.32651E8, Y: 1.3265097E8
Saving state...SAVE #4
State saved #4: X: 1.32651E8, Y: 6029590.909090909
Undo at ...SAVE #2
Retrieving at saved #2: X: 1.32651E8, Y: 10.0

Lợi ích của Memento Pattern là gì?

Lợi ích:
  • Bảo bảo nguyên tắc đóng gói: sử dụng trực tiếp trạng thái của đối tượng có thể làm lộ thông tin chi tiết bên trong đối tượng và vi phạm nguyên tắc đóng gói.
  • Đơn giản code của Originator bằng cách để Memento lưu giữ trạng thái của Originator và Caretaker quản lý lịch sử thay đổi của Originator.
Một số vấn đề cần xem xét khi sử dụng Memento Pattern:
  • Khi có một số lượng lớn Memento được tạo ra có thể gặp vấn đề về bộ nhớ, performance của ứng dụng.
  • Khó đảm bảo trạng thái bên trong của Memento không bị thay đổi.

Sử dụng Memento Pattern khi nào?

  • Các ứng dụng cần chức năng cần Undo/ Redo: lưu trạng thái của một đối tượng bên ngoài và có thể restore/ rollback sau này.
  • Thích hợp với các ứng dụng cần quản lý transaction.

Friday, February 21, 2020

Builder Design Patterns in Java

Builder Design Patterns in Java

Dec 21, 2017 · 3 min read

While Considering the builder pattern you need to look weather the object is having

Complex constructor.

Multiple constructor having combinations of multiple parameter with nested objects

Large number of parameters.

having large number of field parameter is also the key point to consider.

Immutability.

You can force the immutability to the object once you are done with creation of object.
Builder pattern is a creational design pattern it means its solves problem related to object creation.Best example would be an AlertDialog class from AOSP, StringBuilder, DocumentBuilder best to see how the compex object can be created.
It typically solve problem in object oriented programming i.e determining what constructor to use. Often we write many constructor and it is really hard to manage them. The multiple constructor with combination of multiple parameters variation is called the telescoping constructor.
Builder pattern is used to create instance of very complex object having telescoping constructor in easiest way.
Constructors in Java are used to create object and can take parameters required to create object. Lets see an example and learn how to implement builder pattern. Consider a pojo of Person below.
public class Person {
    private String firstName;
    private String middleName;
    private String lastName;
    private int age;

    public Person(String firstName, String middleName, String lastName, int age) {
        this.firstName = firstName;
        this.middleName = middleName;
        this.lastName = lastName;
        this.age = age;
    }
    public Person(String firstName, String lastName, int age) {
        this(firstName, null, lastName, age);
    }
    public Person(String firstName, int age) {
        this(firstName, null, age);
    }
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getMiddleName() {
        return middleName;
    }
    public void setMiddleName(String middleName) {
        this.middleName = middleName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

}
Things are simple if there are only 4 fields but, lets say if you want to add more fields to the pojo then it becomes hectic to maintain complex pojo plus the order of the minimum fields to required to create the object. Also it can leads to bugs in code base.

Convert to Builder Pattern

Let’s now add some extra fields fathersName, mothersName, height, weight to the pojo and convert it to the Builder pattern. Create static anonymous inner class named Builder to the pojo, why static because we want to return/use current object. Add same fields to it from pojo. Also add the empty constructor and setter of each filed with return type of Builder class. And last but not least add method build which will return the new Person object instance.
public class Person {
    private String firstName;
    private String middleName;
    ....    public Person(String firstName, String middleName, String lastName, int age, String fathersName, String mothersName, double height, double weight) {
        this.firstName = firstName;
        this.middleName = middleName;
    ....
    }

    public static class Builder {
        private String firstName;
        private String middleName;
        private String lastName;
        private int age;
        private String fathersName;
        private String mothersName;
        private double height;
        private double weight;        public Builder(){}        public Builder setFirstName(String firstName) {
            this.firstName = firstName;
            return this;
        }

        public Builder setMiddleName(String middleName) {
            this.middleName = middleName;
            return this;
        }

        public Builder setLastName(String lastName) {
            this.lastName = lastName;
            return this;
        }

        ...

        public Person build() {
            return new Person(firstName, middleName, lastName, age, fathersName, mothersName, height, weight);
        }
    }
The resulting builder create code:
Person person = new Person.Builder()
        .setAge(5)
        .setFirstName("Bob")
        .setHeight(6)
        .setAge(19)
        .build();

Pros

1) Code is more maintainable if number of fields required to create object is more than 4 or 5.
2) Object Creation code less error-prone as user will know what they are passing because of explicit method call.
3) Builder pattern increase robustness, as only fully constructed object will be available to client.
4) You can force immutability to the object once its created.

Cons

1) Builder pattern is verbose and requires code duplication as Builder needs to copy all fields from Original or Item class.

Monday, February 17, 2020

Design Patterns (P2): Singleton có thực sự dễ?

https://medium.com/@kevalpatel2106/how-to-make-the-perfect-singleton-de6b951dfdb0

Khi nói về Design Patterns, gần 100% những người tôi tiếp xúc đều thực hành Singleton như một design pattern phổ biến và dễ nhất (nhiều người chỉ biết/nhớ mỗi Singleton trong GoF). Tôi thường có 2 câu hỏi:
  1. Sử dụng Singleton và static trong class có gì khác nhau?
  2. Có tự tin cài đặt đúng Singleton?
Thường thì mọi người gặp khó khăn ở câu hỏi #1, và rất chắc chắn ở câu hỏi #2. Tôi thấy, #2 khó hơn #1 nhiều, và nếu bạn không tự tin trả lời #2 thì tôi khuyên bạn nên sử dụng static trong class thay vì Singleton. Tại sao? Đọc tiếp nhé.

Sử dụng Singleton và static trong class có gì khác nhau?

Nếu bạn chưa rõ về câu hỏi thì tôi giải thích thêm một chút: Mục đích của Singleton là tạo ra một object duy nhất trên toàn ứng dụng, kéo theo các thuộc tính của nó cũng là duy nhất; điều này hoàn toàn giống / có thể thực hiện bởi thuộc tính static của class – là nơi duy nhất lưu trữ dữ liệu. Ví dụ:

// Using Singleton
public class AppConfig {
    public int appId;
    private static AppConfig self;
    public static AppConfig getInstance() {
        if (self == null) {
            self = new AppConfig();
        }
        return self;
    }
}
config = AppConfig.getInstance();
appId = config.appId;
// Using static
public class AppConfig {
    public static int appId;
}
appId = AppConfig.appId;
Cả 2 cách cài đặt trên, appId đều chỉ có một nơi duy nhất để lưu trữ và truy xuất giá trị. Sử dụng static theo #2 gọn và tường minh hơn, vậy cần gì Singleton?
Để ý một chút, một cách cài đặt sử dụng class, một cách sử dụng object. Có 2 sự khác nhau giữa class và object trong trường hợp này:
  1. Life-time: Life cycle của class gắn với ứng dụng, life cycle của object gắn với việc sử dụng. Sử dụng static trong class, những thuộc tính này sẽ được load theo class khi chạy ứng dụng; sử dụng Singleton, những thuộc tính này sẽ được load chỉ khi object được khởi tạo. Trong cả 2 trường hợp, thuộc tính không được tự động giải phóng cho tới khi ứng dụng kết thúc. (do object vẫn có reference được giữ bởi self, tồn tại theo class).
  2. Abstraction: Sử dụng static trong class là concrete implement, sử dụng object cho một tầng abstraction nữa nên dễ thay đổi hơn. Ví dụ, chúng ta cần thay đổi AppConfig sang WindowsAppConfig, theo #2, tất cả mọi nơi truy cập tới appId đều phải sửa đổi; theo #1, nơi duy nhất cần thay đổi là AppConfig.getInstance() (có thể không cần thay đổi nếu áp dụng với những design pattern khác như Factory, DI).
Chẳng có gì đặc biệt, lý thuyết thôi. Chính xác, và vì thế bạn vẫn cài đặt Singleton như đã học? Nhưng thử tìm hiểu thêm nhé.


Có tự tin cài đặt đúng Singleton?

Cài đặt Singleton thế nào?

Dễ ợt, có 2 việc:
  1. Đặt constructor là private để object không thể khởi tạo được từ bên ngoài. 
  2. Cung cấp 1 method duy nhất trả về giá trị static 
Code thường được cài đặt như sau:

public class AppConfig {
    private static AppConfig self;
    public int appId;
    
    private AppConfig() {
    }
    
    public static AppConfig getInstance() {
        if (self == null) {
            self = new AppConfig();
        }
        return self;
    }
}
// Use
AppConfig config = AppConfig.getInstance();
appId = config.appId;
Có một vài câu hỏi phía dưới.

Chỉ có đúng 1 object được tạo ra?

Bạn có chắc rằng với cách cài đặt trên chỉ có đúng 1 object có thể được tạo ra? Còn cách nào có thể tạo ra object không?
Reflection là một trong những thứ rất hay ho của một số ngôn ngữ (những ngôn ngữ khác nhau có thể có những tên gọi khác nhau: runtime reference…), và cũng là một thứ khó bởi nó nâng abstraction lên mức runtime. Những pattern như Object Mapper hay DI… không dễ cài đặt nếu không có reflection. Thử đoạn code sau nhé.

public class AppConfig {
    private static AppConfig self;
    public int appId;
    
    private AppConfig() {
        appId = new Random().nextInt(100);
    }
    
    public static AppConfig getInstance() {
        if (self == null) {
            self = new AppConfig();
        }
        return self;
    }
    
    public static void main(String[] args) {
        AppConfig config = AppConfig.getInstance();
        System.out.println(config.appId);
        
        AppConfig appConfig = null;
        try {
            Class<AppConfig> clazz = AppConfig.class;
            Constructor<AppConfig> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            appConfig = constructor.newInstance();
        } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
        System.out.println(appConfig.appId);
    }
}
Output của đoạn code trên thường sẽ là 2 số nguyên khác nhau, chính là appId. Về lý thuyết, appId chỉ được gán giá trị một lần duy nhất khi khởi tạo object. Như vậy đã có 2 object configappConfig được tạo ra. Điều chúng ta mong muốn là configappConfig là 2 biến cùng tham chiếu tới 1 object trong bất cứ trường hợp nào. Vậy nên giải pháp là kiểm soát constructor.
private AppConfig() {
    if (self != null) {
        throw new UnsupportedOperationException("Use getInstance() to create object.");
    }
    appId = new Random().nextInt(100);
}

Chỉ có đúng 1 object được tạo ra?

Bạn có chắc rằng với cách cài đặt trên chỉ có đúng 1 object có thể được tạo ra? Còn cách nào có thể tạo ra object không?
Threading là một trong những thứ rất hay ho nhưng cũng làm đau đầu developer vì nó không đi theo flow thông thường. Thử đoạn code sau nhé.
public class AppConfig {
    private static AppConfig self;
    public int appId;
    
    private AppConfig() {
        if (self != null) {
            throw new UnsupportedOperationException("Use getInstance()");
        }
        appId = new Random().nextInt(100);
    }
    
    public static AppConfig getInstance() {
        if (self == null) {
            self = new AppConfig();
        }
        return self;
    }
    
    public static void main(String[] args) {
        Thread threadDownload = new Thread(new Runnable() {
            @Override
            public void run() {
                AppConfig config = AppConfig.getInstance();
                System.out.println(config.appId);
            }
        });
 
        Thread threadUpload = new Thread(new Runnable() {
            @Override
            public void run() {
                AppConfig config = AppConfig.getInstance();
                System.out.println(config.appId);
            }
        });
 
        threadDownload.start();
        threadUpload.start();
    }
}
Hãy thử chạy một vài lần vì cơ hội gặp trường hợp hai số khác nhau được in ra thấp hơn ví dụ trên. Tương tự ví dụ trên, ta có thể khẳng định: có 2 object đã được tạo ra. Sai lầm ở đâu? Hãy để ý method getInstance(), điều gì xảy ra nếu câu lệnh if (self == null) được thực thi đồng thời ở cả threadDownloadthreadUpload? Chúng đều đúng, và câu lệnh tạo object và return vẫn được tiếp tục trên 2 thread độc lập, và trả ra 2 object độc lập. Vậy nên cần phải synchronized việc tạo object.
public class AppConfig {
    private static volatile AppConfig self;
    public int appId;
    
    private AppConfig() {
        if (self != null) {
            throw new UnsupportedOperationException("Use getInstance()");
        }
        appId = new Random().nextInt(100);
    }
    
    public static synchronized AppConfig getInstance() {
        if (self == null) {
            self = new AppConfig();
        }
        return self;
    }
}

Chỉ có đúng 1 object được tạo ra?

Bạn có chắc rằng với cách cài đặt trên chỉ có đúng 1 object có thể được tạo ra? Còn cách nào có thể tạo ra object không?
Serializable có thể là một vấn đề. Chúng ta muốn lưu lại cấu hình AppConfig xuống file sau đó load lại khi cần. Thử đoạn code sau nhé.
public class AppConfig implements Serializable {
    private static volatile AppConfig self;
    public int appId;
    
    private AppConfig() {
        if (self != null) {
            throw new UnsupportedOperationException("Use getInstance()");
        }
        appId = new Random().nextInt(100);
    }
    
    public static synchronized AppConfig getInstance() {
        if (self == null) {
            self = new AppConfig();
        }
        return self;
    }
    
    public static void main(String[] args) {
        AppConfig config = AppConfig.getInstance();
        
        try {
            // Save config to file
            ObjectOutput objectOutput = new ObjectOutputStream(new FileOutputStream("app.conf"));
            objectOutput.writeObject(config);
            objectOutput.close();
            System.out.println(config.appId);
            System.out.println(config);
 
 
            // Load config from file
            ObjectInput objectIutput = new ObjectInputStream(new FileInputStream("app.conf"));
            config = (AppConfig) objectIutput.readObject();
            objectIutput.close();
            System.out.println(config.appId);
            System.out.println(config);
        } catch (IOException | ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }
}
Bạn sẽ thấy output có dạng này:
singletontester.AppConfig@55f96302
singletontester.AppConfig@776ec8df
Tức là giá trị của object (các properties như appId) được giữ nguyên song object thực chất đã được tạo mới (tìm hiểu thêm về method toString()). Như vậy không đảm bảo 1 object được tạo ra duy nhất trên toàn bộ life cycle của ứng dụng. Vì cơ chế deserialize sẽ tạo mới object. Chúng ta cần sửa thành:
public class AppConfig implements Serializable {
    private static volatile AppConfig self;
    public int appId;
    
    private AppConfig() {
        if (self != null) {
            throw new UnsupportedOperationException("Use getInstance()");
        }
        appId = new Random().nextInt(100);
    }
    
    public static synchronized AppConfig getInstance() {
        if (self == null) {
            self = new AppConfig();
        }
        return self;
    }
    
    protected AppConfig readResolve() {
        return getInstance();
    }
}

Chỉ có đúng 1 object được tạo ra?

Bạn có chắc rằng với cách cài đặt trên chỉ có đúng 1 object có thể được tạo ra? Còn cách nào có thể tạo ra object không?
Đáng tiếc là vẫn còn, clone thì sao?
 

Kết

Singleton “chuẩn” nên được cài đặt như sau:
public class AppConfig {
    private static volatile AppConfig self;
    
    private AppConfig() {
        if (self != null) {
            throw new UnsupportedOperationException("Use getInstance()");
        }
    }
    
    public static synchronized AppConfig getInstance() {
        if (self == null) {
            self = new AppConfig();
        }
        return self;
    }
}
  • Bài viết này tôi lấy cảm hứng từ: https://medium.com/exploring-code/how-to-make-the-perfect-singleton-de6b951dfdb0, viết lại theo cách dễ đọc hơn.
  • Đến đây chắc bạn đã hiểu tại sao tôi khuyến nghị dùng static trong class nếu bạn không thực sự rõ về Singleton. Với class bạn không có vấn đề với serializable và reflection… Điều bạn gặp phải về threading có thể được IDE warning.
  • Hãy nhớ nhé, Singleton không dễ đâu.
 
 

 

Các dạng giải thuật cơ bản

https://www.youtube.com/watch?v=COtr_e5zqBA   < script type = "text/javascript" > //<![CDATA[ document . write ( '...