Lập Trình

Tổng hợp các thông tin, kinh nghiệm hữu ích và mới nhất về lập trình cần học gì, phỏng vấn, mức lương trong ngành IT như thế nào, tìm hiểu ngay!

398 bài viết

Streaming Media với Nginx và nginx-rtmp module

Streaming Media với Nginx và nginx-rtmp module

Lộ trình khá chi tiết Streaming server Media với Nginx và nginx-rtmp module. 1. Về nginx-rtmp module Nginx-rtmp là module mở rộng, mà kết hợp với Nginx để cho phép xây dựng máy chủ streaming media. Một số tính năng mà nginx-rtmp hỗ trợ: RTMP/HLS/MPEG-DASH live streaming RTMP Video on demand FLV/MP4, phát từ local file hoặc qua HTTP Stream relay support for distributed streaming: push & pull models Ghi streams vào nhiều tệp FLV Hỗ trợ H264/AAC Transcode trực tuyến với FFmpeg HTTP callbacks (publish/play/record/update etc) Module điều khiển HTTP để recording audio/video and dropping clients Kỹ thuật buffer tiên tiến để giữ cho bộ nhớ được cấp ở mức thấp nhất mà streaming vẫn nhanh. Kết hợp được với các ứng dụng như Wirecast, FMS, Wowza, JWPlayer, FlowPlayer, StrobeMediaPlayback, ffmpeg, avconv, rtmpdump, flvstreamer, .. Thống kê stream với định dạng XML/XSL Linux/FreeBSD/MacOS/Windows Có thể bạn muốn xem thêm: Triển khai dịch vụ High Available với Keepalived + HAproxy trên server Ubuntu Cách cài đặt cấu hình máy tính cá nhân thành một public server trên Internet 2. Cài đặt Nginx với module nginx-rtmp Step1 : Download & unpack latest stable nginx & nginx-rtmp version cd /opt sudo git clone git://github.com/arut/nginx-rtmp-module.git sudo wget http://nginx.org/download/nginx-1.14.1.tar.gz sudo tar xzf nginx-1.14.1.tar.gz mv nginx-1.14.1 nginx Step2 : Build nginx với nginx-rtmp sudo ./configure --prefix=/etc/nginx --pid-path=/var/run/nginx.pid --conf-path=/etc/nginx/nginx.conf --sbin-path=/usr/sbin/nginx --user=nginx --group=nginx --with-file-aio --with-http_ssl_module --add-module=nginx-rtmp-module sudo make sudo make install Step3 : Run Nginx với systemd Create tệp /lib/systemd/system/nginx.service với nội dung sau: Description=nginx - high performance web server Documentation=http://nginx.org/en/docs/ After=network-online.target remote-fs.target nss-lookup.target Wants=network-online.target [Service] Type=forking ExecStartPre=/usr/sbin/nginx -t -c /etc/nginx/nginx.conf ExecStart=/usr/sbin/nginx -c /etc/nginx/nginx.conf ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s QUIT $MAINPID [Install] WantedBy=multi-user.target Start Nginx service systemctl enable nginx.service systemctl start nginx.service 3. VOD qua RTMP Chúng ta sẽ cấu hình để cho phép các video...

By stationd
Semaphore trong Java

Semaphore trong Java

