Статьи

Использование файлов в ваших интерфейсах не очень хорошая идея

Я использовал некоторые проприетарные фреймворки и библиотеки, которые используют объекты File и только объекты File, повсюду в интерфейсах.

Нет ничего плохого в том, чтобы иметь методы, принимающие объекты File, но не стоит принимать только это. Например, анализатор XML DOM / SAX в JDK принимает объекты File, но включает и другие источники, такие как InputStream , URL и т. Д. Класс Properties даже не принимает объекты File. На самом деле, класс File не очень популярен в интерфейсах хороших библиотек Java, включая JRE.
Причина проста: InputStream гораздо более абстрактный, чем File.

Когда вы указываете, например, [1] , такой интерфейс:

public void sendMail(String email , String message, File attachment);

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

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

Это потому, что вложение — это «контент», а не файл. Также конфигурация приложения — это «контент», а не файл. Файл XML и значки ваших приложений — это не просто файлы, это данные и ресурсы. И данные, ресурсы и содержимое, они могут поступать не только из Файлов, но и из удаленных Систем (URL), из баз данных, из JAR-файла в classpath или из других систем, которые вы можете даже не представить.

В приведенном выше примере интерфейс будет намного лучше, если он определен так:

public void sendMail(String email, String message, InputStream attachment);

Теперь … если вы хотите прикрепить трассировку стека, вы можете сделать что-то вроде:

try {
    ...
} catch (Exception ex) {
    sendMail(
        "foo@bar.com",
        "something bad happened!",
        new ThrowableInputStream(ex));
}

Насколько сложно преобразовать исключение в InputStream? Это оно:

public class ThrowableInputStream extends InputStream {
	private byte[] bytes;
	private int i = 0;

	public ThrowableInputStream(Throwable throwable) {
		this.bytes = toByteArray(throwable);
	}

	private byte[] toByteArray(Throwable throwable) {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		PrintStream printer = new PrintStream(output);
		try {
			throwable.printStackTrace(printer);
			printer.flush();
			return output.toByteArray();
		} finally {
			printer.close();
		}
	}

	@Override
	public int read() throws IOException {
		if (i < bytes.length)
			return bytes[i++];
		else
			return -1;
	}
}

Выше или проще распечатать исключение во временный файл? И вышеупомянутое менее или более эффективно? Если вы предпочитаете временный файл … по крайней мере, не забудьте удалить файл. И не забудьте проверить, доступна ли файловая система для записи … и убедиться, что на диске осталось достаточно места … и т. Д. И т. Д. …

Вы хотите прикрепить некоторый XML-контент, который вы получили в формате String? Опять же, вместо печати String во временный файл и т. Д. И т. Д. … вы можете написать класс, который преобразует Strings в InputStream:

public class StringInputStream extends InputStream {
	private String string;
	private int i;

	public StringInputStream(String string) {
		this.string = string;
	}

	@Override
	public int read() throws IOException {
		if (i < string.length())
			return string.charAt(i++);
		else
			return -1;
	}
}

Всего 16 строк кода.

Невероятная сила URL

Также конфигурация вашего приложения является частью интерфейса с пользователями … и здесь файлы имеют свои недостатки.

Но давайте пойдем дальше со сложным вариантом использования: вам нужно проанализировать некоторые XML-данные, развернутые внутри zip-файла на удаленном веб-сервере.
У вас есть два варианта:

  1. Вы можете записать URL файла на веб-сервере в вашу конфигурацию. Ваша программа получит URL из конфигурации, получит доступ к веб-серверу с помощью http-клиента, прочитает заархивированный файл, сохранит его где-нибудь, распакует его, затем проанализирует xml и будет управлять данными. И, наконец, навести порядок.
  2. Или вы можете указать в своей конфигурации URL-адрес, например «jar: http: //www.partnerwebsite.com/zippedfile.zip! Path / to / data.xml», и передать этот URL-код в анализатор XML. Готово.

Теперь … если вы выбрали второй вариант и требования вашего приложения изменились, и вам нужен доступ к одному и тому же XML-файлу, в сжатом виде или нет, с веб-сервера, с сервера ftp, из локальной файловой системы и т. Д., Вам просто нужно изменить URL в конфигурации. И, если вам нужно получить доступ к ресурсу в протоколе, который изначально не поддерживается URL, вы можете реализовать URLStreamHandler .
Да, URL очень мощный и гибкий в Java. И они возвращают вам InputStream . Не файл! Это не жертва.

Как «починить» библиотеку с такими недостатками?

Ладно … ты несчастный парень, как и я, который должен использовать плохие библиотеки более или менее часто. Как вы исправляете интерфейс как первый метод sendMail? (тот, что с файлом)
Нередко такие плохо спроектированные библиотеки ДЕЙСТВИТЕЛЬНО плохие. Поэтому вы можете написать собственный (объектно-ориентированный) интерфейс поверх него: оболочку или что-то в этом роде. Ваш интерфейс будет использовать InputStream, конечно. Поэтому вам нужно что-то, что преобразует ваш InputStream в файл, который может быть переварен плохой библиотекой.
Вот пример:

public class FileDump {
        private static final Random random = new Random();      

        public static File dump(InputStream input, String fileName) {
                File path = createTempDir();
                return dump(input, path, fileName);
        }

        private static File createTempDir() {
                String tmpDirName =
                                System.getProperty("java.io.tmpdir");
                File tmpDir = new File(tmpDirName);
                File newTempDir = new File(tmpDir,
                                String.valueOf(random.nextInt()));
                newTempDir.mkdirs();
                return newTempDir;
        }

        private static File dump(InputStream input, File path,
                                             String fileName) {
                File file = new File(path, fileName);
                try {
                        OutputStream output =
                                        new FileOutputStream(file);
                        try {
                                int data;
                                while ((data = input.read()) != -1)
                                        output.write(data);
                        } finally {
                                output.close();
                        }
                        return file;
                } catch (IOException ex) {
                        throw new RuntimeException(ex);
                }
        }
}

Приведенный выше класс является примером. Вы можете улучшить его, проверяя, есть ли место на диске, что целевой каталог доступен для записи и т. Д. Затем не забудьте удалить файл после того, как плохая библиотека использовала содержимое файла.

Выводы

  1. В следующий раз, когда вы определяете интерфейс библиотеки и, по любой причине, вы импортируете java.io.File … подумайте об использовании (также) InputStream [2], пожалуйста.
  2. Если вы склонны написать в своей конфигурации «путь к файлу» ресурса, который вам нужно прочитать, подумайте о возможности использовать URL-адрес с протоколом file: /path/to/file.ext. Кстати, имейте в виду, что, к сожалению, вы не можете писать в URL-адрес Java независимо от протокола, но вы все равно можете получить путь к файлу из URL-адреса, используя схему URI «file» .

Заметки

[1] Приведенный выше пример не является хорошим интерфейсом объекта почтовой программы. Это просто для того, чтобы объяснить, почему указывать файл как вложение не очень хорошая идея.
[2] Приведенное выше обсуждение действительно не только для InputStream и OutputStream, но также для Reader и Writer. Вы должны использовать Reader / Writer для работы с текстовыми данными, а InputStream / OutputStream для работы с двоичными данными.

 

От http://en.newinstance.it/2010/09/10/using-files-in-your-interfaces-is-not-a-good-idea