Добрую четверть века назад, когда я изучал C ++, меня учили, что интерфейсы должны быть прощающими и, насколько это возможно, не заботиться о порядке вызова методов, поскольку потребитель может не иметь доступа к источнику или документации вместо это.
Однако всякий раз, когда я наставлял младших программистов и старших разработчиков, меня подслушивали, они реагировали с удивлением, что заставляло меня задуматься, действительно ли это было чем-то особенным или просто вышло из моды.
Так ясно, как грязь?
Рассмотрим интерфейс с этими методами (для создания файлов данных):
OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile
Теперь вы можете, конечно, просто пройтись по порядку, но сказать, что вас не волнует имя файла (подумайте a.out
) или какие заголовок и строка трейлера были включены, вы можете просто вызвать AddDataLine
.
Менее крайним примером может быть опускание заголовков и трейлеров.
Еще одним может быть установка заголовка и строк трейлера до открытия файла.
Это принцип дизайна интерфейса, который признан, или это просто способ POLA до того, как ему дали имя?
NB не увязайте в деталях этого интерфейса, это всего лишь пример для этого вопроса.
источник
Ответы:
Одним из способов, которым вы можете придерживаться принципа наименьшего удивления, является рассмотрение других принципов, таких как ISP и SRP , или даже DRY .
В приведенном вами конкретном примере кажется, что существует определенная зависимость порядка манипулирования файлом; но ваш API контролирует как доступ к файлам, так и формат данных, что немного напоминает нарушение SRP.
Редактирование / обновление: это также предполагает, что сам API просит пользователя нарушить DRY, потому что ему нужно будет повторять одни и те же шаги каждый раз, когда они используют API .
Рассмотрим альтернативный API, в котором операции ввода-вывода отделены от операций с данными. и где сам API «владеет» порядком:
ContentBuilder
FileWriter
При вышеупомянутом разделении
ContentBuilder
не нужно ничего «делать», кроме как хранить строки / заголовок / трейлер (возможно, такжеContentBuilder.Serialize()
метод, который знает порядок). Следуя другим принципам SOLID, больше не имеет значения, устанавливаете ли вы заголовок или трейлер до или после добавления строк, потому что ничто вContentBuilder
действительности не записывается в файл до его передачиFileWriter.Write
.Это также имеет дополнительное преимущество в том, чтобы быть немного более гибким; например, может быть полезно записать содержимое в диагностический журнал или передать его по сети, а не записывать непосредственно в файл.
При разработке API вы также должны учитывать сообщения об ошибках, будь то состояние, возвращаемое значение, исключение, обратный вызов или что-то еще. Пользователь API, вероятно, ожидает, что сможет программно обнаруживать любые нарушения его контрактов или даже другие ошибки, которые он не может контролировать, такие как ошибки ввода-вывода файла.
источник
SetHeader
или имелAddLine
значение. Чтобы устранить эту зависимость заказа не является ни ISP, ни SRP, это просто POLA.FileWriter
может затем потребовать значение из последнегоContentBuilder
шага вWrite
методе, чтобы убедиться, что весь входной контент завершен, что делаетInvalidContentException
ненужным.ContentBuilder
и позволитьFileWriter.Write
инкапсулировать этот бит знаний. Исключение будет необходимо в случае, если с содержимым что-то не так (например, отсутствует заголовок). Возврат также может сработать, но я не фанат превращения исключений в коды возврата.Это касается не только POLA, но и предотвращения недопустимого состояния как возможного источника ошибок.
Давайте посмотрим, как мы можем предоставить некоторые ограничения для вашего примера без предоставления конкретной реализации:
Первый шаг: не позволяйте ничего вызывать до открытия файла.
Теперь должно быть очевидно, что
CreateDataFileInterface.OpenFile
нужно вызывать, чтобы получитьDataFileInterface
экземпляр, в который можно записать фактические данные.Второй шаг: убедитесь, что заголовки и трейлеры всегда установлены.
Теперь вы должны предоставить все необходимые параметры заранее, чтобы получить
DataFileInterface
: имя файла, заголовок и трейлер. Если строка трейлера недоступна до тех пор, пока не будут записаны все строки, можно также переместить этот параметр вClose()
(возможно, переименование метода вWriteTrailerAndClose()
), чтобы, по крайней мере, файл не мог быть завершен без строки трейлера.Чтобы ответить на комментарий:
Правда. Я не хотел больше концентрироваться на примере, чем необходимо, чтобы высказать свою точку зрения, но это хороший вопрос. В этом случае я думаю, что назвал бы это
Finalize(trailer)
и утверждал, что это не делает слишком много. Написание трейлера и закрытие - это просто детали реализации. Но если вы не согласны или у вас похожая ситуация, когда она отличается, вот возможное решение:Я бы на самом деле не делал этого для этого примера, но он показывает, как последовательно выполнять технику.
Кстати, я предположил, что методы на самом деле должны вызываться в таком порядке, например, чтобы последовательно писать много строк. Если это не требуется, я бы всегда предпочел застройщика, как предложил Бен Коттрел .
источник
WriteTrailerAndClose()
) граничит с нарушением ПСП. (Это то, с чем я боролся несколько раз, но ваше предложение кажется возможным примером.) Как бы вы ответили?OpenFile
перегрузку, которая не требуется.