Рубрики
4. Interface segregation principle SOLID

SOLID «EXAMPLE» класс клиент-сервер

И на клиенте и на сервере
мы используем StateManager например Redux
там и там создаём соответствующий Store

на сервере нам необходимо
отрендерить соотвествующую страницу
вызываем функцию renderHtmlPage

для того чтобы работать правильно с путями
переходить с одной страницы на другую
нам необходим роутер Router
роутер нужен как на клиенте, так и на сервере
причем работать будут по разному

  • на сервере роутер нужен
    правильно распарсить путь, определить страницу, которую необходимо отрендерить и после чего вернуть её уже на клиент
  • на клиенте роутер нужен
    для перемещения с одной страницы на другую

const renderHtmlPage = (store: any, url: any) => {
 const router = new Router();
}

const client = () => { 
 const store = createStore(initialData);
 const router = new Router();
}
const server = () => { 
 const store = createStroe(initialData);
 const htmlPage = renderHtmlPage(stroe, req.url)
}

общий интерфейс для клиенского и серверного роутера

  • создаём класс Router и имплементируем туда интерфейс
  • создаём один единственный метод
    который предназначен для парсить url parseUrl
    он может использоваться как на клиенте, так и на сервере
interface IRouter {
 parseUrl: (url) => void
}
class Router implements IRouter {
 parseUrl(url): void {}
}

в интерфейсе роутер IRouter
Добавляем метод navigate
это перемещение с одной страницы на другую
использоваться он будет только на клиенте client

enum Route {
 ABOUT='about_page',
 HOME='home_page',
}
interface IRouter {
 parseUrl: (url) => void
 navigate: (route: Route) => void
}
class Router implements IRouter {
 parseUrl(url): void {}
 navigate(route: Route): void {}
}

в интерфейсе роутер IRouter
Добавляем ещё один метод attachEventListeners
позволяет привязать, слушать события
использоваться он будет только на клиенте client

enum Route {
 ABOUT='about_page',
 HOME='home_page',
}
interface IRouter {
 parseUrl: (url) => void
 navigate: (route: Route) => void
 attachEventListeners: () => void
}
class Router implements IRouter {
 parseUrl(url): void {}
 navigate(route: Route): void {}
 attachEventListeners: () => void {}
}

в интерфейсе роутер IRouter
Добавляем ещё один метод addQueryParams
позволяет в строку запроса добавить какие-то record параметры
использоваться он будет как на клиенте client так и на server

enum Route {
 ABOUT='about_page',
 HOME='home_page',
}
interface IRouter {
 parseUrl: (url) => void
 navigate: (route: Route) => void
 attachEventListeners: () => void
 addQueryParams: (params: Record<string, string>) => void
}
class Router implements IRouter {
 parseUrl(url): void {}
 navigate(route: Route): void {}
 attachEventListeners: () => void {}
 addQueryParams: (params: Record<string, string>) => void {}
}

Мы получили один обобщенный класс
который содержит лишние методы

как для клиента, так и для сервера

1 Способ
Расширение интерфейсов

  • Создаём общий интерфейс Router
    отвечает только за parserUrl и addQuerParams
  • отдельный интерфейс для клиенского роутера ClientRouter
    наследует от Router и отвечает только за
    навигацию navigate и attachEventListeners
  • отдельный интерфейс для серверного роутера ServerRouter
    наследует от Router и отвечает только за
    навигацию prepareUrlForClient
interface IRouter {
 parseUrl: (url) => void
 addQueryParams: (params: Record<string, string>) => void
}
interface ClientRouter extends IRouter {
 navigate: (route: Route) => void
 attachEventListeners: () => void
}
interface ServerRouter extends IRouter {
 prepareUrlForClient: (url: string) => void
}

В соответствующие классы мы
имплементируем серверный роутер и клиенский роутер
реализуем в каждом классе нужные методы

class ServerRouter implements IServerRouter {
 parseUrl(url): void {}
 addQueryParams: (params: Record<string, string>): void {}
 prepareUrlForClient: (url: string): void {}
}
class ClientRouter implements IClientRouter {
 parseUrl(url): void {}
 addQueryParams: (params: Record<string, string>): void {}
 navigate: (route: Route): void {}
 attachEventListeners: (): void {}
}
  • В серверной части приложения
    мы создаём серверный роутер ServerRouter
  • В клиенской части приложения
    мы создаём клиенский роутер ClientRouter
const renderHtmlPage = (store: any, url: any) => {
 const router = new ServerRouter();
}

const client = () => { 
 const store = createStore(initialData);
 const router = new ClientRouter();
}
const server = () => { 
 const store = createStroe(initialData);
 const htmlPage = renderHtmlPage(stroe, req.url)
}

Мы могли создать два разных класса и создать там методы
Но по хорошему чтобы эти два роутера

  • либо наследовались от какого-то одного базового класса
  • либо имплементировали какой-то общий интерфейс для того чтобы мы могли работать с этими роутарами независимо от того находимся мы в серверной части, либо на клиенской
как на клиенте, так и на сервере этот код одинаков
const createDependencyContainer = (router: Router, store) => {
 return {
  getRouter: () => router,
  getStore: () => store,
 }
}

2 Способ
Без расширения интерфейсов
отдельные интерфейсы

  • Создать отдельный интерфейс роутер Router
  • Создать отдельный интерфейс для парсинга и преобразования
  • Создать отдельный интерфейс для url подготовки для клиента
interface UrlParser {
 parseUrl: (url) => void
 addQueryParams: (params: Record<string, string>) => void
}
interface Router {
 navigate: (route: Route) => void
 attachEventListeners: () => void
}
interface UrlPreparer extends IRouter {
 prepareUrlForClient: (url: string) => void
}

В нужные нам классы имплементировать нам нужные интерфейсы и реализовать методы

class ServerRouter implements UrlParser, UrlPreparer {
 parseUrl(url): void {}
 addQueryParams: (params: Record<string, string>): void {}
 prepareUrlForClient: (url: string): void {}
}
class ClientRouter implements Router, UrlParser{
 parseUrl(url): void {}
 addQueryParams: (params: Record<string, string>): void {}
 navigate: (route: Route): void {}
 attachEventListeners: (): void {}
}

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *