Какой смысл делать конструктор закрытым в классе?


134

Почему мы должны сделать конструктор закрытым в классе? Как нам всегда нужно, чтобы конструктор был публичным.

Ответы:


129

Некоторые причины, по которым вам может понадобиться приватный конструктор:

  1. Доступ к конструктору возможен только из статического метода фабрики внутри самого класса. Синглтон также может принадлежать к этой категории.
  2. Служебный класс , который содержит только статические методы.

9
Я бы даже не удосужился создать приватный конструктор для служебного класса.
Петеш

16
@Patesh: это ваше решение. Другие (и) и я предпочли бы предотвратить создание экземпляров служебного класса, а не оставить один строчный закрытый конструктор.
Нанда

1
в некоторых языках программирования (особенно в Java) это также предотвращает наследование
dfa

9
@dfa: чтобы предотвратить наследование, вы должны поставить finalна уровень класса. По этой причине помещать приватный конструктор практически бесполезно.
Нанда

3
@Will: Нет, если вы используете отражение. Объявление конструкторов частными предназначено для предотвращения создания экземпляров (запрет на отражение), но предотвращение создания подклассов является побочным эффектом, а не намерением. Подходящим инструментом для этого является объявление класса final. Это то, что Sun сделала для класса String и разумного фрагмента API, который не следует расширять.
BobMcGee

96

Предоставляя приватный конструктор, вы предотвращаете создание экземпляров класса в любом месте, кроме этого самого класса. Существует несколько вариантов использования такого конструктора.

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

E. Ваш класс - это служебный класс . Это означает, что он содержит только staticчленов. В этом случае экземпляр программы никогда не должен создаваться в программе.


3
В большинстве случаев вы хотели бы предотвратить копирование объекта. Таким образом, вы не предоставите реализацию для конструктора частной копии.
2010 г.

из руководства по стилю Google C ++ (другой пример не в списке, а обычный) -> если вам нужно выполнить работу в конструкторе «рассмотрите функцию фабрики [и сделайте конструктор частным]» (текст в квадратных скобках написан мной )
Тревор Бойд Смит

12

Оставить «черный ход», который позволяет другому классу / функции друга создать объект способом, запрещенным для пользователя. В качестве примера можно привести контейнер, создающий итератор (C ++):

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

Это достаточно отличается, Терри? ;)
Эмиль Корниер

Хороший ответ. Почему бы не упомянуть рефлексию, поскольку это один из способов достижения того, чего пользователь не может сделать?
BobMcGee

@Bob: Вы должны будете просветить меня об этом, так как я в основном парень C ++, а рефлексия на самом деле не входит в словарь C ++. ;)
Эмиль Кормье

3
Никто не собирается читать это, но здесь идет: я был понижен пару раз по этому. Не расстроен или что-то еще, но (ради обучения) я хотел бы знать, почему это плохо? Как еще можно построить итератор с данными, необходимыми для доступа к данным контейнера?
Эмиль Кормиер

2
@EmileCormier, я думаю, что вы получили несправедливо проголосовали, потому что людям, изучающим C ++, снова и снова говорят: «Избегайте объявлений друзей». Этот совет, похоже, нацелен на неопытных программистов на С ++, которые иначе могли бы использовать friendего там, где это не оправдано, и во многих случаях это плохая идея. К сожалению, сообщение было получено слишком хорошо, и многие разработчики никогда не изучают язык достаточно хорошо, чтобы понять, что случайное использование friendне только приемлемо, но и предпочтительно . Ваш пример был именно таким. Умышленное сильное взаимодействие - это не преступление, это дизайнерское решение.
evadeflow

10

Все застряли на Синглтоне, вау.

Другие вещи:

  • Не позволяйте людям создавать ваш класс в стеке; создавать частные конструкторы и возвращать указатели только через фабричный метод.
  • Предотвращение создания копий класса (конструктор личных копий)

8

