为什么C#不提供C ++样式的“ friend”关键字?[关闭]


208

C ++朋友关键字允许class A指定class B作为其朋友。这允许Class B访问的private/ protected成员class A

我从来没有读过任何关于为什么C#(和VB.NET)不包含此内容的信息。对于这个较早的StackOverflow问题的大多数答案似乎都是在说它是C ++的有用组成部分,并且有充分的理由使用它。以我的经验,我必须同意。

在我看来,另一个问题确实是在问如何friend在C#应用程序中执行类似的操作。尽管答案通常围绕嵌套类,但似乎并不像使用friend关键字那样优雅。

原始的《设计模式》一书在整个示例中都定期使用它。

因此,总而言之,为什么friendC#缺少它?在C#中模拟它的“最佳实践”方法是什么?

(顺便说一句,internal关键字不是同一回事,它允许整个程序集中的所有类访问internal成员,而friend允许您为某个类提供另一个类的完全访问权限


5
我觉得受保护的内部是一个很好的妥协..
古勒扎尔纳济姆

3
这不是很混乱吗?正如我在上面所说的,GOF书在示例中经常使用它。对我来说,再也没有内部混乱。
灰烬

7
正如已经提到的单元测试,我当然可以看到它们非常有用的场景。但是,您是否真的希望朋友访问您的私人空间?
danswain's

2
这很容易回答:C#提供“内部”作为访问修饰符,该访问修饰符允许访问同一模块/程序集中的所有代码。这消除了对像朋友这样的东西的需要。在Java中,关键字“ protected”的行为与从同一包进行的访问类似。
sellibitze 2010年

2
@sellibitze,我提到了与内部问题的区别。Interanl的问题在于,程序集中的所有类都可以访问内部成员。这将破坏封装,因为这些类中的某些类可能不需要访问。
Ash Ash

Answers:


67

在编程中拥有朋友或多或少被认为是“肮脏的”并且易于滥用。它破坏了类之间的关系,并破坏了OO语言的一些基本属性。

话虽这么说,这是一个很好的功能,我自己在C ++中已经使用了很多次。并且也想在C#中使用它。但是我打赌,因为C#的“纯” OOness(与C ++的伪OOness相比),MS决定因为Java没有朋友关键字,C#也不应(只是在开玩笑;))

值得一提的是:内部工作不如朋友好,但确实可以完成工作。请记住,很少会不通过DLL将代码分发给第三方开发人员;因此,只要您和您的团队了解内部类及其使用,您就可以了。

编辑让我澄清一下,Friend关键字如何破坏OOP。

私有和受保护的变量和方法也许是OOP中最重要的部分之一。对象可以保存仅其可以使用的数据或逻辑的想法使您可以编写独立于您的环境的功能实现-并且您的环境不能更改不适合处理的状态信息。通过使用friend,您将两个类的实现耦合在一起-如果您仅耦合两个类的接口,这会更糟。


167
实际上,C#“内部”对封装的伤害远大于C ++“朋友”。通过“友谊”,所有者将许可明确授予任何想要的人。“内部”是一个开放式的概念。
Nemanja Trifunovic

21
应该赞扬安德斯(Anders)所遗漏的功能,而不是他所包含的功能。
Mehrdad Afshari

21
我不同意C#的内部会伤害封装。我认为相反。您可以使用它在程序集中创建一个封装的生态系统。许多对象可以共享该生态系统内的内部类型,这些内部类型不会暴露给应用程序的其余部分。我使用“朋友”程序集技巧为这些对象创建单元测试程序集。
Quibblesome

26
这没有道理。friend不允许任意的类或函数访问私有成员。它允许标记为好友的特定函数或类这样做。拥有私有成员的类仍可100%控制对其的访问。您不妨争论一下公共成员方法。两者均确保类中明确列出的方法具有访问该类私有成员的权限。
jalf

8
我认为恰恰相反。如果程序集有50个类,如果其中3个类使用某个实用程序类,那么今天,您唯一的选择是将该实用程序类标记为“内部”。这样,您不仅使它可用于3个类,而且可用于装配中的其余类。如果您只想提供更细粒度的访问权限,那么只有所涉及的三个类都可以访问实用程序类。实际上,它提高了封装性,因此使其更加面向对象。
zumalifeguard

104

附带说明。使用friend并不是在违反封装,而是相反地是在强制执行。像访问器+更改器,运算符重载,公共继承,向下转换等一样,它经常被滥用,但这并不意味着该关键字没有坏的目的,或者更糟的是,它的目的不好。

在其他线程中查看Konrad Rudolph消息,或者,如果您愿意,请参见C ++ FAQ中的相关条目


...以及FQA中的相应条目:yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee,2009年

28
+1为“ 使用朋友不是在违反封装,而是在执行
Nawaz

2
我认为是语义上的意见;可能与实际情况有关。将类设置friend为a 可使它访问所有私有成员,即使您只需要让它设置其中一个即可。有一个很强的论点是,使不需要其他字段的其他类可以使用的字段被视为“违反封装”。C ++中有一些地方是必须的(重载运算符),但是有一个封装权衡需要仔细考虑。似乎C#设计人员认为权衡是不值得的。
2015年

3
封装是关于强制不变量。当我们将一个类定义为与另一个类的朋友时,我们明确地说,关于第一个类的不变量的管理,这两个类紧密相关。让班级成为朋友比直接暴露所有属性(作为公共领域)或通过setter更好。
Luc Hermitte

1
究竟。当您想使用朋友但不能使用时,您被迫使用内部或公共场所,这对于不应该使用的东西更加开放。特别是在团队环境中。
孵化

50

对于信息,.NET中的另一个相关但不完全相同的东西是[InternalsVisibleTo],它使程序集可以指定另一个(有效地)“内部”访问类型/成员的程序集(例如单元测试程序集)。原始装配体。


3
这是一个很好的提示,因为可能经常有人在寻找朋友,他们希望找到一种具有更多内部“知识”的单元测试方法。
SwissCoder 2012年

14

通过使用C#中的接口,您应该能够完成与“朋友”在C ++中相同的工作。它要求您显式定义在两个类之间传递的成员,这是额外的工作,但也可能使代码更易于理解。

如果有人举了一个合理使用“朋友”的示例,而该示例不能使用界面来模拟,请分享一下!我想更好地了解C ++和C#之间的区别。


好点子。你有机会看一下早期的SO问题(由一氧化碳问及以上?联系我很有兴趣在如何最好地解决,在C#?

我不确定确切如何在C#中做到这一点,但我认为Jon Skeet对一氧化碳问题的回答指向了正确的方向。C#允许嵌套类。不过,我实际上并没有尝试编写出代码并编译解决方案。:)
帕拉帕

我觉得调解员模式是一个很好的例子,说明朋友会很好。我将表单重构为具有调解器。我希望我的表单能够像调解器中那样调用“事件”之类的方法,但是我真的不希望其他类调用这些“事件”之类的方法。“朋友”功能将使窗体可以访问方法,而无需将其打开到装配中的所有其他对象。
瓦卡诺2009年

3
替换“ Friend”的接口方法的问题是对象公开了接口,这意味着任何人都可以将对象强制转换为该接口并可以访问方法!将接口的作用域设置为内部,受保护或私有都无法正常工作,就可以确定所讨论方法的范围!想想购物中心里的母亲和孩子。公众是世界上的每个人。内部只是购物中心里的人。私人只是母亲或孩子。“朋友”只是母女之间的某种东西。
Mark A. Donohoe

1
这是一个:stackoverflow.com/questions/5921612/… 由于我来自C ++背景,缺乏朋友几乎每天都使我发疯。在使用C#的几年中,我实际上公开了数百种方法,这些方法可以私有并且更安全,所有这些都是由于缺乏朋友而引起的。我个人认为,朋友是应该添加到.NET的最重要的东西
kaalus 2012年

14

使用friendC ++,设计人员可以精确控制private *成员所面向的对象。但是,他被迫公开每个私人成员。

使用internalC#时,设计师可以精确控制他要公开的一组私人成员。显然,他只能公开一个私人成员。但是,它将在程序集中的所有类中公开。

通常,设计人员只希望将少数私有方法公开给选定的少数其他类。例如,在类工厂模式中,可能希望仅由类工厂CF1实例化类C1。因此,类C1可能具有受保护的构造函数和朋友类工厂CF1。

如您所见,我们有2个维度可以破坏封装。 friend沿着一个维度违反它,沿着另一个维度internal做到。封装概念中哪一个是更严重的违反?很难说。但是同时拥有friendinternal可用会很好。此外,这两个关键字的一个很好的补充是关键字的第三种类型,该关键字将在每个成员的基础上使用(如internal)并指定目标类(如friend)。

*为简洁起见,我将使用“私有”而不是“私有和/或受保护”。

-尼克


13

实际上,C#提供了以纯OOP方式获得相同行为的可能性,而无需使用特殊词-它是私有接口。

就问题而言,朋友的C#等效项是什么?被标记为与本文重复,并且没有人提出很好的实现-我将在此处显示两个问题的答案。

主要思想是从这里获取的:什么是私有接口?

假设,我们需要一些可以管理另一个类的实例并对其调用一些特殊方法的类。我们不想给任何其他类调用此方法。朋友c ++关键字在c ++世界中所做的事情完全相同。

我认为实际实践中的一个很好的例子是全状态机模式,其中某些控制器更新当前状态对象,并在必要时切换到另一个状态对象。

你可以:

  • 公开Update()方法的最简单,最糟糕的方法-希望每个人都明白为什么它不好。
  • 下一步是将其标记为内部。如果将您的类放在另一个程序集中就足够了,但是即使那样,该程序集中的每个类都可以调用每个内部方法。
  • 使用私有/受保护的接口-我遵循这种方式。

Controller.cs

public class Controller
{
    private interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() {  }
    }

    public Controller()
    {
        //it's only way call Update is to cast obj to IState
        IState obj = new StateBase();
        obj.Update();
    }
}

Program.cs

class Program
{
    static void Main(string[] args)
    {
        //it's impossible to write Controller.IState p = new Controller.StateBase();
        //Controller.IState is hidden
        var p = new Controller.StateBase();
        //p.Update(); //is not accessible
    }
}

好吧,继承呢?

因为显式接口成员的实现不能被声明为虚拟的,所以我们需要使用中描述的技术,并将IState标记为受保护的,以便也有可能从Controller派生。

Controller.cs

public class Controller
{
    protected interface IState
    {
        void Update();
    }

    public class StateBase : IState
    {
        void IState.Update() { OnUpdate(); }
        protected virtual void OnUpdate()
        {
            Console.WriteLine("StateBase.OnUpdate()");
        }
    }

    public Controller()
    {
        IState obj = new PlayerIdleState();
        obj.Update();
    }
}

PlayerIdleState.cs

public class PlayerIdleState: Controller.StateBase
{
    protected override void OnUpdate()
    {
        base.OnUpdate();
        Console.WriteLine("PlayerIdleState.OnUpdate()");
    }
}

最后一个示例如何测试类Controller抛出继承: ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

希望我能涵盖所有情况,并且我的回答是有用的。


这是一个很棒的答案,需要更多的投票。
KthProg '17

@max_cn这是一个非常好的解决方案,但是我看不到一种方法可以与用于框架测试的模拟框架一起使用。有什么建议?
史蒂夫·兰德

我有点困惑。如果我希望外部类能够在您的第一个示例(私有接口)中调用Update,那么我将如何修改Controller以使其发生呢?
AnthonyMonterrosa

@Steve Land,您可以使用如ControllerTest.cs中所示的继承。在派生的测试类中添加任何必要的公共方法,您将可以访问您在基类中拥有的所有受保护人员。
max_cn

@AnthonyMonterrosa,我们从所有外部资源中隐藏了Update,所以为什么要与某人共享它;)?当然,您可以添加一些公共方法来访问私有成员,但这对于任何其他类来说都是后门。无论如何,如果您出于测试目的需要它-使用继承来制作类似ControllerTest类的内容以在其中进行任何测试用例。
max_cn

