Decoratorパターン
概要
既存の機能に対して、それを変更することなく、前後に機能を追加するためのデザインパターン。 ラッパーを作りまくるとラッパークラスが増えていくので、ラッパーの数が多くなりすぎると管理が大変になる。
インターフェースを統一して、ベース機能 x ラッパー機能の組み合わせで、クラスの数を増やさずに機能を追加できる。
使い方
bad
無限ラッパー編
interface UserRepositoryInterface {
find(id: number): Promise<User>;
}
class UserRepository implements UserRepositoryInterface {
async find(id: number): Promise<User> {
return await db.findUser(id);
}
}
class UserRepositoryWithLog implements UserRepositoryInterface {
async find(id: number): Promise<User> {
logger.info(`Finding user with id: ${id}`);
const user = await db.findUser(id);
logger.info(`Found user: ${user}`);
return user;
}
}
class UserRepositoryWithCache implements UserRepositoryInterface {
async find(id: number): Promise<User> {
const cachedUser = cache.get(id);
if (cachedUser) {
return cachedUser;
}
const user = await db.findUser(id);
cache.set(id, user);
return user;
}
}
class UserRepositoryWithLogAndCache implements UserRepositoryInterface {
async find(id: number): Promise<User> {
logger.info(`Finding user with id: ${id}`);
const cachedUser = cache.get(id);
if (cachedUser) {
return cachedUser;
}
const user = await db.findUser(id);
logger.info(`Found user: ${user}`);
cache.set(id, user);
return user;
}
}
純粋なUserRepositoryとLog付きのUserRepositoryWithLog、Cache付きのUserRepositoryWithCache、LogとCache両方付きのUserRepositoryWithLogAndCacheといった具合に、ラッパーが増えていく。
責務も増えていくし、ラッパーの数が増えすぎると管理が大変になる。(それぞれのUserRepositoryの実装も重複してしまう)
good
interface UserRepositoryInterface {
find(id: number): Promise<User>;
}
abstract class UserRepositoryDecorator extends UserRepositoryInterface {
constructor(userRepository: UserRepositoryInterface);
find(id: number): Promise<User>;
}
class UserRepository implements UserRepositoryInterface {
async find(id: number): Promise<User> {
return await db.findUser(id);
}
}
class UserRepositoryDecorator implements UserRepositoryDecorator {
constructor(private userRepository: UserRepositoryInterface) {}
async find(id: number): Promise<User> {
return await this.userRepository.find(id);
}
}
class LoggerDecorator extends UserRepositoryDecorator {
async find(id: number): Promise<User> {
logger.info(`Finding user with id: ${id}`);
const user = await super.find(id);
logger.info(`Found user: ${user}`);
return user;
}
}
class CacheDecorator extends UserRepositoryDecorator {
async find(id: number): Promise<User> {
const cachedUser = cache.get(id);
if (cachedUser) {
return cachedUser;
}
const user = await super.find(id);
cache.set(id, user);
return user;
}
}
const userRepository = new CacheDecorator(new LoggerDecorator(new UserRepository()));
Decoratorに注入することによって機能を追加できるようになった。
LogやCacheの機能を追加するごとにラッパークラスを作る必要がなくなった。
使い所
- 追加する機能が本来の処理の前後に挟みたいとき
- 基本の機能はそのままにして、複数の機能を追加したいとき
- 機能を追加するときに、継承が難しいパターン