Как я могу осмысленно использовать контракты предварительных условий в D-интерфейсах?

Когда я переопределяю функции в D с помощью «внутренних» контрактов, проверяются унаследованные «внутренние» контракты. Если они терпят неудачу, то проверяются переопределенные «входящие» контракты. Если я ничего не указываю в контракте, то это интерпретируется как наличие пустого контракта «в». Таким образом, следующий код успешно компилируется и запускается.

module main;
import std.stdio;

interface I
{
    void write( int i )
    in
    {
        assert( i > 0 );
    }
}

class C : I
{
    void write( int i )
    {
        writeln( i );
    }
}

int main()
{
    I i = new C;

    i.write( -5 );
    getchar();

    return 0;
}

Я хочу только, чтобы предварительное условие I.write() проверялось, когда я вызываю i.write(), так как это то, что статически известно, что этого достаточно для того, чтобы I.write() работал компилятором правильно. Проверка всех предварительных условий после динамической диспетчеризации кажется мне странной с точки зрения объектно-ориентированного программирования, поскольку инкапсуляция теряется.

Я мог бы повторить предварительное условие или написать in { assert( false ); } во всех классах, реализующих интерфейс, но это больно. Является ли это ошибкой дизайна языка D? Или есть какой-то правильный масштабируемый способ сделать это?


person Ralph Tandetzky    schedule 27.07.2012    source источник


Ответы (3)


http://dlang.org/dbc.html

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

Функция без контракта означает, что разрешены любые значения параметров функции. Это означает, что если какая-либо функция в иерархии наследования не имеет контракта, то в контрактах функции, переопределяющие ее, не имеют никакого полезного эффекта.

И наоборот, все внешние контракты должны быть выполнены, поэтому переопределение функций становится процессом ужесточения внешних контрактов.

На самом деле это сложная головоломка, когда речь идет о полиморфном поведении. Посмотрите, например, на этот отчет об ошибке с соответствующим длинным обсуждением: http://d.puremagic.com/issues/show_bug.cgi?id=6857

Что касается вопроса о том, как добиться желаемого поведения - миксины всегда работают, когда необходимо предотвратить копирование-вставку, но я не уверен, что это нормально делать с точки зрения парадигмы Design By Contract. К сожалению, нужен кто-то более теоретически компетентный в этом вопросе совет.

person Mihails Strasuns    schedule 27.07.2012
comment
Я не уверен, что эта страница документации не просто устарела. Чтобы быть уверенным, мне нужно взглянуть на TDPL, но есть ошибка, похожая на проблему OP: d.puremagic.com/issues/show_bug.cgi?id=6549 - person jpf; 27.07.2012
comment
@jpf Это запрос на улучшение. Некоторые люди хотят изменить формулировку в отношении этой проблемы. Документация верна в отношении текущего поведения. - person Jonathan M Davis; 27.07.2012

Предварительное условие в D — это требование для правильной работы функции. Если вы перегружаете функцию, вы пишете для нее новый код, старое предварительное условие, являющееся требованием для старого кода, не обязательно является требованием для нового кода.

person Idan Arye    schedule 27.07.2012

Таким образом, эта проблема, хотя и не обсуждается напрямую об интерфейсах, является http://d.puremagic.com/issues/show_bug.cgi?id=6856

Хотя это может быть сложно, Уолтер хорошо разбирается в том, что ничего не ломается.

person he_the_great    schedule 31.07.2012