9

编写单元测试时,Friend非常有用。

尽管这样做会以稍微污染类声明的代价为代价,但它还是由编译器强制提醒您哪些测试实际上可能关心类的内部状态。

我发现的一个非常有用且简洁的习惯用法是,当我拥有工厂类时,使他们成为自己创建的具有受保护的构造函数的项目的朋友。更具体地说,这是当我只有一个工厂负责为报表编写器对象创建匹配的呈现对象,并呈现给定环境时。在这种情况下,您对报告编写器类(如图片块,布局带,页面标题等)及其匹配的呈现对象之间的关系具有单一的了解。


我打算评论“朋友”对于工厂课堂很有用,但是我很高兴看到有人已经做到了。
爱德华·罗伯逊

9

C#缺少“朋友”关键字的原因与丢失确定性销毁的原因相同。不断变化的约定使人们感到聪明,好像他们的新方法优于别人的旧方法一样。这都是关于骄傲。

说“朋友班不好”与其他不合格的语句(如“不要使用gotos”或“ Linux比Windows更好”)一样短视。

“ friend”关键字与代理类结合使用是一种仅将类的某些部分暴露给特定的其他类的好方法。代理类可以充当针对所有其他类的可信任屏障。“公共”不允许进行任何此类定位,并且如果确实没有概念上的“是”关系,则使用“受保护”来获得继承效果是尴尬的。


C#缺少确定性销毁,因为它比垃圾回收慢。确定性销毁会为创建的每个对象花费时间,而垃圾回收仅会花费当前活动对象的时间。在大多数情况下,这样做速度更快,并且系统中存在的寿命较短的对象越多,垃圾收集的速度就越快。
爱德华·罗伯逊

1
@Edward Robertson除非定义了析构函数,否则确定性销毁不会花费任何时间。但是在这种情况下,垃圾回收会变得更糟,因为这时您需要使用终结器,终结器的速度较慢且不确定。
kaalus

1
...而内存不是唯一的资源。人们常常以真实的态度行事。
cubuspl42

8

您可以使用C#关键字“ internal”接近C ++“ friend


3
靠近,但不完全在那。我想这取决于您如何构造程序集/类,但是最好减少使用friend进行访问的类数。例如,在实用程序/帮助程序程序集中,并非所有类都有权访问内部成员。
灰烬

3
@Ash:您需要习惯它,但是一旦您将程序集视为应用程序的基本构建块,internal就有意义得多了,并在封装和实用程序之间进行了很好的权衡。不过,Java的默认访问权限甚至更好。
康拉德·鲁道夫

7

这实际上不是C#的问题。这是IL中的一个基本限制。C#受到此限制,其他任何可验证的.Net语言也受到此限制。此限制还包括C ++ / CLI(规范第20.5节)中定义的托管类。

话虽如此,我认为纳尔逊对这是一件坏事有很好的解释。


7
-1。friend这不是一件坏事;相反,它远比更好internal。尼尔森的解释是错误和错误的。
纳瓦兹

4

停止为此限制找借口。朋友不好,但内部好吗?它们是一回事,只有那个朋友才可以让您更精确地控制允许访问的人和不允许访问的人。

这是强制执行封装范例吗?所以您必须编写访问器方法,现在呢?您应该如何阻止所有人(B类的方法除外)调用这些方法?您不能,因为您也无法控制它,因为缺少“朋友”。

没有一种编程语言是完美的。C#是我见过的最好的语言之一,但是为缺少的功能提供愚蠢的借口并不能帮助任何人。在C ++中,我错过了简单的事件/委托系统,反射(+自动反序列化)和foreach,但是在C#中,我错过了运算符重载(是的,一直告诉我您不需要它),默认参数,一个const不能回避的多重继承(是的,告诉我您不需要它,并且接口可以替代),并具有决定从内存中删除实例的能力(不,除非您是继承人,否则这并不算糟糕)小叮当)


在C#中,您可以使大多数运算符重载。您不能重载赋值运算符,但是另一方面,您可以覆盖||/ &&并使它们保持短路。在C#4中添加默认的参数
CodesInChaos

2

从.Net 3开始有InternalsVisibleToAttribute,但是我怀疑他们只是在单元测试兴起之后才将其添加到测试装配中。我看不到其他许多原因来使用它。

它可以在组装级别工作,但可以在内部不工作的地方工作;也就是说,您要在其中分发程序集,但又希望另一个未分发的程序集具有对该程序集的特权访问。

正确地,它们要求对朋友程序集进行强键锁定,以避免有人在您受保护的程序集旁边创建一个伪装的朋友。


2

我已经读过许多关于“ friend”关键字的聪明评论,我同意这是有用的,但是我认为“ internal”关键字不太有用,并且它们都对纯OO编程不利。

我们有什么?(说到“朋友”,我也说到“内部”)

  • 使用“朋友”会使代码对oo的纯度降低?
  • 是;

  • 是不是使用“朋友”使代码更好?

  • 不,我们仍然需要在类之间建立一些私人关系,并且仅当我们破坏漂亮的封装时才可以这样做,所以它也不是一件好事,我可以说比使用“朋友”更邪恶。

