#java #design-patterns
#java #шаблоны проектирования
Вопрос:
У меня есть класс, и я хотел бы ограничить его создание только одним фабричным классом, как я могу предотвратить создание экземпляра этого объекта оператором new?
Комментарии:
1. Я не уверен, что в этом есть реальное преимущество, см. Ответ @leet3lite. Кстати, зачем тратить время на все это, когда следующему пользователю просто придется изменить видимость, если он все равно захочет создать экземпляр класса?
2. Это звучит как шаблон builder вместо factory.
3. Какой-либо из этих ответов приемлем для вас?
Ответ №1:
Сделайте все пакеты конструкторов закрытыми; в этом же пакете должен быть один объект Factory, которому принадлежит общедоступный метод создания для клиентов вне пакета.
Это легко сделать с помощью одного класса, но я предполагаю, что вы хотели бы, чтобы эта фабрика была виртуальной: способной создавать несколько реализаций одного интерфейса или абстрактного класса.
Ответ №2:
Jigar прав, учитывая ваш вопрос, однако это антишаблон с серьезными недостатками:
http://en.wikipedia.org/wiki/Singleton_pattern#Drawbacks
http://blogs.msdn.com/b/scottdensmore/archive/2004/05/25/140827.aspx
Я бы посоветовал вам взглянуть на дизайн системы, прежде чем внедрять такое решение. Никогда не должно быть необходимости блокировать создание экземпляра класса описанным вами способом.
Ответ №3:
Вы можете сделать класс внутренним (членом) класса factory. Экземпляр singleton factory должен быть доступен только внутри класса factory. Фабричный метод должен возвращать открытый интерфейс, реализованный внутренним классом.
public interface FooBehavior { ... }
public class FooFactory {
private static FooFactory factory = new FooFactory();
private FooFactory() { ... }
class Foo implements FooBehavior { ... }
public static FooBehavior createFoo() {
return factory.new Foo();
}
}
Экземпляры Foo
привязаны к жизненному циклу фабрики. Поскольку экземпляр фабрики требуется для создания экземпляра Foo
, и поскольку единственный экземпляр фабрики содержится внутри фабрики, только фабрика может создавать экземпляры Foo
.
Кроме того, будьте осторожны при использовании шаблона singleton.
Комментарии:
1. @Jigar Joshi: Не могли бы вы пояснить, пожалуйста?
2. Какая часть не является потокобезопасной?
factory
должно быть окончательным, поскольку оно нигде не изменено. Это не потокобезопасно, еслиfactory
изменено.3. Кстати, это не значит, что приведенный выше код великолепен. Вы могли бы просто вообще пропустить фабрику и определить общедоступный статический метод создания экземпляра class
Foo
, который вызывает частный конструктор classFoo
.4. @Nathan конечно, рассмотрим на данный момент два потока, вызывающих
createFoo()
5. @Jigar Joshi: Не уверен, что вижу проблему с этим.
factory
Поле фактически является неизменяемым объектом, который передается в качестве первого (скрытого) параметра конструктора внутреннего класса. Если конструкторFoo
(подробности опущены) не является потокобезопасным, я не вижу, как это могло бы стать проблемой.
Ответ №4:
Более простой подход заключается в том, чтобы доверять другому классу в том же пакете.
Таким образом, конструктор имеет область действия по умолчанию, и фабрика должна быть в том же пакете.
Однако, чтобы ответить на ваш вопрос, вы можете сделать фабрику внутренним классом, он может вызывать частный конструктор внешнего класса. например
public class MyObject {
private MyObject() { }
public static class Factory {
public MyObject create() {
// can call private members of the outer class.
return new MyObject();
}
}
}
MyObject.Factory mof = new MyObject.Factory();
MyObject mo = mof.create();
Использование этого подхода ограничивает доступ к внутренним и вложенным классам.
Ответ №5:
Это будет нелегко. Вы могли бы сделать пакет конструктора локальным, а затем поместить фабрику в тот же пакет. Это все равно означало бы, что классы, определенные в одном пакете, все равно могли бы создавать экземпляр этого класса.
Вы могли бы запросить стек из текущего потока, выполнив что-то вроде —
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
if ("your.package.Factory".equals(stackTrace[1].getClassName()) {
instantiateClass();
} else {
throw new InstantiationException("Can only instantiate from factory");
}
Вам придется проверить, какой это StackTraceElement, но это должно сработать.
Ответ №6:
Другой шаблон, который начинает заменять одноэлементный шаблон, — это использование перечислений:
public enum Factory { INSTANCE;
public SomeObject create(Params someParams) {
if (isTypeA(someParams))
return new TypeASubclass();
else if (isTypeB(someParams))
return new TypeBSubclass();
else
return new SomeDefaultObject();
}
}
Вы будете использовать его стандартным способом:
SomeObject someObject = Factory.INSTANCE.create(myParams);
и enum
гарантирует, что в системе есть только одна фабрика *)
*) с обычными ограничениями «один на jvm» / «один на classloader»