Вопрос

Я пытаюсь сравнить сравнительные критерии.Простые, такие как "между" и "inArray" или "Greater Than".Я использую полиморфизм для этих классов.Одним из методов, которыми они совместно пользуются в интерфейсе 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 проверяет, содержится ли один compareCriteria в другом.Если все возможные значения одного из них содержатся в другом, оно возвращает true .Для многих комбинаций compareCriteria даже не имеет смысла сравнивать их, поэтому они возвращают false (например, betweenAlfa и betweenNum были бы несовместимы).

Это было полезно?

Решение

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

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

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

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

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

        return comparisonMethod(other);
    }
}

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

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));
    }
}

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

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

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

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

Другие советы

Поскольку вы ссылаетесь 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 (который оценивается во время выполнения).Даже если вы сделаете это таким образом, это все равно будет плохой дизайн.Каждый из ваших классов должен знать обо всех остальных.Если вы решите добавить еще один, вам придется вернуться ко всем существующим, чтобы добавить функциональность для обработки нового класса.В спешке это становится ужасно недостижимым, что является хорошим признаком того, что дизайн плохой.

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

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

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

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

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

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

Заключение:используя instanceof в данном случае это нормально.

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

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

Подход Smalltalk заключался бы в том, чтобы ввести больше уровней в иерархию.Итак между и Больше , чем были бы подклассами Ранжированные сравнительные критерии (или что-то еще), и Сравнительные критерии диапазона::Сопоставительные критерии соответствия бы вернуться верно когда его спросили, были ли сопоставимы два экземпляра самого себя.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top