2009-09-08 45 views
16

Chỉ một ngày khác tôi đã thấy mã sử dụng mẫu gọi là singleton. Có nghĩa là một cái gì đó dọc theo dòng củaSingleton - Tại sao nên sử dụng các lớp học?

class MySingleton{ 
public: 
    void foo() { ... } 
    static MySingleton&get_instance(){ 
     static MySingleton singleton; 
     return singleton 
    } 
private: 
    MySingleton(){ ... } 
    ~MySingleton(){ ... } 
    int bar; 
}; 

Tôi thấy lý do tại sao người ta sẽ muốn làm điều đó:

  • Hãy trường hợp truy cập trên toàn cầu.
  • Đảm bảo rằng không bao giờ có nhiều hơn một phiên bản của lớp đó.

Tuy nhiên, tôi không hiểu tại sao cách làm này lại vượt trội so với một vài chức năng miễn phí. Con đường tôi muốn thực hiện nó là đặt

namespace some_name{ 
    void foo(); 
} 

trong tiêu đề và

namespace some_name{ 
    void foo(){ 
     ... 
    } 
} 

trong file thực thi. Nếu tôi cần khởi tạo và/hoặc dọn dẹp, tôi có thể thêm một số chức năng phải được gọi rõ ràng hoặc tôi thêm

namespace{ 
    class Dummy{ 
     Dummy(){ ... } 
     ~Dummy(){ ... } 
    }dummy; 
} 

vào tệp triển khai.

Tôi biết rằng đây là từ một điểm ngữ nghĩa của xem một singleton, tuy nhiên tôi thấy biến thể đầu tiên được sử dụng thường xuyên hơn trong mã C++ so với thứ hai. Tại sao? Tôi coi phiên bản thứ hai là hơi cao hơn, vì vậy tôi tự hỏi bản thân mình nếu tôi thiếu một cái gì đó hiển nhiên.

  • Phiên bản thứ hai đơn giản hơn để triển khai và ít bị lỗi hơn. Trong biến thể đầu tiên, hàm tạo bản sao riêng tư bị thiếu mục đích để chứng minh điều này. Trong biến thể thứ hai không có cách nào để làm lỗi này.
  • Triển khai và giao diện được tách biệt tốt hơn trong phiên bản thứ hai. Trong tất cả các thành viên tư nhân đầu tiên phải được khai báo trong tiêu đề. Điều này có lợi thế là bạn có thể viết lại việc thực hiện từ đầu và thậm chí không cần phải biên dịch lại bất cứ thứ gì sử dụng singleton. Khi sử dụng biến thể đầu tiên, rất có khả năng bạn phải biên dịch lại tất cả mã người dùng ngay cả khi chỉ thay đổi các chi tiết triển khai nhỏ.
  • Chi tiết triển khai bị ẩn trong cả hai trường hợp. Trong biến thể đầu tiên sử dụng riêng tư và trong biến thể thứ hai bằng cách sử dụng không gian tên không tên.

Bạn có thể giải thích cho tôi lý do mọi người sử dụng biến thể đầu tiên không? Tôi không thấy một lợi thế nào so với cách làm cũ tốt khi làm việc trong C.

+0

Bạn không cần một hàm tạo bản sao trong mẫu đơn. Toàn bộ mục đích của mẫu đơn là để đảm bảo rằng một và chỉ một thể hiện của lớp có thể được tạo ra. – erelender

+4

Khi thảo luận về singletons, cần thêm rằng singletons là mẫu thiết kế * bad * và cần tránh vì các lý do được nêu ở đây: http://stackoverflow.com/questions/1392315/problems-with-singleton-pattern –

+5

zealots are vui vẻ –

Trả lời

4

điều này có giúp ích gì không?

What is so bad about singletons? http://steve.yegge.googlepages.com/singleton-considered-stupid

rephrased: Một singleton là một vinh quang toàn cầu, vì vậy chỉ cần 'thực hiện' nó như là một toàn cầu.

