如何基于调用者确定方法的可见性?

huangapple go评论62阅读模式
英文:

How to determine method visibility based on the caller?

问题

我想知道通常接受的模式是为需要“特权访问”的方法提供什么,只有某些其他对象类型可能调用。

例如,如果你有一个银行对象,其中包含一组帐户,你可能希望让“银行”调用account.sendMoneyTo(...),但让更广泛的一组对象调用account.balanceaccount.name

我能想到的所有方法似乎都有些笨拙,唯一似乎有任何意义的方法是为帐户对象创建多个接口,一个用于特权函数,另一个用于更“公共”的函数,但也许我漏掉了一些明显的东西,

谢谢。

这里有一个简单的实现来说明。假设你有一个叫做“MoneyBag”的东西,想法是一个“Person”可以借钱或通过MoneyBag接收钱,但他们不能创建Money。假设唯一能够创建货币的实体是“Treasury”,你可以从Treasury借钱。这可能看起来像下面这样。

问题是如何处理MoneyBag的mint函数,
理想情况下,只有国库应该能够调用它。但由于没有“friend”函数,也不可能在MoneyBag前面创建一个接口,以便只有Treasury看到mint函数(因为静态方法不能包含在接口中),所以我不得不实现这个需要呼叫者通过“requestor”参数来识别自己的功能。这对我来说似乎不太理想。如果有一种机制,只有“Treasury”能够在MoneyBag上调用mint,那将更好。


<details>
<summary>英文:</summary>

I&#39;m wondering what the generally accepted pattern would be for providing methods with that require &quot;privileged access&quot; which only certain other object types might call.

For example, if you have a Bank object, with a collection of Accounts, you might want to let &quot;Bank&quot; call account.sendMoneyTo(...), but let a much broad set of objects call account.balance or account.name.  

All the ways I can think of to do this seem clunky, the only approach that seems to make any kind of sense is to have multiple interfaces to the account object, one for privileged functions, and one for more &quot;public&quot; functions, but perhaps I&#39;m missing something obvious,

Thanks.

Here&#39;s a simple implmentation to illustrate.  Let&#39;s say you have thing called a &quot;MoneyBag&quot;, and the idea is that a &quot;Person&quot; can lend money or receive Money via a MoneyBag, but that they can&#39;t create Money.  Assume the only entity that can create money is a &quot;Treasury&quot;,and you can borrow money from the Treasury.  That might look like the below.

