Я думаю, что это скорее концептуальная проблема объектно-ориентированного программирования , чем техническая, у меня есть несколько идей, которые я мог бы использовать, но каждая из них больше похожа на хакерский обходной путь, чем на легко расширяемые решения. Я пытаюсь найти более или менее приемлемый способ решения этой проблемы.
Допустим, у меня есть интерфейс Scalar
public interface Scalar {
public Scalar add(Scalar addend);
...
с двумя конкретными реализациями: RealScalar
который представляет десятичные значения и RationalScalar
, который представляет дроби
public class RealScalar implements Scalar {
private double value;
public RealScalar(double input)
{
value=input;
}
...
public class RationalScalar implements Scalar {
private int numerator,denominator;
public RationalScalar(int numerator, int denominator)
{
this.numerator=numerator;
this.denominator=denominator;
}
...
Я использую фабричный класс ScalarFactory
для создания новых скаляров как способ сделать реализацию безразличной к какому типу Scalar
в настоящее время упоминается.
Если каждый подтип имеет свойства (и методы), уникальные для этого подтипа (в данном случае value
для RealScalar
и numerator
, denominator
для RationalScalar
) и компилятор java не может заранее знать, каким будет тип времени выполнения Scalar, в результате чего реализованные методы (объявленные в родительском интерфейсе), такие как:
@Override
public RealScalar multi(Scalar multiplicand)
{
if (getClass()!=multiplicand.getClass())
multiplicand=multiplicand.toReal();
return new RealScalar(value*(
(RealScalar)multiplicand).getValue());
}
, приведут к компиляции ошибки времени, так как toReal
- это метод дочернего класса RationalScalars
:
./RealScalar.java:22: error: cannot find symbol
multiplicand=multiplicand.toReal();
^
symbol: method toReal()
location: variable multiplicand of type Scalar
Как правильно заставить эти подклассы взаимодействовать?
РЕДАКТИРОВАТЬ: Я действительно получил это работает, добавив функция преобразования в интерфейс, который принял тип класса в качестве аргумента, а затем реализовал это в каждом из подтипов. Хотя это работает, я все еще не уверен, является ли это концептуально правильным. С одной стороны, возможность преобразования в другие типы значений - это то, что вы хотели бы получить от объекта, предназначенного для математической сущности, с другой - это делает подтипы слегка взаимозависимыми.
Возможные решения
1) включить метод convert
в подтипы, который принимает скаляр и создает скаляр дочернего типа.
проблема в том, что подтипы должны быть относительно независимыми и при нормальных обстоятельствах имеют схожие свойства. Это требует, чтобы каждый класс был явно закодирован для манипулирования уникальными свойствами других подтипов, и если будет реализовано больше скаляров, это может привести к растущей базе хакерских обходных путей, необходимых в каждой реализации Scalar
.
2) сделать значение списком обобщенных c числовых типов в интерфейсе
Это уродливо и проблематично c, так как обобщенные c числовые типы не поддерживают математические операции, что означает, что значения будет храниться в виде обобщенных c чисел, а затем приводиться в качестве примитивов для каждой операции, что, вероятно, будет слишком вычислительно дорого для варианта использования (полиномиальный калькулятор)
3) хранилище для RationalScalar
в виде десятичной дроби и преобразуется только в дробь для отображения.
Определенно не то направление, которое я хочу go, учитывая, как трудно сохранить десятичное число в формате, который легко преобразовать в дробь, которая не в форме 2345883/4232569