Это может быть очень полезно для конструктора, который содержит общий код; частные конструкторы могут быть вызваны другими конструкторами, используя 'this (...);' нотации. Делая общий код инициализации в частном (или защищенном) конструкторе, вы также четко даете понять, что он вызывается только во время построения, что не так, если бы это был просто метод:

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

3

В некоторых случаях вы можете не захотеть использовать публичный конструктор; например, если вы хотите одноэлементный класс.

Если вы пишете сборку, используемую сторонними разработчиками, может существовать ряд внутренних классов, которые вы хотите создавать только вашей сборкой, а не создавать экземпляры для пользователей вашей сборки.


3

Это гарантирует, что вы (класс с закрытым конструктором) управляете тем, как вызывается конструктор.

Пример: метод статической фабрики в классе может возвращать объекты, так как метод фабрики выбирает их размещение (например, фабрика-одиночка).


@Skilldrick: одним из примеров может быть то, что вы можете заставить класс выделяться только кучей.
Дирк

@dirk Я удалил свой комментарий, так как он больше не актуален.
Скиллдрик

3

У нас также может быть приватный конструктор, чтобы предвидеть создание объекта только определенным классом (из соображений безопасности).

Один из способов сделать это - иметь класс друзей.

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.


2

2

когда вы не хотите, чтобы пользователи создавали экземпляры этого класса или создавали класс, который наследует этот класс, как, например java.lang.math, вся функция в этом пакете static, все функции можно вызывать без создания экземпляра math, поэтому конструктор объявляется как статический ,


1

Если это личное, то вы не можете его назвать ==> вы не можете создать экземпляр класса. Полезно в некоторых случаях, например, синглтон.

Там в обсуждении и еще несколько примеров здесь .


1

Я видел вопрос от вас, касающийся той же проблемы.

Просто, если вы не хотите, чтобы другие могли создавать экземпляры, оставьте конструктор в ограниченной области. Практическое применение (пример) - шаблон синглтона.


1

Вы не должны делать конструктор приватным. Период. Сделайте его защищенным, чтобы вы могли расширить класс, если вам нужно.

Изменить: Я поддерживаю это, независимо от того, сколько отрицательных голосов вы бросаете на это. Вы отсекаете потенциал для будущего развития кода. Если другие пользователи или программисты действительно решили расширить класс, то они просто изменят конструктор на защищенный в исходном коде или байт-коде. Вы ничего не сделаете, кроме как сделать их жизнь немного сложнее. Включите предупреждение в комментарии вашего конструктора и оставьте это при этом.

Если это служебный класс, то более простое, правильное и элегантное решение - пометить весь класс как «статический финал», чтобы предотвратить расширение. Бесполезно просто пометить конструктор как приватный; действительно определенный пользователь всегда может использовать отражение для получения конструктора.

Допустимое использование:

  • Одним из хороших применений protected конструктора является принудительное использование статических методов фабрики, которые позволяют ограничивать создание экземпляров или объединять и повторно использовать дорогие ресурсы (соединения с БД, собственные ресурсы).
  • Одиночки (обычно не очень хорошая практика, но иногда необходимо)

2
В моем опыте нет абсолютных истин, даже goto может быть использовано, если того требует ситуация. Есть плохие и злые пути, но, как говорит Маршалл Клайн, иногда нужно выбирать между меньшим из зол. parashift.com/c++-faq-lite/big-picture.html#faq-6.15 Что касается частных конструкторов, они необходимы и даже не плохи для вас. Это просто означает, что никто, включая ваши подклассы, не должен его использовать.
Дарамарак

Правда, у Gotos есть свое место, но пометка частного конструктора не принесет вам ничего, кроме неприятностей в будущем. Я отредактировал свой пост, чтобы объяснить более подробно, почему.
BobMcGee

Не имеет смысла копировать конструкцию или конструкцию по умолчанию для некоторых классов. В C ++ вы указываете это, объявляя частный ctor, не определяя его (что также характерно для operator =).

Оптимизирующие компиляторы, такие как Java JIT и GWT, на самом деле используют частные конструкторы: чтобы ограничиться областью создания экземпляров и лучше выполнять встраивание / сокращение кода.
Аякс

1

