Надеюсь, название не звучит слишком субъективно;Я абсолютно не хочу начинать дебаты по ОО в целом.Я просто хотел бы обсудить основные плюсы и минусы для различных способов решения проблемы следующего типа.
Давайте возьмем этот минимальный пример: вы хотите выразить абстрактный тип данных T сфункции, которые могут принимать T в качестве входа, выхода или оба:
- f1 : принимает T , возвращает int
- f2 : принимает строку , возвращает T
- f3 : Принимает T и double , возвращает другое T
Я бы хотел избежать даункинга и любой другой динамикитипирование.Я также хотел бы по возможности избегать мутаций.
1: попытка на основе абстрактного класса
abstract class T {
abstract int f1();
// We can't have abstract constructors, so the best we can do, as I see it, is:
abstract void f2(string s);
// The convention would be that you'd replace calls to the original f2 by invocation of the nullary constructor of the implementing type, followed by invocation of f2. f2 would need to have side-effects to be of any use.
// f3 is a problem too:
abstract T f3(double d);
// This doesn't express that the return value is of the *same* type as the object whose method is invoked; it just expresses that the return value is *some* T.
}
2: параметрический полиморфизм и вспомогательный класс
(всеклассы реализации TImpl будут одноэлементными классами):
abstract class TImpl<T> {
abstract int f1(T t);
abstract T f2(string s);
abstract T f3(T t, double d);
}
Мы больше не выражаем, что какой-то конкретный тип фактически реализует нашу исходную спецификацию - реализация это просто тип Foo, для которого у нас есть экземпляр TImpl .Кажется, это не проблема: если вы хотите, чтобы функция работала в произвольных реализациях, вы просто делаете что-то вроде:
// Say we want to return a Bar given an arbitrary implementation of our abstract type
Bar bar<T>(TImpl<T> ti, T t);
На этом этапе можно также вообще пропустить наследование и синглтоны ииспользуйте
3 Первоклассную таблицу функций
class /* or struct, even */ TDict<T> {
readonly Func<T,int> f1;
readonly Func<string,T> f2;
readonly Func<T,double,T> f3;
TDict( ... ) {
this.f1 = f1;
this.f2 = f2;
this.f3 = f3;
}
}
Bar bar<T>(TDict<T> td; T t);
Хотя я не вижу большого практического различия между # 2 и # 3.
Пример реализации
class MyT {
/* raw data structure goes here; this class needn't have any methods */
}
// It doesn't matter where we put the following; could be a static method of MyT, or some static class collecting dictionaries
static readonly TDict<MyT> MyTDict
= new TDict<MyT>(
(t) => /* body of f1 goes here */ ,
// f2
(s) => /* body of f2 goes here */,
// f3
(t,d) => /* body of f3 goes here */
);
Мысли?№ 3 однотипен, но кажется довольно безопасным и чистым.Один вопрос заключается в том, есть ли какие-либо проблемы с производительностью.Обычно мне не нужна динамическая диспетчеризация, и я бы предпочел, чтобы эти тела функций были статически встроены в местах, где конкретный тип реализации известен статически.# 2 лучше в этом отношении?