ServiceLocator có phải là mẫu chống?


137

Gần đây tôi đã đọc bài viết của Mark Seemann về mô hình chống định vị dịch vụ.

Tác giả chỉ ra hai lý do chính tại sao ServiceLocator là một mô hình chống:

  1. Vấn đề sử dụng API (mà tôi hoàn toàn ổn)
    Khi lớp sử dụng trình định vị dịch vụ, rất khó để thấy các phụ thuộc của nó vì, trong hầu hết các trường hợp, lớp chỉ có một hàm tạo PARAMETERLESS. Ngược lại với ServiceLocator, cách tiếp cận DI phơi bày rõ ràng các phụ thuộc thông qua các tham số của hàm tạo để các phụ thuộc được nhìn thấy dễ dàng trong IntelliSense.

  2. Vấn đề bảo trì (đánh đố tôi)
    Hãy xem xét các ví dụ sau

Chúng tôi có một lớp 'MyType' sử dụng phương pháp định vị dịch vụ:

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();
    }
}

Bây giờ chúng tôi muốn thêm một phụ thuộc khác vào lớp 'MyType'

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Và đây là nơi hiểu lầm của tôi bắt đầu. Tác giả nói:

Nó trở nên khó khăn hơn rất nhiều để nói liệu bạn có đang giới thiệu một sự thay đổi đột phá hay không. Bạn cần hiểu toàn bộ ứng dụng mà Bộ định vị dịch vụ đang được sử dụng và trình biên dịch sẽ không giúp bạn.

Nhưng đợi một chút, nếu chúng ta đang sử dụng phương pháp DI, chúng tôi sẽ giới thiệu một phụ thuộc với một tham số khác trong hàm tạo (trong trường hợp hàm tạo của hàm tạo). Và vấn đề sẽ vẫn còn đó. Nếu chúng ta có thể quên thiết lập ServiceLocator, thì chúng ta có thể quên thêm ánh xạ mới vào bộ chứa IoC và cách tiếp cận DI sẽ có cùng một vấn đề về thời gian chạy.

Ngoài ra, tác giả đã đề cập về những khó khăn thử nghiệm đơn vị. Nhưng, chúng ta sẽ không có vấn đề với phương pháp DI chứ? Chúng ta có cần cập nhật tất cả các bài kiểm tra bắt đầu lớp đó không? Chúng tôi sẽ cập nhật chúng để vượt qua một phụ thuộc bị chế giễu mới chỉ để làm cho bài kiểm tra của chúng tôi có thể biên dịch được. Và tôi không thấy bất kỳ lợi ích nào từ bản cập nhật và chi tiêu thời gian đó.

Tôi không cố gắng bảo vệ phương pháp tiếp cận dịch vụ định vị. Nhưng sự hiểu lầm này khiến tôi nghĩ rằng tôi đang mất đi thứ gì đó rất quan trọng. Ai đó có thể xua tan nghi ngờ của tôi?

CẬP NHẬT (TÓM TẮT):

Câu trả lời cho câu hỏi của tôi "Bộ định vị dịch vụ có phải là mẫu chống" thực sự phụ thuộc vào hoàn cảnh. Và tôi chắc chắn sẽ không đề xuất gạch bỏ nó khỏi danh sách công cụ của bạn. Nó có thể trở nên rất tiện dụng khi bạn bắt đầu xử lý mã kế thừa. Nếu bạn đủ may mắn để bắt đầu dự án của mình thì phương pháp DI có thể là lựa chọn tốt hơn vì nó có một số lợi thế so với Trình định vị dịch vụ.

Và đây là những khác biệt chính đã thuyết phục tôi không sử dụng Trình định vị dịch vụ cho các dự án mới của mình:

  • Rõ ràng và quan trọng nhất: Trình định vị dịch vụ che giấu các phụ thuộc lớp
  • Nếu bạn đang sử dụng một số bộ chứa IoC, nó có thể sẽ quét tất cả các hàm tạo khi khởi động để xác thực tất cả các phụ thuộc và cung cấp cho bạn phản hồi ngay lập tức về ánh xạ bị thiếu (hoặc cấu hình sai); điều này là không thể nếu bạn đang sử dụng bộ chứa IoC của mình làm Trình định vị dịch vụ