Конструктор является закрытым для некоторых целей, например, когда вам нужно реализовать синглтон или ограничить количество объектов в классе. Например, в реализации синглтона мы должны сделать конструктор приватным

#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

}

Спасибо


1

Вы можете иметь более одного конструктора. C ++ предоставляет конструктор по умолчанию и конструктор копирования по умолчанию, если вы не предоставили его явно. Предположим, у вас есть класс, который может быть создан только с использованием какого-либо параметризованного конструктора. Может быть, это инициализированные переменные. Если пользователь затем использует этот класс без этого конструктора, он не может вызвать никаких проблем. Хорошее общее правило: если реализация по умолчанию недопустима, сделайте как конструктор по умолчанию, так и конструктор копирования закрытым и не предоставляйте реализацию:

class C
{
public:
    C(int x);

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

Используйте компилятор, чтобы запретить пользователям использовать объект с недопустимыми конструкторами по умолчанию.


2
Когда вы предоставляете конструктор не по умолчанию без указания конструктора по умолчанию, конструктор по умолчанию не существует. Не нужно создавать его и делать его приватным.
TT_

0

Цитируя Effective Java , вы можете иметь класс с закрытым конструктором, чтобы иметь вспомогательный класс, который определяет константы (как статические конечные поля).

( РЕДАКТИРОВАТЬ: согласно комментарию это то, что может быть применимо только с Java, я не знаю, применима ли эта конструкция / нужна в других языках OO (скажем, C ++))

Пример как ниже:

public class Constants {
    private Contants():

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

EDIT_1 : Опять- таки , нижеприведенное объяснение применимо к Java: (и со ссылкой на книгу, эффективная Java )

Создание экземпляра класса утилит, подобного приведенному ниже, хотя и не является вредным, не имеет никакого значения, поскольку не предназначено для реализации.

Например, скажем, нет частного конструктора для констант класса. Блок кода, подобный приведенному ниже, действителен, но не лучше передает намерение пользователя класса Constants.

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

в отличие от кода, как

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

Также я думаю, что частный конструктор лучше передает замысел дизайнера класса Constants .

Java предоставляет общедоступный конструктор по умолчанию без параметров, если конструктор не предусмотрен, и если вы намерены предотвратить создание экземпляров, тогда нужен частный конструктор.

Нельзя пометить класс верхнего уровня как статический, и даже конечный класс может быть создан.


2
Но в C ++ вам не нужен класс для этого. Если что-то не зависит от данных внутри объекта, запишите это как свободные функции и свободные переменные. Хотите инкапсуляцию? Используйте пространство имен.
Дарамарак

@daramarak: спасибо, у меня нет опыта работы с C ++. Я обновил ответ, чтобы отразить, что это применимо только в Java
сатеешь

2
Если ваш объект просто инкапсулирует статические переменные, зачем ограничивать реализацию? Зачем указывать что-либо о конструкторе? Это не принесет никакого вреда, если оно будет создано. Похоже, вы могли бы получить аналогичные результаты, объявив класс static и final.
BobMcGee

@BobMcGee, спасибо за ваш комментарий. Это заставило меня думать (и ссылаться) больше о том, что я опубликовал. Я отредактировал свой ответ, чтобы добавить больше рассуждений о полезности частного конструктора.
сатеешь

0

У служебных классов могут быть частные конструкторы. Пользователи классов не должны иметь возможность создавать экземпляры этих классов:

public final class UtilityClass {
    private UtilityClass() {}

