Chính xác. Như mình đã đề cập trong bài, nó chỉ tối ưu hơn khi bạn sử dụng map nhiều lần. Còn thật sự trong trường hợp bạn chỉ sử dụng map 1 lần thì không có khác biệt.
Việc áp dụng Map phải thật sự cân nhắc rõ ràng trong các trường hợp cụ thể, vì cái gì sinh ra cũng có chức năng của nó.
Cảm ơn bạn đã commnet nhé. 🙌🫶
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
THẢO LUẬN
Tính ra mình code vi phạm hết bà 5 nguyên lý , nhưng nó run được ))))
@refacore ♥️
ko sài đc nữa rầu
Chính xác. Như mình đã đề cập trong bài, nó chỉ tối ưu hơn khi bạn sử dụng map nhiều lần. Còn thật sự trong trường hợp bạn chỉ sử dụng map 1 lần thì không có khác biệt. Việc áp dụng Map phải thật sự cân nhắc rõ ràng trong các trường hợp cụ thể, vì cái gì sinh ra cũng có chức năng của nó. Cảm ơn bạn đã commnet nhé. 🙌🫶
Ở ví dụ phía trên khi tạo mới Map vẫn phải gọi hàm .map khởi tạo value cho nó thì cũng đã trải qua vòng lặp rồi => cũng chưa thực sự tối ưu lắm.
@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