Để biết chi tiết đọc câu trả lời tuyệt vời được đưa ra dưới đây.


"Chúng ta có cần cập nhật tất cả các bài kiểm tra bắt đầu lớp đó không?" Không nhất thiết đúng nếu bạn đang sử dụng một trình xây dựng trong các thử nghiệm của mình. Trong trường hợp này, bạn sẽ chỉ phải cập nhật trình xây dựng.
Peter Karlsson

Bạn nói đúng, điều đó còn tùy. Ví dụ, trong các ứng dụng Android lớn, mọi người đã rất miễn cưỡng sử dụng DI vì lo ngại về hiệu suất trên các thiết bị di động có thông số kỹ thuật thấp. Trong những trường hợp như vậy, bạn phải tìm một giải pháp thay thế để vẫn viết mã có thể kiểm tra được và tôi muốn nói Bộ định vị dịch vụ là một sự thay thế đủ tốt trong trường hợp đó. (Lưu ý: mọi thứ có thể thay đổi đối với Android khi khung Dagger 2.0 DI mới đủ trưởng thành.)
G. Lombard

1
Lưu ý rằng vì câu hỏi này đã được đăng, có một bản cập nhật trên Trình định vị dịch vụ của Mark Seemann là một bài chống mô hình mô tả cách trình định vị dịch vụ vi phạm OOP bằng cách phá vỡ đóng gói, đó là lý lẽ tốt nhất của anh ấy (và lý do cơ bản cho tất cả các triệu chứng mà ông đã sử dụng trong tất cả các đối số trước). Cập nhật 2015-10-26: Vấn đề cơ bản với Trình định vị dịch vụ là nó vi phạm đóng gói .
NightOwl888

Câu trả lời:


125

Nếu bạn định nghĩa các mẫu là chống mẫu chỉ vì có một số tình huống không phù hợp, thì CÓ, đó là mẫu chống. Nhưng với lý do đó, tất cả các mẫu cũng sẽ là các mẫu chống.

Thay vào đó, chúng ta phải xem liệu có các cách sử dụng hợp lệ của các mẫu hay không và đối với Trình định vị dịch vụ, có một số trường hợp sử dụng. Nhưng hãy bắt đầu bằng cách xem xét các ví dụ mà bạn đã đưa ra.

public class MyType
{
    public void MyMethod()
    {
        var dep1 = Locator.Resolve<IDep1>();
        dep1.DoSomething();

        // new dependency
        var dep2 = Locator.Resolve<IDep2>();
        dep2.DoSomething();
    }
}

Cơn ác mộng bảo trì với lớp đó là sự phụ thuộc bị ẩn đi. Nếu bạn tạo và sử dụng lớp đó:

var myType = new MyType();
myType.MyMethod();

Bạn không hiểu rằng nó có phụ thuộc nếu chúng bị ẩn sử dụng vị trí dịch vụ. Bây giờ, nếu chúng ta thay vì sử dụng tiêm phụ thuộc:

public class MyType
{
    public MyType(IDep1 dep1, IDep2 dep2)
    {
    }

    public void MyMethod()
    {
        dep1.DoSomething();

        // new dependency
        dep2.DoSomething();
    }
}

Bạn có thể trực tiếp phát hiện ra các phụ thuộc và không thể sử dụng các lớp trước khi đáp ứng chúng.

Trong một dòng ứng dụng kinh doanh điển hình, bạn nên tránh sử dụng vị trí dịch vụ vì lý do đó. Nó nên là mẫu để sử dụng khi không có tùy chọn khác.

Là mô hình chống mẫu?

Không.

Ví dụ, đảo ngược các container điều khiển sẽ không hoạt động nếu không có vị trí dịch vụ. Đó là cách họ giải quyết các dịch vụ trong nội bộ.

Nhưng một ví dụ tốt hơn nhiều là ASP.NET MVC và WebApi. Bạn nghĩ gì làm cho việc tiêm phụ thuộc có thể có trong các bộ điều khiển? Đó là đúng - vị trí dịch vụ.

Những câu hỏi của bạn

Nhưng đợi một chút, nếu chúng ta đang sử dụng phương pháp DI, chúng tôi sẽ giới thiệu một phụ thuộc với một tham số khác trong hàm tạo (trong trường hợp hàm tạo của hàm tạo). Và vấn đề sẽ vẫn còn đó.

