Какой дизайн лучше для класса, который просто выполняет автономные вычисления? - PullRequest
0 голосов
/ 24 сентября 2008

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

1) Одноразовый экземпляр класса. Принимает объекты для сравнения в конструкторе и рассчитывает их для этого.

public class MyObjDiffer {
  public MyObjDiffer(MyObj o1, MyObj o2) {
    // Calculate diff here and store results in member variables
  }

  public boolean areObjectsDifferent() {
    // ...
  }

  public Vector getOnlyInObj1() {
    // ...
  }

  public Vector getOnlyInObj2() {
    // ...
  }

  // ...
}

2) Повторно используемый экземпляр класса. Конструктор не принимает аргументов. Имеет метод Calculate (), который переводит объекты в diff и возвращает результаты.

public class MyObjDiffer {
  public MyObjDiffer() { }

  public DiffResults getResults(MyObj o1, MyObj o2) {
    // calculate and return the results.  Nothing is stored in this class's members.
  }
}

public class DiffResults {
  public boolean areObjectsDifferent() {
    // ...
  }

  public Vector getOnlyInObj1() {
    // ...
  }

  public Vector getOnlyInObj2() {
    // ...
  }
}

Различия будут довольно сложными (детали не имеют значения для вопроса), поэтому потребуется множество вспомогательных функций. Если я приму решение 1, то смогу хранить данные в переменных-членах, и мне не придется все передавать. Это немного более компактно, так как все обрабатывается в одном классе.

Однако, концептуально, кажется странным, что «Разница» была бы специфична для определенного набора результатов. Вариант 2 разделяет результаты по логике, которая фактически их вычисляет.

РЕДАКТИРОВАТЬ: Вариант 2 также предоставляет возможность сделать класс «MyObjDiffer» статическим. Спасибо, Кицунэ, я забыл упомянуть об этом.

У меня проблемы с просмотром каких-либо значительных плюсов или минусов в любом из вариантов Я полагаю, что такие вещи (класс, который просто обрабатывает вычисления одним выстрелом) должны появляться довольно часто, и, возможно, я что-то упускаю. Итак, я решил задать вопрос облаку. Есть ли существенные плюсы или минусы в том или ином варианте здесь? Один по своей сути лучше? Имеет ли это значение?

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

Ответы [ 8 ]

3 голосов
/ 24 сентября 2008

Использование объектно-ориентированного программирования

Используйте вариант 2, но не сделайте его статичным.

Шаблон стратегии

Таким образом, экземпляр MyObjDiffer может быть передан любому, кому нужна Стратегия для вычисления разницы между объектами.

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

Любой приличный инструмент рефакторинга сможет «извлечь интерфейс» из MyObjDiffer и заменить ссылки на этот тип на тип интерфейса через некоторое время, если вы захотите отложить принятие решения. Использование «Варианта 2» с методами экземпляра, а не с процедурами класса, дает вам такую ​​гибкость.

Настройка экземпляра

Даже если вам никогда не нужно писать новый метод сравнения, вы можете обнаружить, что указание параметров для настройки поведения вашего основного метода полезно. Если вы подумаете об использовании команды «diff» для сравнения текстовых файлов, вы вспомните, сколько существует различных опций: чувствительность к пробелам и регистру, опции вывода и т. Д. Лучший аналог этого в программировании ОО - рассмотреть каждый diff процесс как объект, с параметрами, установленными как свойства для этого объекта.

1 голос
/ 24 сентября 2008

Требуется решение № 2 по ряду причин. И вы не хотите, чтобы оно было статичным.

Несмотря на то, что статическая игра кажется забавной, это кошмар обслуживания, когда вы придете либо (а) к новому алгоритму с такими же требованиями, либо (б) к новым требованиям.

Первоклассный объект (без особого внутреннего состояния) позволяет вам развиваться в иерархии классов, отличающихся друг от друга - некоторые медленнее, некоторые быстрее, некоторые с большим объемом памяти, некоторые с меньшим объемом памяти, некоторые для старых требований, некоторые для новые требования.

Некоторые из ваших отличий могут привести к сложному внутреннему состоянию или памяти, либо к инкрементному разнесению, либо к различию на основе хеш-кода. Все виды возможностей могут существуют.

Многократно используемый объект позволяет вам выбрать разницу во время запуска приложения, используя файл свойств.

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

  1. Написать новый подкласс.

  2. Обновите файл свойств, чтобы начать использовать новый подкласс.