使用friend会带来一些局部问题,而不使用它会给代码库用户带来麻烦。

我看到的编程语言的通用解决方案是:

// c++ style
class Foo {
  public_for Bar:
    void addBar(Bar *bar) { }
  public:
  private:
  protected:
};

// c#
class Foo {
    public_for Bar void addBar(Bar bar) { }
}

你怎么看待这件事?我认为这是最常见,最纯粹的面向对象解决方案。您可以打开选择的任何方法访问所需的任何类。


我不是OOP的哨兵,但看起来它可以解决每个人对朋友和内部人的困扰。但是我建议使用一个属性来避免C#语法的扩展:[PublicFor Bar] void addBar(Bar bar){} ...不是我确定这是有意义的。我不知道属性如何工作或它们是否可以摆弄可访问性(它们还有很多其他“神奇”的东西)。
Grault

2

我只会回答“如何”问题。

这里有很多答案,但是我想提出一种“设计模式”来实现该功能。我将使用简单的语言机制,其中包括:

  • 介面
  • 嵌套类

例如,我们有2个主要班级:学生和大学。学生拥有GPA,只有大学才可以访问。这是代码:

public interface IStudentFriend
{
    Student Stu { get; set; }
    double GetGPS();
}

public class Student
{
    // this is private member that I expose to friend only
    double GPS { get; set; }
    public string Name { get; set; }

    PrivateData privateData;

    public Student(string name, double gps) => (GPS, Name, privateData) = (gps, name, new PrivateData(this);

    // No one can instantiate this class, but Student
    // Calling it is possible via the IStudentFriend interface
    class PrivateData : IStudentFriend
    {
        public Student Stu { get; set; }

        public PrivateData(Student stu) => Stu = stu;
        public double GetGPS() => Stu.GPS;
    }

    // This is how I "mark" who is Students "friend"
    public void RegisterFriend(University friend) => friend.Register(privateData);
}

public class University
{
    var studentsFriends = new List<IStudentFriend>();

    public void Register(IStudentFriend friendMethod) => studentsFriends.Add(friendMethod);

    public void PrintAllStudentsGPS()
    {
        foreach (var stu in studentsFriends)
            Console.WriteLine($"{stu.Stu.Name}: stu.GetGPS()");
    }
}

public static void Main(string[] args)
{
    var Technion = new University();
    var Alex     = new Student("Alex", 98);
    var Jo       = new Student("Jo", 91);

    Alex.RegisterFriend(Technion);
    Jo.RegisterFriend(Technion);
    Technion.PrintAllStudentsGPS();

    Console.ReadLine();
}

1

我怀疑这与C#编译模型有关-在运行时构建IL JIT进行编译。即:C#泛型与C ++泛型根本不同的相同原因。


我认为编译器至少在程序集中的类之间至少可以合理地支持它。只需放宽对目标类上的朋友类的访问检查即可。外部程序集中的类之间的朋友可能需要对CLR进行更根本的更改。
Ash Ash

1

您可以将其设为私有并使用反射来调用函数。如果您要求测试框架测试私有功能,则测试框架可以执行此操作


除非您正在使用Silverlight应用程序。
kaalus

1

我以前经常使用朋友,但我认为这不违反OOP或任何设计缺陷的迹象。在很多地方,它是用最少的代码来达到正确结果的最有效方法。

一个具体的示例是在创建提供与某些其他软件的通信接口的接口程序集时。通常,有一些重量级类可以处理协议的复杂性和对等特性,并提供相对简单的连接/读取/写入/转发/断开连接模型,该模型涉及在客户端应用程序与程序集之间传递消息和通知。这些消息/通知需要包装在类中。通常,属性是属性的创建者,因此必须由协议软件对其进行操作,但是许多内容必须对外界保持只读状态。

声明对协议/“创建者”类进行所有创建的类的亲密访问是违反OOP的做法,这简直是愚蠢的。创建者类必须在不断上升的过程中逐一点处理所有数据。我发现最重要的是最小化“ OOP for SOP Sake”模型通常导致的所有BS额外代码行。多余的意大利面条只会使更多的错误。

人们是否知道您可以在属性,属性和方法级别应用内部关键字?它不仅用于顶层类声明(尽管大多数示例似乎都表明了这一点。)

如果您有一个使用friend关键字的C ++类,并且想在C#类中模拟它:1.声明C#类public 2.声明所有在C ++中受保护并因此可以作为朋友访问的属性/属性/方法C#3中的internal。创建只读属性以供公众访问所有内部属性和属性

我同意与朋友不是100%一样,并且单元测试是需要像朋友这样的非常有价值的示例(协议分析器日志记录代码也是如此)。但是internal提供了您要公开的类的公开信息,而[InternalVisibleTo()]处理其余部分-似乎它是专门为单元测试而生的。

至于朋友“变得更好,因为您可以显式地控制哪些类可以访问” –一堆在同一程序集中执行的可疑邪恶类到底是什么呢?对程序集进行分区!


1

可以通过分离接口和实现来模拟友谊。这个想法是:“ 需要一个具体实例,但限制对该实例的构造访问 ”。

例如

interface IFriend { }

class Friend : IFriend
{
    public static IFriend New() { return new Friend(); }
    private Friend() { }

