A che serve rendere privato il costruttore in una classe?


134

Perché dovremmo rendere il costruttore privato in classe? Come abbiamo sempre bisogno che il costruttore sia pubblico.

Risposte:


129

Alcuni motivi per cui potresti aver bisogno di un costruttore privato:

  1. È possibile accedere al costruttore solo dal metodo factory statico all'interno della classe stessa. Anche Singleton può appartenere a questa categoria.
  2. Una classe di utilità che contiene solo metodi statici.

9
Non mi sarei nemmeno preso la briga di creare un costruttore privato per una classe di utilità.
Petesh,

16
@Patesh: Questa è la tua decisione. Altri e io preferiremmo impedire un'istanza della classe di utilità piuttosto che tralasciare un costruttore privato di una riga.
nanda,

1
in alcuni linguaggi di programmazione (in particolare Java) impedisce anche l'ereditarietà
dfa

9
@dfa: per prevenire l'ereditarietà, devi metterti finala livello di classe. Mettere costruttore privato è praticamente inutile per questo motivo.
nanda,

3
@Will: Non se usi la riflessione. La dichiarazione di costruttori privati ​​ha lo scopo di prevenire l'istanza (blocco della riflessione), ma impedire la sottoclasse è un effetto collaterale, non l'intento. Lo strumento appropriato per questo è dichiarare la classe final. Questo è ciò che Sun ha fatto per la classe String e una porzione ragionevole dell'API che non deve essere estesa.
BobMcGee,

96

Fornendo un costruttore privato si impedisce la creazione di istanze di classe in qualsiasi luogo diverso da questa stessa classe. Esistono diversi casi d'uso per fornire tale costruttore.

A. Le tue istanze di classe vengono create in un staticmetodo. Il staticmetodo viene quindi dichiarato come public.

class MyClass()
{
private:
  MyClass() { }

public:
  static MyClass * CreateInstance() { return new MyClass(); }
};

B. La tua classe è un singleton . Ciò significa che nel programma non esiste più di un'istanza della classe.

class MyClass()
{
private:
  MyClass() { }

public:
  MyClass & Instance()
  {
    static MyClass * aGlobalInst = new MyClass();
    return *aGlobalInst;
  }
};

C. (Si applica solo al prossimo standard C ++ 0x) Hai diversi costruttori. Alcuni sono dichiarati public, altri private. Per ridurre la dimensione del codice, i costruttori pubblici "chiamano" i costruttori privati ​​che a loro volta svolgono tutto il lavoro. I vostri publiccostruttori sono così chiamati costruttori delegati :

class MyClass
{
public:
  MyClass() : MyClass(2010, 1, 1) { }

private:
  MyClass(int theYear, int theMonth, int theDay) { /* do real work */ }
};

D. Si desidera limitare la copia degli oggetti (ad esempio, a causa dell'utilizzo di una risorsa condivisa):

class MyClass
{
  SharedResource * myResource;

private:
  MyClass(const MyClass & theOriginal) { }
};

E. La tua classe è una classe di utilità . Ciò significa che contiene solo staticmembri. In questo caso, nessuna istanza di oggetto deve mai essere creata nel programma.


3
Nella maggior parte dei casi, si desidera impedire la copia degli oggetti. Quindi non forniresti un'implementazione per il costruttore di copie private.
frast,

da google c ++ style guide (un altro esempio non nell'elenco ma comune) -> se devi lavorare nel costruttore "considera una funzione di fabbrica [e rendi il costruttore privato]" (il testo tra parentesi quadre è scritto da me )
Trevor Boyd Smith il

12

Lasciare una "backdoor" che consente a un'altra classe / funzione di amico di costruire un oggetto in un modo proibito all'utente. Un esempio che viene in mente sarebbe un contenitore che costruisce un iteratore (C ++):

Iterator Container::begin() { return Iterator(this->beginPtr_); }
// Iterator(pointer_type p) constructor is private,
//     and Container is a friend of Iterator.

È abbastanza diverso, Terry? ;)
Emile Cormier il

Buona risposta. Perché non menzionare la riflessione, dal momento che questa è una strada per realizzare qualcosa che l'utente non può?
BobMcGee

@Bob: Dovrai illuminarmi su quello, dato che sono principalmente un tipo C ++ e la riflessione non è proprio nel vocabolario C ++. ;)
Emile Cormier il

