Câu trả lời:
Một số lý do mà bạn có thể cần nhà xây dựng riêng:
final
lên cấp độ lớp học. Đặt constructor riêng là khá nhiều vô dụng vì lý do này.
final
. Đây là những gì Sun đã làm cho lớp String và một đoạn API hợp lý không nên được mở rộng.
Bằng cách cung cấp một hàm tạo riêng, bạn ngăn các thể hiện của lớp được tạo ở bất kỳ nơi nào khác ngoài chính lớp này. Có một số trường hợp sử dụng để cung cấp hàm tạo như vậy.
A. Các thể hiện lớp của bạn được tạo trong một static
phương thức. Các static
phương pháp sau đó được khai báo là public
.
class MyClass()
{
private:
MyClass() { }
public:
static MyClass * CreateInstance() { return new MyClass(); }
};
B. Lớp của bạn là một người độc thân . Điều này có nghĩa, không có nhiều hơn một thể hiện của lớp bạn trong chương trình.
class MyClass()
{
private:
MyClass() { }
public:
MyClass & Instance()
{
static MyClass * aGlobalInst = new MyClass();
return *aGlobalInst;
}
};
C. (Chỉ áp dụng cho tiêu chuẩn C ++ 0x sắp tới) Bạn có một số hàm tạo. Một số trong số họ được tuyên bố public
, những người khác private
. Để giảm kích thước mã, các nhà xây dựng công cộng 'gọi' các nhà xây dựng riêng tư lần lượt thực hiện tất cả công việc. public
Do đó, các hàm tạo của bạn được gọi là các hàm tạo ủy nhiệm :
class MyClass
{
public:
MyClass() : MyClass(2010, 1, 1) { }
private:
MyClass(int theYear, int theMonth, int theDay) { /* do real work */ }
};
D. Bạn muốn hạn chế sao chép đối tượng (ví dụ: vì sử dụng tài nguyên được chia sẻ):
class MyClass
{
SharedResource * myResource;
private:
MyClass(const MyClass & theOriginal) { }
};
E. Lớp của bạn là một lớp tiện ích . Điều đó có nghĩa, nó chỉ chứa static
các thành viên. Trong trường hợp này, không có đối tượng nào phải được tạo trong chương trình.
Để lại một "cửa sau" cho phép một lớp / chức năng bạn bè khác xây dựng một đối tượng theo cách bị cấm đối với người dùng. Một ví dụ xuất hiện trong tâm trí sẽ là một thùng chứa xây dựng một trình vòng lặp (C ++):
Iterator Container::begin() { return Iterator(this->beginPtr_); }
// Iterator(pointer_type p) constructor is private,
// and Container is a friend of Iterator.
friend
ở nơi không được bảo hành và có rất nhiều trường hợp đó là một ý tưởng tồi. Đáng buồn thay, thông điệp đã được đón nhận quá tốt và nhiều nhà phát triển không bao giờ học ngôn ngữ đủ tốt để hiểu rằng việc sử dụng không thường xuyên friend
không chỉ được chấp nhận, mà còn tốt hơn . Ví dụ của bạn chỉ là một trường hợp như vậy. Cố ý khớp nối mạnh mẽ là không có tội, đó là một quyết định thiết kế.
Mọi người bị mắc kẹt vào điều Singleton, wow.
Những thứ khác:
Điều này có thể rất hữu ích cho một hàm tạo có chứa mã chung; Các hàm tạo riêng có thể được gọi bởi các hàm tạo khác, sử dụng 'this (...);' ký hiệu. Bằng cách tạo mã khởi tạo chung trong một hàm tạo riêng tư (hoặc được bảo vệ), bạn cũng đang làm rõ ràng rằng nó chỉ được gọi trong khi xây dựng, điều này không phải vậy nếu nó chỉ đơn giản là một phương thức:
public class Point {
public Point() {
this(0,0); // call common constructor
}
private Point(int x,int y) {
m_x = x; m_y = y;
}
};
Có một số trường hợp bạn có thể không muốn sử dụng một hàm tạo công khai; ví dụ nếu bạn muốn một lớp singleton.
Nếu bạn đang viết một hội đồng được sử dụng bởi các bên thứ 3, có thể có một số lớp bên trong mà bạn chỉ muốn được tạo bởi hội đồng của bạn và không được người dùng của hội đồng của bạn tạo ra.
Điều này đảm bảo rằng bạn (lớp có hàm tạo riêng) kiểm soát cách gọi của bộ điều khiển.
Một ví dụ: Một phương thức nhà máy tĩnh trên lớp có thể trả về các đối tượng khi phương thức nhà máy chọn để phân bổ chúng (ví dụ như một nhà máy đơn lẻ).
Chúng ta cũng có thể có hàm tạo riêng, để chỉ tạo ra đối tượng theo một lớp cụ thể (Vì lý do bảo mật).
Ví dụ về C ++:
class ClientClass;
class SecureClass
{
private:
SecureClass(); // Constructor is private.
friend class ClientClass; // All methods in
//ClientClass have access to private
// & protected methods of SecureClass.
};
class ClientClass
{
public:
ClientClass();
SecureClass* CreateSecureClass()
{
return (new SecureClass()); // we can access
// constructor of
// SecureClass as
// ClientClass is friend
// of SecureClass.
}
};
Lưu ý: Lưu ý: Chỉ ClientClass (vì nó là bạn của SecureClass) mới có thể gọi Trình xây dựng của SecureClass.
Dưới đây là một số cách sử dụng của constructor riêng:
Tôi thấy một câu hỏi từ bạn giải quyết vấn đề tương tự.
Đơn giản là nếu bạn không muốn cho phép những người khác tạo phiên bản, thì hãy giữ nguyên tắc trong phạm vi giới hạn. Ứng dụng thực tế (Một ví dụ) là mẫu singleton.
Bạn không nên đặt công cụ xây dựng riêng tư. Giai đoạn = Stage. Làm cho nó được bảo vệ, vì vậy bạn có thể mở rộng lớp nếu bạn cần.
Chỉnh sửa: Tôi đang đứng trước điều đó, bất kể bạn ném bao nhiêu lần vào đây. Bạn đang cắt đứt tiềm năng phát triển mã trong tương lai. Nếu người dùng hoặc lập trình viên khác thực sự quyết tâm mở rộng lớp, thì họ sẽ chỉ thay đổi hàm tạo thành được bảo vệ trong nguồn hoặc mã byte. Bạn sẽ không làm được gì ngoài việc làm cho cuộc sống của họ khó khăn hơn một chút. Bao gồm một cảnh báo trong ý kiến của nhà xây dựng của bạn và để nó ở đó.
Nếu đó là một lớp tiện ích, giải pháp đơn giản hơn, chính xác hơn và thanh lịch hơn là đánh dấu toàn bộ "lớp cuối cùng tĩnh" để ngăn chặn sự mở rộng. Nó không làm gì tốt khi chỉ đánh dấu nhà xây dựng riêng tư; một người dùng thực sự xác định có thể luôn luôn sử dụng sự phản chiếu để có được hàm tạo.
protected
tạo là bắt buộc sử dụng các phương thức tĩnh của nhà máy, cho phép bạn hạn chế khởi tạo hoặc nhóm và sử dụng lại các tài nguyên đắt tiền (kết nối DB, tài nguyên riêng).Trình xây dựng là riêng tư cho một số mục đích như khi bạn cần triển khai singleton hoặc giới hạn số lượng đối tượng của một lớp. Ví dụ trong triển khai singleton, chúng ta phải đặt constructor ở chế độ riêng tư
#include<iostream>
using namespace std;
class singletonClass
{
static int i;
static singletonClass* instance;
public:
static singletonClass* createInstance()
{
if(i==0)
{
instance =new singletonClass;
i=1;
}
return instance;
}
void test()
{
cout<<"successfully created instance";
}
};
int singletonClass::i=0;
singletonClass* singletonClass::instance=NULL;
int main()
{
singletonClass *temp=singletonClass::createInstance();//////return instance!!!
temp->test();
}
Một lần nữa nếu bạn muốn giới hạn việc tạo đối tượng tối đa 10 thì hãy sử dụng như sau
#include<iostream>
using namespace std;
class singletonClass
{
static int i;
static singletonClass* instance;
public:
static singletonClass* createInstance()
{
if(i<10)
{
instance =new singletonClass;
i++;
cout<<"created";
}
return instance;
}
};
int singletonClass::i=0;
singletonClass* singletonClass::instance=NULL;
int main()
{
singletonClass *temp=singletonClass::createInstance();//return an instance
singletonClass *temp1=singletonClass::createInstance();///return another instance
}
Cảm ơn
Bạn có thể có nhiều hơn một constructor. C ++ cung cấp một hàm tạo mặc định và một hàm tạo sao chép mặc định nếu bạn không cung cấp một hàm rõ ràng. Giả sử bạn có một lớp chỉ có thể được xây dựng bằng cách sử dụng một số hàm tạo tham số. Có lẽ nó khởi tạo biến. Nếu người dùng sau đó sử dụng lớp này mà không có hàm tạo đó, họ có thể không gây ra sự cố. Một quy tắc chung tốt: Nếu việc triển khai mặc định không hợp lệ, hãy đặt cả hàm tạo mặc định và sao chép thành riêng tư và không cung cấp triển khai:
class C
{
public:
C(int x);
private:
C();
C(const C &);
};
Sử dụng trình biên dịch để ngăn người dùng sử dụng đối tượng với các hàm tạo mặc định không hợp lệ.
Trích dẫn từ Java hiệu quả , bạn có thể có một lớp với hàm tạo riêng để có một lớp tiện ích xác định các hằng số (như các trường cuối cùng tĩnh).
( EDIT: Theo nhận xét, đây là điều có thể chỉ áp dụng được với Java, tôi không biết liệu cấu trúc này có thể áp dụng / cần thiết trong các ngôn ngữ OO khác hay không (giả sử C ++))
Một ví dụ như dưới đây:
public class Constants {
private Contants():
public static final int ADDRESS_UNIT = 32;
...
}
EDIT_1 : Một lần nữa, giải thích bên dưới được áp dụng trong Java: (và tham khảo từ cuốn sách, Java hiệu quả )
Một khởi tạo của lớp tiện ích như lớp bên dưới, mặc dù không có hại, không phục vụ bất kỳ mục đích nào vì chúng không được thiết kế để khởi tạo.
Ví dụ, giả sử không có Trình xây dựng riêng cho các hằng số lớp. Một đoạn mã như dưới đây là hợp lệ nhưng không truyền đạt tốt hơn ý định của người dùng lớp Hằng
unit = (this.length)/new Constants().ADDRESS_UNIT;
trái ngược với mã như
unit = (this.length)/Constants.ADDRESS_UNIT;
Ngoài ra tôi nghĩ rằng một nhà xây dựng tư nhân truyền đạt ý định của người thiết kế lớp Hằng (nói) tốt hơn.
Java cung cấp một hàm tạo công khai không tham số mặc định nếu không có hàm tạo nào được cung cấp và nếu ý định của bạn là ngăn chặn việc khởi tạo thì cần một hàm tạo riêng.
Người ta không thể đánh dấu một lớp tĩnh cấp cao nhất và thậm chí một lớp cuối cùng có thể được khởi tạo.
Các lớp tiện ích có thể có các nhà xây dựng tư nhân. Người dùng của các lớp sẽ không thể khởi tạo các lớp này:
public final class UtilityClass {
private UtilityClass() {}
public static utilityMethod1() {
...
}
}
Bạn có thể muốn ngăn chặn một lớp được khởi tạo tự do. Xem mẫu thiết kế singleton làm ví dụ. Để đảm bảo tính duy nhất, bạn không thể cho phép bất kỳ ai tạo phiên bản của nó :-)
Một trong những ứng dụng quan trọng là trong lớp SingleTon
class Person
{
private Person()
{
//Its private, Hense cannot be Instantiated
}
public static Person GetInstance()
{
//return new instance of Person
// In here I will be able to access private constructor
}
};
Nó cũng phù hợp, Nếu lớp của bạn chỉ có các phương thức tĩnh. tức là không ai cần phải khởi tạo lớp của bạn
Đó thực sự là một lý do rõ ràng: bạn muốn xây dựng một đối tượng, nhưng nó không thực tế để làm điều đó (về mặt giao diện) trong hàm tạo.
Các Factory
ví dụ là khá rõ ràng, hãy để tôi chứng minh Named Constructor
thành ngữ.
Nói rằng tôi có một lớp Complex
có thể đại diện cho một số phức.
class Complex { public: Complex(double,double); .... };
Câu hỏi đặt ra là: liệu nhà xây dựng có mong đợi phần thực và phần ảo hay không, hay nó mong đợi định mức và góc (tọa độ cực)?
Tôi có thể thay đổi giao diện để làm cho nó dễ dàng hơn:
class Complex
{
public:
static Complex Regular(double, double = 0.0f);
static Complex Polar(double, double = 0.0f);
private:
Complex(double, double);
}; // class Complex
Đây được gọi là Named Constructor
thành ngữ: lớp chỉ có thể được xây dựng từ đầu bằng cách nêu rõ ràng hàm tạo nào chúng ta muốn sử dụng.
Đó là một trường hợp đặc biệt của nhiều phương pháp xây dựng. Các mẫu thiết kế cung cấp một số lượng tốt của cách để xây dựng đối tượng: Builder
, Factory
, Abstract Factory
, ... và xây dựng tư nhân sẽ đảm bảo rằng người dùng bị hạn chế đúng cách.
Ngoài những công dụng được biết đến nhiều hơn
Để triển khai mẫu Phương thức đối tượng mà tôi tóm tắt là:
“Constructor tư nhân, phương pháp tĩnh công cộng”
“Object để thực hiện, chức năng cho giao diện”
Nếu bạn muốn thực hiện một hàm bằng cách sử dụng một đối tượng và đối tượng không hữu ích ngoài việc thực hiện tính toán một lần (bằng cách gọi phương thức), thì bạn có Đối tượng Ném đi . Bạn có thể gói gọn việc tạo đối tượng và gọi phương thức trong một phương thức tĩnh, ngăn chặn kiểu chống phổ biến này:
z = new A(x,y).call();
Thay thế nó bằng một cuộc gọi chức năng (không gian tên):
z = A.f(x,y);
Người gọi không bao giờ cần biết hoặc quan tâm rằng bạn đang sử dụng một đối tượng bên trong, mang lại giao diện sạch hơn và ngăn rác từ đối tượng treo xung quanh hoặc sử dụng đối tượng không chính xác.
Ví dụ, nếu bạn muốn chia tay một tính toán trên phương pháp foo
, bar
và zork
, ví dụ trạng thái cổ phiếu mà không cần phải vượt qua nhiều giá trị trong và ngoài chức năng, bạn có thể thực hiện nó như sau:
class A {
public static Z f(x, y) {
A a = new A(x, y);
a.foo();
a.bar();
return a.zork();
}
private A(X x, Y y) { /* ... */ };
}
Mẫu Đối tượng Phương thức này được đưa ra trong Các mẫu thực hành tốt nhất của Smalltalk , Kent Beck, trang 34 Ném37, trong đó đây là bước cuối cùng của mẫu tái cấu trúc, kết thúc:
- Thay thế phương thức ban đầu bằng một phương thức tạo ra một thể hiện của lớp mới, được xây dựng với các tham số và bộ thu của phương thức ban đầu và gọi ra tính toán điện tử.
Điều này khác biệt đáng kể so với các ví dụ khác ở đây: lớp có thể thực hiện được (không giống như lớp tiện ích), nhưng các thể hiện là riêng tư (không giống như các phương thức của nhà máy, bao gồm cả singletons, v.v.) và có thể sống trên stack, vì chúng không bao giờ thoát.
Mẫu này rất hữu ích trong OOP từ dưới lên, trong đó các đối tượng được sử dụng để đơn giản hóa việc thực hiện ở mức độ thấp, nhưng không nhất thiết phải lộ ra bên ngoài và tương phản với OOP từ trên xuống thường được trình bày và bắt đầu với giao diện cấp cao.
Đôi khi rất hữu ích nếu bạn muốn kiểm soát các trường hợp (và bao nhiêu) đối tượng được tạo.
Trong số những người khác, được sử dụng trong các mẫu:
Singleton pattern
Builder pattern
Việc sử dụng các nhà xây dựng tư nhân cũng có thể là để tăng khả năng đọc / bảo trì khi đối mặt với thiết kế hướng tên miền. Từ "Microsoft .NET - Ứng dụng Architecing cho doanh nghiệp, Phiên bản 2":
var request = new OrderRequest(1234);
Trích dẫn: "Có hai vấn đề ở đây. Đầu tiên, khi nhìn vào mã, người ta khó có thể đoán được chuyện gì đang xảy ra. Một phiên bản của OrderRequest đang được tạo, nhưng tại sao và sử dụng dữ liệu nào? Cái gì 1234? Điều này dẫn đến vấn đề thứ hai: Bạn đang vi phạm ngôn ngữ phổ biến của bối cảnh bị ràng buộc. Ngôn ngữ có thể nói như thế này: khách hàng có thể đưa ra yêu cầu đặt hàng và được phép chỉ định ID mua hàng. Nếu đó là cách tốt hơn để có được một ví dụ OrderRequest mới : "
var request = OrderRequest.CreateForCustomer(1234);
Ở đâu
private OrderRequest() { ... }
public OrderRequest CreateForCustomer (int customerId)
{
var request = new OrderRequest();
...
return request;
}
Tôi không ủng hộ điều này cho mỗi lớp duy nhất, nhưng đối với kịch bản DDD ở trên, tôi nghĩ rằng nó có ý nghĩa hoàn hảo để ngăn chặn việc tạo trực tiếp một đối tượng mới.