Разбить класс пополам или наложить интерфейс для ограниченного доступа? - PullRequest
2 голосов
/ 25 мая 2010

Каков наилучший способ разбиения класса, когда его функциональные возможности должны по-разному использоваться различными классами? Надеюсь, следующий пример прояснит вопрос:)

У меня есть класс Java, который обращается к одному месту в каталоге, позволяя внешним классам выполнять операции чтения / записи в него. Операции чтения возвращают статистику использования в каталоге (например, доступное дисковое пространство, количество записей и т. Д.); Операции записи, очевидно, позволяют внешним классам записывать данные на диск. Эти методы всегда работают в одном и том же месте и получают свою конфигурацию (например, какой каталог использовать, минимальное дисковое пространство и т. Д.) Из внешнего источника (переданного конструктору).

Этот класс выглядит примерно так:

public class DiskHandler {
    public DiskHandler(String dir, int minSpace) {
        ...
    }
    public void writeToDisk(String contents, String filename) {
        int space = getAvailableSpace();
        ...
    }
    public void getAvailableSpace() {
        ...
    }
}

Там происходит немного больше, но этого будет достаточно.

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

public class DiskWriter {
    DiskHandler diskHandler;

    public DiskWriter() {
        diskHandler = new DiskHandler(...);
    }
    public void doSomething() {
        diskHandler.writeToDisk(...);
    }
}

public class DiskReader {
    DiskHandler diskHandler;

    public DiskReader() {
        diskHandler = new DiskHandler(...);
    }
    public void doSomething() {
       int space = diskHandler.getAvailableSpace(...);
    }    
}

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

Решение 1

Я мог бы разбить этот класс на две части. Один класс будет обрабатывать операции чтения, а другой - записи:

// NEW "UTILITY" CLASSES
public class WriterUtil {
    private ReaderUtil diskReader;

    public WriterUtil(String dir, int minSpace) {
        ...
        diskReader = new ReaderUtil(dir, minSpace);
    }
    public void writeToDisk(String contents, String filename) {
        int = diskReader.getAvailableSpace();
        ...
    }
}
public class ReaderUtil {
    public ReaderUtil(String dir, int minSpace) {
        ...
    }
    public void getAvailableSpace() {
        ...
    }
}

// MODIFIED EXTERNALLY-ACCESSING CLASSES
public class DiskWriter {
    WriterUtil diskWriter;

    public DiskWriter() {
        diskWriter = new WriterUtil(...);
    }
    public void doSomething() {
        diskWriter.writeToDisk(...);
    }
}

public class DiskReader {
    ReaderUtil diskReader;

    public DiskReader() {
        diskReader = new ReaderUtil(...);
    }
    public void doSomething() {
       int space = diskReader.getAvailableSpace(...);
    }    
}

Это решение предотвращает доступ классов к методам, которые им не нужны, но также нарушает инкапсуляцию. Первоначальный класс DiskHandler был полностью автономным и требовал только параметров конфигурации через один конструктор. Разбивая функциональность на классы чтения / записи, они оба связаны с каталогом, и оба должны быть созданы с соответствующими значениями. По сути, мне не важно дублировать проблемы.

Решение 2

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

Интерфейс может выглядеть примерно так:

public interface Readable {
    int getAvailableSpace();
}

Класс Reader будет создавать экземпляр объекта следующим образом:

Readable diskReader;
public DiskReader() {
    diskReader = new DiskHandler(...);
}

Это решение кажется хрупким и может привести к путанице в будущем. Это не гарантирует, что разработчики будут использовать правильный интерфейс в будущем. Любые изменения в реализации DiskHandler могут также потребовать обновления интерфейса и доступа к классам. Мне нравится это лучше, чем предыдущее решение, но не намного.

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

Есть ли другие решения, которые мне не хватает?

Ответы [ 2 ]

3 голосов
/ 25 мая 2010

Я бы пошел с интерфейсом, в сочетании с небольшим количеством Внедрения зависимостей - вы не создаете экземпляр new DiskHandler непосредственно внутри ваших классов чтения или записи, они принимают объект соответствующего введите их конструкторы.

Таким образом, ваш DiskReader примет Readable, а ваш DiskWriter получит ReadWrite (или DiskHandler напрямую, если вы не хотите создавать интерфейс для режима чтения-записи). , хотя я бы предложил иначе - через interface ReadWrite extends Readable или подобное). Если вы последовательно вводите его, используя соответствующий интерфейс, вам не придется беспокоиться о неправильном использовании.

1 голос
/ 25 мая 2010

Я думаю, что интерфейс также является наиболее объектно-ориентированным подходом. Первый подход в основном преобразует вашу коллекцию семантически связанных методов в кучу маленьких служебных функций: не то, что вы хотите.

Второе решение позволяет пользователям вашего класса точно указать, почему они его используют. Так же, как хороший Java-код обычно объявляет List, Set и NavigableMap вместо ArrayList, HashSet и TreeMap, пользователи вашего класса могут объявить переменную только как Readable или Writeable вместо объявления зависимости от какого-либо конкретного подкласса.

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

Мне любопытно: почему вы думаете, что любые изменения в реализации DiskHandler приведут к изменениям в классах, которые используют Reader? Если Reader можно определить как стабильный интерфейс, интерфейс должен четко прописать его семантический контракт (в Javadoc). Если пользователи кодируют этот интерфейс, реализация может быть изменена негласно без их ведома. Конечно, если сам интерфейс меняется, они должны измениться, но как это отличается от первого решения?

Еще одна вещь, о которой стоит подумать: допустим, у вас есть несколько потоков, большинству из которых требуется Reader, но некоторым из них требуется Writer, все в один файл. Вы могли бы DiskHandler реализовать оба Reader и Writer и внедрить один экземпляр во все потоки. Параллельность может быть обработана внутренне для этого объекта, имея соответствующие ReadWriteLocks и synchronizeds, куда они должны идти. Как это возможно в вашем первом решении?

...