3
Nessuno leggerà questo, ma qui va: sono stato retrocesso un paio di volte su questo. Non arrabbiato o altro, ma (per motivi di apprendimento) mi piacerebbe sapere perché questo è negativo? In quale altro modo è possibile costruire un iteratore con i dati necessari per accedere ai dati di un contenitore?
Emile Cormier,

2
@EmileCormier, penso che tu sia stato ingiustamente sottovalutato perché alle persone che imparano il C ++ vengono ripetute ripetutamente: "Evita dichiarazioni di amici". Questo consiglio sembra essere rivolto a programmatori C ++ inesperti che potrebbero altrimenti utilizzare frienddove non è garantito, e ci sono molti casi in cui è una cattiva idea. Purtroppo, il messaggio è stato ricevuto troppo bene e molti sviluppatori non imparano mai abbastanza bene la lingua per capire che l'uso occasionale di friendnon è solo accettabile, ma preferibile . Il tuo esempio è stato proprio un caso del genere. Il forte accoppiamento intenzionale non è un crimine, è una decisione di progettazione.
evadeflow,

10

Tutti sono bloccati sulla cosa di Singleton, wow.

Altre cose:

  • Impedisci alle persone di creare la tua classe in pila; crea costruttori privati ​​e restituisci i puntatori solo tramite un metodo factory.
  • Impedire la creazione di copie della classe (costruttore di copie private)

8

Questo può essere molto utile per un costruttore che contiene codice comune; i costruttori privati ​​possono essere chiamati da altri costruttori, usando "this (...);" notazione. Rendendo il codice di inizializzazione comune in un costruttore privato (o protetto), stai anche chiarendo esplicitamente che viene chiamato solo durante la costruzione, il che non è così se fosse semplicemente un metodo:

public class Point {
   public Point() {
     this(0,0); // call common constructor
   }
   private Point(int x,int y) {
     m_x = x; m_y = y;
   }
};

3

Ci sono alcuni casi in cui potresti non voler usare un costruttore pubblico; ad esempio se si desidera una classe singleton.

Se si sta scrivendo un assembly utilizzato da terze parti, potrebbero esserci un numero di classi interne che si desidera creare solo dall'assembly e non essere istanziate dagli utenti dell'assembly.


3

Ciò garantisce che tu (la classe con costruttore privato) controlli il modo in cui viene chiamato il contraente.

Un esempio: un metodo factory statico sulla classe potrebbe restituire oggetti quando il metodo factory sceglie di allocarli (come ad esempio una factory singleton).


@Skilldrick: un esempio potrebbe essere quello di forzare l'assegnazione di un heap a una classe.
Dirk,

@dirk Ho eliminato il mio commento in quanto non è più pertinente.
Skilldrick

3

Possiamo anche avere un costruttore privato, per enfore la creazione dell'oggetto solo da una classe specifica (per motivi di sicurezza).

Un modo per farlo è attraverso una classe di amici.

Esempio 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.
     }
};

Nota: Nota: solo ClientClass (poiché è amico di SecureClass) può chiamare il costruttore di SecureClass.



2

quando non si desidera che gli utenti creino istanze di questa classe o creino una classe che eredita questa classe, come la java.lang.math, tutte le funzioni in questo pacchetto sono static, tutte le funzioni possono essere chiamate senza creare un'istanza di math, quindi il costruttore è annunciato come statico .


1

Se è privato, non puoi chiamarlo ==> non puoi creare un'istanza della classe. Utile in alcuni casi, come un singleton.

C'è una discussione e altri esempi qui .


1

Ho visto una tua domanda che affronta lo stesso problema.

Semplicemente se non vuoi consentire agli altri di creare istanze, allora mantieni il constuctor in un ambito limitato. L'applicazione pratica (un esempio) è il modello singleton.


1

Non dovresti rendere privato il costruttore. Periodo. Renderlo protetto, in modo da poter estendere la classe, se necessario.

Modifica: lo sto aspettando, non importa quanti voti negativi si danno a questo. Stai tagliando il potenziale per lo sviluppo futuro del codice. Se altri utenti o programmatori sono davvero determinati a estendere la classe, cambieranno semplicemente il costruttore in protetto in codice sorgente o bytecode. Non avrai realizzato nulla oltre a rendere la loro vita un po 'più dura. Includi un avviso nei commenti del costruttore e lascialo a quello.