    private void CallTheBody() 
    {  
        var body = new Body();
        body.ItsMeYourFriend(this);
    }
}

class Body
{ 
    public void ItsMeYourFriend(Friend onlyAccess) { }
}

尽管ItsMeYourFriend()只有公共Friend类才可以访问它,因为没有其他人可以获取Friend该类的具体实例。它具有私有构造函数,而factory New()方法则返回一个接口。

有关详细信息,请免费阅读我的文章“ 朋友和内部接口成员”,其中包括对接口进行编码


可悲的是,无法以任何可能的方式模拟友谊。看到这个问题:stackoverflow.com/questions/5921612/...
kaalus

1

有些人建议通过使用朋友可以使事情失去控制。我会同意,但这并不会减少它的用处。我不确定朋友是否会比公开所有班级成员更伤害OO范式。当然,这种语言可以使您将所有成员公开,但是它是一个有纪律的程序员,可以避免这种类型的设计模式。同样,训练有素的程序员会在有意义的特定情况下保留使用Friend的权限。在某些情况下,我感到内部暴露过多。为什么将类或方法公开给程序集中的所有内容?

我有一个ASP.NET页面,该页面继承了我自己的基础页面,而该基础页面又继承了System.Web.UI.Page。在此页面中,我有一些代码以受保护的方法处理应用程序的最终用户错误报告

ReportError("Uh Oh!");

现在,页面中包含一个用户控件。我希望用户控件能够调用页面中的错误报告方法。

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

如果ReportError方法受到保护,则无法执行此操作。我可以将其设置为内部,但可以在程序集中的任何代码中使用。我只希望它公开给当前页面一部分的UI元素(包括子控件)。更具体地说,我希望基本控件类定义完全相同的错误报告方法,并仅在基本页面中调用方法。

protected void ReportError(string str) {
    MyBasePage bp = Page as MyBasePage;
    bp.ReportError(str);
}

我相信类似“朋友”之类的东西可以在该语言中有用并实现,而又不会使该语言减少“ OO”之类的(也许作为属性),以便您可以让类或方法成为特定类或方法的朋友,从而使开发人员能够提供具体的访问权限。也许类似...(伪代码)

[Friend(B)]
class A {

    AMethod() { }

    [Friend(C)]
    ACMethod() { }
}

class B {
    BMethod() { A.AMethod() }
}

class C {
    CMethod() { A.ACMethod() }
}

在我之前的示例中,可能有类似以下内容的内容(可以争论语义,但我只是想将其理解):

class BasePage {