Bài viết được sự cho phép của tác giả Giang Phan Semaphore là gì? Semaphore là một cơ chế giúp quản lý các nguồn chia sẻ và đảm bảo access không bị tắc nghẽn. Có hai loại semaphore: binary semaphore và counting semaphore. Binary semaphore (Mutex): được dùng làm lock vì nó chỉ có 2 giá trị là 0 và 1. Hai giá trị này đại diện cho trạng thái lock hay unlock. Counting semaphore: thực hiện đếm resource để cho biết mức độ sẵn sàng của resource. Xem thêm tuyển dụng Java lương hấp dẫn trên Station D Cơ chế hoạt động Một Semaphore lưu trữ một danh sách các permit (hay ticket), mỗi khi gọi acquire() sẽ lấy 1 ticket từ Semaphore, mỗi khi gọi release() sẽ trả ticket về Semaphore. Nếu ticket không có sẵn, acquire() sẽ bị lock cho đến khi có ticket. Để kiểm tra số lượng ticket còn lại, sử dụng phương thức availablePermits() . Ví dụ chúng ta gọi các phương thức tuần tự như sau: // Tạo một Semaphore có 5 ticket Semaphore semaphore = new Semaphore(5); // Yêu cầu lấy 1 ticket để sử dụng semaphore.acquire(); // 5-1 // Đếm về số lượng ticket có sẵn int numberOfAvailableTickets = semaphore.availablePermits(); // 4 // Trả 1 ticket về Semaphore semaphore.release(); // 4+1 // Đếm về số lượng ticket có sẵn semaphore.availablePermits(); // 5 Ví dụ sử dụng Semaphore Giả sử một ngân hàng có 4 cây ATM, mỗi cây chỉ có thể phục vụ được một khách hàng tại một thời điểm. Chương trình bên dưới cho thấy Semaphore có thể đảm bảo chỉ tối đa 4 người có thể truy cập tại một thời điểm. WorkerThread.java package com.gpcoder.semaphore; import java.util.concurrent.Semaphore; public class WorkerThread extends Thread { private final Semaphore semaphore; private...

By stationd
Phương thức List append() trong Python

Phương thức List append() trong Python

List là một trong những kiểu dữ liệu cơ bản trong Python lưu trữ các giá trị dưới dạng mảng (collection) có thứ tự, có thể thay đổi và cho phép chứa dữ liệu trùng lặp. Thao tác với List thông qua các phương thức thêm, sửa, xóa các phần tử trong đó là những kiến thức bắt buộc mọi lập trình viên Python đều nắm vững. Mặc dù vậy, có nhiều anh em mới làm Python còn nhầm lẫn trong việc sử dụng một số hàm có chức năng tương đối giống nhau với List, vì vậy bài viết hôm nay chúng ta cùng nhau làm rõ về phương thức append trong Python và so sánh nó với một số hàm khác tương tự nhé. Phương thức Append Append là phương thức dùng để thêm một phần tử vào cuối một List trong Python. Phần tử được thêm vào có thể là một số, một List, một chuỗi hoặc một Tuple. Cú pháp hàm append trong Python: list.append(obj) trong đó obj là đối tượng được dùng để thêm vào cuối list Ví dụ: Thêm một ký tự/ số vào cuối List mylist = [ "A" , "B" , "C" ] mylist.append( "D" ) print (mylist) #>> ["A", "B", "C", "D"] Thêm một List vào cuối List mylist = [ "A" , "B" , "C" ] addlist = [ "D" , "E" ] mylist.append(addlist) print (mylist) #>> ['A', 'B', 'C', ['D', 'E']] Thêm một Tuple vào cuối List mylist = [ "A" , "B" , "C" ] addtuple = ( 1 , 2 ) mylist.append(addtuple) print (mylist) #>> ['A', 'B', 'C', (1, 2)] Ở các ví dụ trên chúng ta có thể thấy rằng khi thêm 1 phần tử vào cuối List thì chiều dài (length) của List luôn chỉ...

By stationd
IronPython

IronPython