Se si tratta di una classe di utilità, la soluzione più semplice, corretta e più elegante è contrassegnare l'intera classe "finale statico" per impedire l'estensione. Non serve a niente contrassegnare il costruttore come privato; un utente veramente determinato può sempre usare la riflessione per ottenere il costruttore.

Usi validi:

  • Un buon uso di un protected costruttore è quello di forzare l'uso di metodi statici di fabbrica, che consentono di limitare l'istanza o raggruppare e riutilizzare risorse costose (connessioni DB, risorse native).
  • Singletons (di solito non è una buona pratica, ma a volte è necessario)

2
Nella mia esperienza non ci sono verità assolute, anche goto potrebbe essere usato se la situazione lo richiede. Ci sono modi Cattivi e Malvagi, ma come dice Marshall Cline, a volte devi scegliere tra il minore dei mali. parashift.com/c++-faq-lite/big-picture.html#faq-6.15 Per quanto riguarda i costruttori privati, sono necessari e neanche male per te. Significa solo che nessuno, comprese le sottoclassi, dovrebbe usarlo.
daramarak,

Le goto hanno il loro posto, vero, ma contrassegnare un costruttore come privato non guadagna altro che problemi lungo la strada. Ho modificato il mio post per spiegare meglio perché.
BobMcGee

Non ha senso copiare il costrutto o il costrutto predefinito alcune classi. In C ++ lo indichi dichiarando un ctor privato senza definirlo (che è anche comune per operator =).

I compilatori di ottimizzazione come Java JIT e GWT fanno effettivamente uso di costruttori privati: limitare l'ambito dell'istanza e fare un lavoro migliore inserendo / eliminando il codice.
Ajax

1

Il costruttore è privato per alcuni scopi, ad esempio quando è necessario implementare singleton o limitare il numero di oggetti di una classe. Ad esempio nell'implementazione di singleton dobbiamo rendere privato il costruttore

#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();
}

Ancora una volta, se si desidera limitare la creazione dell'oggetto a 10, utilizzare quanto segue

#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

}

Grazie


1

Puoi avere più di un costruttore. C ++ fornisce un costruttore predefinito e un costruttore di copie predefinito se non ne viene fornito uno esplicitamente. Supponiamo di avere una classe che può essere costruita solo usando un costruttore con parametri. Forse ha inizializzato le variabili. Se un utente utilizza quindi questa classe senza tale costruttore, può causare problemi senza fine. Una buona regola generale: se l'implementazione predefinita non è valida, rendi privati ​​sia il costruttore predefinito sia il costruttore della copia e non fornire un'implementazione:

class C
{
public:
    C(int x);

private:
    C();
    C(const C &);
};

Utilizzare il compilatore per impedire agli utenti di utilizzare l'oggetto con i costruttori predefiniti non validi.


2
Quando si fornisce un costruttore non predefinito senza specificare un costruttore predefinito, il costruttore predefinito non esiste. Non è necessario crearlo e renderlo privato.
TT_

0

Citando da Effective Java , è possibile avere una classe con costruttore privato per avere una classe di utilità che definisce le costanti (come campi finali statici).

( EDIT: secondo il commento questo è qualcosa che potrebbe essere applicabile solo con Java, non sono consapevole se questo costrutto è applicabile / necessario in altri linguaggi OO (diciamo C ++))

Un esempio come di seguito:

public class Constants {
    private Contants():

    public static final int ADDRESS_UNIT = 32;
    ...
}

EDIT_1 : Ancora una volta, la spiegazione di seguito è applicabile in Java: (e riferendosi al libro, Java efficace )

Un'istanza di classe di utilità come quella seguente, sebbene non dannosa, non serve a nulla poiché non è progettata per essere istanziata.

Ad esempio, supponiamo che non esista un costruttore privato per le costanti di classe. Un blocco di codice come di seguito è valido ma non trasmette meglio l'intenzione dell'utente della classe Costanti

unit = (this.length)/new Constants().ADDRESS_UNIT;

in contrasto con il codice come

unit = (this.length)/Constants.ADDRESS_UNIT;

Inoltre penso che un costruttore privato trasmetta meglio l'intenzione del progettista della classe Costanti (diciamo).