Có hai vấn đề nghiêm trọng hơn:

  1. Với vị trí dịch vụ, bạn cũng sẽ thêm một phụ thuộc khác: Trình định vị dịch vụ.
  2. Làm thế nào để bạn biết phụ thuộc trọn đời nên có, và làm thế nào / khi nào họ nên được làm sạch?

Với phương thức tiêm xây dựng bằng cách sử dụng một container bạn có được điều đó miễn phí.

Nếu chúng ta có thể quên thiết lập ServiceLocator, thì chúng ta có thể quên thêm ánh xạ mới vào bộ chứa IoC và cách tiếp cận DI sẽ có cùng một vấn đề về thời gian chạy.

Đúng. Nhưng với phương thức tiêm constructor, bạn không phải quét toàn bộ lớp để tìm ra phần phụ thuộc nào bị thiếu.

Và một số container tốt hơn cũng xác nhận tất cả các phụ thuộc khi khởi động (bằng cách quét tất cả các hàm tạo). Vì vậy, với các container đó, bạn nhận được lỗi thời gian chạy trực tiếp, và không phải ở một số điểm tạm thời sau này.

Ngoài ra, tác giả đã đề cập về những khó khăn thử nghiệm đơn vị. Nhưng, chúng ta sẽ không có vấn đề với phương pháp DI chứ?

Không. Vì bạn không phụ thuộc vào công cụ định vị dịch vụ tĩnh. Bạn đã thử để có được các bài kiểm tra song song làm việc với các phụ thuộc tĩnh? Không vui chút nào.


2
Jgauffin, cảm ơn câu trả lời của bạn. Bạn đã chỉ ra một điều quan trọng về kiểm tra tự động khi khởi động. Tôi đã không nghĩ về điều đó và bây giờ tôi thấy một lợi ích khác của DI. Bạn cũng đã đưa ra một ví dụ: "var myType = new MyType ()". Nhưng tôi không thể coi đó là hợp lệ vì chúng tôi không bao giờ khởi tạo các phụ thuộc trong một ứng dụng thực (IoC Container làm điều đó cho chúng tôi mọi lúc). Tức là: trong ứng dụng MVC, chúng tôi có một bộ điều khiển, phụ thuộc vào IMyService và MyServiceImpl phụ thuộc vào IMyRep repository. Chúng tôi sẽ không bao giờ khởi tạo MyRep repository và MyService. Chúng tôi nhận các phiên bản từ thông số Ctor (như từ ServiceLocator) và sử dụng chúng. Chúng ta đừng
davidoff

33
Đối số duy nhất của bạn cho Trình định vị dịch vụ không phải là mô hình chống là: "đảo ngược các vùng chứa điều khiển sẽ không hoạt động nếu không có vị trí dịch vụ". Tuy nhiên, đối số này không hợp lệ, vì Bộ định vị dịch vụ là về ý định chứ không phải về cơ học, như được giải thích rõ ràng ở đây bởi Mark Seemann: "Một thùng chứa DI được gói trong Root Root không phải là Bộ định vị dịch vụ - đó là một thành phần cơ sở hạ tầng."
Steven

4
@jgauffin API Web không sử dụng Vị trí dịch vụ cho DI vào Bộ điều khiển. Nó hoàn toàn không làm DI. Những gì nó làm là: Nó cung cấp cho bạn tùy chọn để tạo Trình điều khiển Trình điều khiển của riêng bạn để chuyển vào Dịch vụ Cấu hình. Từ đó, bạn có thể tạo một gốc thành phần, cho dù đó là Pure DI hay Container. Ngoài ra, bạn đang trộn lẫn việc sử dụng Vị trí dịch vụ, như là một thành phần của mẫu của bạn, với định nghĩa của "Mẫu định vị dịch vụ". Với định nghĩa đó, Thành phần gốc DI có thể được coi là "Mẫu định vị dịch vụ". Vì vậy, toàn bộ quan điểm của định nghĩa đó là moot.
Suamere

1
Tôi muốn chỉ ra rằng đây là một câu trả lời thường tốt, tôi chỉ nhận xét về một điểm sai lệch mà bạn đưa ra.
Suamere

