一个安全地提供公共API(只读)和私有API(读写)的对象

时间:2014-06-14 04:38:05

标签: c# oop architecture encapsulation

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

相关问题:

readonly class design when a non-readonly class is already in place

Controlling read/write access to fields

通常,在OOP范例中,对象将其数据存储在字段中。类自己的方法可以完全访问其字段。当您需要返回值时,您只需返回数据的副本,以便外部代码不会破坏数据。

现在假设数据块很复杂,因此它们本身就封装在类对象中,并且这些对象不容易被复制。现在,如果从某个属性返回此类对象,则外部代码与内部代码具有相同的访问权限。例如,如果您返回List<int>,则每个人都可以为其添加值。这通常是不受欢迎的。

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

我想要一个模式:

  • 数据(例如,int值)具有“公共/只读API”和“私有/可修改的API”。
  • 只有对象创建者才能访问“私有/可修改的API”。
  • 私有/公共API可能包含被动部分(例如方法,属性)和活动部分(例如事件)。
  • 除了在对象创建阶段,不应使用代理。所有电话都应该是直接的。
  • 从“公共/只读API”(以及最好来自“私有/可修改的API”)访问内部数据应该尽可能直接。在编写这样的对象时,我不希望堆积大量的包装器。

以下是示例界面:

interface IPublicApi {
    int GetValue();
}

interface IPrivateApi {
    void SetValue(int value);
}

interface IPrivateConsumer {
    void OnValueChanged(); //Callback
}

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

2 个答案:

答案 0 :(得分:1)

有几个子问题需要解决。

  1. 如何允许&#34;私有API&#34;代码访问私有数据而不允许外部代码调用它?
  2. 如何提供&#34;私有API&#34;访问对象创建者?
  3. 如何使用私有API(调用/调用)建立对象和代码之间的双向通信?
  4. 我的系统由以下类组成:

    ReadableInt是公共API

    ReadableInt.PrivateApi是原始私有API代理对象

    ReadableInt.IPrivateConsumer是公用私有回调接口

    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是一些私有API使用者,可能位于另一个程序集中。

    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!");
        }
    }
    

    可以使用这样的类:

    var writeableInt = new WritableInt();
    var readableInt = writeableInt.ReadOnlyInt;
    

    这就是系统的工作方式:

    • 私有API(ReadableInt.PrivateApi)通过作为内部类来获得对主要对象(ReadableInt)私有成员的访问权限。没有上行/下行安全漏洞。
    • 请注意,ReadableInt.PrivateApi构造函数已标记为internal,因此只有ReadableInt才能创建实例。我找不到更优雅的方法来阻止任何人从ReadableInt.PrivateApi对象创建ReadableInt
    • 通常,ReadableInt需要引用私有API使用者来调用它(通知等)。要将公共API与具体的私有API使用者分离,私有API使用者将被抽象为ReadableInt.IPrivateConsumer接口。 ReadableInt通过构造函数接收对ReadableInt.IPrivateConsumer对象的引用。
    • 私有API控制器对象(ReadableInt.PrivateApi)通过传递给WriteableInt构造函数的回调(Action<PrivateApi>)提供给创建者(ReadableInt)。 非常难看。任何人都可以提出另一种方式吗?
    • 存在一个小问题:WritableInt.OnValueChanged()方法是私有的,但实际上是公共的,因为它是一种接口方法。这可以通过代理或代理来解决。还有其他办法吗?

    这个系统有效,但有些部分我并不自豪。当所有部件链接在一起时,我特别不喜欢初始化阶段。这可以以某种方式简化吗?

答案 1 :(得分:0)

我是怎么做的

这个问题非常有趣。我不是OOP的专家(上帝!我希望我愿意!),但这就是我的方式:

public interface IReadOnlyFoo
{
    int SomeValue
    {
        get;
    }
}

public class Foo: IReadOnlyFoo
{
    public int SomeValue
    {
        get;
        set;
    }
}

public class Bar
{
    private Foo foo;

    public IReadOnlyFoo Foo
    {
        get
        {
            return foo;
        }
    }
}

它不是很安全,因为你可以将IReadOnlyFoo强制转换为Foo。但我的理念如下:当你演员时,你要对自己承担全部责任。所以,如果你用脚射击自己,那就是你的错。

如果我要避免施放问题

,我会怎么做

首先要考虑的是有值类型和引用类型。

值类型

为了这个答案,我将为纯数据类型(int,float,bool等)和结构分类值类型。

纯数据类型

有趣的是,您使用价值类型int来解释您的问题。值类型将通过赋值进行复制。因此,对于int,您不需要任何类型的包装器或只读参考机制。这是肯定的。只需使用私有/受保护的setter制作只读属性或属性即可。故事结束。

结构

基本上,同样的事情。在设计良好的代码中,您不需要任何结构包装器。如果你在struct中有一些引用类型值:我会说这是一个糟糕的设计。

参考类型

对于参考类型,您提出的解决方案看起来太复杂了。我会做这样的事情:

public class ReadOnlyFoo
{
    private readonly Foo foo;

    public ReadOnlyFoo(Foo foo)
    {
        this.foo = foo;
    }

    public SomeReferenceType SomeValue
    {
        get
        {
            return foo.SomeValue;
        }
    }
}

public class Foo
{
    public int SomeValue
    {
        get;
        set;
    }
}

public class Bar
{
    private Foo foo;

    public readonly ReadOnlyFoo Foo;

    public Bar()
    {
        foo = blablabla;
        Foo = new ReadOnlyFoo(foo);
    }
}