Bài viết được sự cho phép của tác giả Nguyễn Việt Hưng IronPython là gì? Theo giới thiệu ở trang chủ IronPython : IronPython is an open-source implementation of the Python programming language which is tightly integrated with the .NET Framework. IronPython can use the .NET Framework and Python libraries, and other .NET languages can use Python code just as easily. Ironpython là 1 “implementation” mã nguồn mở của ngôn ngữ lập trình Python, tích hợp chặt chẽ với .NET Framework. IronPython có thể sử dụng .NET Framework và các thư viện Python, các ngôn ngữ .NET khác cũng có thể đọc và chạy code Python dễ dàng. File I/O trong Python Cài đặt IronPython Vào trang chủ download bộ cài đặt về (link download ở trang chủ là link github). Thời điểm viết bài, IronPython đang ở phiên bản 2.7.8 . IronPython 3 đang trong quá trình phát triển, chưa có bản chính thức. Sử dụng Bật IronPython interpreter bằng cách chạy (bấm đúp – double click) vào file ở đường dẫn: C:Program Files (x86)IronPython 2.7ipy.exe hoặc C:Program Files (x86)IronPython 2.7ipy64.exe . Hoặc bấm nút Start -> gõ cmd -> Enter, sau khi cửa sổ Command Prompt hiện lên thì gõ đường dẫn file: C:Program Files (x86)IronPython 2.7ipy.exe hoặc C:Program Files (x86)IronPython 2.7ipy64.exe : Microsoft Windows [ Version 6.1 . 7601 ] Copyright ( c ) 2009 Microsoft Corporation . All rights reserved . C : Users HTL > "c:Program Files (x86)IronPython 2.7ipy.exe" IronPython 2.7 . 3 ( 2.7 . 0.40 ) on . NET 4.0 . 30319.42000 ( 32 - bit ) Type "help" , "copyright" , "credits" or "license" for more information . >>> print ( "Hello from IronPython" ) Hello from IronPython Chạy 1 python script đã viết sẵn bằng cách gõ vào Command Prompt: C:Program...

By stationd
Làm app giao diện đồ hoạ với Python

Làm app giao diện đồ hoạ với Python

Bài viết được sự cho phép của tác giả Nguyễn Việt Hưng Giao diện đồ hoạ (GUI – Graphic User Interface) vốn từng là một phần không thể thiếu khi nói về lập trình. Dù học ngôn ngữ lập trình nào, người ta cũng nghĩ tới chuyện “làm sao để có giao diện đồ hoạ”. Thế giới thay đổi, thứ từng quan trọng của ngày hôm qua thì hôm nay chưa chắc đã cần tới. Thời đại tất cả mọi thứ đều chuyển lên web, thì web/app mobile trở thành giao diện để tương tác với người dùng, chứ không phải các phần mềm có giao diện chạy trên máy tính như trước kia. Giờ đây người ta: nghe nhạc trên web, xem film trên web, chơi game trên web, soạn thảo văn bản trên web… khó còn ứng dụng nào không đưa lên web nữa. Vậy nên về mặt “sự nghiệp”, có vẻ như bạn nên đầu tư vào kỹ năng làm web thay vì học để tạo một app trên desktop như cách đây chục năm. Dĩ nhiên, GUI không ngay lập tức biến mất, vẫn có nhu cầu sử dụng, vẫn có người dùng, vẫn có hàng tá thư viện đồ hoạ tồn tại từ lâu (và vẫn tiếp tục phát triển), vẫn có những game mà chỉ chơi được trên máy tính do yêu cầu về hiệu năng mà web không đáp ứng nổi (như Half-Life/ đế chế / đua xe …). Python hỗ trợ không ít các thư viện làm GUI app, có thể kể tới: Qt, WxWidgets, Tkinter, Kivy (làm cả app mobile) … xem đầy đủ tại: https://docs.python.org/3/faq/gui.html https://www.python.org/about/apps/#desktop-guis https://docs.python-guide.org/scenarios/gui/ Qt là nền tảng phát triển ứng dụng dùng trong công nghiệp, hỗ trợ mọi hệ điều hành phổ biến, và rất “xịn”....

By stationd
Hướng dẫn cài đặt và cấu hình để dùng nhiều version PHP (Multiple versions PHP)

Hướng dẫn cài đặt và cấu hình để dùng nhiều version PHP (Multiple versions PHP)