+1

Giả sử singleton mở tệp cấu hình hoặc luồng ghi nhật ký, id bạn nhớ gọi hàm cài đặt chung trước khi bạn sử dụng nhật ký hoặc cấu hình lần đầu tiên? –

+0

@MGB của nó vẫn là một lớp học với ctors và các công cụ –

+0

Không thực sự. Nó bỏ lỡ điểm của câu hỏi của tôi. Tôi đang đề cập đến cách để thực hiện một singleton trong C + +. Không phải là câu hỏi nếu người ta nên sử dụng nó ngay từ đầu. –

3

Việc xây dựng static MySingleton singleton; được gọi vào lần sử dụng đầu tiên. (Khi get_instance() được gọi.) Nếu nó không bao giờ được gọi là nó không bao giờ gọi hàm tạo. Phương thức của bạn sẽ gọi hàm tạo tại thời gian xây dựng tĩnh. Phương thức trước đó cho phép thứ tự và thời gian của các hàm tạo được gọi. Phương thức của bạn sẽ yêu cầu xây dựng từng singleton theo thứ tự khởi tạo tĩnh của trình biên dịch.

+0

Nó được thực hiện được định nghĩa khi hàm tạo của các biến tĩnh cục bộ được chạy. Sử dụng biến thể thứ hai bạn có hành vi đáng tin cậy. Ngoài ra, bạn có thể thêm cờ và nhận hành vi lười biếng theo cách di động. –

+2

Nó được thực hiện được xác định khi hàm tạo của các biến tĩnh cục bộ được chạy. -> Không đúng khi chúng ở trong một hàm hoặc một statment etc ... Bộ nhớ có thể được cấp phát bất cứ lúc nào trước main() nhưng hàm tạo của các kiểu statics này được gọi khi con trỏ chương trình chuyển nó lần đầu tiên. Khi chúng được định nghĩa bên ngoài một hàm của một số sắp xếp thì khi hàm tạo được gọi là thực hiện cụ thể. –

+0

@Ben. Thứ tự đánh giá các biến chức năng cũ đã được xác định rõ. Đó là vào thời điểm sử dụng đầu tiên. Vì vậy, cho bạn tương đương với cờ bạn đề nghị nhưng được thực hiện bởi trình biên dịch (do đó ít cơ hội của một lỗi (không nói compielr là hoàn hảo nhưng thường tốt hơn so với một con người)) (cũng trên gcc nó là thread an toàn, và hy vọng thread an toàn được tiêu chuẩn trong phiên bản tiếp theo của C++). –

7

Theo dòng bên (E. Gamma, R. Helm, R. Johnson và J. Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading, MA, 1995, tr. 128), singleton cung cấp những ưu điểm sau qua giải pháp bạn đề xuất.

  • Bạn có thể tinh chỉnh hoạt động và biểu diễn, ví dụ: thông qua phân lớp.
  • Bạn có thể đổi ý sau này và có nhiều phiên bản.
  • Bạn có thể ghi đè các phương thức của đa giác một cách đa hình.
  • Bạn có thể định cấu hình ứng dụng của mình trong thời gian chạy bằng cách khởi tạo bản sao đơn với lớp bạn cần.

Có nói rằng, trong hầu hết các trường hợp, tôi xem xét độ phức tạp bổ sung quá mức và hiếm khi sử dụng mẫu trong mã tôi viết. Nhưng tôi có thể thấy giá trị của nó khi bạn thiết kế một API mà những người khác sẽ sử dụng.

+0

"Bạn có thể đổi ý sau này và có nhiều phiên bản". Trong thực tế điều này không đúng vì nó, er, khuyến khích mã kết hợp. –

+1

@Dustin Getz Làm cách nào để khuyến khích mã kết hợp hơn bất kỳ mã nào khác? – Imagist

+0

