安全地提供公共 API(只读)和私有 API(读写)的对象 [英] An object that securely provides both public API (read-only) and private API (read-write)

查看:28
本文介绍了安全地提供公共 API(只读)和私有 API(读写)的对象的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个架构问题.程序员经常会遇到这个封装问题,但我还没有看到一个完整干净的解决方案.

相关问题:

只读类设计非只读类已经到位

控制对字段的读/写访问

通常,在 OOP 范式中,对象将其数据存储在字段中.类自己的方法可以完全访问其字段.当需要返回值时,只返回一份数据,这样外部代码就不会破坏数据.

现在假设数据片段很复杂,因此它们本身被封装在类对象中,并且这些对象不能轻易复制.现在,如果您从某个属性返回此类对象,则外部代码可以像内部代码一样访问它.例如,如果您返回一个 List,则每个人都可以向其添加值.这通常是不可取的.

这个问题通常可以使用只读包装器来解决 - 在返回之前,您将完全访问的内部对象包装在只读包装器中.这种方法的问题在于包装器可能无法替代被包装的值——包装器是一个不同的类.(如果您从可修改类(或反之亦然)派生只读包装器,则任何人都可以将只读"对象向上/向下转换为可修改对象,从而破坏保护.)

我想要这样的模式:

  • 数据(例如,int 值)具有公共/只读 API"和私有/可修改 API".
  • 只有对象创建者才能访问私有/可修改 API".
  • 私有/公共 API 可能具有被动部分(例如方法、属性)和主动部分(例如事件).
  • 除非在对象创建阶段,否则不应使用委托.所有电话都应该是直接的.
  • 从公共/只读 API"(最好也从私有/可修改 API")访问内部数据应该尽可能直接.我不希望在组合此类对象时堆积一大堆包装器.

以下是示例界面:

interface IPublicApi {int GetValue();}接口 IPrivateApi {无效设置值(整数值);}接口 IPrivateConsumer {void OnValueChanged();//打回来}

我设计了这样的方案.我希望你批评我的解决方案或给出你自己的解决方案.

解决方案

有几个子问题需要解决.

  1. 如何允许私有API"代码访问私有数据而不允许外部代码调用?
  2. 如何向对象创建者授予私有 API"访问权限?
  3. 如何使用私有 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.

  1. How to allow the "private API" code to access the private data without allowing the outside code to call it?
  2. How to give the "private API" access to the object creator?
  3. 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 marked internal, so only ReadableInt can create the instances. I could not find a more elegant way to prevent anyone from creating a ReadableInt.PrivateApi from a ReadableInt 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 the ReadableInt.IPrivateConsumer interface. ReadableInt receives the reference to a ReadableInt.IPrivateConsumer object through the constructor.
  • The private API controller object (ReadableInt.PrivateApi) is given to the creator (WriteableInt) via callback (Action<PrivateApi>) passed to the ReadableInt 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