Bài viết được sự cho phép của tác giả Lê Chí Dũng Để cài đặt nhiều version php cho nhiều project website, dùng yum-config-manager để cài đặt multiple versions of PHP. Và lưu ý bài viết này dành cho những người có kiến thức cơ bản về Nginx và PHP. 1. Cài đặt và cấu hình các version PHP 1.1 Cài đặt PHP 7.1 Version # yum - config - manager - - enable remi - php71 [ Default ] # yum install php php - common php - fpm # yum install php - mysql php - pecl - memcache php - pecl - memcached php - gd php - mbstring php - mcrypt php - xml php - pecl - apc php - cli php - pear php - pdo Copy 1.2 Cài đặt PHP 5.6 Version # yum install php56 php56 - php - common php56 - php - fpm # yum install php56 - php - mysql php56 - php - pecl - memcache php56 - php - pecl - memcached php56 - php - gd php56 - php - mbstring php56 - php - mcrypt php56 - php - xml php56 - php - pecl - apc php56 - php - cli php56 - php - pear php56 - php - pdo Copy Kiểm tra version PHP mặc định. # php - v Copy 1.3 Cấu hình PHP-FPM và PHP56-PHP-FPM Đây là phần cấu hình php-fpm sẽ hoạt động tương thích với Nginx. Cần thiết lập user/group của FastCGI khi hoạt động trên Nginx sẽ listen đúng port. php-fpm (default 7.1): /etc/php-fpm.d/www.conf php56-php-fpm: /opt/remi/php56/root/etc/php-fpm.d/www.conf Mở file config đề thiết lập user/group của FastCGI. # vi / etc / php - fpm.d / www.conf [ PHP 7.1 ] # vi / opt / remi / php56...

By stationd
Cấu trúc dữ liệu từ điển Dictionary trong Python

Cấu trúc dữ liệu từ điển Dictionary trong Python

Bài viết được sự cho phép của tác giả Kien Dang Chung Trong Python có tới 4 kiểu cấu trúc dữ liệu là List, Tuple, Set và Dictionary. Trong các bài trước chúng ta đã lần lượt làm quen với các kiểu dữ liệu này và còn lại Dictionary, một cấu trúc rất hay dùng trong Python. Trong bài viết này, cùng Station D tìm hiểu về cấu trúc dữ liệu từ điển Dictionary trong Python . Từ điển – Dictionary trong Python Từ điển dữ liệu (Dictionary) còn được gọi là mảng liên kết (associative array) trong một số ngôn ngữ lập trình, là một dạng danh sách như bạn đã được tìm hiểu. Có một điểm khác là các phần tử trong danh sách được truy xuất thông qua vị trí thì phần tử trong từ điển được truy xuất qua khóa (key). Bạn có thể định nghĩa khóa này, nó có thể là một chuỗi hoặc số nhưng nó phải là duy nhất trong từ điển. Tại sao vậy? Chúng ta liên tưởng đến nhưng tình huống thực tế như, hai người có cùng một số điện thoại, vậy khi gọi đến biết ai là người nghe máy. Hai người có cùng một số tài khoản, vậy biết chuyển khoản cho ai bây giờ? Chính vì vậy, khóa (key) trong từ điển phải là duy nhất. Vậy kiểu dữ liệu Dict trong Python là gì? Kiểu dữ liệu Dict (viết tắt của Dictionary) trong Python là một kiểu dữ liệu lưu trữ các cặp key-value, tương tự như List và Tuple. Tuy nhiên, các giá trị trong Dict không được sắp xếp theo một trật tự cụ thể nào. Từ điển trong Python cũng có thể tưởng tượng giống như một cuốn từ điển Anh – Việt...

By stationd
Hướng dẫn Java Design Pattern – DAO

Hướng dẫn Java Design Pattern – DAO