2
@jgauffin DI và SL đều là phiên bản tái hiện của IoC. SL chỉ là cách sai để làm điều đó. Một container IoC có thể là SL hoặc nó có thể sử dụng DI. Nó phụ thuộc vào cách nó có dây. Nhưng SL là xấu, xấu xấu. Đó là một cách để che giấu sự thật rằng bạn đang kết nối chặt chẽ mọi thứ lại với nhau.
MirroredFate

37

Tôi cũng muốn chỉ ra rằng NẾU bạn đang tái cấu trúc mã kế thừa rằng mẫu Trình định vị dịch vụ không chỉ không phải là mẫu chống mà còn là một điều cần thiết thực tế. Không ai sẽ vẫy một cây đũa thần qua hàng triệu dòng mã và đột nhiên tất cả mã đó sẽ sẵn sàng DI. Vì vậy, nếu bạn muốn bắt đầu giới thiệu DI với cơ sở mã hiện có, thông thường bạn sẽ thay đổi mọi thứ để trở thành dịch vụ DI chậm và mã tham chiếu các dịch vụ này thường sẽ KHÔNG phải là dịch vụ DI. Do đó, các dịch vụ THOSE sẽ cần sử dụng Trình định vị dịch vụ để có được các phiên bản của các dịch vụ đã được chuyển đổi sang sử dụng DI.

Vì vậy, khi tái cấu trúc các ứng dụng kế thừa lớn để bắt đầu sử dụng các khái niệm DI, tôi sẽ nói rằng không chỉ là Bộ định vị dịch vụ KHÔNG phải là một mẫu chống, mà đó là cách duy nhất để áp dụng dần các khái niệm DI cho cơ sở mã.


12
Khi bạn đang xử lý mã kế thừa, mọi thứ đều hợp lý để giúp bạn thoát khỏi mớ hỗn độn đó, ngay cả khi điều đó có nghĩa là thực hiện các bước trung gian (và không hoàn hảo). Bộ định vị dịch vụ là bước trung gian như vậy. Nó cho phép bạn thoát khỏi địa ngục đó một bước tại thời điểm đó, miễn là bạn nhớ rằng một giải pháp thay thế tốt tồn tại được ghi lại, lặp lại và được chứng minh là có hiệu quả . Giải pháp thay thế này là Dependency Injection và đây chính là lý do tại sao Bộ định vị dịch vụ vẫn là một mẫu chống; phần mềm được thiết kế đúng không sử dụng nó.
Steven

RE: "Khi bạn xử lý mã kế thừa, mọi thứ đều hợp lý để giúp bạn thoát khỏi mớ hỗn độn đó" Đôi khi tôi tự hỏi liệu chỉ có một chút mã kế thừa đã tồn tại, nhưng bởi vì chúng tôi có thể biện minh cho bất cứ điều gì để khắc phục chúng tôi bằng cách nào đó không bao giờ quản lý để làm như vậy.
Drew Delano

8

Từ quan điểm thử nghiệm, Dịch vụ định vị là xấu. Xem phần giải thích hay về Google Tech Talk của Misko Hevery với các ví dụ mã http://youtu.be/RlfLCWKxHJ0 bắt đầu từ phút 8:45. Tôi thích sự tương tự của anh ấy: nếu bạn cần 25 đô la, hãy hỏi trực tiếp tiền thay vì đưa ví của bạn từ nơi sẽ lấy tiền. Anh ta cũng so sánh Bộ định vị dịch vụ với một đống cỏ khô có kim bạn cần và biết cách lấy nó. Các lớp sử dụng Trình định vị dịch vụ khó sử dụng lại vì nó.


10
Đây là một ý kiến ​​tái chế và sẽ tốt hơn như là một nhận xét. Ngoài ra, tôi nghĩ rằng sự tương tự của bạn (của anh ấy) phục vụ để chứng minh rằng một số mô hình phù hợp hơn cho một số vấn đề so với những vấn đề khác.
8bitjunkie

6

Vấn đề bảo trì (đánh đố tôi)

