Two Step View (Двухшаговая шаблонизация)

Паттерн проектирования 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' => '&copy; 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'а.

Использована иллюстрация с сайта Мартина Фаулера.

Источник