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