C#がC ++スタイルの 'friend'キーワードを提供しないのはなぜですか?[閉まっている]


208

C ++の友人のキーワードが可能にclass A指定するclass Bその友人として。これによりClass Bprivate/のprotectedメンバーにアクセスできますclass A

これがC#(およびVB.NET)から除外された理由について、私は何も読んだことがありません。この以前のStackOverflowの質問に対するほとんどの回答は、C ++の有用な部分であり、それを使用する十分な理由があると述べています。私の経験では同意する必要があります。

もう1つの質問friendは、C#アプリケーションと同様のことをする方法を本当に尋ねているようです。答えは一般にネストされたクラスを中心に展開しますが、friendキーワードを使用するほどエレガントではありません。

オリジナルのDesign Patternsブックでは、例全体で定期的に使用しています。

つまり、要約すると、なぜfriendC#に欠けているのか、そしてそれをC#でシミュレートする「ベストプラクティス」の方法とは何でしょうか。

(ちなみに、internalキーワードは同じものではなく、アセンブリ全体のすべてのクラスがinternalメンバーにアクセスfriendできるようにする一方で、特定のクラスに他の1つのクラスへの完全なアクセス権を与えることができます)


5
内部の保護は妥協案だと思います
。– Gulzar Nazim

3
混乱しすぎではないですか?上で述べたように、GOFの本では例の中でかなり頻繁に使用されています。私にとって、それは内部よりも混乱することはありません。
アッシュ、

7
すでに単体テストで触れたように、それらが非常に役立つシナリオを確実に見ることができます。しかし、本当にあなたの友人があなたのプライベートにアクセスすることを望みますか?
danswain

2
答えは簡単です。C#は、同じモジュール/アセンブリ内のすべてのコードへのアクセスを許可するアクセス修飾子として「内部」を提供します。これにより、友人などの必要がなくなります。Javaでは、キーワード "protected"は同じパッケージからのアクセスに対して同様に動作します。
sellibitze

2
@sellibitze、私は質問で内部との違いに言及します。Interanlの問題は、アセンブリ内のすべてのクラスが内部メンバーにアクセスできることです。これらのクラスの多くはアクセスを必要としない可能性があるため、これはカプセル化を解除します。
アッシュ

回答:


67

プログラミングに友人がいることは、多かれ少なかれ「汚い」と見なされ、悪用されやすい。クラス間の関係を壊し、オブジェクト指向言語のいくつかの基本的な属性を損なう。

そうは言っても、それはすばらしい機能であり、私はC ++で何度も自分自身でそれを使用しました。C#でも使用したいと考えています。しかし、C#の「純粋な」OOness(C ++の疑似OOnessと比較して)のために私は賭けます

深刻な注意点として、内部は友達ほど良くはありませんが、仕事を成し遂げます。DLLを介さずにサードパーティの開発者にコードを配布することはまれであることに注意してください。あなたとあなたのチームが内部クラスとその使用法を知っている限り、あなたは大丈夫です。

編集 friendキーワードがOOPをどのように損なうのかを明確にしましょう。

プライベート変数と保護変数およびメソッドは、おそらくOOPの最も重要な部分の1つです。オブジェクトは、オブジェクトだけが使用できるデータまたはロジックを保持できるという考え方により、環境に依存せずに機能の実装を作成できます。また、環境では、処理に適さない状態情報を変更できません。friendを使用することで、2つのクラスの実装を結合します。これは、インターフェースを結合しただけの場合よりもはるかに悪いです。


167
C#の「内部」は、実際にはC ++の「友達」よりもカプセル化をはるかに害しています。「友情」を使用すると、所有者は明示的に、誰にでも許可を与えます。「内部」はオープンエンドのコンセプトです。
Nemanja Trifunovic

21
アンダースは、彼が含めたものではなく、彼が省いた機能について賞賛されるべきです。
Mehrdad Afshari、

21
C#の内部がカプセル化を損なうことには同意しません。私の意見では反対です。これを使用して、アセンブリ内にカプセル化されたエコシステムを作成できます。多数のオブジェクトが、このエコシステム内の残りのアプリケーションに公開されない内部型を共有できます。「フレンド」アセンブリトリックを使用して、これらのオブジェクトの単体テストアセンブリを作成します。
2009年

26
これは意味がありません。friend任意のクラスまたは関数がプライベートメンバーにアクセスすることを許可しません。フレンドとしてマークされた特定の関数またはクラスがそうすることができます。プライベートメンバーを所有するクラスは、そのクラスへのアクセスを100%制御します。パブリックメンバーメソッドを主張することもできます。どちらも、クラスに具体的にリストされているメソッドに、クラスのプライベートメンバーへのアクセス権が確実に付与されるようにします。
2010年

8
私は全く逆だと思います。アセンブリに50のクラスがあり、そのうちの3つのクラスがユーティリティクラスを使用している場合、今日の唯一の選択肢は、そのユーティリティクラスを「内部」としてマークすることです。これにより、3つのクラスだけでなく、アセンブリ内の残りのクラスでも使用できるようになります。問題の3つのクラスのみがユーティリティクラスにアクセスできるように、よりきめ細かいアクセスを提供する場合はどうでしょうか。カプセル化が向上するため、実際にはオブジェクト指向になっています。
zumalifeguard、2011年

104

余談です。friendを使用することはカプセル化に違反することではありませんが、逆にそれを強制することです。アクセサー+ミューテーター、演算子のオーバーロード、パブリック継承、ダウンキャストなどと同様に、誤用されることがよくありますが、キーワードに悪い目的がない、または悪いことに、目的が悪いというわけではありません。

参照してくださいコンラートルドルフメッセージを他のスレッドに、またはあなたが好むかどうかを確認し、関連するエントリ C ++よくある質問でを。


...そしてFQAの対応するエントリ:yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee

28
友達を使用することは、カプセル化に違反することではありませんが、逆にそれを強制することです
Nawaz

2
意味論的な意見の問題だと思います。そしておそらく問題の正確な状況に関連しています。クラスa friendを作成すると、クラスの1つだけを設定する必要がある場合でも、すべてのプライベートメンバーにアクセスできます。フィールドを必要としない別のクラスがフィールドを使用できるようにすることは、「カプセル化違反」としてカウントされるという強い主張があります。C ++には必要な場所(演算子のオーバーロード)がありますが、カプセル化のトレードオフがあり、慎重に検討する必要があります。C#の設計者は、トレードオフに価値がないと感じたようです。
GrandOpener、2015年

3
カプセル化とは、不変条件を強制することです。クラスを別のクラスのフレンドとして定義する場合、2つのクラスは最初のクラスの不変式の管理に関して強く結びついていると明示的に言います。クラスをフレンドにすることは、すべての属性を直接(パブリックフィールドとして)またはセッターを通じて公開するよりも優れています。
Luc Hermitte、2015年

1
丁度。フレンドを使用したいが使用できない場合は、内部またはパブリックを使用せざるを得ません。内部またはパブリックを使用すると、禁止されているものに突き刺されやすくなります。特にチーム環境では。
孵化

50

参考までに、.NETの他の関連[InternalsVisibleTo]する重要ではないものはです。これにより、アセンブリは、ユニットテストアセンブリなど、別のアセンブリ(ユニットテストアセンブリなど)を指定できます。元のアセンブリ。


3
良いヒントです。おそらく友人を探している人は、より内部的な「知識」を持つ能力を備えた単体テストの方法を見つけたいと思うことが多いからです。
SwissCoder 2012年

14

C#のインターフェイスを使用することで、C ++で「友達」が使用されているのと同じ種類のことを実行できるはずです。2つのクラス間で渡されるメンバーを明示的に定義する必要があります。これは追加の作業ですが、コードを理解しやすくする場合もあります。

インターフェースを使用してシミュレートできない「友達」の合理的な使用例が誰かにあれば、それを共有してください!C ++とC#の違いをよりよく理解したいと思います。


いい視点ね。以前のSOの質問(一酸化炭素によって尋ねられ、上記のリンク)を見る機会がありましたか?C#でそれを解決する最善の方法に興味がありますか?
Ash

