#java #refactoring #singleton
#java #рефакторинг #singleton
Вопрос:
Итак, у меня есть этот метод, который выполняется неоднократно
public static boolean isReady(String dirPath, int numPdfInPrintJob){
File dir = new File(dirPath);
String[] fileList = dir.list(new FilenameFilter(){
public boolean accept(File file, String filename) {
return (filename.toLowerCase().endsWith(".pdf"));
}
});
if(fileList.length >= numPdfInPrintJob) return true;
else return false;
}
Этот метод использует anonymous class
, который будет создавать новый экземпляр FilenameFilter
при каждом вызове, и я часто использую этот метод. Итак, я хочу превратить это anonymous class
в singleton
. Итак, моя первоначальная мысль — создать новый singleton
класс, который будет выглядеть следующим образом
public class PdfFileNameFilter implements FilenameFilter{
private PdfFileNameFilter(){} //non-instantible
//guarantee to only have one instance at all time
public static final PdfFileNameFilter INSTANCE = new PdfFileNameFilter();
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(".pdf"));
}
}
Могу ли я еще немного реорганизовать это. Мне также нужно сделать ZipFileNameFilter
, и, возможно, много разных фильтров расширений файлов. Не хочу создавать класс для каждого фильтра. Мне нужно еще немного реорганизовать этот дизайн. Может быть, interface
где-то здесь это встанет на место.
Комментарии:
1. Вы действительно измеряли это? У меня было подобное преобразование, где я заменил анонимные классы статическими экземплярами и обнаружил, что это практически не повлияло на производительность или использование памяти.
2. Вы уверены, что вам нужно это делать? Создание новых объектов на Java обходится очень дешево, поэтому я бы не пытался оптимизировать там.
3. @Jeff: На самом деле я этого не
profile
делал. Это просто мысль. Поскольку я знаю, что этот метод часто используется. Так что, возможно, было бы неплохо уменьшить объем памяти, занимаемый этим. Возможно, это не изменит ситуацию, но я не вижу, что она не может ухудшиться.4. @tangens: Я переосмыслю это. Но если я просто создам статический метод, как
Peter
предлагается ниже, это определенно немного оптимизировало бы, правильно. Я не понимаю, как это может ухудшиться, правильно?5. Объем памяти, вероятно, должен вас беспокоить, поскольку GC удалит все ненужные экземпляры для вас. Вам следует беспокоиться о том, сколько операций выполняет метод, если он вызывается много раз, и любой из ответов, использующих частный статический экземпляр, поможет вам уменьшить количество действий, которые должен выполнять ваш метод. Просто имейте в виду, что это не должно считаться медленным, вам нужно профилировать это, чтобы знать наверняка.
Ответ №1:
Если все, что вы хотели сделать, это уменьшить использование памяти, вы могли бы сделать
private static final FilenameFilter PDF_FILES = new FilenameFilter(){
public boolean accept(File file, String filename) {
return (filename.toLowerCase().endsWith(".pdf"));
}
}
Если вы хотите создать singleton, самый простой способ
public enum PdfFileNameFilter implements FilenameFilter {
INSTANCE;
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(".pdf"));
}
}
Комментарии:
1. 1 Первый вариант кажется мне самым простым выбором. Зачем создавать singleton, если он все равно используется только в 1 классе.
2. @Robin, я согласен. В моей IDE вы можете взять исходный код и нажать <ctrl> <alt> C для «Ввести константу», и она предложит имя для константы (на основе типа и имени параметра) и найти любые другие места в коде, которые также можно заменить.
Ответ №2:
Мне кажется проще просто использовать ваш существующий анонимный класс и создать один экземпляр, который используют все ваши вызовы методов.
private static final FilenameFilter PDF_FILTER = new FilenameFilter() {
public boolean accept(File file, String filename) {
return (filename.toLowerCase().endsWith(".pdf"));
}
}
public static boolean isReady(String dirPath, int numPdfInPrintJob){
File dir = new File(dirPath);
String[] fileList = dir.list(pdfFilter);
if(fileList.length >= numPdfInPrintJob) return true;
else return false;
}
Это тот случай, когда создание подклассов и синглтона кажется немного излишним: вы просто хотите использовать только один экземпляр прямо здесь, тогда как singleton используется, когда есть только один экземпляр, который вы когда-либо захотите использовать.
Комментарии:
1. Я полностью согласен с этим — зачем тратить время на внедрение свойства singleton в сам класс, когда вы можете просто создать только один экземпляр
Ответ №3:
Для этого вы могли бы использовать enum
:
public enum ExtensionFilter implements FilenameFilter {
PDF(".pdf"),
ZIP(".zip");
private final String extension;
private ExtensionFilter(String extension) {
this.extension = extension;
}
@Override
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(extension));
}
}
Теперь вы сможете использовать его как:
dir.list(ExtensionFilter.PDF)
Вы также можете выполнить итерацию по ним, если вам нужно:
for ( FilenameFilter fileNameFilter : ExtensionFilter.values() ) {
....
}
Вы также могли бы использовать vararg в качестве аргумента конструктора, чтобы разрешить несколько расширений для одного и того же фильтра, и использовать имя константы по умолчанию, чтобы упростить его использование:
public enum ExtensionFilter implements FilenameFilter {
PDF,
ZIP(".zip", ".jar", ".war", ".ear");
private final String[] extensions;
private ExtensionFilter(String... extensions) {
if (extensions.length == 0) {
extensions = new String[] {"." name().toLowerCase()};
}
this.extensions = extensions;
}
@Override
public boolean accept(File dir, String name) {
for (String extension : extensions) {
if (name.toLowerCase().endsWith(extension)) {
return true;
}
}
return false;
}
}
Комментарии:
1. Если singleton является излишеством (а я бы сказал, что это так), то перечисление (особенно когда он не указал необходимость фильтрации zip) будет казаться излишним.
2. @CajunLuke: Он действительно указал на необходимость фильтрации zip: «Мне также нужно использовать ZipFileNameFilter и, возможно, множество других фильтров расширений файлов». И его утверждение заключается в том, что создание нового экземпляра каждый раз было излишним, а singleton был решением .
3. Спасибо. Мне нравится ваш ответ, и
rachet
очень. Спасибо. Итак, я предполагаю, что в коде я бы просто сделал этоdir.list(DefaultFileNameFilter.PDF);
для фильтрации4. 1 для простого и эффективного использования перечислений. Здесь вообще нет излишеств, и решение элегантное, эффективное и простое для понимания
Ответ №4:
вы можете отказаться от полного singleton и использовать личное поле для настройки расширения
public class ExtensionFileNameFilter implements FilenameFilter{
private String extension;
private ExtensionFileNameFilter (String extension){this.extension=extension;}
public static final ExtensionFileNameFilter PDFINSTANCE = new ExtensionFileNameFilter (".pdf");
public static final ExtensionFileNameFilter ZIPINSTANCE = new ExtensionFileNameFilter (".zip");
//add instances as you need
public boolean accept(File dir, String name) {
return (name.toLowerCase().endsWith(extension));
}
}
Комментарии:
1. Большое вам спасибо. Мне это очень нравится. Спасибо.
2. Разве это не просто самоделка
enum
? Отсутствует возможность использовать имя константы в виде строки, повторять константы, читатьordinal()
, сериализовать и другие…3. @costi это так, но есть возможность создать фабричный метод с использованием (слабой ссылки) hashmap для повторного использования экземпляров и создания новых
4. Это можно сделать точно так же в enum … за исключением создания новых экземпляров типа enum , но вы можете создавать новые
FilenameFilter
экземпляры.
Ответ №5:
Могу ли я еще немного реорганизовать это.
Да, да, вы можете.
Предполагая, что это не тот ответ, который вы искали (вам следует обновить yoru question, чтобы задать более конкретный вопрос), я бы не стал его реорганизовывать, пока он вам не понадобится; YAGNI.
Как только у вас будет больше кода, например, больше фильтров имен файлов, вы увидите возможные рефакторинги. Вы увидите общий код, возможно, интерфейс, что-то в этом роде. Не пытайтесь преждевременно перенастроить.
TDD также является лучшим способом безопасного проведения рефакторинга. Если у вас есть тесты, показывающие, какой код вам действительно нужен, многие дополнительные материалы исчезнут, если таковые имеются. И с полным набором тестов вы можете рефакторинговать без колебаний, потому что вы знаете, работают ли ваши изменения или нет, основываясь на том, продолжают ли ваши тесты проходить.
Ответ №6:
Для интереса, эта альтернативная accept
реализация будет выполняться намного быстрее в тестовом тестировании. Он не создает новые объекты с сохранением состояния и не несет других накладных расходов String.toLowerCase
, которые не требуются в вашем случае.
public boolean accept(File file, String filename) {
int offset = s.length() - 4;
if (offset >= 0) {
if (s.charAt(offset) == '.') {
offset = 1;
if (s.regionMatches(offset, "pdf", 0, 3)) {
return true;
} else if (s.regionMatches(offset, "PDF", 0, 3)) {
return true;
}
}
}
return false;
}
Если бы это была точка доступа к выполнению, и вы искали оптимизацию, что-то подобное помогло бы.
Комментарии:
1. хммм. Интересно. Строка неизменяема, поэтому
toLowerCase
создаст другой объект. Я думаю, что в случае загрузки большого количества файлов это определенно приведет к уменьшению объема памяти. Но вы сказали, что это также будет работать быстрее? Даже если я просто загружу 1 файл, это будет работать быстрее?2. Он будет работать быстрее, но если у вас не будет большого количества вызовов, вы этого не заметите. Для меня исходные регистры выполняли миллисекунды где-то около 5000 вызовов; оптимизированная версия не регистрируется примерно до 200000. Это может не иметь отношения к вашему приложению — вам скажет только профилирование. Но в горячих точках выполнения создание объектов, таких как Strings, и вызов таких методов, как
toLowerCase
(дорогостоящих, даже если безnew String
них), будут более значимыми, чем создание объектов без состояния с помощью анонимных внутренних классов, которые, я думаю, будут исчезающе незначительными.3. Большое вам спасибо. Это здорово знать. Какой инструмент профилирования вы используете?
4. В прошлом я использовал JProfiler. Для приведенных выше показателей я просто использую простые циклы в основном методе и записываю время до и после использования System.currentTimeMillis.
int invocations = 0; long start = System.currentTimeMillis(); boolean b = false; for (int i = 0; i < 1000000; i ) { for (int j = 0; j < names.length; j ) { b = matches(names[j]); invocations ; } } long duration = System.currentTimeMillis() - start; System.err.println(b); System.err.println(duration " (" invocations " invocations)");