Java fornisce un costruttore pubblico senza parametri predefinito se non viene fornito alcun costruttore e se si intende impedire l'istanza, è necessario un costruttore privato.

Non è possibile contrassegnare una classe di livello superiore statica e persino una classe finale può essere istanziata.


2
Ma in C ++ non hai bisogno di una classe per questo. Se qualcosa non dipende dai dati all'interno dell'oggetto, scrivilo come funzioni libere e variabili libere. Vuoi l'incapsulamento? Usa uno spazio dei nomi.
daramarak,

@daramarak: grazie, non ho esperienza con C ++. Ho aggiornato la risposta per riflettere che ciò è applicabile solo in Java
sateesh il

2
Se il tuo oggetto sta solo incapsulando variabili statiche, perché limitare l'istanza? Perché specificare qualcosa sul costruttore? Non farà alcun danno se viene istanziato. Sembra che tu possa ottenere risultati simili dichiarando la classe statica e finale.
BobMcGee

@BobMcGee, grazie per il tuo commento. Questo mi ha fatto riflettere (e fare riferimento) di più su ciò che ho pubblicato. Ho modificato la mia risposta per aggiungere altri ragionamenti sull'utilità del costruttore privato.
Sateesh,

0

Le classi di utilità potrebbero avere costruttori privati. Gli utenti delle classi non dovrebbero essere in grado di creare un'istanza di queste classi:

public final class UtilityClass {
    private UtilityClass() {}

    public static utilityMethod1() {
        ...
    }
}

1
Perché è importante se la classe di utilità è istanziata? Tutto ciò che fa è creare un oggetto senza campi e consumare pochi byte di memoria.
BobMcGee

Essere in grado di creare un'istanza crea un'API ambigua. Se si progetta una classe come una classe di utilità senza stato e si desidera mantenerla in questo modo. È possibile sottoclassare una classe con un costruttore pubblico. Una sottoclasse di alcuni metodi di utilità è idiota.
gpampara,

@BobMcGee: ovviamente progettare una classe che funziona e progettare una classe per essere utilizzata da altre persone sono cose diverse. Coloro che lavorano nello sviluppo di API (come Sun people o Google Collections guy) uccideranno chiunque cerchi di dire che il costruttore privato in una classe di utilità è inutile.
nanda,

@gpampara: dichiarare il tuo finale di classe impedisce la sottoclasse delle persone. Questo è ciò che Sun ha fatto per la classe String. @Nanda: una classe di utilità non ha bisogno di un costruttore definito. Se non vuoi che sia estensibile, allora dichiaralo non usando "final".
BobMcGee,

1
@BobMcGee: sembra che ti piaccia vedere l'esempio di Sun. Quindi controllare questa raccolta di classi di utilità da Sun: docjar.com/html/api/java/util/Collections.java.html . Sta effettivamente usando un costruttore privato.
nanda,

0

Potresti voler impedire un'istanza libera di una classe. Vedi il modello di progettazione singleton come esempio. Per garantire l'unicità, non puoi permettere a nessuno di crearne un'istanza :-)


0

Uno degli usi importanti è nella classe 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
   }
};

È anche adatto, se la tua classe ha solo metodi statici. cioè nessuno ha bisogno di istanziare la tua classe


Va notato che singleton è considerato da molti come un "anti-pattern" e una decisione di applicarlo non dovrebbe essere presa alla leggera. Un'alternativa comune è l'iniezione di dipendenza.
Michael Aaron Safyan

SingleTon non è un anti-pattern. tuttavia, l'uso eccessivo del modello (a causa della mancanza di conoscenza del suo utilizzo) non è buono. Va notato che è uno dei modelli GOF.
SysAdmin

0

È davvero una ragione ovvia: vuoi costruire un oggetto, ma non è pratico farlo (in termini di interfaccia) all'interno del costruttore.

L' Factoryesempio è abbastanza ovvio, lasciatemi dimostrare il Named Constructorlinguaggio.

Supponiamo che io abbia una classe Complexche può rappresentare un numero complesso.

class Complex { public: Complex(double,double); .... };

La domanda è: il costruttore si aspetta le parti reali e immaginarie o si aspetta la norma e l'angolo (coordinate polari)?

Posso cambiare l'interfaccia per renderlo più semplice:

class Complex
{
public:
  static Complex Regular(double, double = 0.0f);
  static Complex Polar(double, double = 0.0f);
private:
  Complex(double, double);
}; // class Complex

Questo è chiamato Named Constructoridioma: la classe può essere costruita da zero affermando esplicitamente quale costruttore vogliamo usare.

È un caso speciale di molti metodi di costruzione. I Design Patterns forniscono un buon numero di modi per costruire oggetti: Builder, Factory, Abstract Factory, ... e un costruttore privato farà in modo che l'utente sia correttamente vincolato.


0

Oltre agli usi più noti ...

Per implementare il modello Object Method , che riassumerei come:

"Costruttore privato, metodo statico pubblico"
"Oggetto per implementazione, funzione per interfaccia"

Se si desidera implementare una funzione utilizzando un oggetto e l'oggetto non è utile al di fuori di eseguire un calcolo una tantum (mediante una chiamata di metodo), allora si dispone di un oggetto Throwaway . È possibile incapsulare la creazione dell'oggetto e la chiamata del metodo in un metodo statico, impedendo questo modello comune:

z = new A(x,y).call();

... sostituendolo con una chiamata di funzione (spazio dei nomi):

z = A.f(x,y);

Il chiamante non ha mai bisogno di sapere o preoccuparsi del fatto che stai usando un oggetto internamente, producendo un'interfaccia più pulita e prevenendo la spazzatura dall'oggetto in giro o un uso errato dell'oggetto.

Ad esempio, se si vuole rompere un calcolo attraverso metodi foo, bare zork, ad esempio, allo stato condivisione senza dover passare molti valori dentro e fuori di funzioni, è possibile implementare nel modo seguente:

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) { /* ... */ };
}

Questo modello di oggetto metodo è riportato in Smalltalk Best Practice Patterns , Kent Beck, pagine 34–37, dove è l'ultimo passaggio di un modello di refactoring, che termina:

  1. Sostituisci il metodo originale con uno che crea un'istanza della nuova classe, costruita con i parametri e il ricevitore del metodo originale, e invoca il "calcolo".

Ciò differisce in modo significativo dagli altri esempi qui: la classe è istantanea (a differenza di una classe di utilità), ma le istanze sono private (a differenza dei metodi di fabbrica, inclusi i singleton ecc.) E possono vivere nello stack, poiché non scappano mai.

Questo modello è molto utile in OOP dal basso verso l'alto, in cui gli oggetti vengono utilizzati per semplificare l'implementazione di basso livello, ma non sono necessariamente esposti esternamente e contrasta con l'OOP top-down che viene spesso presentato e inizia con interfacce di alto livello.


0

A volte è utile se si desidera controllare come e quando (e quante) istanze di un oggetto vengono create.

Tra gli altri, utilizzato nei modelli:

Singleton pattern
Builder pattern

In che modo un costruttore privato è utile in un oggetto immutabile? L'immutabilità riguarda la prevenzione delle modifiche a un oggetto dopo la creazione , mentre i costruttori privati ​​riguardano la prevenzione della creazione .
Nils von Barth

0

L'uso di costruttori privati ​​potrebbe anche aumentare la leggibilità / manutenibilità di fronte alla progettazione guidata dal dominio. Da "Microsoft .NET - Architecing Applications for the Enterprise, 2nd Edition":

var request = new OrderRequest(1234);

Citazione, "Ci sono due problemi qui. Innanzitutto, quando si guarda il codice, difficilmente si può indovinare cosa sta succedendo. Viene creata un'istanza di OrderRequest, ma perché e utilizzare quali dati? Che cosa è 1234? Questo porta al secondo problema: stai violando la lingua onnipresente del contesto limitato. La lingua probabilmente dice qualcosa del genere: un cliente può inviare una richiesta d'ordine e può specificare un ID di acquisto. In tal caso, ecco un modo migliore per ottenere una nuova istanza OrderRequest :"

var request = OrderRequest.CreateForCustomer(1234);

dove

private OrderRequest() { ... }

public OrderRequest CreateForCustomer (int customerId)
{
    var request = new OrderRequest();
    ...
    return request;
}

Non lo sto sostenendo per ogni singola classe, ma per lo scenario DDD di cui sopra penso che abbia perfettamente senso prevenire una creazione diretta di un nuovo oggetto.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.