C#でこれを行う方法は正確にはわかりませんが、一酸化炭素の質問に対するJon Skeetの回答は正しい方向にあると思います。C#ではネストされたクラスを使用できます。ただし、実際にコードアウトしてソリューションをコンパイルすることは試みていません。:)
パラッパ

メディエーターパターンは、友だちがいい場所の良い例だと思います。フォームをリファクタリングしてメディエーターを作成します。メディエーターの「イベント」のようなメソッドをフォームが呼び出せるようにしたいのですが、他のクラスがそれらの「イベント」のようなメソッドを呼び出すことは望んでいません。「Friend」機能は、アセンブリ内の他のすべてのものまで開かずに、フォームにメソッドへのアクセスを提供します。
ヴァッカーノ2009

3
'Friend'を置き換えるインターフェースアプローチの問題は、オブジェクトがインターフェースを公開することです。つまり、誰でもそのインターフェースにオブジェクトをキャストして、メソッドにアクセスできます。インターフェースを内部、保護、またはプライベートにスコープすることは、それが機能したかのように機能しません。問題のメソッドをスコープするだけです!ショッピングモールの母と子について考えます。パブリックは世界中の誰でもです。内部はモール内の人々です。プライベートは母親か子供です。「友達」は、母と娘だけの間の何かです。
Mark A. Donohoe、2011

1
1つは、次の とおりです。stackoverflow.com / questions / 5921612 / …C ++のバックグラウンドを持っているので、友達の欠如がほぼ毎日狂っています。C#での私の数年間で、私は文字通り何百ものメソッドをパブリックにしましたが、それらはすべてプライベートで安全なものになりました。個人的には、友達が.NETに追加すべき最も重要なものだと思います
kaalus

14

friendC ++のデザイナーは、プライベート・メンバーがにさらされている人を正確に制御されています。しかし、彼は私的なメンバーのすべてを公開することを余儀なくされています。

internalC#を使用すると、デザイナーは公開しているプラ​​イベートメンバーのセットを正確に制御できます。明らかに、彼は単一のプライベートメンバーだけを公開できます。ただし、アセンブリ内のすべてのクラスに公開されます。

通常、デザイナーは、選択した他のいくつかのクラスにいくつかのプライベートメソッドのみを公開することを望んでいます。たとえば、クラスファクトリパターンでは、クラスC1がクラスファクトリCF1によってのみインスタンス化されることが望ましい場合があります。したがって、クラスC1には、保護されたコンストラクターとフレンドクラスファクトリCF1がある場合があります。

ご覧のとおり、カプセル化に違反する可能性のある2つの次元があります。 friend1つの次元に沿ってそれを破り、もう1つの次元internalに沿ってそれを行います。どちらがカプセル化のコンセプトの悪い違反ですか?言いにくい。しかし、両方friendinternal用意し、利用できるようにしておくとよいでしょう。さらに、これら2つに適切な追加は3番目のタイプのキーワードです。これは、メンバーごとに(などinternal)使用され、ターゲットクラス(などfriend)を指定します。

*簡潔にするために、「private and / or protected」ではなく「private」を使用します。

-ニック


13

実際、C#は、特別な言葉なしで純粋なOOPの方法で同じ動作を実現する可能性を提供します。これはプライベートインターフェイスです。

質問までC#の友達と同等のものは何ですか?この記事の重複としてマークされ、誰も本当に良い実現を提案していません-私はここで両方の質問に対する答えを示します。

ここからの主なアイデアは、プライベートインターフェイスとは何ですか。

たとえば、別のクラスのインスタンスを管理し、それらに対して特別なメソッドを呼び出すことができるクラスが必要だとします。このメソッドを他のクラスに呼び出す可能性を与えたくありません。これは、c ++の世界でfriend 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.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 2017

@max_cnこれは本当に素晴らしい解決策ですが、ユニットテスト用のモックフレームワークでこれを機能させる方法がわかりません。助言がありますか?
スティーブランド

私は少し混乱しています。最初の例(プライベートインターフェイス)で外部クラスがUpdateを呼び出せるようにしたい場合、それを可能にするためにコントローラーをどのように変更しますか?
AnthonyMonterrosa