И будьте полностью уверены, что не было спрятанных d= new MyObjDiffer( x, y ), о которых вы не знали.

Вы хотите использовать d= theDiffer.getResults( x, y ) везде.

Что делают библиотеки Java, так это то, что у них есть статический DifferFactory. Фактор проверяет свойства и выдает правильную разницу.

DifferFactory df= new DifferFactory();
MyObjDiffer mod= df.getDiffer();
mod.getResults( x, y );

Фабрика обычно кэширует одну копию - ей не нужно физически читать свойства каждый раз, когда вызывается getDiffer.

Этот дизайн дает вам максимальную гибкость в будущем. На это похоже на другие части библиотек Java.

1 голос
/ 24 сентября 2008

Не могу сказать, что у меня есть четкие причины, почему это «лучший» подход, но я обычно пишу классы для объектов, с которыми можно «поговорить». Так что это будет похоже на ваш вариант «одноразового использования» 1, за исключением того, что, вызывая сеттер, вы «сбросите» его для другого использования.

Вместо предоставления реализации (что довольно очевидно), вот пример вызова:

MyComparer cmp = new MyComparer(obj1, obj2);
boolean match = cmp.isMatch();
cmp.setSubjects(obj3,obj4);
List uniques1 = cmp.getOnlyIn(MyComparer.FIRST);
cmd.setSubject(MyComparer.SECOND,obj5);
List uniques = cmp.getOnlyIn(MyComparer.SECOND);

... и т. Д.

Таким образом, вызывающая сторона решает, хотят ли они создавать экземпляры большого количества объектов или продолжают использовать один.

Это особенно полезно, если объект нуждается в большой настройке. Допустим, ваш компаратор принимает параметры. Там может быть много. Настройте его один раз, затем используйте его много раз.

// set up cmp with options and the master object
MyComparer cmp = new MyComparer();
cmp.setIgnoreCase(true);
cmp.setIgnoreTrailingWhitespace(false);
cmp.setSubject(MyComparer.FIRST,canonicalSubject);

// find items that are in the testSubjects objects,
// but not in the master.
List extraItems = new ArrayList();
for (Iterator it=testSubjects.iterator(); it.hasNext(); ) {
    cmp.setSubject(MyComparer.SECOND,it.next());
    extraItems.append(cmp.getOnlyIn(MyComparer.SECOND);
}

Редактировать: Кстати, я назвал его MyComparer, а не MyDiffer, потому что казалось более естественным иметь метод isMatch (), чем метод isDifferent ().

0 голосов
/ 24 сентября 2008

Мне нравится идея явно начинать работу, а не создавать ее при создании экземпляра. Кроме того, я думаю, что результаты достаточно существенны, чтобы гарантировать их собственный класс. Ваш первый дизайн не так чист для меня. Кто-то, использующий этот класс, должен понимать, что после выполнения вычисления некоторые другие члены класса теперь держат результаты. Вариант 2 более понятен в отношении происходящего.

0 голосов
/ 24 сентября 2008

Я бы выбрал метод статического конструктора, что-то вроде.

Diffs diffs = Diffs.calculateDifferences(foo, bar);

Таким образом, это понятно, когда вы вычисляете различия, и нет никакого способа неправильно использовать интерфейс объекта.

0 голосов
/ 24 сентября 2008

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

0 голосов
/ 24 сентября 2008

Это зависит от того, как вы собираетесь использовать diff. На мой взгляд, имеет смысл рассматривать diff как логическую сущность, потому что она должна поддерживать некоторые операции, такие как 'getDiffString ()', или 'numHunks ()', или 'apply ()'. Я мог бы взять ваш первый и сделать это больше так:

public class Diff
{
    public Diff(String path1, String path2)
    {
        // get diff

        if (same)
            throw new EmptyDiffException();
    }

    public String getDiffString()
    {

    }

    public int numHunks()
    {

    }

    public bool apply(String path1)
    {
        // try to apply diff as patch to file at path1.  Return
        // whether the patch applied successfully or not.
    }

    public bool merge(Diff diff)
    {
        // similar to apply(), but do merge yourself with another diff
    }
}

Использование объекта diff, подобного этому, также может пригодиться для хранения стека исправлений или сериализации в сжатый архив, например, в очередь «отменить» и т. Д.

0 голосов
/ 24 сентября 2008

Я бы взял цифру 2 и подумал, стоит ли мне делать это статичным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...