Принцип подстановки Лисков или нарушение инкапсуляции

В этом посте я хочу показать вам небольшой пример кода с несколькими классами JS и спросить вас, подходит ли этот код из-за LSP или нарушает принципы инкапсуляции.

Переменная _framesMonitor в этом примере является экземпляром некоторой сторонней библиотеки vqt, которую мы используем внутри класса Job. _framesMonitor.stopListen() может вызывать исключения, особенно vqt.Errors.ProcessExitError.

В приведенном ниже примере допустимо ли предоставлять тип vqt.Errors.ProcessExitError классу JobManager (и это нормально из-за LSP) или это нарушает инкапсуляцию, раскрывая детали внутренней реализации.

// Job.js
class Job {
  constructor() {
     this._framesMonitor = new FramesMonitor();
  }

  async stop() {
      await this._framesMonitor.stopListen();
  }
}

// JobsManager.js
class JobsManager {
  async deleteJob() {
    try {
      await job.stop();
    } catch(err) {
      // vqt.Errors.ProcessExitError here
    }
  }
}

person Eduard Bondarenko    schedule 14.11.2017    source источник


Ответы (1)


В этом случае Job является конкретным классом, а JobManager зависит от него напрямую, поэтому можно знать особенности Job, если вы намерены иметь только один вид Job.

Однако, если вы концептуально планируете иметь множество различных реализаций Job (независимо от того, поддерживает ли язык явные интерфейсы или нет), то в идеале JobManager следует попытаться рассматривать все Job в общем и не зависеть ни от каких Job деталей.

Таким образом, JobManager может обнаруживать любую ошибку, но в идеале не должен управлять своим рабочим процессом на основе произвольных сведений об ошибке. Если JobManager должен знать какой-то уровень детализации, чтобы выполнить соответствующие компенсирующие действия, вам следует попытаться придумать нормализованное JobFailureError (или даже возвращаемое значение), которое предоставляет необходимую информацию (оно также может нести низкоуровневую причину ошибки для цель регистрации).

Наконец, если каким-то образом вам нужна специальная обработка случаев для каждого вида работы, которая не может быть нормализована, тогда это будет вопрос компромиссов.

Чтобы избежать проблем с обработкой ошибок Job, вы можете разрешить регистрацию специальных стратегий обработки заданий в файле JobManager. Хотя этот подход придерживается принципа Open-Closed (OCP), он также является реализацией Service Locator, который некоторые считают антишаблоном. При добавлении новых реализаций Job вам также необходимо не забыть добавить соответствующий обработчик.

E.g.

jobManager.registerHandler('some-job-type', function (job) {
    //Special handling code job of some-job-type
});

Если вы не возражаете против некоторого уровня связи между концепцией обработчика ошибок и Job, вы можете сделать что-то вроде new SomeJob(errorHandler) или даже иметь SomeJob связь с конкретным обработчиком.

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

Например, если вы использовали язык со статической типизацией, возможно, использовали методы двойной отправки или даже < href="http://wiki.c2.com/?PatternMatching" rel="nofollow noreferrer">сопоставление с образцом, если оно доступно, было бы лучше, поскольку вы могли бы получать обратную связь во время компиляции.

person plalx    schedule 15.11.2017