安全地提供公共 API(只读)和私有 API(读写)的对象 [英] An object that securely provides both public API (read-only) and private API (read-write)
问题描述
这是一个架构问题.程序员经常会遇到这个封装问题,但我还没有看到一个完整干净的解决方案.
相关问题:
通常,在 OOP 范式中,对象将其数据存储在字段中.类自己的方法可以完全访问其字段.当需要返回值时,只返回一份数据,这样外部代码就不会破坏数据.
现在假设数据片段很复杂,因此它们本身被封装在类对象中,并且这些对象不能轻易复制.现在,如果您从某个属性返回此类对象,则外部代码可以像内部代码一样访问它.例如,如果您返回一个 List
,则每个人都可以向其添加值.这通常是不可取的.
这个问题通常可以使用只读包装器来解决 - 在返回之前,您将完全访问的内部对象包装在只读包装器中.这种方法的问题在于包装器可能无法替代被包装的值——包装器是一个不同的类.(如果您从可修改类(或反之亦然)派生只读包装器,则任何人都可以将只读"对象向上/向下转换为可修改对象,从而破坏保护.)>
我想要这样的模式:
- 数据(例如,
int
值)具有公共/只读 API"和私有/可修改 API". - 只有对象创建者才能访问私有/可修改 API".
- 私有/公共 API 可能具有被动部分(例如方法、属性)和主动部分(例如事件).
- 除非在对象创建阶段,否则不应使用委托.所有电话都应该是直接的.
- 从公共/只读 API"(最好也从私有/可修改 API")访问内部数据应该尽可能直接.我不希望在组合此类对象时堆积一大堆包装器.
以下是示例界面:
interface IPublicApi {int GetValue();}接口 IPrivateApi {无效设置值(整数值);}接口 IPrivateConsumer {void OnValueChanged();//打回来}
我设计了这样的方案.我希望你批评我的解决方案或给出你自己的解决方案.
有几个子问题需要解决.
- 如何允许私有API"代码访问私有数据而不允许外部代码调用?
- 如何向对象创建者授予私有 API"访问权限?
- 如何使用私有 API(调用/获取调用)建立对象和代码之间的双向通信?
我的系统由以下类组成:
ReadableInt
是公共 API
ReadableInt.PrivateApi
是原始私有 API 代理对象
ReadableInt.IPrivateConsumer
是公对私回调接口
public 密封类 ReadableInt {int_value;IPrivateConsumer _privateConsumer;public ReadableInt(IPrivateConsumer privateConsumer, Action privateConsumerInitializer) {_privateConsumer = 私人消费者;var proxy = new PrivateApi(this);privateConsumerInitializer(代理);}公共 int GetValue() {返回_值;}私有无效 SetValue(整数值){_value = 值;_privateConsumer.OnValueChanged();}公共接口 IPrivateConsumer {void OnValueChanged();}公共类 PrivateApi {ReadableInt _readableInt;内部 PrivateApi(ReadableInt publicApi) {_readableInt = publicApi;}公共无效SetValue(整数值){_readableInt.SetValue(value);}}}
WritableInt
是一些私有 API 使用者,它可能驻留在另一个程序集中.
公共密封类 WritableInt : ReadableInt.IPrivateConsumer {ReadableInt _readableInt;ReadableInt.PrivateApi _privateApi;公共 WritableInt() {_readableInt = new ReadableInt(this, Initialize);}无效初始化(ReadableInt.PrivateApi privateApi){_privateApi = privateApi;}public ReadableInt ReadOnlyInt { get { return _readableInt;} }公共无效SetValue(整数值){_privateApi.SetValue(value);}void ReadableInt.IPrivateConsumer.OnValueChanged() {Console.WriteLine("值改变了!");}}
可以像这样使用这些类:
var writeableInt = new WritableInt();var readableInt = writeableInt.ReadOnlyInt;
系统是这样工作的:
- 私有 API (
ReadableInt.PrivateApi
) 作为内部类获得对主对象 (ReadableInt
) 私有成员的访问.没有向上转换/向下转换的安全漏洞. - 注意
ReadableInt.PrivateApi
构造函数被标记为internal
,所以只有ReadableInt
可以创建实例.我找不到更优雅的方法来阻止任何人从ReadableInt
对象创建ReadableInt.PrivateApi
. - 通常,
ReadableInt
需要对私有 API 使用者的引用才能调用它(通知等).为了将公共 API 与具体的私有 API 消费者分离,私有 API 消费者被抽象为ReadableInt.IPrivateConsumer
接口.ReadableInt
通过构造函数接收对ReadableInt.IPrivateConsumer
对象的引用. - 私有 API 控制器对象 (
ReadableInt.PrivateApi
) 通过回调 (Action
) 提供给创建者 (WriteableInt
)传递给ReadableInt
构造函数.太丑了.有人可以提出另一种方法吗? - 有一个小问题:
WritableInt.OnValueChanged()
方法是私有的,但实际上是公开的,因为它是一个接口方法.这可以通过委托或代理来解决.还有其他办法吗?
这个系统有效,但有一些我并不引以为豪的部分.我特别不喜欢所有部分都链接在一起的初始化阶段.这可以以某种方式简化吗?
This is an architecture problem. Programmers encounter this encapsulation problem quite often, but I haven't yet seen a complete and clean solution.
Related questions:
readonly class design when a non-readonly class is already in place
Controlling read/write access to fields
Normally, in OOP paradigm, objects store their data in fields. The class' own methods have full access to its fields. When you need to return value, you just return a copy of the data, so that the outside code cannot break the data.
Now suppose that the data pieces are complex, so they're themselves encapsulated in class objects and that these objects cannot be easily copied. Now, if you return such object from some property, the outside code has the same access to it as your internal code. For example, if you return a List<int>
, everyone can add values to it. This is usually undesirable.
This problem is usually worked around using read-only wrappers - you wrap your full-access internal objects in read-only wrappers before returning. The problem with this approach is that the wrapper may be a poor substitution for the wrapped value - the wrapper is a different class. (And if you derive the read-only wrapper from the modifiable class (or vise-versa), then anybody can up-cast/down-cast the "read-only" object to the modifiable object, breaking the protection.)
I want a pattern such that:
- The data (say, an
int
value) has "public/read-only API" and "private/modifiable API". - Only the object creator has access to the "private/modifiable API".
- The private/public APIs may have both passive parts (e.g. methods, properties) and active parts (e.g. events).
- Delegates should not be used except at the object creation stage. All calls should be direct.
- The access to the internal data from the "public/read-only API" (and, preferably, from the "private/modifiable API" too) should be as direct as possible. I don't want a big stack of wrappers to accumulate when composing such objects.
Here are the sample interfaces:
interface IPublicApi {
int GetValue();
}
interface IPrivateApi {
void SetValue(int value);
}
interface IPrivateConsumer {
void OnValueChanged(); //Callback
}
I have devised such scheme. I want you to critique my solution or give your own solution.
There are several sub-problems that have to be solved.
- How to allow the "private API" code to access the private data without allowing the outside code to call it?
- How to give the "private API" access to the object creator?
- How to establish the two-way communication between the object and the code using the private API (calling/getting called)?
My system consists of these classes:
ReadableInt
is the public API
ReadableInt.PrivateApi
is the raw private API proxy object
ReadableInt.IPrivateConsumer
is the public-to-private callback interface
public sealed class ReadableInt {
int _value;
IPrivateConsumer _privateConsumer;
public ReadableInt(IPrivateConsumer privateConsumer, Action<PrivateApi> privateConsumerInitializer) {
_privateConsumer = privateConsumer;
var proxy = new PrivateApi(this);
privateConsumerInitializer(proxy);
}
public int GetValue() {
return _value;
}
private void SetValue(int value) {
_value = value;
_privateConsumer.OnValueChanged();
}
public interface IPrivateConsumer {
void OnValueChanged();
}
public class PrivateApi {
ReadableInt _readableInt;
internal PrivateApi(ReadableInt publicApi) {
_readableInt = publicApi;
}
public void SetValue(int value) {
_readableInt.SetValue(value);
}
}
}
WritableInt
is some private API consumer, which may reside in another assembly.
public sealed class WritableInt : ReadableInt.IPrivateConsumer {
ReadableInt _readableInt;
ReadableInt.PrivateApi _privateApi;
public WritableInt() {
_readableInt = new ReadableInt(this, Initialize);
}
void Initialize(ReadableInt.PrivateApi privateApi) {
_privateApi = privateApi;
}
public ReadableInt ReadOnlyInt { get { return _readableInt; } }
public void SetValue(int value) {
_privateApi.SetValue(value);
}
void ReadableInt.IPrivateConsumer.OnValueChanged() {
Console.WriteLine("Value changed!");
}
}
One can use the classes like this:
var writeableInt = new WritableInt();
var readableInt = writeableInt.ReadOnlyInt;
This is how the system works:
- The private API (
ReadableInt.PrivateApi
) gains access to the main object (ReadableInt
) private members by being an inner class. No up-casting/down-casting security breaches. - Notice that the
ReadableInt.PrivateApi
constructor is markedinternal
, so onlyReadableInt
can create the instances. I could not find a more elegant way to prevent anyone from creating aReadableInt.PrivateApi
from aReadableInt
object. - In general,
ReadableInt
needs a reference to the private API consumer to call it (notifications etc.). To decouple the public API from concrete private API consumers, the private API consumer is abstracted as theReadableInt.IPrivateConsumer
interface.ReadableInt
receives the reference to aReadableInt.IPrivateConsumer
object through the constructor. - The private API controller object (
ReadableInt.PrivateApi
) is given to the creator (WriteableInt
) via callback (Action<PrivateApi>
) passed to theReadableInt
constructor. It's extremely ugly. Can anyone propose another way? - There is a small problem:
WritableInt.OnValueChanged()
method is private, but is effectively public as it's an interface method. This can be solved with a delegate or a proxy. Is there any other way?
This system works, but has some parts that I'm not proud of. I particularly dislike the initialization stage when all parts are linked together. Can this be simplified somehow?
这篇关于安全地提供公共 API(只读)和私有 API(读写)的对象的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!