Câu hỏi phỏng vấn Design Pattern


1. Singleton Pattern

Câu hỏi:

Singleton là gì? Tại sao lại sử dụng nó? Hãy viết một Singleton Pattern trong JavaScript.

Giải thích:
Singleton đảm bảo rằng một lớp chỉ có duy nhất một thể hiện (instance) và cung cấp một điểm truy cập toàn cục đến nó. Trong React Native, bạn có thể dùng Singleton để quản lý các trạng thái như cấu hình, kết nối API, hoặc quản lý phiên người dùng.

class ApiService {
// Lưu instance duy nhất
static instance;
constructor(baseURL) {
if (ApiService.instance) {
return ApiService.instance; // Nếu đã có instance, trả về instance đó
}
this.baseURL = baseURL;
ApiService.instance = this; // Gán instance hiện tại
}
fetch(endpoint) {
return fetch(`${this.baseURL}/${endpoint}`).then((res) => res.json());
}
}
// Sử dụng Singleton
const apiService1 = new ApiService("https://api.example.com");
const apiService2 = new ApiService("https://api.other.com");
console.log(apiService1 === apiService2); // true, cùng instance

Comment:

  • Singleton ngăn chặn việc tạo nhiều instance không cần thiết, tiết kiệm bộ nhớ.
  • Dễ dàng quản lý trạng thái chung như cấu hình API trong ứng dụng React Native.

2. Factory Pattern

Câu hỏi:

Factory Pattern là gì? Khi nào bạn sử dụng nó trong React Native? Hãy viết một ví dụ minh họa.

Giải thích:
Factory Pattern là một mẫu thiết kế cung cấp một giao diện để tạo ra các đối tượng mà không chỉ định rõ lớp cụ thể của chúng. Trong React Native, bạn có thể dùng Factory để tạo các component UI dựa trên loại (type).

// Factory để tạo Component
const ButtonFactory = (type) => {
switch (type) {
case "primary":
return <PrimaryButton />;
case "secondary":
return <SecondaryButton />;
default:
return <DefaultButton />;
}
};
// Ví dụ các button component
const PrimaryButton = () => <button style={{ backgroundColor: "blue" }}>Primary</button>;
const SecondaryButton = () => <button style={{ backgroundColor: "gray" }}>Secondary</button>;
const DefaultButton = () => <button>Default</button>;
// Sử dụng Factory
const App = () => {
return (
<div>
{ButtonFactory("primary")}
{ButtonFactory("secondary")}
{ButtonFactory("unknown")}
</div>
);
};

Comment:

  • Factory giúp giảm sự phức tạp khi khởi tạo các đối tượng hoặc component.
  • Dễ mở rộng khi cần thêm các loại button khác.

3. Observer Pattern

Câu hỏi:

Observer Pattern hoạt động như thế nào? Hãy minh họa cách sử dụng trong React Native.

Giải thích:
Observer Pattern định nghĩa một mối quan hệ phụ thuộc 1-n giữa các đối tượng, sao cho khi một đối tượng thay đổi trạng thái, tất cả các đối tượng phụ thuộc sẽ được thông báo và cập nhật tự động.

// Subject
class EventManager {
constructor() {
this.subscribers = [];
}
subscribe(callback) {
this.subscribers.push(callback); // Thêm observer
}
unsubscribe(callback) {
this.subscribers = this.subscribers.filter((cb) => cb !== callback); // Gỡ observer
}
notify(data) {
this.subscribers.forEach((callback) => callback(data)); // Gửi thông báo
}
}
// Sử dụng Observer trong React Native
const eventManager = new EventManager();
const App = () => {
const [data, setData] = React.useState(null);
React.useEffect(() => {
const handleEvent = (newData) => setData(newData);
eventManager.subscribe(handleEvent);
return () => eventManager.unsubscribe(handleEvent); // Cleanup khi component unmount
}, []);
return <Text>{data || "Chờ dữ liệu..."}</Text>;
};
// Trigger sự kiện
setTimeout(() => {
eventManager.notify("Dữ liệu mới đã cập nhật!");
}, 3000);

Comment:

  • Observer rất phù hợp để xử lý các sự kiện hoặc thay đổi trạng thái toàn cục.
  • React Native có thể sử dụng Observer Pattern kết hợp với Context API hoặc Redux.

4. Decorator Pattern

Câu hỏi:

Decorator Pattern là gì? Hãy giải thích với một ví dụ trong React Native.

Giải thích:
Decorator Pattern cho phép thêm chức năng mới vào đối tượng mà không làm thay đổi cấu trúc ban đầu của nó. Trong React Native, nó thường được sử dụng để bọc các component với các tính năng bổ sung.

// Decorator để thêm tính năng logging
const withLogging = (Component) => {
return (props) => {
console.log("Props:", props);
return <Component {...props} />;
};
};
// Một component cơ bản
const Button = ({ label }) => {
return <button>{label}</button>;
};
// Bọc component với decorator
const LoggingButton = withLogging(Button);
// Sử dụng LoggingButton
const App = () => {
return <LoggingButton label="Click me!" />;
};

Comment:

  • Decorator dễ mở rộng và tái sử dụng logic.
  • Hữu ích trong việc theo dõi hoặc thêm các tính năng như logging, theo dõi trạng thái.

5. Strategy Pattern

Câu hỏi:

Strategy Pattern là gì? Làm sao để áp dụng nó vào việc quản lý logic trong ứng dụng React Native?

Giải thích:
Strategy Pattern cho phép bạn xác định một họ thuật toán, đặt chúng vào các lớp riêng biệt, và làm cho chúng có thể thay thế nhau. Nó thường được sử dụng để tách logic xử lý trong ứng dụng.

// Các chiến lược xử lý
const strategies = {
add: (a, b) => a + b,
subtract: (a, b) => a - b,
multiply: (a, b) => a * b,
};
// Context để áp dụng chiến lược
const calculate = (strategy, a, b) => {
return strategies[strategy](a, b);
};
// Sử dụng Strategy Pattern
const App = () => {
const [result, setResult] = React.useState(0);
const handleCalculate = () => {
setResult(calculate("multiply", 3, 4)); // Thay đổi chiến lược tại đây
};
return (
<View>
<Text>Kết quả: {result}</Text>
<Button title="Tính toán" onPress={handleCalculate} />
</View>
);
};

Comment:

  • Strategy Pattern giảm sự rối rắm khi quản lý nhiều logic.
  • Dễ dàng thêm hoặc thay đổi thuật toán mà không ảnh hưởng đến phần còn lại của ứng dụng.

Dưới đây là một số câu hỏi chuyên sâu hơn và cách giải thích các Design Pattern khác mà bạn có thể gặp khi phỏng vấn cho React Native.


6. Command Pattern

Câu hỏi:

Command Pattern là gì? Hãy giải thích với một ví dụ trong React Native.

Giải thích:
Command Pattern đóng gói các yêu cầu (requests) thành các đối tượng, cho phép bạn tách rời người gửi yêu cầu khỏi người thực thi yêu cầu. Nó rất hữu ích trong việc xử lý các tác vụ như undo, redo, hoặc quản lý hành động phức tạp.

// Command Pattern
class Command {
execute() {
throw new Error("execute() phải được implement");
}
}
// Các command cụ thể
class TurnOnLightCommand extends Command {
execute() {
console.log("Bật đèn");
}
}
class TurnOffLightCommand extends Command {
execute() {
console.log("Tắt đèn");
}
}
// Invoker
class RemoteControl {
constructor() {
this.commands = [];
}
executeCommand(command) {
this.commands.push(command);
command.execute();
}
undo() {
const command = this.commands.pop();
console.log("Hoàn tác:", command);
}
}
// Sử dụng Command Pattern
const remote = new RemoteControl();
remote.executeCommand(new TurnOnLightCommand());
remote.executeCommand(new TurnOffLightCommand());
remote.undo();

Comment:

  • Command Pattern giúp xử lý các hành động như một thể hiện độc lập.
  • Rất phù hợp trong các ứng dụng cần hỗ trợ undo/redo hoặc quản lý hành động.

7. Builder Pattern

Câu hỏi:

Builder Pattern là gì? Khi nào sử dụng nó trong React Native? Viết một ví dụ.

Giải thích:
Builder Pattern cho phép xây dựng các đối tượng phức tạp từng bước. Nó tách biệt việc xây dựng đối tượng khỏi biểu diễn cuối cùng của nó, giúp mã dễ đọc hơn khi cấu hình nhiều tùy chọn.

