Identity Map (Карта присутствия / Карта соответствия)

Паттерн проектирования Identity Map

Паттерн проектирования Identity Map

Описание Identity Map

Обеспечивает однократную загрузку объекта, сохраняя данные об объекте в карте соответствия. При обращении к объектам, ищет их в карте соответсвия.

Одна старая пословица постулирует, что человек с двумя часами никогда не знает, сколько сейчас времени. И если уж двое часов вносят путаницу, то с загрузкой объектов из БД может получиться гораздо большая путаница. Если разработчик не достаточно аккуратен, может получиться, что он загрузит данные из БД в два объекта. Потом, когда он сохранит их, получится путаница и конкуренция различных данных.

Более того, с этим связаны проблемы производительности. Когда дважды загружается одна и та же информация, увеличиваются затраты на передачу данных. Таким образом, отказ от загрузки одних и тех же данных дважды не только обеспечивает корректность информации, но и ускоряет работу приложения.

Паттерн Identity Map (Карта присутствия / Карта соответствия) хранит записи о всех объектах, которые были считаны из БД за время выполнения одного действия. Когда происходит обращение к объекту, проверяется карта соответствия (присутствия), чтобы узнать, загружен ли объект.

Примеры реализации

// Identity Map Pattern in JavaScript
class User {
    constructor(id, name, email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}

class IdentityMap {
    constructor() {
        this.map = new Map();
    }
    
    get(id) {
        return this.map.get(id);
    }
    
    put(id, obj) {
        this.map.set(id, obj);
    }
    
    has(id) {
        return this.map.has(id);
    }
    
    remove(id) {
        return this.map.delete(id);
    }
    
    clear() {
        this.map.clear();
    }
}

class UserRepository {
    constructor() {
        this.identityMap = new IdentityMap();
    }
    
    findById(id) {
        // Check identity map first
        if (this.identityMap.has(id)) {
            console.log(`User ${id} found in identity map`);
            return this.identityMap.get(id);
        }
        
        // Load from database
        console.log(`Loading user ${id} from database`);
        const user = this.loadFromDatabase(id);
        
        // Store in identity map
        this.identityMap.put(id, user);
        return user;
    }
    
    loadFromDatabase(id) {
        // Simulate database load
        return new User(id, `User ${id}`, `user${id}@example.com`);
    }
}

// Usage
const repo = new UserRepository();
const user1 = repo.findById(1); // Loads from DB
const user2 = repo.findById(1); // Loads from identity map
console.log(user1 === user2); // true
// Identity Map Pattern in C++
#include <iostream>
#include <unordered_map>
#include <memory>

class User {
private:
    int id;
    std::string name;
    std::string email;
    
public:
    User(int id, const std::string& name, const std::string& email)
        : id(id), name(name), email(email) {}
    
    int getId() const { return id; }
    std::string getName() const { return name; }
    std::string getEmail() const { return email; }
};

class IdentityMap {
private:
    std::unordered_map<int, std::shared_ptr<User>> map;
    
public:
    std::shared_ptr<User> get(int id) {
        auto it = map.find(id);
        return (it != map.end()) ? it->second : nullptr;
    }
    
    void put(int id, std::shared_ptr<User> user) {
        map[id] = user;
    }
    
    bool has(int id) const {
        return map.find(id) != map.end();
    }
    
    void remove(int id) {
        map.erase(id);
    }
    
