Interface segregation: cách giải thích dễ nhầm tưởng mỗi method tách 1 interface.
ShapeCalculator là một lớp đặc thù và việc nó có 3 phương thức tính chu vi, diện tích, thể tích là hoàn toàn hợp lý, không có gì sai cả. Vậy nếu có interface IShapeCalculator thì nó nên có cả 3 phương thức.
Nếu 3 phương thức trên thuộc về Shape thì lại khác. Chu vi và diện tích thì của hình 2D, diện tích và thể tích lại của hình 3D, việc gom cả 3 hành vi vào 1 interface là không thể nên cần tách ra để giữ được sự mềm dẻo.
public class Circle: IHasArea, IHasPerimeter
{
public number GetArea();
public number GetPerimeter();
}
public class Cube: IHasArea, IHasVolume
{
public number GetArea();
public number GetVolume();
}
Con vịt trời và con vịt cao su không thể thay thế cho nhau dù cùng là vịt. Phương thức bay của giống vịt sẽ gây lỗi nếu là gọi từ instance con vịt cao su.
Shape. Nếu trừu tượng hóa các đa giác theo số cạnh của nó, các hành vi của nó sẽ không hoạt động giống nhau với mỗi mỗi hình khác nhau. Tam giác có cách tính diện tích khác. Hình vuông có cách tính diện tích khác tam giác (khác số cạnh), và khác hình chữ nhật (dù cùng số cạnh). Cho nên các hình chỉ đơn giản là thừa kế IShape với phương thức computeSurface mà thôi.
Thực ra ví dụ trên vẫn trừu tượng. Hãy xét một bài toán về e-commerce, cách implement chương trình khuyến mãi hay giảm giá cho sản phẩm là coupon (voucher) và giảm giá - discount. Liệu chúng ta có thể tạo một parent chung cho cả hai phương thức là ISaleProgram được không?
public interface ISaleProgram {}
public class Voucher : ISaleProgram {}
public class Discount : ISaleProgram {}
public class Order
{
private readonly IEnumerable<ISaleProgram> salePrograms;
public number Pay()
{
var totalCost = items.Sum(i => i.Price);
var afterTax = totalCost * 1.1; // tax 10%
var matchedSalePrograms = allSalePrograms.Where(p => p.IsMatched(this));
var totalDiscountAmount = matchedSaleProgames.Sum(p => p.Discount(this));
return afterTax - totalDiscountAmount;
}
}
Tổng quan thì là hợp lý, nhưng có sự khác biệt đối với 2 phương thức trên:
Voucher: trừ thẳng tiền mặt, tính sau thuế. Thuế tính theo giá gốc.
Discount: trừ giá trước thuế, thuế tính trên giá mới.
Như vậy Voucher được tính sau khi tính tổng hóa đơn rồi trừ đi. Discount được tính trong khi tính tổng hóa đơn. Sự khác biệt này sẽ làm chương trình tính giá bị lỗi nếu chúng ta coi Voucher và Discount là cùng họ. Hai cách tính là khác nhau và tham gia vào hai bước khác nhau của quá trình tính giá phải trả.
Phần giải thích Open/Close cũng nhầm lẫn giữa trách nhiệm và hành vi. Addition, Subtraction thì đều là hành vi của Calculator. Addition, Subtraction nên thừa kế từ interface Operator chứ không phải Calculator. Thiết kế cho Calculator sẽ giống thế này:
Phần giải thích Single Responsibility có thể gây nhầm lẫn thành mỗi class chỉ nên có một public method, có 2 public method trở lên thì phải tách ra.
Cần phải phần biệt giữa trách nhiệm và hành vi. Nếu nhầm lẫn thì Java đã sai ngay từ đầu khi cho đa thừa kế.
Một class có trách nhiệm duy nhất nhưng nó có thể có nhiều hành vi. Cùng xem một ví dụ của mẫu Facade:
public class AtmFacade
{
public WithdrawalFunction WithdrawalFunction { get; set; }
public DepositFunction DepositFunction { get; set; }
public BalanceFunction BalanceFunction { get; set; }
}
Có thể thấy AtmFacade có trách nhiệm duy nhất là gom các chức năng của cây ATM lại để dễ sử dụng (giảm sự phức tạp). Nếu coi các chức năng con trong đó là trách nhiệm của nó thì thiết kế này sai mất. Các chức năng con là các hành vi mà cây ATM này cung cấp.
Nhưng các chức năng con này cũng thể hiện trách nhiệm duy nhất như cách giải thích của bài viết:
public class WithdrawalFunction
{
public void Withdraw(decimal amount);
}
Single Responsibility là nguyên lý đơn giản nhưng rất linh động và thể hiện nhiều nhất sự khác biệt trong tư duy thiết kế cũng như trình độ của các kĩ sư. Không có công thức cho Single Responsibility. Có lẽ chỉ có một tiêu chí đánh giá xem mã nguồn có đơn nhiệm không: Khi không còn gì để nói về nó nữa.
Dependency Injection là Dependency Injection. Nó che giấu đi cách các concrete class được khởi tạo, chỉ việc gọi tên class, nên làm cho mình cảm tưởng đó là bỏ đi sự phụ thuộc. Dependency Injection hoàn toàn có thể đăng ký 1 concrete class và injection nó trong các class khác. Các thư viện DI thì chỉ nhận là DI chứ không nhận là IoC vì bản chất là khác nhau. DI giống như một thể hiện rõ ràng hơn của Dependency Inversion (hay IoC), giúp lập trình viên cảm nhận và hiểu nguyên lý của Dependency Inversion.
Sự phụ thuộc mà D.Injection và D.Inversion đề cập đến là khác nhau.
D.Injection giải quyết sự phức tạp của constructor và lifetime của một class, hay một phụ thuộc trong class. Một class có thể phụ thuộc vào nhiều class khác, dẫn đến constructor của nó trở nên phức tạp, độ phức tạp còn tăng lên thêm khi nó là một phụ thuộc trong một class khác. Để giải quyết constructor thì ngày trước dùng công cụ thường gọi là ServiceLocator chứ không phải D.Injection (thường là đăng ký factory cho từng class cho ServiceLocator). Ngày nay D.Injection giải quyết constructor tự động, và vẫn cung cấp khả năng đăng ký factory nếu cần. Ngoài ra, D.Injection quản lý cả lifetime của một dependency, tức nó không quản lý 1 instance mà quản lý một chuỗi dependency, khởi tạo và giải phóng cùng nhau (theo lifetime).
D.Inversion là khái niệm về thiết kế phần mềm. Khi chưa có D.Injection, ng ta vẫn thiết kế dùng D.Inversion để giúp mã nguồn mềm dẻo, dễ thích nghi. D.Inversion khuyến khích việc trừu tượng hóa vấn đề trước khi implement nó. Lý do thì đơn giản: các bản phác họa trừu tượng hóa thường thể hiện sự tương tác giữa các lớp trừu tượng chứ không có sự phụ thuộc - như các interface: chỉ thể hiện hành vi. Các phụ thuộc chỉ có khi implement các lớp trừu tượng, tức là chỉ có trong các concrete class. Khi các concrete class thay vì phụ thuộc vào các concrete class mà phụ thuộc vào các lớp trừu tượng thì rõ ràng nó loại bỏ được rất nhiều sự phụ thuộc phát sinh của các concrete class mà nếu nó phụ thuộc vào.
D.Injection là công cụ. Còn D.Inversion là nguyên lý thiết kế.
Cái Dependency Inversion cuối khá giống với DJ, IoC trong spring boot nhỉ , đảo ngược sự phụ thuộc, thay vì bắt buộc khởi tạo Class và phụ thuộc vào class trong hàm contractor, thì no sẽ tim các Interface vào , các Interface đó được thực thi bởi các class service.
Trang chủ</a>
</a>
<script type="text/javascript">
// hàm callback xử lý sự kiện click vào phần tử "a"
function parentEventHandler() {
alert("Bạn nhấp vào phần tử a");
};
// đoạn mã jQuery đăng ký hàm callback để xử lý sự kiện click vào phần tử "p"
$("p").click(function (event) {
alert("Bạn đã nhấp vào phần tử p");
event.stopPropagation();
});
</script>
Bạn bỏ cái href vào thẻ <p> thì nó redirect qua trang web bằng gì ạ ?
Bạn có thể giải thích kĩ hơn về phần độ trễ và băng thông mạng được không?
Những con số này lấy ở đâu ra, và tại sao lại là con số đó mà không phải số khác?
Mình chưa hiểu lắm, cảm ơn bạn
@Huyennv phải có yêu cầu đặc biệt thì mới phải làm căng thế. BT các hệ thống cũng chỉ persist xuống db nào đấy rồi cache lại thôi. Wordpress chẳng hạn. Hoặc youtube.
Dễ thấy nhất là view của youtube thường bị cache. Nó có mâu thuẫn thế này:
trang ít view, con số view nhỏ thì việc cập nhật số view thường xuyên có vẻ được để ý.
trang nhiều view, con số chính xác là không quan trọng, thường người ta hiển thị con số rút ngắn như 12K, 12M. Một con số chi tiết kiểu 12.345 là không ý nghĩa hơn 12K.
Việc kiểm soát số view theo thời gian thực có thể ví dụ trong ecommerce. Khi muốn sale sản phẩm, muốn show số người xem theo thời gian thực để thúc đẩy khách hàng. Ngoài push notification để update số view, khách cũng thường refresh trang liên tục. Thực tế thì có lẽ push notification là cũng đủ hiệu quả. Con số view chỉ là tương đối.
Cần làm rõ 2 vấn đề trong câu hỏi của bạn:
Redis không phải là DB duy nhất trong hệ thống, vẫn cần 1 db để lưu giữ số view lâu dài.
Clear Redis không phải xóa số view của trang. Hệ thống sẽ tải lại số view từ DB dài hạn (persistence storage, db sơ cấp).
Có 3 cách sử dụng Redis:
Đơn thuần là Cache. Khi có lượt view, hệ thống tăng số trong db dài hạn (db sơ cấp). Khi query, hệ thống lấy từ Redis (cache), không có sẽ query DB sơ cấp.
Chạy song song Redis và DB sơ cấp. Khi có lượt view, hệ thống increase số view cả Redis lẫn db sơ cấp. Khi Redis reset, load lại dữ liệu view này từ DB theo 2 cách:
On demand, query đến đâu load từ db sơ cấp đến đó.
Fetch-all - load tất cả từ db bằng Job hoặc trigger.
Cập nhật Redis trước, định kì cập nhật lại db sơ cấp. Khi có request, increase Redis count. Job chạy update lại db sau. Khi redis reset thì fetch-all từ db sơ cấp.
Kết luận:
Chưa tìm ra ứng dụng thực tế của việc lưu số view vào Redis. Số view chỉ là con số tương đối, không cần sử dụng kĩ thuật phức tạp để xử lý.
Trong js, mở một ngoặc nhọn là tạo một scope, nên ko chỉ catch, sau cái ngoặc nhọ nào cũng thế. Mở 1 cái ngoặc nhọn không liên quan cũng cho kết quả tương tự.
const x = 'out';
{
const x = 'in';
console.log(x);
}
console.log(x);
THẢO LUẬN
@H003g nghĩ ra, nói ra là bước đầu tiên để hiểu ra. a vẫn upvote bài viết
Interface segregation: cách giải thích dễ nhầm tưởng mỗi method tách 1 interface.
ShapeCalculator là một lớp đặc thù và việc nó có 3 phương thức tính chu vi, diện tích, thể tích là hoàn toàn hợp lý, không có gì sai cả. Vậy nếu có interface IShapeCalculator thì nó nên có cả 3 phương thức.
Nếu 3 phương thức trên thuộc về Shape thì lại khác. Chu vi và diện tích thì của hình 2D, diện tích và thể tích lại của hình 3D, việc gom cả 3 hành vi vào 1 interface là không thể nên cần tách ra để giữ được sự mềm dẻo.
Liskov thì có 2 ví dụ kinh điển:
Thực ra ví dụ trên vẫn trừu tượng. Hãy xét một bài toán về e-commerce, cách implement chương trình khuyến mãi hay giảm giá cho sản phẩm là coupon (voucher) và giảm giá - discount. Liệu chúng ta có thể tạo một parent chung cho cả hai phương thức là ISaleProgram được không?
Tổng quan thì là hợp lý, nhưng có sự khác biệt đối với 2 phương thức trên:
Như vậy Voucher được tính sau khi tính tổng hóa đơn rồi trừ đi. Discount được tính trong khi tính tổng hóa đơn. Sự khác biệt này sẽ làm chương trình tính giá bị lỗi nếu chúng ta coi Voucher và Discount là cùng họ. Hai cách tính là khác nhau và tham gia vào hai bước khác nhau của quá trình tính giá phải trả.
Bài của e chán quá, a xem mấy cái còn lại có vấn đề gì không, a sửa giúp e với nhé
@refacore Cảm ơn bạn đã chia sẻ
Phần giải thích Open/Close cũng nhầm lẫn giữa trách nhiệm và hành vi. Addition, Subtraction thì đều là hành vi của Calculator. Addition, Subtraction nên thừa kế từ interface Operator chứ không phải Calculator. Thiết kế cho Calculator sẽ giống thế này:
sessionStorage không phải là một phần của Javascript: https://262.ecma-international.org/11.0/
Phần giải thích Single Responsibility có thể gây nhầm lẫn thành mỗi class chỉ nên có một public method, có 2 public method trở lên thì phải tách ra.
Cần phải phần biệt giữa trách nhiệm và hành vi. Nếu nhầm lẫn thì Java đã sai ngay từ đầu khi cho đa thừa kế.
Một class có trách nhiệm duy nhất nhưng nó có thể có nhiều hành vi. Cùng xem một ví dụ của mẫu Facade:
Có thể thấy AtmFacade có trách nhiệm duy nhất là gom các chức năng của cây ATM lại để dễ sử dụng (giảm sự phức tạp). Nếu coi các chức năng con trong đó là trách nhiệm của nó thì thiết kế này sai mất. Các chức năng con là các hành vi mà cây ATM này cung cấp.
Nhưng các chức năng con này cũng thể hiện trách nhiệm duy nhất như cách giải thích của bài viết:
Single Responsibility là nguyên lý đơn giản nhưng rất linh động và thể hiện nhiều nhất sự khác biệt trong tư duy thiết kế cũng như trình độ của các kĩ sư. Không có công thức cho Single Responsibility. Có lẽ chỉ có một tiêu chí đánh giá xem mã nguồn có đơn nhiệm không: Khi không còn gì để nói về nó nữa.
Dependency Injection là Dependency Injection. Nó che giấu đi cách các concrete class được khởi tạo, chỉ việc gọi tên class, nên làm cho mình cảm tưởng đó là bỏ đi sự phụ thuộc. Dependency Injection hoàn toàn có thể đăng ký 1 concrete class và injection nó trong các class khác. Các thư viện DI thì chỉ nhận là DI chứ không nhận là IoC vì bản chất là khác nhau. DI giống như một thể hiện rõ ràng hơn của Dependency Inversion (hay IoC), giúp lập trình viên cảm nhận và hiểu nguyên lý của Dependency Inversion.
Sự phụ thuộc mà D.Injection và D.Inversion đề cập đến là khác nhau.
D.Injection giải quyết sự phức tạp của constructor và lifetime của một class, hay một phụ thuộc trong class. Một class có thể phụ thuộc vào nhiều class khác, dẫn đến constructor của nó trở nên phức tạp, độ phức tạp còn tăng lên thêm khi nó là một phụ thuộc trong một class khác. Để giải quyết constructor thì ngày trước dùng công cụ thường gọi là ServiceLocator chứ không phải D.Injection (thường là đăng ký factory cho từng class cho ServiceLocator). Ngày nay D.Injection giải quyết constructor tự động, và vẫn cung cấp khả năng đăng ký factory nếu cần. Ngoài ra, D.Injection quản lý cả lifetime của một dependency, tức nó không quản lý 1 instance mà quản lý một chuỗi dependency, khởi tạo và giải phóng cùng nhau (theo lifetime).
D.Inversion là khái niệm về thiết kế phần mềm. Khi chưa có D.Injection, ng ta vẫn thiết kế dùng D.Inversion để giúp mã nguồn mềm dẻo, dễ thích nghi. D.Inversion khuyến khích việc trừu tượng hóa vấn đề trước khi implement nó. Lý do thì đơn giản: các bản phác họa trừu tượng hóa thường thể hiện sự tương tác giữa các lớp trừu tượng chứ không có sự phụ thuộc - như các interface: chỉ thể hiện hành vi. Các phụ thuộc chỉ có khi implement các lớp trừu tượng, tức là chỉ có trong các concrete class. Khi các concrete class thay vì phụ thuộc vào các concrete class mà phụ thuộc vào các lớp trừu tượng thì rõ ràng nó loại bỏ được rất nhiều sự phụ thuộc phát sinh của các concrete class mà nếu nó phụ thuộc vào.
D.Injection là công cụ. Còn D.Inversion là nguyên lý thiết kế.
Cái Dependency Inversion cuối khá giống với DJ, IoC trong spring boot nhỉ , đảo ngược sự phụ thuộc, thay vì bắt buộc khởi tạo Class và phụ thuộc vào class trong hàm contractor, thì no sẽ tim các Interface vào , các Interface đó được thực thi bởi các class service.
Bạn ơi Django thì có làm SEO được ko
Hay bạn ơi
Trang chủ</a> </a> <script type="text/javascript"> // hàm callback xử lý sự kiện click vào phần tử "a" function parentEventHandler() { alert("Bạn nhấp vào phần tử a"); };
// đoạn mã jQuery đăng ký hàm callback để xử lý sự kiện click vào phần tử "p" $("p").click(function (event) { alert("Bạn đã nhấp vào phần tử p"); event.stopPropagation(); }); </script>
Bạn bỏ cái href vào thẻ <p> thì nó redirect qua trang web bằng gì ạ ?
cho mình hỏi trong suốt thời gian tạo link ngrok phải mở terminal chạy đúng k ạ ?.. Và nếu tắt thì có chạy ngầm trên máy không ?..Thanks
Bạn có thể giải thích kĩ hơn về phần độ trễ và băng thông mạng được không? Những con số này lấy ở đâu ra, và tại sao lại là con số đó mà không phải số khác? Mình chưa hiểu lắm, cảm ơn bạn
@Huyennv phải có yêu cầu đặc biệt thì mới phải làm căng thế. BT các hệ thống cũng chỉ persist xuống db nào đấy rồi cache lại thôi. Wordpress chẳng hạn. Hoặc youtube.
Dễ thấy nhất là view của youtube thường bị cache. Nó có mâu thuẫn thế này:
Việc kiểm soát số view theo thời gian thực có thể ví dụ trong ecommerce. Khi muốn sale sản phẩm, muốn show số người xem theo thời gian thực để thúc đẩy khách hàng. Ngoài push notification để update số view, khách cũng thường refresh trang liên tục. Thực tế thì có lẽ push notification là cũng đủ hiệu quả. Con số view chỉ là tương đối.
Cần làm rõ 2 vấn đề trong câu hỏi của bạn:
Có 3 cách sử dụng Redis:
Kết luận:
Bài hay
Mình có 1 câu hỏi là làm sao qua mặt được việc phát hiện xài Fake GPS
sao chép y nguyên