// Builder để tạo đối tượng phức tạp
class UserBuilder {
constructor(name) {
this.name = name;
}
setAge(age) {
this.age = age;
return this; // Để chain các phương thức
}
setEmail(email) {
this.email = email;
return this;
}
build() {
return {
name: this.name,
age: this.age,
email: this.email,
};
}
}
// Sử dụng Builder
const user = new UserBuilder("John Doe")
.setAge(25)
.setEmail("john@example.com")
.build();
console.log(user);

Comment:

  • Builder Pattern hữu ích khi khởi tạo các đối tượng có nhiều thuộc tính, đặc biệt là trong việc cấu hình UI hoặc các API options.
  • Dễ dàng mở rộng và bảo trì.

8. Composite Pattern

Câu hỏi:

Composite Pattern là gì? Làm thế nào để áp dụng nó trong việc quản lý giao diện React Native?

Giải thích:
Composite Pattern cho phép bạn tổ chức các đối tượng theo cấu trúc dạng cây. Các nhóm và các đối tượng đơn lẻ đều được xử lý theo cùng một cách.

// Component cơ sở
class Component {
render() {
throw new Error("render() phải được implement");
}
}
// Leaf: Component cơ bản
class TextComponent extends Component {
constructor(text) {
super();
this.text = text;
}
render() {
return this.text;
}
}
// Composite: Component có thể chứa các component khác
class ViewGroup extends Component {
constructor() {
super();
this.children = [];
}
add(component) {
this.children.push(component);
}
render() {
return this.children.map((child) => child.render()).join(", ");
}
}
// Sử dụng Composite Pattern
const view = new ViewGroup();
view.add(new TextComponent("Hello"));
view.add(new TextComponent("World"));
const subView = new ViewGroup();
subView.add(new TextComponent("React"));
subView.add(new TextComponent("Native"));
view.add(subView);
console.log(view.render()); // "Hello, World, React, Native"

Comment:

  • Composite Pattern rất phù hợp để xây dựng giao diện phức tạp có cấu trúc dạng cây, giống như cách React tổ chức các component.
  • Có thể áp dụng để xây dựng hệ thống menu, layout, hoặc danh sách.

9. Proxy Pattern

Câu hỏi:

Proxy Pattern là gì? Cách sử dụng Proxy trong việc quản lý cache trong React Native.

Giải thích:
Proxy Pattern cung cấp một lớp thay thế hoặc trình bao bọc (wrapper) cho một đối tượng khác. Nó thường được sử dụng để kiểm soát truy cập, tối ưu hiệu suất, hoặc quản lý bộ nhớ cache.

// Proxy để quản lý cache
class ApiService {
fetchData(endpoint) {
console.log(`Fetching data from ${endpoint}`);
return Promise.resolve({ data: "Data from " + endpoint });
}
}
class ApiProxy {
constructor() {
this.apiService = new ApiService();
this.cache = {};
}
fetchData(endpoint) {
if (this.cache[endpoint]) {
console.log(`Dữ liệu từ cache: ${endpoint}`);
return Promise.resolve(this.cache[endpoint]);
}
return this.apiService.fetchData(endpoint).then((data) => {
this.cache[endpoint] = data;
return data;
});
}
}
// Sử dụng Proxy
const apiProxy = new ApiProxy();
apiProxy.fetchData("/users").then(console.log);
apiProxy.fetchData("/users").then(console.log);

Comment:

  • Proxy Pattern rất hữu ích trong việc quản lý cache, tối ưu hóa số lần gọi API.
  • Phù hợp với các ứng dụng cần quản lý hiệu năng hoặc tối ưu mạng.

10. Chain of Responsibility

Câu hỏi:

Chain of Responsibility là gì? Hãy giải thích với một ví dụ minh họa.

Giải thích:
Chain of Responsibility cho phép bạn gửi yêu cầu dọc theo một chuỗi các handler. Mỗi handler quyết định có xử lý yêu cầu hay truyền nó cho handler tiếp theo.

class Handler {
setNext(handler) {
this.nextHandler = handler;
return handler;
}
handle(request) {
if (this.nextHandler) {
return this.nextHandler.handle(request);
}
return null;
}
}
class AuthHandler extends Handler {
handle(request) {
if (request.isAuthenticated) {
console.log("Người dùng đã xác thực");
return super.handle(request);
}
console.log("Yêu cầu bị từ chối: Chưa xác thực");
return null;
}
}
class LoggerHandler extends Handler {
handle(request) {
console.log("Ghi log:", request.action);
return super.handle(request);
}
}
class ActionHandler extends Handler {
handle(request) {
console.log("Thực thi hành động:", request.action);
}
}
// Sử dụng Chain of Responsibility
const authHandler = new AuthHandler();
const loggerHandler = new LoggerHandler();
const actionHandler = new ActionHandler();
authHandler.setNext(loggerHandler).setNext(actionHandler);
const request = { isAuthenticated: true, action: "Lấy dữ liệu" };
authHandler.handle(request);

Comment:

  • Chain of Responsibility tách biệt logic xử lý yêu cầu thành các bước nhỏ gọn.
  • Hữu ích trong việc kiểm tra bảo mật, logging, hoặc xử lý yêu cầu API.

Dưới đây là các câu hỏi về Design Patterns chuyên sâu hơn cùng các giải thích và ví dụ minh họa áp dụng trong React Native.


11. Mediator Pattern

Câu hỏi:

Mediator Pattern là gì? Nó giải quyết vấn đề gì và làm thế nào để áp dụng nó trong React Native?

Giải thích:
Mediator Pattern định nghĩa một đối tượng trung gian để điều phối giao tiếp giữa các đối tượng. Nó giúp giảm sự phụ thuộc giữa các đối tượng, cải thiện khả năng bảo trì.

// Mediator
class ChatRoom {
constructor() {
this.users = [];
}
addUser(user) {
this.users.push(user);
user.setChatRoom(this);
}
sendMessage(sender, message) {
this.users.forEach((user) => {
if (user !== sender) {
user.receiveMessage(sender.name, message);
}
});
}
}
// Colleague
class User {
constructor(name) {
this.name = name;
}
setChatRoom(chatRoom) {
this.chatRoom = chatRoom;
}
sendMessage(message) {
console.log(`${this.name} gửi: ${message}`);
this.chatRoom.sendMessage(this, message);
}
receiveMessage(sender, message) {
console.log(`${this.name} nhận từ ${sender}: ${message}`);
}
}
// Sử dụng Mediator Pattern
const chatRoom = new ChatRoom();
const user1 = new User("Alice");
const user2 = new User("Bob");
const user3 = new User("Charlie");
chatRoom.addUser(user1);
chatRoom.addUser(user2);
chatRoom.addUser(user3);
user1.sendMessage("Xin chào mọi người!");

Comment:

  • Mediator Pattern giảm sự phụ thuộc giữa các đối tượng.
  • Hữu ích trong ứng dụng React Native khi cần giao tiếp giữa các thành phần độc lập.

12. State Pattern

Câu hỏi:

State Pattern là gì? Khi nào nên sử dụng trong ứng dụng React Native? Minh họa bằng ví dụ.

Giải thích:
State Pattern cho phép một đối tượng thay đổi hành vi của nó khi trạng thái nội tại thay đổi. Nó rất phù hợp để quản lý trạng thái động trong React Native, chẳng hạn như điều hướng hoặc trạng thái UI.

// Context
class MediaPlayer {
constructor() {
this.state = new StoppedState(this); // Bắt đầu ở trạng thái dừng
}
setState(state) {
this.state = state;
}
play() {
this.state.play();
}
stop() {
this.state.stop();
}
}
// State cơ sở
class State {
constructor(player) {
this.player = player;
}
play() {
throw new Error("Phương thức play() phải được override");
}
stop() {
throw new Error("Phương thức stop() phải được override");
}
}
// Các state cụ thể
class PlayingState extends State {
play() {
console.log("Đang phát nhạc");
}
stop() {
console.log("Dừng phát nhạc");
this.player.setState(new StoppedState(this.player));
}
}
class StoppedState extends State {
play() {
console.log("Phát nhạc");
this.player.setState(new PlayingState(this.player));
}
stop() {
console.log("Nhạc đã dừng");
}
}
// Sử dụng State Pattern
const player = new MediaPlayer();
player.play(); // Chuyển sang trạng thái PlayingState
player.stop(); // Chuyển về trạng thái StoppedState

