如何根据调用者确定方法可见性?特权方法访问控制方案问询
特权方法访问控制的设计模式与TypeScript实现优化
通用设计模式
1. 接口分离模式(ISP)
将类的方法拆分为公共接口和特权接口,仅向授权对象暴露特权接口。以你提到的Bank和Account场景为例:
// 公共接口:所有对象都能访问 interface PublicAccount { get balance(): number; get name(): string; } // 特权接口:仅Bank能访问 interface PrivilegedAccount extends PublicAccount { sendMoneyTo(target: PublicAccount, amount: number): void; } class Account implements PrivilegedAccount { private _balance: number; private _name: string; constructor(name: string, initialBalance: number) { this._name = name; this._balance = initialBalance; } get balance(): number { return this._balance; } get name(): string { return this._name; } // 特权方法 sendMoneyTo(target: PublicAccount, amount: number): void { if (this._balance >= amount) { this._balance -= amount; (target as Account)._balance += amount; } } } class Bank { private accounts: Map<string, PrivilegedAccount> = new Map(); createAccount(name: string, initialBalance: number): PublicAccount { const account = new Account(name, initialBalance); this.accounts.set(name, account); // 返回公共接口,外部无法调用sendMoneyTo return account; } transfer(fromName: string, toName: string, amount: number): void { const from = this.accounts.get(fromName); const to = this.accounts.get(toName); if (from && to) { from.sendMoneyTo(to, amount); } } }
外部代码只能拿到PublicAccount接口,无法直接调用sendMoneyTo,只有Bank持有PrivilegedAccount接口实例才能操作。
2. 权限令牌模式
创建一个仅授权类能生成的权限令牌,特权方法必须传入该令牌才能执行,避免简单的布尔值验证被伪造。核心是令牌的构造函数私有,仅授权类能创建:
// 权限令牌:仅Treasury能生成 class MintAuthorizationToken { private constructor() {} // 仅授权类可调用的创建方法 static create(): MintAuthorizationToken { return new MintAuthorizationToken(); } } class Treasury { // ... 其他代码 requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag { this.loans.set(borrower.id, amount); // 生成令牌并传入mint const token = MintAuthorizationToken.create(); return MoneyBag.mint(amount, token); } } class MoneyBag { // ... 其他代码 static mint(amount: number, token: MintAuthorizationToken): MoneyBag { // 验证令牌有效性(只要存在就有效,因为外部无法创建) if (!token) throw new Error("not authorized"); const bag = new MoneyBag(); bag._balance = amount; return bag; } }
这种方式比isTreasury字段更安全,因为外部无法伪造令牌实例。
3. 模块/闭包级封装
将授权类和被访问类放在同一模块或闭包内,利用语言的作用域规则隐藏特权方法,仅向授权类暴露访问入口。比如在TypeScript中用命名空间封装:
namespace BankingSystem { export class MoneyBag { private _balance: number = 0; constructor() {} get balance(): number { return this._balance; } // 私有方法:仅同一命名空间内的Treasury可访问 private setBalance(amount: number): void { this._balance = amount; } // ... 其他公共方法 } export class Treasury { // ... 其他代码 requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag { this.loans.set(borrower.id, amount); const bag = new MoneyBag(); // 直接调用私有方法设置余额 bag.setBalance(amount); return bag; } } } // 外部只能访问export的类,无法调用MoneyBag的setBalance const T = new BankingSystem.Treasury("USA"); const bag = T.requestLoan(100, ...);
针对你的MoneyBag示例的优化方案
方案1:移除requestor参数,改用权限令牌(推荐)
替换原有的isTreasury验证,用令牌确保只有Treasury能调用mint:
interface PersonOrEntity { id: string; } // 新增权限令牌类 class MintToken { private constructor() {} static create(): MintToken { return new MintToken(); } } class Treasury implements PersonOrEntity { readonly id: string; private loans: Map<string, number> = new Map(); constructor(id: string) { this.id = id; } requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag { this.loans.set(borrower.id, amount); // 生成令牌 const token = MintToken.create(); return MoneyBag.mint(amount, token); } } class Person implements PersonOrEntity { readonly id: string; private bag: MoneyBag = new MoneyBag(); constructor(id: string) { this.id = id; } get balance(): number { return this.bag.balance; } addBag(newBag: MoneyBag) { this.bag.add(newBag); } lend(amount: number): MoneyBag { if (amount > this.bag.balance) throw new Error("insufficient funds"); return this.bag.split(amount); } } class MoneyBag { private _balance: number = 0; constructor() {} get balance(): number { return this._balance; } // 仅接受MintToken作为权限验证 static mint(amount: number, token: MintToken): MoneyBag { if (!token) throw new Error("not authorized"); const bag = new MoneyBag(); bag._balance = amount; return bag; } add(bag: MoneyBag) { this._balance += bag.balance; bag._balance = 0; } split(amount: number): MoneyBag { if (this.balance < amount) throw new Error("Insufficient funds"); const newBag = new MoneyBag(); newBag._balance = amount; this._balance -= amount; return newBag; } } // 测试代码不变 let T = new Treasury("USA"); let steve = new Person("Steve"); let jane = new Person("Jane"); steve.addBag(T.requestLoan(100, steve)); jane.addBag(steve.lend(50)); console.log(steve.balance, jane.balance); // 输出 50 50
方案2:将mint逻辑内聚到Treasury,隐藏MoneyBag的私有操作
把mint的逻辑移到Treasury内部,让MoneyBag的构造函数和余额设置仅对Treasury开放:
interface PersonOrEntity { id: string; } class Treasury implements PersonOrEntity { readonly id: string; private loans: Map<string, number> = new Map(); constructor(id: string) { this.id = id; } requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag { this.loans.set(borrower.id, amount); // 直接创建MoneyBag并设置余额 return MoneyBag.createWithBalance(amount); } } class Person implements PersonOrEntity { // 代码不变 } class MoneyBag { private _balance: number = 0; // 私有构造函数:外部无法直接创建 private constructor() {} // 仅Treasury可调用的创建方法(同一模块内可见) static createWithBalance(amount: number): MoneyBag { const bag = new MoneyBag(); bag._balance = amount; return bag; } get balance(): number { return this._balance; } // 其他公共方法不变 }
这种方式完全隐藏了mint的实现,外部甚至不知道有这个操作,所有货币创建逻辑都由Treasury控制。
内容的提问来源于stack exchange,提问作者GGizmos
相关产品推荐
相关产品推荐