Có 2 lý do khác nhau tại sao sử dụng dịch vụ định vị là xấu trong vấn đề này.

  1. Trong ví dụ của bạn, bạn khó mã hóa một tham chiếu tĩnh đến trình định vị dịch vụ vào lớp của bạn. Điều này kết hợp chặt chẽ lớp của bạn trực tiếp với bộ định vị dịch vụ, điều này có nghĩa là nó sẽ không hoạt động nếu không có bộ định vị dịch vụ . Hơn nữa, các bài kiểm tra đơn vị của bạn (và bất kỳ ai khác sử dụng lớp) cũng hoàn toàn phụ thuộc vào trình định vị dịch vụ. Một điều dường như không được chú ý ở đây là khi sử dụng phương thức tiêm xây dựng, bạn không cần bộ chứa DI khi kiểm tra đơn vị , điều này giúp đơn giản hóa các bài kiểm tra đơn vị của bạn (và khả năng hiểu chúng của nhà phát triển). Đó là lợi ích thử nghiệm đơn vị nhận ra mà bạn nhận được từ việc sử dụng hàm tạo.
  2. Đối với lý do tại sao nhà xây dựng Intellisense là quan trọng, mọi người ở đây dường như đã bỏ lỡ điểm hoàn toàn. Một lớp được viết một lần, nhưng nó có thể được sử dụng trong một số ứng dụng (nghĩa là, một số cấu hình DI) . Theo thời gian, nó trả cổ tức nếu bạn có thể xem định nghĩa của nhà xây dựng để hiểu các phụ thuộc của một lớp, thay vì xem tài liệu (hy vọng cập nhật) hoặc, thất bại, quay lại mã nguồn ban đầu (có thể không có ích) để xác định các phụ thuộc của một lớp là gì. Lớp với trình định vị dịch vụ thường dễ viết hơn , nhưng bạn phải trả nhiều hơn chi phí cho sự thuận tiện này trong việc bảo trì dự án đang diễn ra.

Rõ ràng và đơn giản: Một lớp có bộ định vị dịch vụ trong đó khó sử dụng lại hơn một lớp chấp nhận các phụ thuộc của nó thông qua hàm tạo của nó.

Hãy xem xét trường hợp bạn cần sử dụng một dịch vụ LibraryAmà tác giả của nó quyết định sẽ sử dụng ServiceLocatorAvà một dịch vụ LibraryBmà tác giả quyết định sẽ sử dụng ServiceLocatorB. Chúng tôi không có lựa chọn nào khác ngoài việc sử dụng 2 bộ định vị dịch vụ khác nhau trong dự án của chúng tôi. Có bao nhiêu phụ thuộc cần được cấu hình là một trò chơi đoán nếu chúng ta không có tài liệu tốt, mã nguồn hoặc tác giả về quay số nhanh. Không những lựa chọn, chúng ta có thể cần phải sử dụng một decompiler chỉđể tìm ra những gì phụ thuộc là. Chúng tôi có thể cần phải định cấu hình 2 API định vị dịch vụ hoàn toàn khác nhau và tùy thuộc vào thiết kế, có thể không thể chỉ đơn giản là bọc container DI hiện tại của bạn. Có thể không thể chia sẻ một ví dụ về sự phụ thuộc giữa hai thư viện. Sự phức tạp của dự án thậm chí có thể được tăng thêm nếu các bộ định vị dịch vụ không thực sự cư trú trong cùng các thư viện như các dịch vụ chúng tôi cần - chúng tôi đang ngầm kéo thêm các tham chiếu thư viện vào dự án của chúng tôi.

Bây giờ hãy xem xét hai dịch vụ tương tự được thực hiện với tiêm constructor. Thêm một tài liệu tham khảo LibraryA. Thêm một tài liệu tham khảo LibraryB. Cung cấp các phụ thuộc trong cấu hình DI của bạn (bằng cách phân tích những gì cần thiết thông qua Intellisense). Làm xong.

Mark Seemann có câu trả lời StackOverflow minh họa rõ ràng lợi ích này ở dạng đồ họa , không chỉ áp dụng khi sử dụng công cụ định vị dịch vụ từ thư viện khác mà cả khi sử dụng mặc định của dịch vụ.


1

Kiến thức của tôi không đủ tốt để đánh giá điều này, nhưng nói chung, tôi nghĩ rằng nếu một cái gì đó có sử dụng trong một tình huống cụ thể, điều đó không nhất thiết có nghĩa là nó không thể là một mô hình chống. Đặc biệt, khi bạn đang làm việc với các thư viện của bên thứ 3, bạn không có toàn quyền kiểm soát tất cả các khía cạnh và cuối cùng bạn có thể sử dụng giải pháp không tốt nhất.