Comment:

  • State Pattern giúp quản lý trạng thái phức tạp một cách rõ ràng.
  • Hữu ích trong việc điều khiển UI như chuyển đổi màn hình hoặc trạng thái phát nhạc.

13. Visitor Pattern

Câu hỏi:

Visitor Pattern là gì? Hãy minh họa cách áp dụng nó trong React Native.

Giải thích:
Visitor Pattern tách rời các thuật toán khỏi cấu trúc đối tượng mà chúng hoạt động trên đó. Nó cho phép thêm các hành vi mới mà không thay đổi các lớp hiện tại.

// Visitor Interface
class Visitor {
visit(element) {
throw new Error("visit() phải được implement");
}
}
// Element Interface
class Element {
accept(visitor) {
throw new Error("accept() phải được implement");
}
}
// Các Elements
class TextElement extends Element {
constructor(text) {
super();
this.text = text;
}
accept(visitor) {
visitor.visit(this);
}
}
class ImageElement extends Element {
constructor(url) {
super();
this.url = url;
}
accept(visitor) {
visitor.visit(this);
}
}
// Concrete Visitor
class RenderVisitor extends Visitor {
visit(element) {
if (element instanceof TextElement) {
console.log("Render Text:", element.text);
} else if (element instanceof ImageElement) {
console.log("Render Image:", element.url);
}
}
}
// Sử dụng Visitor Pattern
const elements = [
new TextElement("Hello, Visitor!"),
new ImageElement("https://example.com/image.jpg"),
];
const renderVisitor = new RenderVisitor();
elements.forEach((element) => element.accept(renderVisitor));

Comment:

  • Visitor Pattern hữu ích khi bạn cần áp dụng nhiều loại hành vi lên một tập hợp đối tượng không đồng nhất.
  • Dùng trong React Native để xử lý các kiểu dữ liệu khác nhau như Text, Image, hoặc Video.

14. Flyweight Pattern

Câu hỏi:

Flyweight Pattern là gì? Hãy giải thích và minh họa bằng một ví dụ thực tế.

Giải thích:
Flyweight Pattern là một cách tối ưu hóa việc sử dụng bộ nhớ bằng cách chia sẻ các trạng thái chung giữa các đối tượng. Trong React Native, nó có thể áp dụng khi vẽ nhiều phần tử giống nhau.

// Flyweight
class Shape {
constructor(type) {
this.type = type; // Trạng thái chung
}
render(x, y, color) {
console.log(`Vẽ ${this.type} tại (${x}, ${y}) với màu ${color}`);
}
}
// Flyweight Factory
class ShapeFactory {
constructor() {
this.shapes = {};
}
getShape(type) {
if (!this.shapes[type]) {
this.shapes[type] = new Shape(type); // Tạo nếu chưa tồn tại
}
return this.shapes[type];
}
}
// Sử dụng Flyweight
const factory = new ShapeFactory();
const circle1 = factory.getShape("circle");
circle1.render(10, 20, "red");
const circle2 = factory.getShape("circle");
circle2.render(30, 40, "blue");
console.log(circle1 === circle2); // true, cùng một đối tượng

Comment:

  • Flyweight Pattern giảm thiểu việc tạo nhiều đối tượng giống nhau, tối ưu hóa bộ nhớ.
  • Phù hợp khi render các thành phần giao diện giống nhau nhưng ở các vị trí khác nhau.

15. Adapter Pattern

Câu hỏi:

Adapter Pattern là gì? Khi nào nên sử dụng Adapter Pattern trong React Native?

Giải thích:
Adapter Pattern hoạt động như một bộ chuyển đổi (adapter) giữa hai giao diện không tương thích. Trong React Native, bạn có thể dùng nó để tích hợp các thư viện hoặc API cũ.

// API cũ
class OldApi {
getData() {
return "Dữ liệu từ API cũ";
}
}
// Adapter để chuyển đổi API cũ sang API mới
class ApiAdapter {
constructor(oldApi) {
this.oldApi = oldApi;
}
fetchData() {
return this.oldApi.getData(); // Gọi phương thức từ API cũ
}
}
// Sử dụng Adapter
const oldApi = new OldApi();
const adapter = new ApiAdapter(oldApi);
console.log(adapter.fetchData()); // "Dữ liệu từ API cũ"