@Steve Land、ControllerTest.csで示したように継承を使用できます。派生テストクラスに必要なパブリックメソッドを追加すると、基本クラスにいるすべての保護されたスタッフにアクセスできます。
max_cn

@ AnthonyMonterrosa、Updateをすべての外部リソースから隠しているので、なぜそれを誰かと共有したいのですか;)?もちろん、プライベートメソッドにアクセスするパブリックメソッドを追加できますが、それは他のクラスのバックドアのようなものです。とにかく、テスト目的で必要な場合は、継承を使用してControllerTestクラスのようなものを作成し、そこでテストケースを作成します。
max_cn

9

フレンドはユニットテストを書くときに非常に役立ちます。

これは、クラス宣言をわずかに汚染するという犠牲を伴いますが、どのテストが実際にクラスの内部状態を気にする可能性があるかをコンパイラーが強制するメモでもあります。

私が見つけた非常に便利でクリーンなイディオムは、ファクトリークラスがあり、保護されたコンストラクターを持つそれらが作成するアイテムの友達になるときです。より具体的には、これは、特定の環境にレンダリングする、レポートライターオブジェクトの一致するレンダリングオブジェクトの作成を担当する単一のファクトリがあったときでした。この場合、レポートライタークラス(画像ブロック、レイアウトバンド、ページヘッダーなど)とそれらに対応するレンダリングオブジェクトとの関係に関する単一の知識があります。


「友達」がファクトリークラスに役立つことについてコメントしようと思っていましたが、誰かがすでに作っているのを見てうれしいです。
エドワードロバートソン

9

C#には、確定的破壊が欠落しているのと同じ理由で、「friend」キーワードが欠落しています。慣例を変えることで、新しい方法が他の人の古い方法よりも優れているかのように、人々はスマートに感じられます。それはすべてプライドについてです。

「友達のクラスは悪い」と言うのは、「gotoを使用しない」や「LinuxはWindowsより優れている」など、他の修飾されていないステートメントと同じくらい近視眼的です。

「friend」キーワードをプロキシクラスと組み合わせると、クラスの特定の部分だけを他の特定のクラスに公開できます。プロキシクラスは、他のすべてのクラスに対する信頼できるバリアとして機能できます。"public"はそのようなターゲティングを許可していません。概念的な "is a"関係が実際にない場合、 "protected"を使用して継承の効果を得るのは厄介です。


C#はガベージコレクションよりも低速であるため、確定的な破壊がありません。確定的な破棄は、作成されたすべてのオブジェクトに対して時間がかかりますが、ガベージコレクションは、現在ライブなオブジェクトに対してのみ時間がかかります。ほとんどの場合、これはより高速であり、システム内の存続期間の短いオブジェクトが多いほど、ガベージコレクションは比較的高速になります。
エドワードロバートソン

1
@Edward Robertson確定的破棄は、デストラクタが定義されていない限り、時間がかかりません。ただし、この場合はガベージコレクションの方がはるかに悪くなります。それは、ファイナライザーを使用する必要があるためです。
kaalus 2011年

1
...そしてメモリは唯一の種類のリソースではありません。人々はしばしばそれが本当であるかのように振る舞います。
cubuspl42 2015

8

C#のキーワード「internal」を使用すると、C ++の「友達」に近づくことができます。


3
近いですが、それほどではありません。アセンブリ/クラスの構造に依存すると思いますが、friendを使用してアクセスを許可するクラスの数を最小限に抑える方がよりエレガントです。ユーティリティまたはヘルパーアセンブリの例では、すべてのクラスが内部メンバーにアクセスできる必要はありません。
Ash

3
@Ash:慣れる必要がありますが、アプリケーションの基本的なビルディングブロックとしてアセンブリを確認internalすると、カプセル化とユーティリティの優れたトレードオフが実現し、非常にわかりやすくなります。ただし、Javaのデフォルトアクセスはさらに優れています。
Konrad Rudolph、

7

これは実際にはC#の問題ではありません。これはILの基本的な制限です。C#は、検証可能であることを求める他の.Net言語と同様に、これによって制限されます。この制限には、C ++ / CLI(Specセクション20.5)で定義されたマネージクラスも含まれます。