Tất cả những điều này có thể được thực hiện bằng các chức năng miễn phí và sử dụng các con trỏ hàm bên trong để thay đổi việc thực hiện. Sự khác biệt duy nhất là giao diện. tức là bạn thích gõ instance() hay không. –

0

Vấn đề với việc sử dụng các chức năng miễn phí là những người không theo mặc định có được hành vi được chia sẻ liên tục, như lớp singleton của bạn có thể nhận được với các biến thành viên và hằng số.

Bạn có thể tiến hành sử dụng các biến toàn cầu tĩnh để làm điều tương tự, nhưng đối với ai đó cố gắng tìm ra điều đó, điều đó làm cho phạm vi của những gì họ phải xem xét để hiểu hành vi của những thói quen gần như không giới hạn. Với một lớp singleton, mọi thứ được tổ chức độc đáo thành một lớp để người đọc kiểm tra.

+0

Tôi không hiểu tại sao khó học cách sử dụng các biến toàn cầu không dễ bị lỗi, hơn là học cách làm các công cụ voodoo dễ bị lỗi cần thiết để đảm bảo rằng chỉ tồn tại một thể hiện. Người mới thường sử dụng các biến toàn cầu quá thường xuyên. Điều này cho thấy khái niệm về dữ liệu toàn cầu khá trực quan. –

1

Để có các hàm + dữ liệu tĩnh mô phỏng mẫu đơn sẽ dựa vào phạm vi tệp của C++ và biên dịch riêng biệt. Đây là những cấu trúc trình biên dịch chứ không phải là cấu trúc ngôn ngữ. Mô hình lớp đơn cho phép đóng gói dữ liệu bất kể vị trí liên quan đến các đơn vị biên dịch; nó được đóng gói chính xác ngay cả khi nó được định nghĩa trong một tệp với các lớp và hàm khác.

Ngoài ra, nó sẽ không thực tế mô phỏng hành vi của mẫu đơn, mà chỉ đơn thuần là của một đối tượng tĩnh, mà không phải là một thứ giống nhau. Một đời của singleton độc lập với cuộc đời của quá trình chứa nó. Singleton được định dạng chính xác được khởi tạo ngay lần đầu tiên sử dụng, trong khi dữ liệu tĩnh được khởi tạo và khởi tạo trước khi main() được bắt đầu. Đây có thể là một vấn đề, nếu nói rằng việc xây dựng đối tượng dựa trên sự tồn tại của một số thực thể chạy thời gian khác. Ngoài ra đối tượng singleton không chiếm bộ nhớ (trừ con trỏ tĩnh của nó) cho đến khi nó được khởi tạo. Nó cũng có thể bị phá hủy và tái tạo bất cứ lúc nào, và thực sự nhiều lần. Lưu ý nếu bạn sửa đổi singleton của bạn để làm cho constructor được bảo vệ hơn là private, bạn có thể subclass nó (và do đó dễ dàng áp dụng lại mẫu singleton), bạn không thể làm điều đó với một đối tượng tĩnh hoặc một đối tượng với tất cả các static các thành viên hoặc các hàm có tệp dữ liệu tĩnh được phân lớp hoặc bất kỳ cách nào khác mà bạn có thể cố gắng thực hiện chính xác.

Không có gì sai với đề xuất của bạn, chỉ cần miễn là bạn biết rằng nó không phải là một mẫu đơn, và thiếu sự linh hoạt.

0

Cá nhân, tôi sử dụng các trình đơn khi tôi muốn kiểm soát khi nào hàm tạo được thực hiện lần đầu tiên.

Ví dụ; nếu tôi có một singleton cho hệ thống đăng nhập của tôi, mã để mở một tập tin để viết được đưa vào singleton constructur, được gọi là; Logger.instance(); trong quá trình khởi động của tôi. Nếu tôi sử dụng không gian tên, tôi sẽ có quyền kiểm soát đó.

Các vấn đề liên quan