    public static utilityMethod1() {
        ...
    }
}

1
Почему это важно, если создается экземпляр класса утилит? Все, что он делает, это создает объект без полей и съедает несколько байтов памяти.
BobMcGee

Возможность его создания создает неоднозначный API. Если вы разрабатываете класс как служебный класс без состояния и хотите сохранить его таким образом. Вы можете создать подкласс класса с открытым конструктором. Подкласс некоторых служебных методов идиотский.
gpampara

@BobMcGee: очевидно, что разработка класса, который работает, и разработка класса для использования другими людьми - это разные вещи. Те, кто работает в разработке API (например, люди из Sun или парень из коллекций Google), убьют любого, кто скажет, что приватный конструктор в служебном классе бесполезен.
Нанда

@gpampara: Объявление финала вашего класса не позволяет людям создавать подклассы. Это то, что Sun сделала для класса String. @Nanda: служебный класс не нуждается в определенном конструкторе. Если вы не хотите, чтобы он был расширяемым, объявите, что это не так, с помощью «final».
BobMcGee

1
@BobMcGee: Кажется, вам нравится видеть пример с Sun. Затем проверьте этот служебный класс Collections от Sun: docjar.com/html/api/java/util/Collections.java.html . Это действительно с помощью частного конструктора.
Нанда

0

Вы можете предотвратить создание экземпляра класса. См. Пример шаблона синглтона в качестве примера. Чтобы гарантировать уникальность, вы не можете позволить кому-либо создать его экземпляр :-)


0

Одно из важных применений в классе 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
   }
};

Это также подходит, если в вашем классе есть только статические методы. т.е. никому не нужно создавать экземпляр вашего класса


Следует отметить, что синглтон рассматривается многими как «антишаблон», и решение о его применении не должно приниматься легкомысленно. Распространенной альтернативой является внедрение зависимостей.
Майкл Аарон Сафьян

SingleTon не является анти-паттерном. однако, чрезмерное использование шаблона (из-за недостатка знаний о его использовании) не является хорошим. Следует отметить, что это один из паттернов GOF.
SysAdmin

0

Это на самом деле одна очевидная причина: вы хотите построить объект, но не практично делать это (с точки зрения интерфейса) в конструкторе.

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идиомой: класс может быть построен с нуля только путем явного указания, какой конструктор мы хотим использовать.

Это особый случай многих методов строительства. Шаблоны дизайна обеспечивают большое количество способов объекта сборки: Builder, Factory, Abstract Factory, ... и частный конструктор гарантирует , что пользователь правильно ограничен.


0

В дополнение к более известным применениям ...

Для реализации шаблона метода объекта , который я бы суммировал как:

«Частный конструктор, публичный статический метод»,
«Объект для реализации, функция для интерфейса».

Если вы хотите реализовать функцию с использованием объекта, и этот объект бесполезен вне выполнения одноразовых вычислений (с помощью вызова метода), то у вас есть Throwaway Object . Вы можете инкапсулировать создание объекта и вызов метода в статический метод, предотвращая этот общий анти-шаблон:

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

… Заменив его вызовом (в пространстве имен):

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

Этот шаблон объекта метода приведен в шаблонах Besttalk Best Practice , Kent Beck, стр. 34–37, где он является последним этапом шаблона рефакторинга и заканчивается:

  1. Замените исходный метод на тот, который создает экземпляр нового класса, созданный с параметрами и получателем исходного метода, и вызывает «вычислить».

Это существенно отличается от других примеров: класс является инстанцируемым (в отличие от служебного класса), но экземпляры являются частными (в отличие от фабричных методов, включая синглтоны и т. Д.) И могут жить в стеке, поскольку они никогда не экранируются.

Этот шаблон очень полезен в ООП «снизу вверх», где объекты используются для упрощения низкоуровневой реализации, но не обязательно представлены внешне, и отличается от нисходящего ООП, который часто представлен и начинается с интерфейсов высокого уровня.


0

Иногда полезно, если вы хотите контролировать, как и когда (и сколько) создаются экземпляры объекта.

Среди прочего, используется в шаблонах:

Singleton pattern
Builder pattern

Чем приватный конструктор полезен в неизменяемом объекте? Неизменность - это предотвращение изменений объекта после создания , в то время как частные конструкторы - предотвращение создания .
Нильс фон Барт

0

Использование частных конструкторов также может повысить читабельность / ремонтопригодность перед лицом доменного дизайна. Из «Microsoft .NET - Архитектурные приложения для предприятия, 2-е издание»:

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 я думаю, что имеет смысл предотвратить непосредственное создание нового объекта.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.