Bài viết được sự cho phép của tác giả Giang Phan Một trong những khía cạnh quan trọng của lớp nghiệp vụ (business layer) là lớp truy cập dữ liệu (data access layer) để kết nối các dịch vụ (service) với cơ sở dữ liệu (database). Việc truy cập dữ liệu tùy thuộc vào nguồn dữ liệu, loại lưu trữ như database, text file, xml file, json file, …Thậm chí nó khác với cách triển khai của nó, ví dụ: cú pháp truy vấn SQL khác nhau giữa MySQL, SQL Server, Oracle, … Với mong muốn sẽ không có gì khác biệt khi truy cập cơ sở dữ liệu quan hệ, phân tích xml file hay bất kỳ nguồn dữ liệu nào khác, chúng ta có thể áp dụng mẫu thiết kế đối tượng truy cập dữ liệu ( Data Access Object Pattern – DAO Pattern ). DAO Pattern là gì? Data Access Object (DAO) Pattern là một trong những Pattern thuộc nhóm cấu trúc (Structural Pattern). Mẫu thiết kế DAO được sử dụng để phân tách logic lưu trữ dữ liệu trong một lớp riêng biệt. Theo cách này, các service được che dấu về cách các hoạt động cấp thấp để truy cập cơ sở dữ liệu được thực hiện. Nó còn được gọi là nguyên tắc Tách logic ( Separation of Logic ). Ý tưởng là thay vì có logic giao tiếp trực tiếp với cơ sở dữ liệu, hệ thống file, dịch vụ web hoặc bất kỳ cơ chế lưu trữ nào mà ứng dụng cần sử dụng, chúng ta sẽ để logic này sẽ giao tiếp với lớp trung gian DAO. Lớp DAO này sau đó giao tiếp với hệ thống lưu trữ, hệ quản trị CSDL như thực hiện các công việc liên quan...

By stationd
Tránh lỗi ConcurrentModificationException trong Java như thế nào?

Tránh lỗi ConcurrentModificationException trong Java như thế nào?