そうは言っても、ネルソンはなぜこれが悪いことなのかについて良い説明があると思います。


7
-1。friend悪いことではありません。逆に、それははるかに優れていinternalます。そしてネルソンの説明は悪くて間違っています。
Nawaz

4

この制限の言い訳をやめる。友達は悪いですが、内部は良いですか?それらは同じものです。その友人だけが、誰がアクセスを許可され、誰が許可されないかをより正確に制御できます。

これは、カプセル化パラダイムを実施することですか?だからあなたはアクセサメソッドを書かなければならない、そして今何?(クラスBのメソッドを除く)全員がこれらのメソッドを呼び出せないようにするにはどうすればよいですか?「友達」がいないため、これを制御することもできないため、できません。

完璧なプログラミング言語はありません。C#は私が見た中で最高の言語の1つですが、不足している機能について愚かな言い訳をしても誰にとっても役に立ちません。C ++では、簡単なイベント/デリゲートシステム、リフレクション(+自動逆シリアル化)、およびforeachがありませんが、C#では、オペレーターのオーバーロードがありません(そうです、必要ないことを伝え続けてください)、デフォルトのパラメーター、constそれを回避することはできません、多重継承(そうではありませんが、インターフェイスは十分な置き換えでした)とインスタンスをメモリから削除することを決定する機能(いいえ、あなたがそうでない限り、これはひどく悪いことではありません)ティンカーラー)


C#では、ほとんどの演算子をオーバーロードできます。代入演算子をオーバーロードすることはできませんが、一方で、短絡を維持しながら||/ &&をオーバーライドできます。デフォルトのパラメータは、C#4で追加されました
CodesInChaos

2

.Net 3以降にInternalsVisibleToAttributeがありますが、単体テストの台頭後にアセンブリをテストするためだけに追加したのではないかと思います。それを使用する他の多くの理由がわかりません。

アセンブリレベルで機能しますが、内部では機能しません。つまり、アセンブリを配布したいが、配布されていない別のアセンブリにアクセス権を与えたい場合です。

当然のことながら、保護されたアセンブリと一緒に誰かが偽のフレンドを作成することを避けるために、フレンドアセンブリには強力なキーが必要です。


2

「friend」キーワードについて多くのスマートコメントを読みましたが、それが何に役立つかについては同意しますが、「internal」キーワードはあまり役に立たないと思います。どちらも純粋なOOプログラミングにはまだ悪いと思います。

何がありますか?(「友達」について言って、「内部」についても言っています)

  • 「friend」を使用すると、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#の構文の拡張を回避するために属性を使用することをお勧めします。私は属性がどのように機能するか、またはそれらがアクセシビリティをいじくることができるかどうかを知りません(それらは他の多くの「魔法の」ものをします)。
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にもっと根本的な変更を必要とするかもしれません。
アッシュ

1

プライベートにして、リフレクションを使用して関数を呼び出すことができます。プライベート関数をテストするように依頼すると、テストフレームワークはこれを実行できます


Silverlightアプリケーションで作業している場合を除きます。
kaalus

1

私はいつも友達を使っていましたが、それはOOPの違反やデザインの欠陥の兆候ではないと思います。最小限のコードで適切に終了するには、いくつかの場所が最も効率的な手段です。

具体的な例の1つは、他のソフトウェアへの通信インターフェースを提供するインターフェースアセンブリを作成する場合です。一般に、プロトコルの複雑さとピアの特性を処理し、クライアントアプリとアセンブリの間でメッセージと通知を渡すことを含む比較的単純な接続/読み取り/書き込み/転送/切断モデルを提供する、いくつかのヘビー級クラスがあります。これらのメッセージ/通知はクラスでラップする必要があります。属性は作成者であるため、一般的にプロトコルソフトウェアで操作する必要がありますが、多くの要素は外部からは読み取り専用のままにする必要があります。

