Джава:Неизменяемые преобразования в неизменяемые
-
07-07-2019 - |
Вопрос
Я думал о том, как обеспечить синтаксический сахар для структуры, над которой я работаю.Я хочу иметь дело исключительно с объектами Imitable.
Допустим, у меня есть неизменяемый объект и я хочу создать его модифицированную версию.Нарушит ли, по вашему мнению, класс, не создающий экземпляра, с единственным статическим фабричным методом принципы ООП?
В качестве примера использования строки:
public final class LOWERCASE { private LOWERCASE() {} public static String string( final String STRING ) { return STRING.toLowerCase(); } }
Поэтому из этого примера я мог бы написать:
String lowercaseString = LOWERCASE.string( targetString );
Что я считаю очень читабельным.
Есть ли какие-либо оговорки против такого подхода?
Решение
Я не думаю, что создавать по одному классу для каждого метода — хорошая идея.Вместо этого вы можете создать класс только статических методов с именем, например, StringUtils, и реализовать эти методы.Таким образом, вы бы позвонили:
String lowCaseString = StringUtils.lowercase(targetString);
Это также предложит вам помощь IntelliSense во время набора текста.В противном случае список ваших классов будет слишком большим.Даже в этом простом примере вам следует реализовать более одного класса в нижнем регистре, чтобы можно было учитывать обстоятельства, при которых необходимо учитывать CulutureInfo.
Я не думаю, что это каким-либо образом нарушает принципы ОО или это плохой дизайн.В других языках, например в Ruby, вы можете добавлять свои методы непосредственно в класс String.Методы, которые заканчиваются на !обозначают, что исходный объект изменен.Все остальные методы возвращают измененную копию.Фреймворк Ruby on Rails добавляет некоторые методы в класс String, и ведутся споры о том, хороший ли это метод или нет.Хотя это определенно удобно.
Другие советы
Обычно для неизменяемых объектов у меня есть метод, возвращающий измененную версию объекта.Итак, если у вас есть неизменяемая коллекция, она может иметь метод sort(), который возвращает новую отсортированную коллекцию.Однако в вашем примере String это невозможно, поскольку вы не можете коснуться класса String.
Ваш подход вполне читаем, и я думаю, что для таких крайних случаев он вполне подходит.Для неизменяемых объектов, которые вы пишете сами, у меня будет метод для самого объекта.
Серия Эрика Липперта о неизменяемых объектах в C# кстати, неплохо.
По моему мнению, единственным смыслом такого фабричного класса было бы то, что класс предоставлял бы разные виды неизменяемых объектов.
Бывший:
public final class Lowercase {
private Lowercase() {}
public static String string( final String string ) {
return new String( string.toLowerCase() );
}
public static Foo foo( final Foo f ) {
boolean isLowerCase = true;
return new Foo(f, isLowerCase );
}
}
В противном случае вам следует реализовать свой метод в самом неизменяемом классе, например toLowerCase() в классе String.
В любом случае, я не думаю, что это нарушает какой-либо объектно-ориентированный принцип.
Я согласен с Эриком.Неизменяемые объекты всегда должны иметь метод, возвращающий измененную версию объекта, а не статический метод.В JDK также есть примеры:
- String.subString(целое)
- BigDecimal.movePointLeft(целое)
Преимущество такого подхода заключается в том, что вам не нужно передавать экземпляр, который вы хотите изменить, в качестве аргумента метода.Для таких классов, как String или Integer, я бы предпочел использовать класс-оболочку.Затем вы можете контролировать, когда будет создан такой объект (конструктором класса-оболочки или одним из методов класса).Если бы вы использовали класс Integer, контролировать его было бы гораздо сложнее, поскольку каждый мог бы создать его экземпляр.
С другой стороны, ваш пример относится к некоторым служебным классам, таким как StringUtils пакета apache commons-lang.Просто взгляните на это, я думаю, вы хотели создать что-то подобное.(Не изобретайте велосипед)
new String()
это запах кода - это почти всегда ненужно из-за неизменность из String
.Когда String
экземпляр имеет значение, этот экземпляр никогда и никогда не будет иметь другое значение.
В следующем методе new String()
является излишним:
public static String string( final String string ) {
return new String( string.toLowerCase() );
}
toLowerCase()
возвращает новый (другой) String
пример - new String()
здесь не делает ничего полезного, кроме создания другого объекта, имеющего точное значение String
экземпляр, возвращенный toLowerCase()
Вот небольшой скрипт Groovy, демонстрирующий эту концепцию (надеюсь — обратите внимание, это Java в рамках языка сценариев):
String a = 'CamelCase'
String b = a.toLowerCase()
println "a='${a}'"
println "b='${b}'"
производство
a='CamelCase'
b='camelcase'
Обратите внимание, что a
не изменился - неизменяем; b
это новый String
ценить.
То же самое верно для BigDecimal.movePointLeft()
или любой другой метод на BigDecimal
который возвращает BigDecimal
- это новые экземпляры, исходный экземпляр остается неизменным.
Хорошо, теперь ответим на ваш вопрос:
Наличие набора операций для Strings
которые выполняют полезную функцию в вашем приложении, — хорошая идея.Использование фабрики, вероятно, не требуется для чего-то вроде String
, но может относиться к другому неизменяемому классу, создание которого требует некоторых усилий.
В случае, когда невозможно расширить базовый класс, например String
, а static method
s, как описал @kgiannakakis, в порядке.
В противном случае, если «неизменяемый» класс является частью приложения, где у вас есть доступ к объявлению/определению класса, методы, возвращающие новый экземпляр, в модели BigDecimal
, String
, и т. д. было бы предпочтительнее.По сути, это то, что сказали @Erik Hesselink , @Bruno Conde и @reallyinsane.