Đây là đoạn từ Mã thích ứng qua C # :

"Thật không may, bộ định vị dịch vụ đôi khi là một kiểu chống không thể tránh khỏi. Trong một số loại ứng dụng, đặc biệt là Windows Workflow Foundation, cơ sở hạ tầng không cho vay để xây dựng công cụ. Trong những trường hợp này, cách duy nhất là sử dụng công cụ định vị dịch vụ. Đối với tất cả vitriol của tôi đối với mẫu (chống), nó tốt hơn nhiều so với việc xây dựng các phụ thuộc thủ công. Sau tất cả, nó vẫn cho phép các điểm mở rộng quan trọng được cung cấp bởi các giao diện cho phép trang trí, bộ điều hợp, và những lợi ích tương tự. "

- Hội trường, Gary McLean. Mã thích ứng thông qua C #: Mã hóa linh hoạt với các mẫu thiết kế và nguyên tắc RẮN (Tham khảo dành cho nhà phát triển) (tr. 309). Giáo dục Pearson.


0

Tác giả lý do rằng "trình biên dịch sẽ không giúp bạn" - và đó là sự thật. Khi bạn sắp xếp một lớp, bạn sẽ muốn chọn cẩn thận giao diện của nó - trong số các mục tiêu khác để làm cho nó độc lập như ... vì nó có ý nghĩa.

Bằng cách khách hàng chấp nhận tham chiếu đến một dịch vụ (đến một phụ thuộc) thông qua giao diện rõ ràng, bạn

  • ngầm kiểm tra, vì vậy trình biên dịch "giúp".
  • Bạn cũng đang loại bỏ nhu cầu khách hàng biết điều gì đó về "Trình định vị" hoặc các cơ chế tương tự, vì vậy khách hàng thực sự độc lập hơn.

Bạn đúng rằng DI có vấn đề / nhược điểm của nó, nhưng những ưu điểm được đề cập vượt xa chúng ... IMO. Bạn nói đúng, với DI có một phụ thuộc được giới thiệu trong giao diện (hàm tạo) - nhưng điều này hy vọng là sự phụ thuộc mà bạn cần và bạn muốn hiển thị và kiểm tra được.


Zrin, cảm ơn vì suy nghĩ của bạn. Theo tôi hiểu, với cách tiếp cận DI "phù hợp", tôi không nên khởi tạo sự phụ thuộc của mình ở bất cứ đâu ngoại trừ các bài kiểm tra đơn vị. Vì vậy, trình biên dịch sẽ giúp tôi chỉ với các bài kiểm tra của tôi. Nhưng như tôi đã mô tả trong câu hỏi ban đầu của mình, "trợ giúp" với các bài kiểm tra bị hỏng không mang lại cho tôi bất cứ điều gì. Phải không?
davidoff

Đối số 'thông tin tĩnh' / 'kiểm tra thời gian biên dịch' là một người rơm. Như @davidoff đã chỉ ra, DI cũng dễ bị lỗi thời gian chạy. Tôi cũng sẽ nói thêm rằng các IDE hiện đại cung cấp các khung nhìn công cụ về thông tin bình luận / tóm tắt cho các thành viên và ngay cả ở những người không biết, ai đó vẫn sẽ xem xét tài liệu cho đến khi họ chỉ biết 'API'. Tài liệu là tài liệu, cho dù là tham số hàm tạo cần thiết hay nhận xét về cách định cấu hình phụ thuộc.
tuespetre

Suy nghĩ về việc triển khai / mã hóa và đảm bảo chất lượng - khả năng đọc mã là rất quan trọng - đặc biệt là đối với định nghĩa giao diện. Nếu bạn có thể làm mà không cần kiểm tra thời gian biên dịch tự động và nếu bạn nhận xét / ghi lại đầy đủ giao diện của mình, thì tôi đoán bạn ít nhất có thể khắc phục một phần các nhược điểm của sự phụ thuộc ẩn vào một loại biến toàn cầu với nội dung không dễ nhìn thấy / dự đoán được. Tôi muốn nói rằng bạn cần những lý do chính đáng để sử dụng mô hình này vượt trội hơn những nhược điểm này.
Zrin

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.