Подтвердить что ты не робот

Может ли интерфейс быть зависимым от класса?

Я изучаю принципы SOLID и задаю вопрос об управлении зависимостями по отношению к интерфейсам.

Пример из книги, которую я читаю (Adaptive Code via С# by Gary McLean Hall), показывает класс TradeProcessor, который будет получать торговые данные, обрабатывать их и хранить в базе данных. Данные торговли моделируются классом под названием TradeRecord. Класс TradeParser будет обрабатывать преобразование торговых данных, полученных в экземпляр TradeRecord. Класс TradeProcessor ссылается только на интерфейс ITradeParser так, чтобы он не зависел от реализации TradeParser.

Автор имеет метод Parse (в интерфейсе ITradeParser) возвращает коллекцию IEnumerable<TradeRecord>, которая содержит обработанные данные торговли. Не означает ли это, что ITradeParser теперь зависит от класса TradeRecord?

Не должен ли автор сделать что-то вроде создания интерфейса ITradeRecord и вернуть Parse коллекцию экземпляров ITradeRecord? Или я пропущу что-то важное?

Здесь код (реализация TradeRecord не имеет значения, поэтому он опущен):

TradeProcessor.cs

public class TradeProcessor
{
    private readonly ITradeParser tradeParser;

    public TradeProcessor(ITradeParser tradeParser)
    {
        this.tradeParser = tradeParser;
    }

    public void ProcessTrades()
    {
        IEnumerable<string> tradeData = "Simulated trade data..."
        var trades = tradeParser.Parse(tradeData);

        // Do something with the parsed data...
    }
}

ITradeParser.cs

public interface ITradeParser
{
    IEnumerable<TradeRecord> Parse(IEnumerable<string> tradeData);
}
4b9b3361

Ответ 1

Это хороший вопрос, который входит в компромисс между чистотой и практичностью.

Да, по чистому принципу, вы можете сказать, что ITradeParser.Parse должен возвращать коллекцию интерфейсов ITraceRecord. В конце концов, зачем привязываться к конкретной реализации?

Однако вы можете принять это дальше. Если вы принимаете IEnumerable<string>? Или у вас есть какой-то ITextContainer? I32bitNumeric вместо int? Конечно, это reductio ad absurdum, но это показывает, что мы всегда в какой-то момент достигаем точки, в которой мы работаем над чем-то, конкретным объектом (числом, строкой, TraceRecord, что угодно), а не абстракцией.

Это также объясняет, почему мы используем интерфейсы, в первую очередь, чтобы определить контракты для логики и функциональности. ITradeProcessor - это контракт для неизвестной реализации, который может быть заменен или обновлен. A TradeRecord не является контрактом на реализацию, это реализация. Если это объект DTO, который, как представляется, не будет иметь разницы между интерфейсом и реализацией, это означает, что нет никакой реальной цели в определении этого контракта - это подразумевается в конкретном классе.

Ответ 2

У автора есть метод Parse (в интерфейсе ITradeParser) возвращает коллекцию IEnumerable, которая содержит обработанные данные торговли.

Не означает ли это, что ITradeParser теперь зависит от класса TradeRecord?

Да, ITradeParser теперь тесно связан с TradeRecord. Учитывая более академичный подход к этому вопросу, я могу видеть, откуда вы. Но что такое TradeRecord? Запись, по определению, обычно представляет собой простую, не интеллектуальную часть данных (иногда называемую POCO, DTO или модель).

В какой-то момент потенциальный выигрыш абстракции менее ценен, чем сложности, которые он вызывает. Этот подход довольно распространен на практике. Модели (как я их называю) представляют собой запечатанные типы, которые протекают через слои приложения. Слои, которые воздействуют на модели, абстрагируются до интерфейсов, так что каждый слой может быть посмеян и протестирован отдельно.

Например, клиентское приложение может иметь слой View, ViewModel и Repository. Каждый слой знает, как работать с конкретным типом записи. Но ViewModel может быть подключен для работы с издеваемым IRepository, который создает конкретные типы с жестко закодированными, издевательскими данными. На данный момент нет никакой пользы от абстрагированного IModel - у него просто прямые данные.