Two Step View (Двухшаговая шаблонизация)
Паттерн проектирования Two Step View
Описание Two Step View
Преобразует данные в HTML в два шага: сначала формирует логическую структуру, а позже - заполняет её отформатированными данными
Если web-приложение состоит из множества страниц, необходим единый вид и единая структура сайта. Если каждая страница выглядит по своему, получится сайт, который будет непонятным для пользователя. Также возможна ситуация, когда нужно сделать глобальные изменения на всём сайте (например поменять заголовок), но при использовании Template View или Transform View возникают трудности, потому что код представления дублируется от страницы к странице и надо исправлять его во всех файлах.
Шаблон Two Step View решает эту проблему разбиением шаблонизации на две части. В первой, данные из модели преобразуются в логическое представление без какого-либо другого, специфического форматирования. Второй шаг преобразует это логическое представление с использование необходимого конкретного форматирования. Таким образом, можно делать глобальные изменения, изменяя только второй шаг. Также можно сделать несколько представлений для одной и той же информации, выбирая на лету форматирование для второго шага.
Примеры реализации
// Two Step View Pattern in JavaScript
class TwoStepView {
constructor() {
this.layouts = new Map();
this.templates = new Map();
}
// Step 1: Create logical structure
createLogicalStructure(data) {
console.log('Step 1: Creating logical structure');
const structure = {
title: data.title,
content: data.content,
navigation: data.navigation,
sidebar: data.sidebar,
footer: data.footer
};
console.log('Logical structure created:', structure);
return structure;
}
// Step 2: Apply formatting
applyFormatting(structure, layoutName) {
console.log(`Step 2: Applying formatting with layout: ${layoutName}`);
const layout = this.layouts.get(layoutName) || this.getDefaultLayout();
const formatted = layout(structure);
console.log('Formatting applied');
return formatted;
}
registerLayout(name, layoutFunction) {
this.layouts.set(name, layoutFunction);
console.log(`Layout registered: ${name}`);
}
getDefaultLayout() {
return (structure) => {
return `
${structure.title}
${structure.title}
${structure.content}
`;
};
}
// Complete two-step process
render(data, layoutName = 'default') {
console.log('Starting two-step view rendering');
// Step 1: Create logical structure
const structure = this.createLogicalStructure(data);
// Step 2: Apply formatting
const html = this.applyFormatting(structure, layoutName);
console.log('Two-step view rendering completed');
return html;
}
}
// Usage
const twoStepView = new TwoStepView();
// Register custom layout
twoStepView.registerLayout('mobile', (structure) => {
return `
${structure.title}
${structure.title}
${structure.content}
`;
});
// Sample data
const pageData = {
title: 'My Website',
content: 'Welcome to our site!
This is the main content.
',
navigation: 'Home | About',
sidebar: 'News
Latest updates...
',
footer: '© 2024 My Website. All rights reserved.'
};
// Render with default layout
const defaultHtml = twoStepView.render(pageData);
console.log('Default layout HTML generated');
// Render with mobile layout
const mobileHtml = twoStepView.render(pageData, 'mobile');
console.log('Mobile layout HTML generated');
<?php
// Two Step View Pattern in PHP
class TwoStepView {
private $layouts = [];
private $templates = [];
// Step 1: Create logical structure
public function createLogicalStructure($data) {
echo "Step 1: Creating logical structure\n";
$structure = [
'title' => $data['title'],
'content' => $data['content'],
'navigation' => $data['navigation'],
'sidebar' => $data['sidebar'],
'footer' => $data['footer']
];
echo "Logical structure created\n";
return $structure;
}
// Step 2: Apply formatting
public function applyFormatting($structure, $layoutName) {
echo "Step 2: Applying formatting with layout: $layoutName\n";
$layout = $this->layouts[$layoutName] ?? $this->getDefaultLayout();
$formatted = $layout($structure);
echo "Formatting applied\n";
return $formatted;
}
public function registerLayout($name, $layoutFunction) {
$this->layouts[$name] = $layoutFunction;
echo "Layout registered: $name\n";
}
private function getDefaultLayout() {
return function($structure) {
return "
<!DOCTYPE html>
<html>
<head>
<title>{$structure['title']}</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
.header { background: #333; color: white; padding: 10px; }
.content { margin: 20px 0; }
.sidebar { float: right; width: 200px; background: #f0f0f0; padding: 10px; }
.footer { clear: both; background: #333; color: white; padding: 10px; text-align: center; }
</style>
</head>
<body>
<div class=\"header\">
<h1>{$structure['title']}</h1>
<nav>{$structure['navigation']}</nav>
</div>
<div class=\"content\">
{$structure['content']}
</div>
<div class=\"sidebar\">
{$structure['sidebar']}
</div>
<div class=\"footer\">
{$structure['footer']}
</div>
</body>
</html>
";
};
}
// Complete two-step process
public function render($data, $layoutName = 'default') {
echo "Starting two-step view rendering\n";
// Step 1: Create logical structure
$structure = $this->createLogicalStructure($data);
// Step 2: Apply formatting
$html = $this->applyFormatting($structure, $layoutName);
echo "Two-step view rendering completed\n";
return $html;
}
}
// Usage
$twoStepView = new TwoStepView();
// Register custom layout
$twoStepView->registerLayout('mobile', function($structure) {
return "
<!DOCTYPE html>
<html>
<head>
<title>{$structure['title']}</title>
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 10px; }
.header { background: #333; color: white; padding: 10px; }
.content { margin: 10px 0; }
.sidebar { background: #f0f0f0; padding: 10px; margin: 10px 0; }
.footer { background: #333; color: white; padding: 10px; text-align: center; }
</style>
</head>
<body>
<div class=\"header\">
<h1>{$structure['title']}</h1>
<nav>{$structure['navigation']}</nav>
</div>
<div class=\"content\">
{$structure['content']}
</div>
<div class=\"sidebar\">
{$structure['sidebar']}
</div>
<div class=\"footer\">
{$structure['footer']}
</div>
</body>
</html>
";
});
// Sample data
$pageData = [
'title' => 'My Website',
'content' => '<h2>Welcome to our site!</h2><p>This is the main content.</p>',
'navigation' => '<a href=\"/\">Home</a> | <a href=\"/about\">About</a>',
'sidebar' => '<h3>News</h3><p>Latest updates...</p>',
'footer' => '© 2024 My Website. All rights reserved.'
];
// Render with default layout
$defaultHtml = $twoStepView->render($pageData);
echo "Default layout HTML generated\n";
// Render with mobile layout
$mobileHtml = $twoStepView->render($pageData, 'mobile');
echo "Mobile layout HTML generated\n";
?>
// Two Step View Pattern in Go
package main
import (
"fmt"
"html/template"
"strings"
)
type TwoStepView struct {
layouts map[string]func(Structure) string
templates map[string]func(Structure) string
}
type Structure struct {
Title string
Content string
Navigation string
Sidebar string
Footer string
}
func NewTwoStepView() *TwoStepView {
return &TwoStepView{
layouts: make(map[string]func(Structure) string),
templates: make(map[string]func(Structure) string),
}
}
// Step 1: Create logical structure
func (tsv *TwoStepView) CreateLogicalStructure(data map[string]string) Structure {
fmt.Println("Step 1: Creating logical structure")
structure := Structure{
Title: data["title"],
Content: data["content"],
Navigation: data["navigation"],
Sidebar: data["sidebar"],
Footer: data["footer"],
}
fmt.Println("Logical structure created")
return structure
}
// Step 2: Apply formatting
func (tsv *TwoStepView) ApplyFormatting(structure Structure, layoutName string) string {
fmt.Printf("Step 2: Applying formatting with layout: %s\n", layoutName)
layout, exists := tsv.layouts[layoutName]
if !exists {
layout = tsv.getDefaultLayout()
}
formatted := layout(structure)
fmt.Println("Formatting applied")
return formatted
}
func (tsv *TwoStepView) RegisterLayout(name string, layoutFunction func(Structure) string) {
tsv.layouts[name] = layoutFunction
fmt.Printf("Layout registered: %s\n", name)
}
func (tsv *TwoStepView) getDefaultLayout() func(Structure) string {
return func(structure Structure) string {
return fmt.Sprintf(`
%s
%s
%s
`, structure.Title, structure.Title, structure.Navigation, structure.Content, structure.Sidebar, structure.Footer)
}
}
// Complete two-step process
func (tsv *TwoStepView) Render(data map[string]string, layoutName string) string {
fmt.Println("Starting two-step view rendering")
// Step 1: Create logical structure
structure := tsv.CreateLogicalStructure(data)
// Step 2: Apply formatting
html := tsv.ApplyFormatting(structure, layoutName)
fmt.Println("Two-step view rendering completed")
return html
}
func main() {
twoStepView := NewTwoStepView()
// Register custom layout
twoStepView.RegisterLayout("mobile", func(structure Structure) string {
return fmt.Sprintf(`
%s
%s
%s
`, structure.Title, structure.Title, structure.Navigation, structure.Content, structure.Sidebar, structure.Footer)
})
// Sample data
pageData := map[string]string{
"title": "My Website",
"content": "Welcome to our site!
This is the main content.
",
"navigation": "Home | About",
"sidebar": "News
Latest updates...
",
"footer": "© 2024 My Website. All rights reserved.",
}
// Render with default layout
defaultHtml := twoStepView.Render(pageData, "default")
fmt.Println("Default layout HTML generated")
// Render with mobile layout
mobileHtml := twoStepView.Render(pageData, "mobile")
fmt.Println("Mobile layout HTML generated")
// Print first 100 characters of each
fmt.Printf("Default HTML preview: %s...\n", defaultHtml[:100])
fmt.Printf("Mobile HTML preview: %s...\n", mobileHtml[:100])
}
Пример: хорошая реализация двухшаговой шаблонизации есть в фреймфорке Zend Framework в классе Zend_Layout. Общая оплётка отделяется от конкретного вида посредством layout'а.
Использована иллюстрация с сайта Мартина Фаулера.