Những gì thay thế cho Singleton


114

Chúng tôi có một lớp chứa thông tin cấu hình cho ứng dụng. Nó từng là một singleton. Sau một số đánh giá kiến ​​trúc, chúng tôi được yêu cầu loại bỏ singleton. Chúng tôi đã thấy một số lợi ích của việc không sử dụng singleton trong kiểm thử đơn vị vì chúng tôi có thể kiểm tra các cấu hình khác nhau cùng một lúc.

Nếu không có singleton, chúng ta phải chuyển thể hiện ở khắp mọi nơi trong mã của chúng ta. Nó trở nên rất lộn xộn vì vậy chúng tôi đã viết một trình bao bọc singleton. Bây giờ chúng tôi đang chuyển cùng một mã sang PHP và .NET, tôi đang tự hỏi liệu có mẫu nào tốt hơn mà chúng tôi có thể sử dụng cho đối tượng cấu hình không.

Câu trả lời:


131

Các blog của thử nghiệm của Google có một loạt các mục về việc tránh Singleton (theo thứ tự để tạo mã kiểm chứng). Có thể điều này có thể giúp bạn:

Bài viết cuối cùng giải thích chi tiết cách di chuyển việc tạo các đối tượng mới vào một nhà máy, vì vậy bạn có thể tránh sử dụng các singleton. Giá trị đọc chắc chắn.

Trong ngắn hạn, chúng tôi chuyển tất cả các nhà điều hành mới đến một nhà máy. Chúng tôi nhóm tất cả các đối tượng có tuổi thọ tương tự vào một nhà máy duy nhất.


3
*** Sử dụng tiêm phụ thuộc để tránh những người độc thân
Justin

Những bài viết này tốt như Tiêu chuẩn lập trình C ++ của Google!

2
Chà, không hẳn vậy. Ví dụ, lời khuyên 'Không sử dụng phương pháp tĩnh' đi ngược lại nguyên tắc giao diện tối thiểu của Scott Meyers / Herb Sutters. Có những lời khuyên hữu ích, nhưng chúng thiếu sự đóng góp của nhiều khối óc.
Matthieu M.

@FrankS tại sao bạn chuyển đổi thứ tự của các liên kết? Lúc đầu, nó theo thứ tự thời gian tốt.
cregox

@Cawas thực sự không có ý tưởng, đó là hơn bốn năm trước, vì vậy tôi đoán tôi có một số lý do cho nó hồi đó :-)
FrankS

15

Cách tốt nhất là sử dụng Factory pattern để thay thế. Khi bạn tạo một phiên bản mới của lớp (trong nhà máy), bạn có thể chèn dữ liệu 'toàn cục' vào đối tượng mới được xây dựng, dưới dạng tham chiếu đến một cá thể (mà bạn lưu trữ trong lớp nhà máy) hoặc bằng cách sao chép dữ liệu vào đối tượng mới.

Tất cả các đối tượng của bạn sau đó sẽ chứa dữ liệu đã từng sống trong singleton. Tôi không nghĩ rằng có nhiều sự khác biệt về tổng thể, nhưng nó có thể làm cho mã của bạn dễ đọc hơn.


1
Tôi không đồng ý với tuyên bố "cách tốt nhất", nhưng +1 cho một giải pháp thay thế tốt.
tylermac

Vấn đề với cách tiếp cận này là mọi đối tượng mới đều chứa (hoặc tham chiếu) những gì có thể là một khối dữ liệu khổng lồ. var_dump () trên bất kỳ đối tượng nào chứa gob đó dẫn đến kết quả rất nhanh chóng trong các danh sách khổng lồ được xếp đầy tự do với các cảnh báo đệ quy . Nó xấu xí, không thể hiệu quả khủng khiếp, và làm cho mọi thứ có vẻ khô cứng. Tuy nhiên, cá nhân tôi không tìm ra cách nào tốt hơn. Tôi đã bẻ cong phương thức "factory" thành sử dụng __construct () để tham chiếu đến toàn cục. Tuy nhiên, có cảm giác như mọi thứ đều bị bẻ cong về phía sau để tránh Singleton đáng sợ ...
FYA 10/12/11

2
@EastGhostCom: Chúng ta cũng có thể sử dụng singleton và ngừng cố gắng để làm cho mọi việc khó khăn cho chính mình :)
gbjbaanb

5

Tôi có thể nói rõ ràng ở đây, nhưng có lý do gì khiến bạn không thể sử dụng một khung phụ thuộc như Spring hoặc Guice ? (Tôi tin rằng Spring hiện cũng có sẵn cho .NET).

Bằng cách đó, khung công tác có thể chứa một bản sao duy nhất của các đối tượng cấu hình và các bean của bạn (dịch vụ, DAO, bất cứ thứ gì) không phải lo lắng về việc tra cứu nó.

Đây là cách tôi thường làm!


4

Nếu bạn sử dụng Spring Framework , bạn chỉ có thể tạo một bean thông thường. Theo mặc định (hoặc nếu bạn đặt rõ ràng scope="singleton") chỉ một thể hiện của bean được tạo và thể hiện đó được trả về mỗi khi bean được sử dụng trong một phụ thuộc hoặc được truy xuất qua getBean().

Bạn có được lợi thế của trường hợp đơn lẻ, không có sự ghép nối của mẫu Singleton.


3
Oh sự trớ trêu - sử dụng (singleton) đậu mùa xuân để thay thế singleton của bạn ...
Zack Macomber

4

Giải pháp thay thế là chuyển những gì bạn cần thay vì yêu cầu một đối tượng cho những thứ.


4

không tích lũy các phản hồi vào một đối tượng cấu hình đơn lẻ vì nó sẽ kết thúc bằng một đối tượng rất lớn, vừa khó hiểu vừa dễ vỡ.

Ví dụ: nếu bạn cần một tham số khác cho một lớp cụ thể, bạn thay đổi Configurationđối tượng, sau đó biên dịch lại tất cả các lớp sử dụng nó. Điều này hơi có vấn đề.

Hãy thử cấu trúc lại mã của bạn để tránh một Configurationđối tượng phổ biến, toàn cầu và lớn . Chỉ chuyển các tham số bắt buộc cho các lớp khách hàng:

class Server {

    int port;

    Server(Configuration config) {
        this.port = config.getServerPort();
    } 

}

nên được cấu trúc lại thành:

 class Server {

    public Server(int port) {
       this.port = port;
    }
 }

một khung công tác tiêm phụ thuộc sẽ giúp ích rất nhiều ở đây, nhưng nó không bắt buộc.


Vâng, đó là điểm thực sự tốt. Tôi đã làm điều này trước đây. Đối tượng cấu hình lớn của tôi đang triển khai các giao diện như MailServiceConf, ServerConf .. hơn là khung công tác Dependency Injection chuyển cấu hình cho các lớp để các lớp của tôi không phụ thuộc vào đối tượng Cấu hình lớn.
caltuntas

1

Bạn có thể thực hiện cùng một hành vi của singleton bằng cách sử dụng các phương thức tĩnh. Steve yegge giải thích nó rất tốt trong bài đăng này .


Trên thực tế, bài viết khá hay, và nó không nói rằng bạn nên sử dụng các phương thức tĩnh để thay thế. Thay vào đó, anh ấy nói rằng các phương thức tĩnh cũng chỉ là các đơn và cuối cùng, anh ấy khuyên bạn nên sử dụng mẫu phương thức gốc: "Tôi sẽ kết thúc bằng cách nói rằng nếu bạn vẫn cảm thấy cần sử dụng các đối tượng Singleton, hãy xem xét sử dụng mẫu Factory Method thay thế. ... "
FrankS

0

Một lớp chỉ chứa các phương thức và trường tĩnh có được không? Tôi không chắc về chính xác tình hình của bạn, nhưng có thể đáng để xem xét.


1
Nếu lớp không trạng thái, nó phải là một lớp tĩnh.
AlbertoPL

1
Nó nằm trong C ++ - mẫu được gọi là Monostate.

0

Phụ thuộc vào công cụ / khuôn khổ, v.v. đang được sử dụng. Với các công cụ tiêm / ioc phụ thuộc, người ta thường vẫn có thể có được hiệu suất / tối ưu hóa singleton bằng cách yêu cầu vùng chứa di / ioc sử dụng hành vi singleton cho lớp được yêu cầu - (chẳng hạn như giao diện IConfigSettings) bằng cách chỉ tạo một phiên bản của lớp. Điều này vẫn có thể được thay thế để thử nghiệm

Ngoài ra, người ta có thể sử dụng một nhà máy để tạo lớp và trả lại cùng một phiên bản mỗi khi bạn yêu cầu nó - nhưng để thử nghiệm, nó có thể trả về một phiên bản sơ khai / bị chế nhạo


0

Xem lại khả năng tạo cấu hình làm giao diện gọi lại. Vì vậy, mã nhạy cảm cấu hình của bạn sẽ trông như sau:

MyReuseCode.Configure(IConfiguration)

Mã System-init sẽ trông như sau:

Library.init(MyIConfigurationImpl)

0

Bạn có thể sử dụng một khung công tác tiêm phụ thuộc để giảm bớt khó khăn khi truyền đối tượng cấu hình. Một thứ tốt là ninject có lợi thế là sử dụng mã hơn là xml.


0

Có thể cũng không rõ ràng lắm, nhưng bạn có thể chuyển các bit thông tin mà bạn muốn thay đổi sang phương thức tạo singleton - thay vì sử dụng

public static Singleton getInstance() {
    if(singleton != null)
        createSingleton();
        return singleton;
    }
}

bạn có thể gọi createSingleton(Information info)trực tiếp khi khởi động ứng dụng (và trong setUp-Method của các bài kiểm tra đơn vị).


-1

Singletons không xấu nhưng mẫu thiết kế còn thiếu sót. Tôi có một lớp mà tôi chỉ muốn tạo một phiên bản duy nhất của nó trong thời gian chạy nhưng muốn tạo nhiều phiên bản riêng biệt trong quá trình thử nghiệm đơn vị để đảm bảo kết quả xác định.

DI sử dụng Spring, v.v., là một lựa chọn rất tốt nhưng không phải là lựa chọn duy nhất.

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.