2016-09-05 16 views
5

Gần đây, tôi đã đăng câu hỏi về SO liên quan đến việc sử dụng một lớp học mang một chút chức năng riêng biệt mà lẽ ra nó nên có, lý tưởng. Tôi đã được khuyến khích để tìm hiểu về mô hình singleton để chỉ có một ví dụ được tạo ra của lớp và nó quản lý tập hợp các hoạt động xoay quanh dữ liệu mà nó đóng gói. Bạn có thể xem câu hỏi tại đây - using Static Container for base and derived classes.sử dụng không gian tên thay vì singletons


Bây giờ xem xét mã này -

#include <iostream> 
#include <string> 
#include <unordered_map> 

class A{ 
    std::string id; 
    public: 
    A(std::string _i): id(_i){} 
    virtual void doSomething(){std::cout << "DoSomethingBase\n";} 
}; 

class B : public A{ 
    std::string name; 
    public: 
    B(std::string _n):name(_n), A(_n){} 
    void doSomething(){std::cout << "DoSomethingDerived\n";} 
}; 

namespace ListA{ 
    namespace{ 
     std::unordered_map<std::string, A*> list; 
    } 
    void init(){ 
     list.clear(); 
    } 
    void place(std::string _n, A* a){ 
     list[_n] = a; 
    } 
} 


int main() { 
    ListA::init(); 
    ListA::place("b1", new B("b1")); 
    ListA::place("a1", new A("a1")); 
    return 0; 
} 

Bỏ qua thực tế là tôi vẫn đang sử dụng con trỏ nguyên được rò rỉ bộ nhớ nếu chương trình không chấm dứt vì nó là, đây là một tốt thay thế cho việc sử dụng các biến tĩnh toàn cầu, hoặc một singleton?


Đối với câu hỏi trước, tôi đã sắp xếp lại lớp A (lớp cơ sở) và lớp B (lớp bắt nguồn) độc lập với không gian tên quản lý danh sách các đối tượng này. Vì vậy, đây là một ý tưởng tốt, hoặc một thực hành hoàn toàn xấu? Có bất kỳ thiếu sót nào cho nó?

Một thực hiện singleton tốt tôi đã được đề xuất như sau -

class EmployeeManager 
{ 
    public: 
     static EmployeeManager& getInstance() 
     { 
      static EmployeeManager instance; // Guaranteed to be destroyed. 
            // Instantiated on first use. 
      return instance; 
     } 
    private: 
     EmployeeManager() {}; 
     std::unordered_map<std::string, Employee&> list; 
    public: 
     EmployeeManager(EmployeeManager const&) = delete; 
     void operator=(const&) = delete; 
     void place(const std::string &id, Employee &emp){ 
      list[id] = emp; 
     } 
}; 

class Employee 
{ 
    public: 
     virtual void doSomething() = 0; 
}; 

class Writer : public Employee 
{ 
    private: 
     std::string name_; 
    public: 
     Writer(std::string name) : name_(name) {}; 
     void doSomething() { }; 
}; 

Thành thực mà nói tôi chưa bao giờ cố gắng Singleton pattern và tôi ngần ngại sử dụng nó trực tiếp kể từ khi tôi đã không có kinh nghiệm trước và tôi muốn sử dụng nó lần đầu tiên trong các dự án thú cưng của tôi.

+6

Phản ứng của bạn đối với ai đó đề xuất mẫu đơn nên giống như khi bạn gặp một người đưa bạn đến phòng sau và sau một vài vòng xì phé gợi ý rằng "chúng ta nên làm cho nó thú vị". –

+2

Mẫu đơn là một cách để * ép buộc * người dùng chỉ tạo một thể hiện. Bạn có cảm thấy một yêu cầu để tạo ra nhiều trường hợp khi bạn thực sự cần chỉ một? Nếu không, chỉ cần tạo đối tượng bạn cần và được thực hiện với nó! –

+0

@BoPersson tôi có thực sự cần tạo đối tượng cho trường hợp sử dụng của mình không? Tôi không chỉ cần một std :: bản đồ với một số chức năng chuyên ngành liên quan đến nó? –

Trả lời

2

đây có phải là giải pháp thay thế tốt cho việc sử dụng các biến tĩnh toàn cầu hay một singleton không?

không, bởi vì bạn có thể gặp phải sự cố khác: static initialization order fiasco. Có nhiều cách để khắc phục nó - nhưng với các hàm có biến tĩnh - trông giống như các trình đơn.