    void clear() {
        map.clear();
    }
};

class UserRepository {
private:
    IdentityMap identityMap;
    
public:
    std::shared_ptr<User> findById(int id) {
        // Check identity map first
        if (identityMap.has(id)) {
            std::cout << "User " << id << " found in identity map" << std::endl;
            return identityMap.get(id);
        }
        
        // Load from database
        std::cout << "Loading user " << id << " from database" << std::endl;
        auto user = loadFromDatabase(id);
        
        // Store in identity map
        identityMap.put(id, user);
        return user;
    }
    
private:
    std::shared_ptr<User> loadFromDatabase(int id) {
        return std::make_shared<User>(id, "User " + std::to_string(id), 
                                      "user" + std::to_string(id) + "@example.com");
    }
};
// Identity Map Pattern in Go
package main

import "fmt"

type User struct {
    ID    int
    Name  string
    Email string
}

type IdentityMap struct {
    map map[int]*User
}

func NewIdentityMap() *IdentityMap {
    return &IdentityMap{
        map: make(map[int]*User),
    }
}

func (im *IdentityMap) Get(id int) *User {
    return im.map[id]
}

func (im *IdentityMap) Put(id int, user *User) {
    im.map[id] = user
}

func (im *IdentityMap) Has(id int) bool {
    _, exists := im.map[id]
    return exists
}

func (im *IdentityMap) Remove(id int) {
    delete(im.map, id)
}

func (im *IdentityMap) Clear() {
    im.map = make(map[int]*User)
}

type UserRepository struct {
    identityMap *IdentityMap
}

func NewUserRepository() *UserRepository {
    return &UserRepository{
        identityMap: NewIdentityMap(),
    }
}

func (r *UserRepository) FindByID(id int) *User {
    // Check identity map first
    if r.identityMap.Has(id) {
        fmt.Printf("User %d found in identity map\n", id)
        return r.identityMap.Get(id)
    }
    
    // Load from database
    fmt.Printf("Loading user %d from database\n", id)
    user := r.loadFromDatabase(id)
    
    // Store in identity map
    r.identityMap.Put(id, user)
    return user
}

func (r *UserRepository) loadFromDatabase(id int) *User {
    return &User{
        ID:    id,
        Name:  fmt.Sprintf("User %d", id),
        Email: fmt.Sprintf("user%d@example.com", id),
    }
}

// Usage
func main() {
    repo := NewUserRepository()
    user1 := repo.FindByID(1) // Loads from DB
    user2 := repo.FindByID(1) // Loads from identity map
    fmt.Println(user1 == user2) // true
}
# Identity Map Pattern in Python
class User:
    def __init__(self, user_id, name, email):
        self.id = user_id
        self.name = name
        self.email = email

class IdentityMap:
    def __init__(self):
        self._map = {}
    
    def get(self, id):
        return self._map.get(id)
    
    def put(self, id, obj):
        self._map[id] = obj
    
    def has(self, id):
        return id in self._map
    
    def remove(self, id):
        return self._map.pop(id, None)
    
    def clear(self):
        self._map.clear()

class UserRepository:
    def __init__(self):
        self.identity_map = IdentityMap()
    
    def find_by_id(self, id):
        # Check identity map first
        if self.identity_map.has(id):
            print(f"User {id} found in identity map")
            return self.identity_map.get(id)
        
        # Load from database
        print(f"Loading user {id} from database")
        user = self._load_from_database(id)
        
        # Store in identity map
        self.identity_map.put(id, user)
        return user
    
    def _load_from_database(self, id):
        return User(id, f"User {id}", f"user{id}@example.com")

# Usage
if __name__ == "__main__":
    repo = UserRepository()
    user1 = repo.find_by_id(1)  # Loads from DB
    user2 = repo.find_by_id(1)  # Loads from identity map
    print(user1 is user2)  # True
<?php
// Identity Map Pattern in PHP
class User {
    private $id;
    private $name;
    private $email;
    
    public function __construct($id, $name, $email) {
        $this->id = $id;
        $this->name = $name;
        $this->email = $email;
    }
    
    public function getId() { return $this->id; }
    public function getName() { return $this->name; }
    public function getEmail() { return $this->email; }
}

class IdentityMap {
    private $map = [];
    
    public function get($id) {
        return $this->map[$id] ?? null;
    }
    
    public function put($id, $obj) {
        $this->map[$id] = $obj;
    }
    
    public function has($id) {
        return isset($this->map[$id]);
    }
    
    public function remove($id) {
        unset($this->map[$id]);
    }
    
    public function clear() {
        $this->map = [];
    }
}

class UserRepository {
    private $identityMap;
    
    public function __construct() {
        $this->identityMap = new IdentityMap();
    }
    
    public function findById($id) {
        // Check identity map first
        if ($this->identityMap->has($id)) {
            echo "User $id found in identity map\n";
            return $this->identityMap->get($id);
        }
        
        // Load from database
        echo "Loading user $id from database\n";
        $user = $this->loadFromDatabase($id);
        
        // Store in identity map
        $this->identityMap->put($id, $user);
        return $user;
    }
    
    private function loadFromDatabase($id) {
        return new User($id, "User $id", "user$id@example.com");
    }
}

// Usage
$repo = new UserRepository();
$user1 = $repo->findById(1); // Loads from DB
$user2 = $repo->findById(1); // Loads from identity map
var_dump($user1 === $user2); // true
?>

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

Источник