Comment:

  • Adapter Pattern rất hữu ích khi bạn cần tích hợp các thư viện hoặc API không tương thích với kiến trúc hiện tại.
  • Giúp giảm sự phụ thuộc và cải thiện khả năng mở rộng.

1. Các nguyên lý cơ bản của OOP là gì?

Câu hỏi:

  • Các nguyên lý cơ bản của OOP là gì? Giải thích ý nghĩa của từng nguyên lý và cách áp dụng chúng trong JavaScript/TypeScript.

Giải thích: OOP có bốn nguyên lý chính:

  1. Encapsulation (Đóng gói): Tập trung dữ liệu và các phương thức xử lý dữ liệu trong một lớp. Bảo vệ trạng thái của đối tượng bằng cách ẩn các chi tiết không cần thiết (sử dụng private, protected, getter/setter).
  2. Abstraction (Trừu tượng): Chỉ hiển thị các chi tiết quan trọng và ẩn đi các chi tiết triển khai cụ thể (sử dụng abstract class, interface).
  3. Inheritance (Kế thừa): Một lớp có thể kế thừa thuộc tính và phương thức từ lớp khác.
  4. Polymorphism (Đa hình): Một hành động có thể thực hiện theo nhiều cách khác nhau tùy thuộc vào ngữ cảnh.

Ví dụ minh họa:

abstract class Animal {
constructor(private name: string) {}
abstract makeSound(): void; // Trừu tượng (Abstraction)
getName(): string {
return this.name; // Encapsulation: Ẩn thuộc tính name, chỉ truy cập qua getter
}
}
class Dog extends Animal {
makeSound() {
console.log("Woof!"); // Kế thừa và đa hình
}
}
class Cat extends Animal {
makeSound() {
console.log("Meow!"); // Đa hình: Cách hành xử khác so với Dog
}
}
const animals: Animal[] = [new Dog("Buddy"), new Cat("Kitty")];
animals.forEach((animal) => {
console.log(animal.getName());
animal.makeSound();
});

2. Sự khác biệt giữa class và prototype trong JavaScript là gì?

Câu hỏi:

  • JavaScript có hỗ trợ OOP không? class và prototype khác nhau như thế nào?

Giải thích:

  • Prototype là cách cũ của JavaScript để đạt được OOP trước khi class được giới thiệu trong ES6. Prototype dựa trên chuỗi (prototype chain) để chia sẻ các thuộc tính và phương thức giữa các đối tượng.
  • Class trong ES6 chỉ là cú pháp mới giúp viết code dễ đọc hơn nhưng vẫn dựa trên nguyên tắc prototype.

Ví dụ minh họa:

// Prototype-based
function Person(name) {
this.name = name;
}
Person.prototype.greet = function () {
console.log(`Hello, my name is ${this.name}`);
};
const john = new Person("John");
john.greet();
// Class-based
class PersonClass {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello, my name is ${this.name}`);
}
}
const jane = new PersonClass("Jane");
jane.greet();

3. TypeScript hỗ trợ OOP tốt hơn JavaScript như thế nào?

Câu hỏi:

  • Lợi ích của TypeScript trong việc lập trình hướng đối tượng so với JavaScript là gì?

Giải thích: TypeScript bổ sung các tính năng mạnh mẽ cho OOP:

  1. Type Checking: Kiểm tra kiểu tại thời điểm biên dịch.
  2. Access Modifiers: Hỗ trợ private, protected, public để kiểm soát truy cập.
  3. Interface: Tạo hợp đồng cho các lớp hoặc đối tượng.
  4. Abstract Class: Hỗ trợ định nghĩa trừu tượng.
  5. Decorators: Thêm metadata hoặc xử lý đặc biệt vào các lớp/phương thức.

Ví dụ minh họa:

interface Shape {
area(): number;
}
class Rectangle implements Shape {
constructor(private width: number, private height: number) {}
area(): number {
return this.width * this.height;
}
}
class Circle implements Shape {
constructor(private radius: number) {}
area(): number {
return Math.PI * this.radius ** 2;
}
}
const shapes: Shape[] = [new Rectangle(10, 5), new Circle(7)];
shapes.forEach((shape) => {
console.log(`Area: ${shape.area()}`);
});

4. Sự khác biệt giữa private, protected và public trong TypeScript là gì?

Câu hỏi:

  • Access Modifiers hoạt động như thế nào trong TypeScript?

Giải thích:

  • public: Mặc định. Các thuộc tính/phương thức có thể truy cập từ bất cứ đâu.
  • private: Chỉ có thể truy cập bên trong lớp.
  • protected: Có thể truy cập bên trong lớp và các lớp con.

Ví dụ minh họa:

class Parent {
public publicProp = "Public Property";
private privateProp = "Private Property";
protected protectedProp = "Protected Property";
showProperties() {
console.log(this.publicProp); // OK
console.log(this.privateProp); // OK
console.log(this.protectedProp); // OK
}
}
class Child extends Parent {
showProperties() {
console.log(this.publicProp); // OK
// console.log(this.privateProp); // Error: Không thể truy cập privateProp
console.log(this.protectedProp); // OK
}
}
const child = new Child();
child.showProperties();
// console.log(child.protectedProp); // Error: Không thể truy cập từ bên ngoài

5. Tính kế thừa (Inheritance) hoạt động thế nào trong TypeScript?

Câu hỏi:

  • Làm thế nào để triển khai tính kế thừa trong TypeScript? Tính đa hình (Polymorphism) có vai trò gì?

Giải thích: Kế thừa cho phép lớp con sử dụng lại các thuộc tính và phương thức của lớp cha. Đa hình cho phép lớp con triển khai các phương thức theo cách riêng.

Ví dụ minh họa:

class Vehicle {
constructor(protected brand: string) {}
start(): void {
console.log(`${this.brand} is starting`);
}
}
class Car extends Vehicle {
constructor(brand: string, private model: string) {
super(brand);
}
start(): void {
console.log(`${this.brand} ${this.model} is starting`);
}
}
const myCar = new Car("Toyota", "Corolla");
myCar.start(); // "Toyota Corolla is starting"

6. Abstract Class và Interface khác nhau như thế nào?

Câu hỏi:

  • Khi nào nên dùng abstract class và khi nào dùng interface trong TypeScript?

Giải thích:

  • Interface:
    • Chỉ định hình dạng (shape) của đối tượng hoặc lớp.
    • Không hỗ trợ phương thức có sẵn (implementation).
  • Abstract Class:
    • Dùng để định nghĩa lớp trừu tượng, có thể chứa phương thức cụ thể và phương thức trừu tượng.

Ví dụ minh họa:

// Interface chỉ định hình dạng
interface Flyable {
fly(): void;
}
// Abstract class cung cấp logic chung
abstract class Bird {
abstract fly(): void;
eat(): void {
console.log("Eating...");
}
}
class Sparrow extends Bird implements Flyable {
fly() {
console.log("Sparrow is flying");
}
}
const sparrow = new Sparrow();
sparrow.fly();
sparrow.eat();

7. Làm thế nào để thực hiện Overloading trong TypeScript?

Câu hỏi:

  • TypeScript hỗ trợ Overloading (nạp chồng phương thức) không? Nếu có, hãy giải thích.

Giải thích: TypeScript hỗ trợ Overloading bằng cách sử dụng nhiều chữ ký (signature) cho một phương thức.

Ví dụ minh họa:

class Calculator {
add(a: number, b: number): number;
add(a: string, b: string): string;
add(a: any, b: any): any {
return a + b;
}
}
const calc = new Calculator();
console.log(calc.add(1, 2)); // 3
console.log(calc.add("Hello, ", "World!")); // "Hello, World!"

8. Làm thế nào để tạo Singleton Pattern trong TypeScript?

Câu hỏi:

  • Singleton Pattern trong TypeScript được triển khai như thế nào?

Giải thích: Singleton Pattern đảm bảo chỉ có một instance của lớp trong suốt vòng đời ứng dụng.

Ví dụ minh họa:

class Singleton {
private static instance: Singleton;
private constructor() {}
static getInstance(): Singleton {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
showMessage(): void {
console.log("Singleton instance");
}
}
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true

Để lại một bình luận