プロトコル/「作成者」クラスが作成されたすべてのクラスに密接にアクセスすることはOOPの違反であることを宣言するのは、ばかげたことです。作成者クラスは、すべてのデータを少しずつ変更する必要がありました。私が最も重要だと思ったのは、 "OOP for SOP"モデルが通常もたらすBSの余分なコード行をすべて最小化することです。余分なスパゲッティはバグを増やすだけです。

内部キーワードを属性、プロパティ、メソッドレベルで適用できることを知っていますか?それはトップレベルのクラス宣言のためだけではありません(ほとんどの例はそれを示しているようですが)。

friendキーワードを使用するC ++クラスがあり、それをC#クラスでエミュレートしたい場合は、次のようにします。1. C#クラスpublicを宣言します。2. C ++で保護されているため、次のように友達にアクセスできるすべての属性/プロパティ/メソッドを宣言します。 C#3の内部。すべての内部属性とプロパティへのパブリックアクセス用の読み取り専用プロパティを作成します。

私はそれが友達と100%同じではないことに同意します、そしてユニットテストは友達のようなものの必要性の非常に貴重な例です(プロトコルアナライザーロギングコードも同様です)。ただし、内部は、公開したいクラスへの公開を提供し、[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クラスの具体的なインスタンスを取得できないためです。これにはプライベートコンストラクターがあり、ファクトリーNew()メソッドはインターフェースを返します。

詳細については、インターフェースのコーディングを使用して、私の記事「Friends and internal interface members」を無料で参照してください。


悲しいことに、友情はいかなる方法でもシミュレートできません。:この質問を参照してくださいstackoverflow.com/questions/5921612/...
kaalus

1

友人を使用することで物事が暴走する可能性があることを示唆する人もいます。私は同意しますが、それはその有用性を低下させません。クラスのメンバー全員を公開する以上に、友達がオブジェクト指向のパラダイムを傷つけるとは限らない。確かに、この言語ではすべてのメンバーをパブリックにすることができますが、そのタイプのデザインパターンを回避するのは規律あるプログラマーです。同様に、訓練されたプログラマは、それが理にかなっている特定の場合のために友達の使用を予約します。場合によっては、内部の露出が多すぎると感じています。なぜクラスまたはメソッドをアセンブリ内のすべてに公開するのですか?

自分のベースページを継承し、次にSystem.Web.UI.Pageを継承するASP.NETページがあります。このページには、保護されたメソッドでアプリケーションのエンドユーザーエラーレポートを処理するコードがあります

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キーワードは必要ありません。通常、複雑なメンテナンスの問題を解決するために使用されます。
エドワールA.

7
オペレーターの過負荷、誰ですか?
私たちは全員モニカです。

3
オペレーターのオーバーロードに真剣に対応する良い例を何回見つけましたか?
bashmohandes 09/09/30

8
「デザインの問題」のコメントを完全に覆い隠す非常に簡単なケースを紹介します。親子。親は子供を所有します。親の「Children」コレクションの子は、その親が所有しています。Childに対するParentプロパティのセッターは、誰もが設定できるようにするべきではありません。子が親のChildrenコレクションに追加された場合にのみ割り当てる必要があります。t公開されている場合は、誰でも変更できます。内部:そのアセンブリの誰でも。民間?誰も。セッターを親の友達にすることで、これらのケースを排除し、クラスを別々に保ちますが、密接に関連しています。フザー!!
Mark A. Donohoe

2
最も基本的なマネージャー/管理パターンなど、そのようなケースはたくさんあります。SOについての別の質問があり、誰も友人なしでそれを行う方法を考え出していません。1つのクラスで「常に十分である」と人々に何を思わせるのかわからない。時には、複数のクラスが連携する必要があります。
kaalus、2011年

0

BSD

友人は純粋なオブジェクト指向を傷つけると述べられました。私はそれに同意します。

友人がカプセル化を手伝うとも述べられましたが、私も同意します。

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)。どのクラスからでも呼び出すことができます。
Jesse McGrew 2012

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.
  }
}

静的メンバーのみへのアクセスに使用すると、はるかに単純になる可能性があります。このような実装の利点は、すべての型が友情クラスの内部スコープで宣言され、インターフェイスとは異なり、静的メンバーにアクセスできることです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.