【问题标题】:Have a method being only usable by a unique type of class有一个方法只能由唯一类型的类使用
【发布时间】:2012-01-22 06:19:43
【问题描述】:

我目前正在寻找一种适当的方法来限制特定类型的类对方法的使用。更清楚地说,情况如下:

我有一个专门用于服务器网络的课程(我们称之为ClientSession)。客户端连接到服务器后,有 30 秒的时间进行身份验证,否则服务器将关闭连接。

身份验证由服务器上的另一个类处理(我们称之为Authenticator),它应该“警告”ClientSession 客户端身份验证成功并且可以取消 30 秒计时器。

问题是,这需要Authenticator 调用ClientSession 的公共方法。但我不希望任何人都能调用该方法。

总而言之,我希望Authenticator 成为唯一能够调用ClientSession::clientAuthSuccessful() 方法的类。

我研究了两件事:

  • 朋友方法,但我不希望Authenticator 能够通过朋友方法访问所有私有方法和属性或ClientSession
  • Visitor 设计模式,但它似乎并没有完全符合我的要求(除非我误解了它的用法)。

还有其他方法可以有这样的特定限制吗?还是一个干净的替代品?

【问题讨论】:

标签: c++ encapsulation


【解决方案1】:

定义一个接口AuthenticatorListener,其中定义了clientAuthSuccessful() 方法(作为纯虚拟)。

使ClientSession 私下继承AuthenticatorListener 并实现该方法(私下)。

然后让ClientSession 实例将自身传递给Authenticator

现在没有其他类可以访问该方法。并且Authenticator 不耦合到ClientSession,只耦合到适当隔离的接口。

【讨论】:

  • 它似乎完成了这项工作,而且很干净。谢谢!
  • 嗯,事实上有些事情我没有想到...任何有指向ClientSession 的类只需将其转换为AuthenticatorListener 即可访问私有方法。所以它在某种程度上更安全​​,但仍然不完美。
【解决方案2】:

有一个更简单的方法。只需将一些特殊用途的代码从ClientSession 传递给Authenticator。您可以使用函数指针、函数对象或 lambda 函数来实现此目的。

class ClientSession
{
public:
    void startSession ( Authenticator& authenticator )
    {
        // pass function pointer to method to call when
        // the timeout for authentication has elapsed.
        authenticator.start(this, &ClientSession::warnThat30SecsHasPassed);

        // alternative syntax, using new lambda function syntax,
        // which will end up reducing coupling between the two classes.
        authenticator.start([this](){ warnThat30SecsHasPassed() });
    }
private:
    void warnThat30SecsHasPassed () { ... }
};

class Authenticator
{
public:
    // classic way.
    void start ( ClientSession * session, void(ClientSession::*callback)() );

    // using new syntax.
    void start ( std::function<void()> callback );
}

【讨论】:

  • 感谢您的想法,这似乎是“强制”绕过方法隐私的好方法。我以前从未研究过 lambda 函数,花了一些时间来理解(我不确定我是否仍然明白整个想法,它似乎只是在使用函数指针时避免进入新的范围),但它确实成功了。不确定它是否是最干净的东西,但我可能会使用它。谢谢。
  • @Jukurrpa:鉴于 C++ 在 C++11 之前缺乏 lambda 函数,这(还)不是编写 C++ 的惯用方式,而是在许多“处理”这个问题的常用方式语言,包括 C#。随着对 lambda 函数的支持越来越广泛,我怀疑这种技术会变得越来越普遍。
  • 既然我对 lambda 表达式有了更好的理解,我再次阅读了您的答案,在我看来,这是最好和最简单的解决方案。再次感谢。
【解决方案3】:

没有干净的方法可以做到这一点,不。您要么使用友元类,要么使用公共方法。

没有干净的方法并不意味着不可能。我永远不会写这样的代码,你也不应该,但以下确实符合要求:

class Lock
{
    friend class B;
private:
    Lock() {};
};


class A
{
public:
    A() {}
    void foo(Lock x){}
};

class B
{
    void foo()
    {
        Lock l;
        A a;
        a.foo(l);
    }
};

由于class B是唯一可以创建Lock实例的类,而A::foo需要Lock实例作为参数,所以只能由B调用。

不要这样做。我建议在标题中添加 cmets 和文档。

【讨论】:

  • 这看起来确实很丑。这不是一个“常见”问题吗?这不是我第一次需要做这种限制,但我从来没有找到任何关于它的东西......这让我想知道是否要求这样做只是意味着我所做的设计不“干净”,如果有完全不同的做事方式。
  • 我猜应该叫pass-key idiom。
  • @Jukurrpa 可能有更简洁的设计,但可能需要更多的重构。另外,一个问题经常遇到,并不意味着它有解决方案。 C++没有反射,Java没有朋友,等等……
【解决方案4】:

您可以创建ClientSession 朋友和朋友Authenticator 的代理类。

【讨论】:

  • 嗯,这在某种程度上会是同样的问题,代理类将可以访问所有ClientSessions 成员......即使我“信任”代理,我也可能不得不这样做这种对其他方法的限制,也就是说每个方法都有一个代理类,会有点乱:)
  • 不,不会。友谊不是传递的:我的朋友朋友不是我的朋友。 (快说十倍。)至于组合多就笨拙,不用争辩。
  • @smparkes:OP 所说的与传递性无关。如果ClientSession 朋友Authenticator,那么Authenticator 可以访问ClientSession所有 私有成员,而不仅仅是一个方法。
  • @AndréCaron:我知道。我对传递性的评论是针对他的评论,即代理也有同样的问题,但由于友谊缺乏传递性,情况并非如此。
  • 我想我们都从不同的角度说同样的话。 Jukurrpa 意味着代理类定义将拥有对 ClientSession 内部的完全访问权限(ClientSession 仍然被赋予对 some 其他类的完全访问权限),并且您的意思是代理类的客户端将只能访问代理类授予的访问权限。
【解决方案5】:

当遇到问题时,人们通常会想到如何解决它,然后将精力花在如何实施该解决方案上。有时,最好重新审视问题并考虑其他解决方案。

例如,与其让Authenticator 尝试通知传递给它的ClientSession(或者更好的是,通过调用传递给它的某种回调),不如让让ClientSession(或其他一些对象/线程)调用像Authenticator::waitForAuthentication(int timeout)这样的方法?


编辑:我想我会详细说明 callback 的概念。关键是Authenticator 不需要知道ClientSession 类来完成它的工作;它只需要知道调用某个函数或其他可调用对象。

所以,你写你的Authenticator 有一个方法来接受一些正确签名的std::function 对象,并让ClientSession(或其他)创建一个合适的东西来传递。它可能是一个专门的函数对象类似于 Luchian Grigore 所建议的。也许它是一个稍微通用的对象,或者可能是一个通过引用 ClientSession 对象的私有部分来初始化的对象。或者它是一个 lambda,如果你可以使用 C++11。

也就是说,我怀疑拥有一个 waitForAuthentication 方法(或完全是其他东西)很可能是您真正想要为您的应用程序提供的。

【讨论】:

  • 好吧,我已经尝试过考虑其他方式来传递消息,但问题是 ClientSession 必须处理其他事情并且专门等待响应是不可能的,或者检查是否身份验证不时成功,对我来说似乎并不干净。
  • 为您的对象设置消息队列很常见,因此只需将消息放入正确的队列即可传递消息。如果您只是确保只有 ClientSessionAuthenticator 获得对该队列的引用,我认为这符合您的需求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-05-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多