... nhưng tại sao bạn cần một biến toàn cầu (ngay cả trong không gian tên) hoặc đơn? Trong ví dụ đầu tiên của bạn, nó sẽ là hoàn toàn tốt nếu thay vì namespace ListA bạn có struct ListA - cộng với loại bỏ rằng namespace{. Sau đó, bạn có:

int main() { 
    ListA list; 
    list.init(); 
    list.place("b1", new B("b1")); 
    list.place("a1", new A("a1")); 
} 

và có vẻ ổn.

Sau đó, singleton aproach của bạn, một lần nữa - không cần nó - tạo biến loại EmployeeManager trong chức năng chính của bạn, nếu bạn cần sử dụng nó trong một số lớp khác, sau đó vượt qua nó bằng cách tham khảo hoặc con trỏ.

+0

Tôi không biết, mọi người bảo tôi chỉ có một đối tượng ở mức tối đa. Có lẽ vì điều này có thể dẫn đến lỗi khi khai báo hai hoặc nhiều hơn? tbh idk:/ –

+0

Tôi vẫn cần hỏi, trong ví dụ của mình nếu tôi có các tệp truyền thống như ah & a.cpp, tương tự cho bh và b.cpp và sau đó là ListA.cpp khác với không gian tên ListA và sử dụng mã ở trên, làm thế nào 'fiasco' khởi tạo tĩnh thậm chí xảy ra? Chúng độc lập với nhau và ListA.cpp chỉ cung cấp một số chức năng và một biến hoạt động theo:/ –

+0

Vấn đề sẽ là khi bạn cố gắng truy cập (ví dụ bên trong một hàm tạo) các cá thể lớp toàn cục của bạn trước chính được thực hiện. Mã mẫu của bạn không bị ảnh hưởng. – marcinj

0

Câu hỏi của bạn có thể được trả lời mà không có bất kỳ dòng mã nào, vì nó đã được trả lời bởi rất nhiều người trong quá khứ. Singletons là xấu vì mã của bạn sẽ phụ thuộc vào một lớp và việc thực thi nó. Những gì bạn muốn mặc dù là có các đơn vị độc lập mà không biết về việc thực hiện các giao diện mà họ nói chuyện. Tuyên truyền giá trị/tham chiếu nên (trên thực tế nó phải được thực hiện cho các hệ thống lớn có thể duy trì) thông qua tham chiếu truyền từ đối tượng chứa cho con của nó, hệ thống quan sát/sự kiện hoặc bus sự kiện/tin nhắn. Nhiều khuôn khổ sử dụng tại leat hai trong số các phương pháp tiếp cận ... Tôi khuyên bạn nên gắn bó với thực hành tốt nhất.

+2

Bạn có thể mở rộng thêm một chút - "Tuyên truyền giá trị/tham chiếu nên (thực tế nó phải được thực hiện cho các hệ thống có thể duy trì lớn) thông qua tham chiếu truyền từ đối tượng chứa cho con của nó, hệ thống quan sát/sự kiện hoặc xe buýt sự kiện/tin nhắn . " Ví dụ ngắn sẽ làm tốt cho tôi. –

1

Tôi không chắc chắn nếu bạn biết rằng đã có, nhưng bạn cần phải nhớ rằng Singleton thực sự là một biến toàn cầu với khởi tạo lười biếng. Bắt đầu lười biếng là một công cụ để sửa chữa một vấn đề của việc có đối tượng khởi tạo luôn luôn tại thời điểm khi bạn thực sự muốn sử dụng nó - có thể là một số chức năng chương trình thực, hoặc khởi tạo một đối tượng phụ thuộc khác. Điều này được thực hiện để trì hoãn khởi tạo cho đến khi thời điểm đầu tiên khi bạn sử dụng đối tượng.

Đối tượng tĩnh đơn giản được khởi tạo tại thời điểm đầu tiên dường như cần được tạo - tuy nhiên khi thời điểm này thực sự là không xác định, ít nhất là trong C++.

Bạn có thể thay thế khởi tạo lười với khởi tạo tĩnh, nhưng bạn phải đảm bảo bằng cách nào đó rằng việc khởi tạo xảy ra theo thứ tự được xác định.

Xác định các biến bên trong vùng tên không có gì khác ngoài việc khai báo các biến trên toàn cầu. Không gian tên được mở, các quy tắc bên trong không gian tên giống như bên ngoài không gian tên, ngoại trừ độ phân giải biểu tượng.

Những gì bạn có thể làm để thực thi khởi ra lệnh là để tạo ra một biến toàn cầu với tất cả các đối tượng toàn cầu phụ thuộc bên trong, theo hình thức struct sẽ chứa tất cả chúng như các lĩnh vực (không lĩnh vực tĩnh!). Lưu ý rằng mặc dù thứ tự chính xác của việc khởi tạo sẽ chỉ được đảm bảo giữa các đối tượng là các trường của cấu trúc đó chứ không phải giữa chúng và các đối tượng toàn cục khác.

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