Bài viết được sự cho phép của tác giả Nguyễn Hữu Khanh Một trong những vấn đề phổ biến trong khi loại bỏ các phần tử từ một ArrayList trong Java là ConcurrentModificationException . Nếu bạn sử dụng vòng lặp foreach và cố gắng thêm/ xóa phần tử khỏi ArrayList bằng phương thức remove(), bạn sẽ nhận được ConcurrentModificationException . Tuy nhiên, nếu bạn sử dụng phương thức xóa của Iterator hoặc ListIterator bằng phương thức remove() , bạn sẽ không gặp lỗi này và có thể xóa phần tử đó. Trong bài viết này, tôi sẽ giải thích, đưa ra một vài ví dụ cho bạn thấy được trường hợp xảy ra lỗi ConcurrentModificationException và cách có thể tránh lỗi này trong khi sửa đổi một ArrayList trong Java. Phân biệt ArrayList và LinkedList Code ví dụ Callable, Future, Executors trong Java Ví dụ xảy ra lỗi ConcurrentModificationException Thêm/ xóa phần tử khi sử dụng ArrayList.remove() khi duyệt qua for-each package com.gpcoder.collection.list.ConcurrentModificationException; import java.util.ArrayList; import java.util.List; public class ConcurrentModificationException1 { public static void main(String[] args) { List<String> languages = new ArrayList<>(); languages.add("Java"); languages.add("C#"); languages.add("PHP"); languages.add("C++"); languages.add("Ruby"); // Using forEach loop to iterate and add/ removing element during iteration will // throw ConcurrentModificationException in Java for (String language : languages) { if (language.equals("C#")) { languages.remove(language); } } } } Output của chương trình: Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.gpcoder.collection.list.ConcurrentModificationException.ConcurrentModificationException1.main(ConcurrentModificationException1.java:18) Ứng tuyển các vị trí việc làm Java lương cao trên Station D Thêm/ xóa phần tử sử dụng ArrayList.remove() khi duyệt qua Iterator Iterator<String> iterator = languages.iterator(); while (iterator.hasNext()) { String language = iterator.next(); if (language.equals("C#")) { languages.remove(language); } } Output của chương trình: Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at com.gpcoder.collection.list.ConcurrentModificationException.ConcurrentModificationException2.main(ConcurrentModificationException2.java:19) Tránh lỗi ConcurrentModificationException Sử dụng vòng lặp for-index for (int...

By stationd
Replace Python và cách sử dụng

Replace Python và cách sử dụng

String replace là một bài toán cơ bản, quen thuộc thường gặp trong quá trình viết code thực hiện chương trình của chúng ta. Trong Python, chúng ta được cung cấp sẵn hàm cho thao tác xử lý này. Mặc dù vậy, không dễ để nắm rõ được hết các tham số đầu vào của hàm xử lý thay thế chuỗi và áp dụng một cách hiệu quả. Bài viết này chúng ta cùng tìm hiểu về hàm replace string Python và cách sử dụng nó thông qua các ví dụ cụ thể nhé. Replace Python là gì? String replace là một hàm tích hợp sẵn trong Python, có chức năng để thay thế một chuỗi con trong chuỗi đầu vào bằng một chuỗi khác. Nó sẽ thực hiện việc duyệt chuỗi đầu vào, tìm kiếm tất cả các vị trí mà chuỗi con cần thay thế xuất hiện và thực hiện việc thay thế tất cả bằng chuỗi mới. Cú pháp của hàm: string .replace( oldvalue, newvalue, count ) Trong đó: string : chuỗi đầu vào cần thực hiện thay thế giá trị oldvalue : chuỗi con cần tìm kiếm để thay thế, tham số này là bắt buộc newvalue : chuỗi mới sẽ thay thế cho oldvalue , tham số này là bắt buộc count : tham số tùy chọn. Sử dụng để xác định số lần thực hiện việc thay thế giá trị oldvalue bằng newvalue Lưu ý khi sử dụng hàm replace trong Python : Python replace là hàm tích hợp sẵn của các đối tượng trong lớp String. Bạn không thể sử dụng với các kiểu dữ liệu khác, nếu cố ý gọi thì sẽ nhận về lỗi “ AttributeError: ‘int’ object has no attribute ‘replace’ ” Tham số count nếu không truyền vào thì...

By stationd
9 dự án mới nhất giúp bạn thành trùm Frontend trong năm 2024

9 dự án mới nhất giúp bạn thành trùm Frontend trong năm 2024

Dù xuất phát điểm bạn ở đâu, là một tay gà mờ hay lão làng trong ngành, việc liên tục cập nhật khái niệm, ngôn ngữ và framework mới là điều bắt buộc để theo kịp xu hướng, đặc biệt là các tip và trick về frontend. Lấy ví dụ, cách đây 4 năm, React, nguồn mở của Facebook trở thành lựa chọn số 1 của các nhà lập trình JavaScript trên toàn cầu. Tuy nhiên, Vue và Angular có chỗ đứng nhất định trong thị trường, và theo sau đó là sự ra đời hàng loạt của các framework Next.js, Nuxt.js và Gatsby, Gridsome, Quasar. Nếu bạn định hướng bản thân là một nhà lập trình JavaScript chuyên nghiệp, bạn cần có sự phong phú trong kinh nghiệm ở nhiều framework khác nhau. Và nếu bạn thực sự đã vạch ra kế hoạch để trở thành một master, thì xin chúc mừng bạn, những tổng hợp dưới đây là 9 dự án, bao gồm những topic cụ thể kèm theo thư viện JavaScript để bạn tham khảo. Hãy nhớ rằng bạn chỉ thực sự thành thạo nó khi bắt tay thực hành và tự tạo cho riêng mình. Xây dựng ứng dụng tìm kiếm phim bằng React (với Hook) Bạn sẽ học được gì? Xây dựng ứng dụng này, bạn sẽ cải thiện Kỹ năng React của mình bằng Hooks API. Dự án minh họa dưới đây sử dụng các thành phần của React, tập hợp nhiều hook, API bên ngoài và tất nhiên là một số định dạng thông qua CSS. Tech Stack & Features React with Hooks create-react-app JSX CSS Dù không sử dụng bất kì một class nào, những dự án này mang đến cho bạn một điểm khởi đầu hoàn hảo với các chức năng...

By stationd
Hướng dẫn Java Design Pattern – Memento

Hướng dẫn Java Design Pattern – Memento

Bài viết được sự cho phép của tác giả Giang Phan Đô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)....

By stationd