как я могу заменить instanceof в этом случае? - PullRequest
2 голосов
/ 10 сентября 2009

Я пытаюсь сравнить сравнить критерии. Простые, такие как «между» и «inArray» или «больший». Я использую полиморфизм для этих классов. Один из методов, которыми они делятся из интерфейса compareCriteria, - это matchCompareCriteria.

Чего я стараюсь избегать, так это чтобы каждый класс проверял тип сравнения, которому они должны соответствовать. Например, объект inArray проверит, передан ли matchCompareCriteria объект inArray, в противном случае он вернет false, если он знает, как сравнивать.

Может быть, instanceof вполне законен в этом случае (объекты знают о себе), но все же я ищу возможные способы избежать этого. Есть идеи?

Пример псевдокода:

betweenXandY = create new between class(x, y)
greaterThanZ = create new greaterThan class(z)
greaterThanZ.matchCompareCriteria(betweenXandY)

если X и Y больше Z, он вернет true.

редактирование:

1) instanceof - это то, что я вижу на данный момент по мере необходимости в методе matchCompareCriteria. Я хотел бы избавиться от этого

2) matchCompareCritera проверяет, содержится ли в критерии сравнения другая. Если все возможные значения одного содержатся в другом, он возвращает true. Для многих комбинаций CompareCriteria даже не имеет смысла сравнивать их, поэтому они возвращают false (например, между Alfa и Между Num будет несовместимо).

Ответы [ 5 ]

8 голосов
/ 13 сентября 2009

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

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

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

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

class OperatorBase
{
    bool matchCompareCriteria(var other)
    {
        var comparisonMethod = this.GetMethod("matchCompareCriteria" + other.TypeName);
        if (comparisonMethod == null)
            return false;

        return comparisonMethod(other);
    }
}

Здесь я представляю, что в языке есть встроенный метод в каждом классе с именем GetMethod, который позволяет мне искать метод по имени, а также свойство TypeName для каждого объекта, который возвращает мне имя тип объекта. Поэтому, если другой класс - GreaterThan, а у производного класса есть метод matchCompareCriteriaGreaterThan, мы вызовем этот метод:

class SomeOperator : Base
{
    bool matchCompareCriteriaGreaterThan(var other)
    {
        // 'other' is definitely a GreaterThan, no need to check
    }
}

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

В статически типизированном языке, который поддерживает перегрузку методов по типу аргумента, мы можем избежать необходимости придумывать соглашение о сцепленном именовании - например, здесь это в C #:

class OperatorBase
{
    public bool CompareWith(object other)
    {
        var compare = GetType().GetMethod("CompareWithType", new[] { other.GetType() });
        if (compare == null)
            return false;

        return (bool)compare.Invoke(this, new[] { other });
    }
}

class GreaterThan : OperatorBase { }
class LessThan : OperatorBase { }

class WithinRange : OperatorBase
{
    // Just write whatever versions of CompareWithType you need.

    public bool CompareWithType(GreaterThan gt)
    {
        return true;
    }

    public bool CompareWithType(LessThan gt)
    {
        return true;
    }
}

class Program
{
    static void Main(string[] args)
    {
        GreaterThan gt = new GreaterThan();
        WithinRange wr = new WithinRange();

        Console.WriteLine(wr.CompareWith(gt));
    }
}

Если бы вы добавили новый тип в вашу модель, вам нужно было бы посмотреть на каждый предыдущий тип и спросить себя, нужно ли им каким-либо образом взаимодействовать с новым типом. Следовательно, каждый тип должен определять способ взаимодействия с каждым другим типом - даже если это какое-то действительно простое значение по умолчанию (например, «ничего не делать, кроме return true»). Даже это простое значение по умолчанию представляет собой осознанный выбор, который вы должны сделать. Это скрыто за удобством отсутствия необходимости явно писать какой-либо код для наиболее распространенного случая.

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

Таким образом, вы можете иметь словарь / карту / хеш-таблицу (как бы она ни называлась на вашем языке), которая отображает тип в другой словарь. Второй словарь отображает второй тип на правильную функцию сравнения для этих двух типов. Общая функция CompareWith будет использовать эту структуру данных для поиска нужной функции сравнения для вызова.

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

5 голосов
/ 10 сентября 2009

Поскольку вы ссылаетесь на instanceof, я предполагаю, что мы здесь работаем на Java. Это может позволить вам использовать перегрузку. Рассмотрим интерфейс с именем SomeInterface, который имеет единственный метод:

public interface SomeInterface {
    public boolean test (SomeInterface s);
}

Теперь мы определим два (с умным именем) класса, которые реализуют SomeInterface: Some1 и Some2. Some2 скучно: test всегда возвращает false. Но Some1 переопределяет функцию test, когда ему присваивается Some2:

public class Some1 implements SomeInterface {
    public boolean test (SomeInterface s) {
        return false;
    }

    public boolean test (Some2 s) {
        return true;
    }
}

Это позволяет нам не иметь строку за строкой операторов if для проверки типов. Но есть одна оговорка. Рассмотрим этот код:

Some1 s1 = new Some1 ();
Some2 s2 = new Some2 ();
SomeInterface inter = new Some2 ();

System.out.println(s1.test(s2));     // true
System.out.println(s2.test(s1));     // false
System.out.println(s1.test(inter));  // false

Видите этот третий тест? Несмотря на то, что inter имеет тип Some2, он обрабатывается как SomeInterface. Разрешение перегрузки определяется во время компиляции в Java, что может сделать его совершенно бесполезным для вас.

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

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

1 голос
/ 13 сентября 2009

Подход Smalltalk заключался бы в том, чтобы ввести больше уровней в иерархию. Таким образом, между и moreThan будет подклассами rangedCompareCriteria (или чего-то еще), а rangeCompareCriteria :: matchCompareCriteria вернет true когда его спросили, сопоставимы ли два его экземпляра.

Кстати, вы, вероятно, захотите переименовать "matchCompareCriteria" во что-то, что выражает намерение немного лучше.

1 голос
/ 13 сентября 2009

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

Правило не использовать instanceof действует, когда вы проверяете тип объекта, чтобы выбрать, какое поведение иметь. Понятно, что это поведение относится к разным объектам, тип которых вы проверяете. Но в этом случае поведение вашего объекта зависит от типа передаваемого вами объекта и принадлежит к вызывающему объекту, а не к вызываемым объектам, как раньше. Случай аналогичен тому, когда вы переопределяете equals(). Вы делаете проверку типа, чтобы передаваемый объект был того же типа, что и объект this, а затем реализуете свое поведение: если тест не пройден, вернуть false; в противном случае выполните тесты на равенство.

Вывод: в этом случае можно использовать instanceof.

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

Помните: Полиморфизм хорош, кроме случаев, когда он не . :)

1 голос
/ 10 сентября 2009

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

класс Criteria будет определять метод matchCompareCriteria, который принимает критерии. Фактическая логика будет находиться в подклассах.

Вы ищете либо шаблон дизайна стратегии, либо шаблон шаблона дизайна.

...