    [Friend(BaseControl.ReportError(string)]
    protected void ReportError(string str) { }
}

class BaseControl {
    protected void ReportError(string str) {
        MyBasePage bp = Page as MyBasePage;
        bp.ReportError(str);
    }
}

正如我所看到的,与公开事物或创建公共方法或属性以访问成员相比,朋友概念没有更多的风险。如果有任何朋友允许对数据的可访问性进行另一级别的细化,并允许您缩小该可访问性,而不是通过内部或公共范围来扩展它。


0

如果您使用的是C ++,并且使用friend关键字找到了自己,这是一个非常有力的信号,表明您遇到了设计问题,因为为什么某个类需要访问其他类的私有成员?


我同意。我永远不需要friend关键字。通常用于解决复杂的维护问题。
爱德华A.

7
操作员超载,有人吗?
我们都是莫妮卡

3
您有多少次发现操作员严重超载的好案例?
bashmohandes

8
我将为您提供一个非常简单的案例,该案例完全掩盖了您的“设计问题”评论。亲子 父母拥有自己的孩子。父母的“孩子”集合中的一个孩子由该父母拥有。Parent属性对Child的设置者不应由任何人设置。仅在将子级添加到父级的“子级”集合中时,才需要对其进行分配。t如果是公开的,任何人都可以更改。内部:该程序集中的任何人。私人的?没有人。与二传手成为父母的朋友可以消除这些情况,并且仍然使您的课程分开但紧密相关。头晕!
Mark A. Donohoe

2
有很多这样的情况,例如大多数基本的经理/托管模式。关于SO的另一个问题是,没有朋友,没人想出一种方法。不知道是什么使人们认为一个阶级“永远满足”。有时不止一个班级需要一起工作。
kaalus

0

s

据说,朋友伤害纯粹的OOness。我同意。

也有人说,朋友帮助封装,我也同意。

我认为应该将友情添加到OO方法中,但不完全像C ++中那样。我想有一些我的朋友班级可以访问的字段/方法,但是我不希望他们访问我所有的字段/方法。在现实生活中,我会让我的朋友使用我的个人冰箱,但不要让他们使用我的银行帐户。

可以实现如下

    class C1
    {
        private void MyMethod(double x, int i)
        {
            // some code
        }
        // the friend class would be able to call myMethod
        public void MyMethod(FriendClass F, double x, int i)
        {
            this.MyMethod(x, i);
        }
        //my friend class wouldn't have access to this method 
        private void MyVeryPrivateMethod(string s)
        {
            // some code
        }
    }
    class FriendClass
    {
        public void SomeMethod()
        {
            C1 c = new C1();
            c.MyMethod(this, 5.5, 3);
        }
    }

这当然会产生编译器警告,并且会损害智能感知。但是它将完成工作。

附带一提,我认为一个有信心的程序员应该在不访问私有成员的情况下执行测试单元。这超出了范围,但请尝试阅读有关TDD的内容。但是,如果您仍然想这样做(像朋友一样使用c ++),请尝试类似

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

因此,您在编写所有代码时都没有定义UNIT_TESTING,并且当您要进行单元测试时,请将#define UNIT_TESTING添加到文件的第一行(并在#if UNIT_TESTING下编写所有进行单元测试的代码)。那应该小心处理。

由于我认为单元测试对于使用朋友来说是一个不好的例子,所以我举一个为什么我认为朋友可以成为一个好例子的例子。假设您有一个中断系统(类)。使用后,破碎系统会磨损并且需要翻新。现在,您只希望有执照的机械师可以修复它。为了简化示例,我想说技工将使用他的私人(专用)螺丝刀对其进行修复。这就是为什么机械师类应该成为BreakingSystem类的朋友的原因。


2
该FriendClass参数不用作访问控制:您可以c.MyMethod((FriendClass) null, 5.5, 3)从任何类调用。
杰西·麦格鲁

0

也可以通过使用“代理”(某些内部类)来模拟友谊。考虑以下示例:

public class A // Class that contains private members
{
  private class Accessor : B.BAgent // Implement accessor part of agent.
  {
    private A instance; // A instance for access to non-static members.
    static Accessor() 
    { // Init static accessors.
      B.BAgent.ABuilder = Builder;
      B.BAgent.PrivateStaticAccessor = StaticAccessor;
    }
    // Init non-static accessors.
    internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
    // Agent constructor for non-static members.
    internal Accessor(A instance) { this.instance = instance; }
    private static A Builder() { return new A(); }
    private static void StaticAccessor() { A.PrivateStatic(); }
  }
  public A(B friend) { B.Friendship(new A.Accessor(this)); }
  private A() { } // Private constructor that should be accessed only from B.
  private void SomePrivateMethod() { } // Private method that should be accessible from B.
  private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
  // Agent for accessing A.
  internal abstract class BAgent
  {
    internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
    internal static Action PrivateStaticAccessor;
    internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
  }
  internal static void Friendship(BAgent agent)
  {
    var a = BAgent.ABuilder(); // Access private constructor.
    BAgent.PrivateStaticAccessor(); // Access private static method.
    agent.PrivateMethodAccessor(); // Access private non-static member.
  }
}

当仅用于访问静态成员时,它可能会更简单。这种实现的好处是,所有类型都在友谊类的内部范围中声明,并且与接口不同,它允许访问静态成员。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.