เหตุใดเราจึงควรทำให้ตัวสร้างเป็นส่วนตัวในชั้นเรียน เนื่องจากเราต้องการให้ผู้สร้างเป็นสาธารณะเสมอ
เหตุใดเราจึงควรทำให้ตัวสร้างเป็นส่วนตัวในชั้นเรียน เนื่องจากเราต้องการให้ผู้สร้างเป็นสาธารณะเสมอ
คำตอบ:
เหตุผลบางประการที่คุณอาจต้องการตัวสร้างส่วนตัว:
final
ระดับชั้นเรียน การใส่คอนสตรัคเตอร์ส่วนตัวค่อนข้างไร้ประโยชน์ด้วยเหตุผลนี้
final
เครื่องมือที่เหมาะสมสำหรับการนี้จะประกาศในชั้นเรียน นี่คือสิ่งที่ Sun ทำกับคลาส String และส่วนที่สมเหตุสมผลของ API ที่ไม่ควรขยาย
การจัดเตรียมคอนสตรัคเตอร์ส่วนตัวคุณจะป้องกันไม่ให้สร้างอินสแตนซ์คลาสในที่อื่นนอกเหนือจากคลาสนี้ มีหลายกรณีการใช้งานสำหรับการจัดหาตัวสร้างดังกล่าว
A. อินสแตนซ์คลาสของคุณถูกสร้างขึ้นด้วยstatic
วิธีการ วิธีการประกาศแล้วstatic
public
class MyClass()
{
private:
MyClass() { }
public:
static MyClass * CreateInstance() { return new MyClass(); }
};
บีชั้นของคุณเป็นเดี่ยว ซึ่งหมายความว่ามีคลาสของคุณไม่เกินหนึ่งอินสแตนซ์ในโปรแกรม
class MyClass()
{
private:
MyClass() { }
public:
MyClass & Instance()
{
static MyClass * aGlobalInst = new MyClass();
return *aGlobalInst;
}
};
C. (ใช้กับมาตรฐาน C ++ 0x ที่กำลังจะมาถึงเท่านั้น) คุณมีตัวสร้างหลายตัว บางคนก็มีการประกาศอื่น ๆpublic
private
สำหรับการลดขนาดรหัสผู้สร้างสาธารณะ 'เรียก' ตัวสร้างส่วนตัวซึ่งจะทำงานทั้งหมด ผู้public
สร้างของคุณจึงเรียกว่าผู้สร้างมอบหมาย :
class MyClass
{
public:
MyClass() : MyClass(2010, 1, 1) { }
private:
MyClass(int theYear, int theMonth, int theDay) { /* do real work */ }
};
D. คุณต้องการ จำกัด การคัดลอกออบเจ็กต์ (ตัวอย่างเช่นเนื่องจากการใช้ทรัพยากรที่ใช้ร่วมกัน):
class MyClass
{
SharedResource * myResource;
private:
MyClass(const MyClass & theOriginal) { }
};
อีชั้นของคุณเป็นระดับยูทิลิตี้ นั่นหมายความว่ามีเฉพาะstatic
สมาชิกเท่านั้น ในกรณีนี้ไม่ต้องสร้างอินสแตนซ์อ็อบเจ็กต์ในโปรแกรม
ในการออกจาก "ประตูหลัง" ที่อนุญาตให้เพื่อนชั้นเรียน / ฟังก์ชันอื่นสร้างวัตถุในลักษณะที่ห้ามไม่ให้ผู้ใช้ ตัวอย่างที่อยู่ในใจคือคอนเทนเนอร์ที่สร้างตัววนซ้ำ (C ++):
Iterator Container::begin() { return Iterator(this->beginPtr_); }
// Iterator(pointer_type p) constructor is private,
// and Container is a friend of Iterator.
friend
ที่ที่ไม่ได้รับการรับประกันและมีหลายกรณีที่เป็นความคิดที่ไม่ดี น่าเศร้าที่ข้อความที่ได้รับการตอบรับอย่างดีเกินไปและนักพัฒนาหลายคนไม่เคยเรียนรู้ภาษาดีพอที่จะเข้าใจว่าการใช้งานเป็นครั้งคราวของfriend
เป็นที่ยอมรับไม่เพียง แต่ที่นิยม ตัวอย่างของคุณเป็นเพียงกรณีนี้ การมีเพศสัมพันธ์ที่แข็งแกร่งโดยเจตนาไม่ใช่อาชญากรรม แต่เป็นการตัดสินใจในการออกแบบ
ทุกคนติดอยู่กับสิ่ง Singleton ว้าว
สิ่งอื่น ๆ:
สิ่งนี้มีประโยชน์มากสำหรับตัวสร้างที่มีรหัสทั่วไป ผู้สร้างส่วนตัวสามารถเรียกได้โดยผู้สร้างรายอื่นโดยใช้ 'this (... );' สัญกรณ์ ด้วยการสร้างรหัสเริ่มต้นทั่วไปในตัวสร้างส่วนตัว (หรือที่มีการป้องกัน) คุณยังทำให้ชัดเจนว่าจะถูกเรียกในระหว่างการก่อสร้างเท่านั้นซึ่งจะไม่เป็นเช่นนั้นหากเป็นเพียงวิธีการ:
public class Point {
public Point() {
this(0,0); // call common constructor
}
private Point(int x,int y) {
m_x = x; m_y = y;
}
};
มีบางกรณีที่คุณอาจไม่ต้องการใช้ตัวสร้างสาธารณะ ตัวอย่างเช่นหากคุณต้องการคลาสซิงเกิลตัน
หากคุณกำลังเขียนแอสเซมบลีที่ใช้โดยบุคคลที่สามอาจมีคลาสภายในจำนวนหนึ่งที่คุณต้องการสร้างโดยแอสเซมบลีของคุณเท่านั้นและไม่ต้องสร้างอินสแตนซ์โดยผู้ใช้แอสเซมบลีของคุณ
เพื่อให้แน่ใจว่าคุณ (คลาสที่มีคอนสตรัคเตอร์ส่วนตัว) ควบคุมวิธีการเรียกใช้คอนสตรัคเตอร์
ตัวอย่าง: วิธีการโรงงานแบบคงที่ในคลาสสามารถส่งคืนอ็อบเจ็กต์ได้เนื่องจากวิธีการของโรงงานเลือกที่จะจัดสรรให้ (เช่นโรงงานซิงเกิลตันเป็นต้น)
นอกจากนี้เรายังสามารถมีคอนสตรัคเตอร์ส่วนตัวเพื่อให้ออบเจ็กต์สร้างขึ้นโดยคลาสเฉพาะเท่านั้น (เพื่อเหตุผลด้านความปลอดภัย)
ตัวอย่าง 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.
}
};
หมายเหตุ: หมายเหตุ: เฉพาะ ClientClass (เนื่องจากเป็นเพื่อนกับ SecureClass) เท่านั้นที่สามารถเรียกใช้ตัวสร้างของ SecureClass ได้
เมื่อคุณไม่ต้องการให้ผู้ใช้สร้างอินสแตนซ์ของคลาสนี้หรือสร้างคลาสที่สืบทอดคลาสนี้เช่นjava.lang.math
ฟังก์ชันทั้งหมดในแพ็กเกจนี้คือstatic
ฟังก์ชันทั้งหมดสามารถเรียกใช้ได้โดยไม่ต้องสร้างอินสแตนซ์math
ดังนั้นคอนสตรัคเตอร์จึงประกาศเป็นแบบคงที่ .
ฉันเห็นคำถามจากคุณในประเด็นเดียวกันนี้
เพียงแค่ถ้าคุณไม่ต้องการอนุญาตให้ผู้อื่นสร้างอินสแตนซ์ให้กำหนดคอนฟิกไว้ในขอบเขตที่ จำกัด การนำไปใช้จริง (ตัวอย่าง) คือรูปแบบซิงเกิลตัน
คุณไม่ควรกำหนดให้ตัวสร้างเป็นแบบส่วนตัว ระยะเวลา ทำให้ได้รับการป้องกันเพื่อให้คุณสามารถขยายชั้นเรียนได้หากต้องการ
แก้ไข: ฉันยืนอยู่ข้าง ๆ ไม่ว่าคุณจะโหวตลดลงกี่ครั้งก็ตาม คุณกำลังตัดศักยภาพในการพัฒนาโค้ดในอนาคต หากผู้ใช้หรือโปรแกรมเมอร์คนอื่นตั้งใจที่จะขยายคลาสจริงๆพวกเขาก็แค่เปลี่ยนคอนสตรัคเตอร์เพื่อป้องกันในซอร์สหรือไบต์โค้ด คุณจะไม่ประสบความสำเร็จอะไรเลยนอกจากทำให้ชีวิตของพวกเขายากขึ้นเล็กน้อย รวมคำเตือนไว้ในความคิดเห็นของผู้สร้างของคุณและทิ้งไว้ที่นั้น
หากเป็นคลาสยูทิลิตี้วิธีแก้ปัญหาที่ง่ายกว่าถูกต้องและสวยงามกว่าคือการทำเครื่องหมายทั้งคลาสเป็น "static final" เพื่อป้องกันการขยาย การทำเครื่องหมายคอนสตรัคเตอร์เป็นส่วนตัวไม่ได้ผลดี ผู้ใช้ที่ตั้งใจจริงอาจใช้การสะท้อนเพื่อให้ได้ตัวสร้าง
protected
สร้างคือการบังคับใช้วิธีการโรงงานแบบคงที่ซึ่งช่วยให้คุณสามารถ จำกัด การสร้างอินสแตนซ์หรือพูลและนำทรัพยากรที่มีราคาแพงกลับมาใช้ใหม่ (การเชื่อมต่อ DB ทรัพยากรดั้งเดิม)Constructor เป็นแบบส่วนตัวสำหรับจุดประสงค์บางอย่างเช่นเมื่อคุณต้องการใช้ singleton หรือ จำกัด จำนวนอ็อบเจ็กต์ของคลาส ตัวอย่างเช่นในการใช้งานซิงเกิลตันเราต้องทำให้ตัวสร้างเป็นแบบส่วนตัว
#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();
}
อีกครั้งหากคุณต้องการ จำกัด การสร้างวัตถุไม่เกิน 10 ให้ใช้สิ่งต่อไปนี้
#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 ++ มีตัวสร้างเริ่มต้นและตัวสร้างสำเนาเริ่มต้นหากคุณไม่ได้ระบุอย่างชัดเจน สมมติว่าคุณมีคลาสที่สามารถสร้างได้โดยใช้ตัวสร้างพารามิเตอร์บางตัวเท่านั้น บางทีอาจเป็นตัวแปรเริ่มต้น หากผู้ใช้ใช้คลาสนี้โดยไม่มีตัวสร้างนั้นก็สามารถทำให้เกิดปัญหาได้ไม่สิ้นสุด กฎทั่วไปที่ดี: หากการใช้งานเริ่มต้นไม่ถูกต้องให้กำหนดทั้งตัวสร้างเริ่มต้นและตัวคัดลอกเป็นแบบส่วนตัวและอย่าให้การใช้งาน
class C
{
public:
C(int x);
private:
C();
C(const C &);
};
ใช้คอมไพลเลอร์เพื่อป้องกันไม่ให้ผู้ใช้ใช้อ็อบเจ็กต์กับตัวสร้างดีฟอลต์ที่ไม่ถูกต้อง
การอ้างอิงจากJava ที่มีประสิทธิภาพคุณสามารถมีคลาสที่มีตัวสร้างส่วนตัวเพื่อให้มีคลาสยูทิลิตี้ที่กำหนดค่าคงที่ (เป็นฟิลด์สุดท้ายแบบคงที่)
( แก้ไข:ตามความคิดเห็นนี่เป็นสิ่งที่อาจใช้ได้กับ Java เท่านั้นฉันไม่ทราบว่าโครงสร้างนี้ใช้ได้ / จำเป็นในภาษา OO อื่น ๆ หรือไม่ (พูด C ++))
ตัวอย่างด้านล่าง:
public class Constants {
private Contants():
public static final int ADDRESS_UNIT = 32;
...
}
EDIT_1 : อีกครั้งคำอธิบายด้านล่างใช้ได้กับ Java: (และอ้างอิงจากหนังสือEffective Java )
การสร้างอินสแตนซ์ของคลาสยูทิลิตี้เช่นเดียวกับด้านล่างแม้ว่าจะไม่เป็นอันตราย แต่ก็ไม่ได้ตอบสนองวัตถุประสงค์ใด ๆ เนื่องจากไม่ได้ออกแบบมาเพื่อสร้างอินสแตนซ์
ตัวอย่างเช่นสมมติว่าไม่มีตัวสร้างส่วนตัวสำหรับค่าคงที่ของคลาส โค้ดด้านล่างนี้ใช้ได้ แต่ไม่ได้สื่อถึงเจตนาของผู้ใช้คลาส Constants ได้ดีขึ้น
unit = (this.length)/new Constants().ADDRESS_UNIT;
ตรงกันข้ามกับรหัสเช่น
unit = (this.length)/Constants.ADDRESS_UNIT;
ฉันคิดว่าคอนสตรัคเตอร์ส่วนตัวบ่งบอกถึงความตั้งใจของผู้ออกแบบ คลาสConstants (พูด) ได้ดีกว่า
Java จัดเตรียมคอนสตรัคเตอร์สาธารณะที่ไม่มีพารามิเตอร์ดีฟอลต์หากไม่มีการจัดเตรียมคอนสตรัคเตอร์และหากคุณตั้งใจที่จะป้องกันการสร้างอินสแตนซ์จำเป็นต้องใช้คอนสตรัคเตอร์ส่วนตัว
เราไม่สามารถทำเครื่องหมายสแตติกคลาสระดับบนสุดได้และแม้แต่คลาสสุดท้ายก็สามารถสร้างอินสแตนซ์ได้
ชั้นเรียนยูทิลิตี้อาจมีตัวสร้างส่วนตัว ผู้ใช้ชั้นเรียนไม่ควรสร้างอินสแตนซ์ของคลาสเหล่านี้:
public final class UtilityClass {
private UtilityClass() {}
public static utilityMethod1() {
...
}
}
คุณอาจต้องการป้องกันไม่ให้ชั้นเรียนสามารถสร้างอินสแตนซ์ได้อย่างอิสระ ดูรูปแบบการออกแบบซิงเกิลตันเป็นตัวอย่าง เพื่อรับประกันความเป็นเอกลักษณ์คุณไม่สามารถให้ใครสร้างอินสแตนซ์ได้ :-)
การใช้งานที่สำคัญอย่างหนึ่งคือในคลาส 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
}
};
นอกจากนี้ยังเหมาะสมหากชั้นเรียนของคุณมีวิธีการคงที่เท่านั้น กล่าวคือไม่มีใครต้องการสร้างอินสแตนซ์ชั้นเรียนของคุณ
เป็นเหตุผลหนึ่งที่ชัดเจนจริงๆ: คุณต้องการสร้างวัตถุ แต่ไม่สามารถทำได้จริง (ในแง่ของอินเทอร์เฟซ) ภายในตัวสร้าง
Factory
ตัวอย่างค่อนข้างชัดเจนให้ฉันแสดงให้เห็นถึงNamed Constructor
สำนวน
สมมติว่าฉันมีคลาสComplex
ที่สามารถแทนจำนวนเชิงซ้อนได้
class Complex { public: Complex(double,double); .... };
คำถามคือผู้สร้างคาดหวังส่วนจริงและส่วนจินตภาพหรือไม่หรือคาดว่าจะมีบรรทัดฐานและมุม (พิกัดเชิงขั้ว)
ฉันสามารถเปลี่ยนอินเทอร์เฟซเพื่อให้ง่ายขึ้น:
class Complex
{
public:
static Complex Regular(double, double = 0.0f);
static Complex Polar(double, double = 0.0f);
private:
Complex(double, double);
}; // class Complex
สิ่งนี้เรียกว่าNamed Constructor
idiom: คลาสสามารถสร้างได้ตั้งแต่เริ่มต้นโดยระบุอย่างชัดเจนว่าตัวสร้างใดที่เราต้องการใช้
เป็นกรณีพิเศษของการก่อสร้างหลายวิธี รูปแบบการออกแบบให้เป็นจำนวนที่ดีของวิธีการที่จะสร้างวัตถุ: Builder
, Factory
, Abstract Factory
... และนวกรรมิกส่วนตัวจะให้แน่ใจว่าผู้ใช้จะถูก จำกัด อย่างถูกต้อง
นอกเหนือจากการใช้งานที่รู้จักกันดี ...
ในการใช้รูปแบบMethod Objectซึ่งฉันจะสรุปเป็น:
"ตัวสร้างส่วนตัววิธีการคงที่สาธารณะ"
"วัตถุสำหรับการใช้งานฟังก์ชันสำหรับอินเทอร์เฟซ"
หากคุณต้องการที่จะใช้ฟังก์ชั่นการใช้วัตถุและวัตถุไม่ได้ออกไปข้างนอกที่มีประโยชน์ของการทำคำนวณ one-off (โดยเรียกวิธี) แล้วคุณมีวัตถุใบปลิว คุณสามารถห่อหุ้มการสร้างอ็อบเจ็กต์และการเรียกเมธอดในวิธีการแบบคงที่เพื่อป้องกันการต่อต้านรูปแบบทั่วไปนี้:
z = new A(x,y).call();
…แทนที่ด้วยการเรียกฟังก์ชัน (namespaced):
z = A.f(x,y);
ผู้โทรไม่จำเป็นต้องรู้หรือสนใจว่าคุณกำลังใช้วัตถุภายในทำให้มีอินเทอร์เฟซที่สะอาดขึ้นและป้องกันขยะจากวัตถุที่ห้อยอยู่รอบ ๆ หรือใช้วัตถุอย่างไม่ถูกต้อง
ตัวอย่างเช่นถ้าคุณต้องการที่จะเลิกการคำนวณข้ามวิธีfoo
, bar
และzork
ยกตัวอย่างให้กับรัฐหุ้นโดยไม่ต้องส่งผ่านค่าจำนวนมากในและออกจากฟังก์ชั่นที่คุณสามารถใช้มันเป็นดังนี้:
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) { /* ... */ };
}
รูปแบบวัตถุวิธีนี้มีให้ในSmalltalk Best Practice Patterns , Kent Beck, หน้า 34–37 ซึ่งเป็นขั้นตอนสุดท้ายของรูปแบบการปรับโครงสร้างใหม่โดยสิ้นสุด:
- แทนที่เมธอดเดิมด้วยวิธีที่สร้างอินสแตนซ์ของคลาสใหม่โดยสร้างด้วยพารามิเตอร์และตัวรับของเมธอดเดิมและเรียกใช้ "คำนวณ"
สิ่งนี้แตกต่างอย่างมีนัยสำคัญจากตัวอย่างอื่น ๆ ที่นี่: คลาสเป็นแบบทันทีได้ (ไม่เหมือนคลาสยูทิลิตี้) แต่อินสแตนซ์เป็นแบบส่วนตัว (ไม่เหมือนกับวิธีการของโรงงานรวมถึง singletons เป็นต้น) และสามารถอยู่บนสแต็กได้เนื่องจากไม่เคยหนี
รูปแบบนี้มีประโยชน์มากใน OOP ด้านล่างที่ซึ่งออบเจ็กต์ใช้เพื่อลดความซับซ้อนของการนำไปใช้งานระดับต่ำ แต่ไม่จำเป็นต้องเปิดเผยภายนอกและตรงกันข้ามกับ OOP จากบนลงล่างที่มักจะถูกนำเสนอและเริ่มต้นด้วยอินเทอร์เฟซระดับสูง
บางครั้งก็มีประโยชน์หากคุณต้องการควบคุมว่าจะสร้างอินสแตนซ์ของออบเจ็กต์อย่างไรและเมื่อไหร่ (และจำนวน)
อื่น ๆ ที่ใช้ในรูปแบบ:
Singleton pattern
Builder pattern
การใช้คอนสตรัคเตอร์ส่วนตัวอาจช่วยเพิ่มความสามารถในการอ่าน / บำรุงรักษาเมื่อเผชิญกับการออกแบบที่ขับเคลื่อนด้วยโดเมน จาก "Microsoft .NET - Architecing Applications for the Enterprise, 2nd Edition":
var request = new OrderRequest(1234);
ใบเสนอราคา "มีปัญหาสองประการที่นี่ประการแรกเมื่อดูโค้ดเราแทบเดาไม่ออกว่าเกิดอะไรขึ้นมีการสร้างอินสแตนซ์ของ OrderRequest แต่ทำไมและใช้ข้อมูลใด 1234 คืออะไรสิ่งนี้นำไปสู่ปัญหาที่สอง: คุณกำลังละเมิดภาษาที่แพร่หลายของบริบทที่มีขอบเขตภาษานี้อาจระบุว่าลูกค้าสามารถส่งคำขอสั่งซื้อและได้รับอนุญาตให้ระบุรหัสการซื้อหากเป็นเช่นนั้นนี่เป็นวิธีที่ดีกว่าในการรับอินสแตนซ์ OrderRequest ใหม่ : "
var request = OrderRequest.CreateForCustomer(1234);
ที่ไหน
private OrderRequest() { ... }
public OrderRequest CreateForCustomer (int customerId)
{
var request = new OrderRequest();
...
return request;
}
ฉันไม่ได้สนับสนุนสิ่งนี้สำหรับทุกคลาส แต่สำหรับสถานการณ์ DDD ข้างต้นฉันคิดว่ามันเหมาะสมอย่างยิ่งที่จะป้องกันไม่ให้มีการสร้างวัตถุใหม่โดยตรง