The problem is how to deal with the mint function of the MoneyBag, 
Ideally only the Treasury should be able to call it.  But since there is no &quot;friend&quot; function, nor is it possible to create an interface in front of the MoneyBag so that only Treasury sees the mint function (because static methods can&#39;t be included in interfaces), it seems I have to implement this function that requires the caller to identify themselves (via the &quot;requestor&quot; parameter.). This seems suboptimal to me.  Better if there were a mechanism where only &quot;Treasury&quot; could call call `mint` on a MoneyBag.
interface PersonOrEntity {
id : string
isTreasury : boolean;

}
class Treasury implements PersonOrEntity {
readonly isTreasury : boolean = true;
private loans : Map<string, number> = new Map();
id : string;
constructor(id : string) {this.id = id}
requestLoan(amount : number, borrower : PersonOrEntity) : MoneyBag {
this.loans.set(borrower.id, amount);
return MoneyBag.mint(amount, this);
}
}
class Person implements PersonOrEntity {
readonly id: string;
constructor(id: string) {this.id = id}
readonly isTreasury = false;
private bag : MoneyBag = new MoneyBag();
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");
}
let newBag = this.bag.split(amount);
return newBag;
}
}
class MoneyBag {
private _balance: number = 0;
constructor() {}
get balance() : number {return this._balance}

//Only Treasury instance can call?
static mint(amount: number, requestor: PersonOrEntity): MoneyBag {
    if (requestor.isTreasury || amount == 0) {
        let bag = new MoneyBag()
        bag._balance = amount;
        return bag;
    }
    throw new Error(&quot;not authorized&quot;);
}
 public add(bag : MoneyBag) {
    this._balance += bag.balance;
    bag._balance = 0;
}   
 
public split(amount: number): MoneyBag {
     if (this.balance &lt; amount) {
        throw new Error(&quot;Insufficient funds&quot;);  
     }
     let 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);

答案1

得分: 1

不使用请求的“友元类”microsoft/TypeScript#7692 中所请求的方式,很遗憾,没有干净和符合人体工程学的方法来执行此操作。

一个简单但不干净的解决方法是只是使用一个private方法,但允许您的预期的友元类通过括号符号访问,如microsoft/TypeScript#19335中所述,来忽略对隐私的请求:

class MoneyBag {
  // ...
  private static mint(amount: number): MoneyBag {
    let bag = new MoneyBag()
    bag._balance = amount;
    return bag;
  }
}

class Treasury {
  // ... 
  requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag {
    this.loans.set(borrower.id, amount);
    // 明确绕过私有属性
    return MoneyBag["mint"](amount);
  }
}

现在,入侵者仍会受到使用该方法的警告:

MoneyBag.mint(10); // 错误!
// 属性“mint”是私有的,只能在类“MoneyBag”内访问。

当然,一个恶意的个体可以编写MoneyBag["mint"](10),但我们大多数时候只是在谈论一种类型系统,而不是一种已知安全的运行时系统。如果您需要类似于这种功能,最好忽略类型警告,也许干脆不使用类和只使用作用域和闭包来隐藏东西。但我岔开了话题。

您可以采取将预期的友元保留在一起的方法,然后仅导出要从外部访问的部分。但这变得有点丑陋:

namespace Capsule {    
  export class Treasury {
    // ...
    requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag {
        this.loans.set(borrower.id, amount);
        return _MoneyBag.mint(amount);
    }
  }    
  class _MoneyBag {
    // ...
    _balance: number = 0;
    get balance(): number { return this._balance }
    static mint(amount: number): MoneyBag {
      let bag = new _MoneyBag()
      bag._balance = amount;
      return bag;
    }
    public add(bag: MoneyBag) {
      if (!(bag instanceof _MoneyBag)) throw new Error("hey");
      this._balance += bag.balance;
      bag._balance = 0;
    }    
  }    
  export type MoneyBag = Omit<_MoneyBag, "_balance">;
  export const MoneyBag = _MoneyBag as (new () => MoneyBag) & Omit<typeof _MoneyBag, "mint">    
}    
const Treasury = Capsule.Treasury;
type Treasury = Capsule.Treasury;
const MoneyBag = Capsule.MoneyBag;
type MoneyBag = Capsule.MoneyBag;

在这里,我们将友元封装在Capsule namespace中,并且只公开了TreasuryMoneyBag类的构造函数和类型,它们不包含mint静态方法或_balance实例属性。这有点笨重,因为您必须进行大量的重复命名,尽管Omit<T, K>实用类型使这一过程稍微不那么乏味......而且因为您必须 assert add() 中传递的 bag 实际上来自友元区域内的 _MoneyBag,而不是恰好具有导出的 MoneyBag 类型相同成员的东西。

但它能正常工作:

MoneyBag.mint(10); // 错误!
// 属性“mint”在类型上不存在

再次强调,实际上在运行时执行了不好的事情;这只是一个类型错误。如果您需要Fort Knox,您应该首先注意运行时保证,然后再添加类型。

英文:

Without friend classes as requested in microsoft/TypeScript#7692 there's no clean and ergonomic way to do this, unfortunately.

One easy but unclean workaround is to just use a private method but to allow your intended friend class to ignore the request for privacy by indexing via bracket notation, as described in microsoft/TypeScript#19335:

class MoneyBag {
  // ...
  private static mint(amount: number): MoneyBag {
    let bag = new MoneyBag()
    bag._balance = amount;
    return bag;
  }
}

class Treasury {
  // ... 
  requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag {
    this.loans.set(borrower.id, amount);
    // explicitly work around private
    return MoneyBag[&quot;mint&quot;](amount);
  }
}

Now, interlopers will still be warned against using that method:

MoneyBag.mint(10); // error!
// Property &#39;mint&#39; is private and only accessible within class &#39;MoneyBag&#39;.

Yes, of course, a nefarious individual could write MoneyBag[&quot;mint&quot;](10) but we're mostly just talking about a type system here and not some hardened known-secure-at-runtime system. If you need something like that, you'd probably be best ignoring the type warnings and maybe classes altogether and just use scoping and closures to hide things. But I digress.


You could take the approach of keeping your intended friends in a scope together and then exporting only those pieces you want to be accessible from the outside. But this gets kind of ugly:

namespace Capsule {    
  export class Treasury {
    // ...
    requestLoan(amount: number, borrower: PersonOrEntity): MoneyBag {
        this.loans.set(borrower.id, amount);
        return _MoneyBag.mint(amount);
    }
  }    
  class _MoneyBag {
    // ...
    _balance: number = 0;
    get balance(): number { return this._balance }
    static mint(amount: number): MoneyBag {
      let bag = new _MoneyBag()
      bag._balance = amount;
      return bag;
    }
    public add(bag: MoneyBag) {
      if (!(bag instanceof _MoneyBag)) throw new Error(&quot;hey&quot;);
      this._balance += bag.balance;
      bag._balance = 0;
    }    
  }    
  export type MoneyBag = Omit&lt;_MoneyBag, &quot;_balance&quot;&gt;;
  export const MoneyBag = _MoneyBag as 
    (new () =&gt; MoneyBag) &amp; Omit&lt;typeof _MoneyBag, &quot;mint&quot;&gt;    
}    
const Treasury = Capsule.Treasury;
type Treasury = Capsule.Treasury;
const MoneyBag = Capsule.MoneyBag;
type MoneyBag = Capsule.MoneyBag;

Here we've scoped the friends into a Capsule namespace, and are only exposing Treasury and MoneyBag class constructors and types, which don't contain the mint static method or the _balance instance property. It's clunky, because you have to do a lot of redundant naming, even though the Omit&lt;T, K&gt; utility type makes that a little less tedious... and because you have to assert that the bag passed to add() is actually from _MoneyBag inside the friend zone and not something that happens to have the same members as the exported MoneyBag type.

But it works:

MoneyBag.mint(10); // error!
// Property &#39;mint&#39; does not exist on type

Again, this actually does do the bad thing at runtime; it's just a typing error to do this. If you need Fort Knox, you should pay attention to runtime guarantees first and then add types later.

Playground link to code

huangapple
  • 本文由 发表于 2023年3月4日 06:45:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75632467.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定