C #에서 C ++ 스타일의 'friend'키워드를 제공하지 않는 이유는 무엇입니까? [닫은]


208

C ++ 친구 키워드는 A가 있습니다 class A지정 class B의 친구로. 이것으로 /의 멤버에 Class B액세스 할 수 있습니다 .privateprotectedclass A

왜 이것이 C # (및 VB.NET)에서 제외되었는지에 대해서는 아무것도 읽지 못했습니다. 이 이전 StackOverflow 질문에 대한 대부분의 답변 은 C ++의 유용한 부분이라고 말하고 사용해야하는 이유가 있습니다. 내 경험상 나는 동의해야한다.

또 다른 질문friendC # 응용 프로그램에서 비슷한 작업을 수행하는 방법을 묻는 것 같습니다 . 대답은 일반적으로 중첩 클래스를 중심으로 이루어 지지만 friend키워드 를 사용하는 것만 큼 우아하지는 않습니다 .

원본 디자인 패턴 책 은 예제 전체에서 정기적으로 사용합니다.

요약하면 friendC #에서 누락 된 이유는 무엇이며 C #에서이를 시뮬레이션하는 "모범 사례"방법은 무엇입니까?

( internal키워드는 동일 하지 않으므로 전체 어셈블리 내의 모든 클래스가 internal멤버 에 액세스 friend할 수있는 반면 특정 클래스 에 정확히 하나의 다른 클래스에 대한 완전한 액세스 권한 을 부여 할 수 있습니다 )


5
나는 내부 보호 좋은 타협이다 .. 느낌
굴자는 나짐에게

3
너무 혼란스럽지 않습니까? 위에서 말했듯이 GOF 책은 예제에서 자주 사용합니다. 나에게는 더 이상 혼란스럽지 않습니다.
Ash

7
이미 유닛 테스트에서 언급했듯이 시나리오가 매우 유용 할 수있는 시나리오를 확실히 볼 수 있습니다. 그러나 당신은 정말로 당신의 친구들이 당신의 개인에게 접근하기를 원합니까?
danswain

2
대답하기 쉽습니다. C #은 "내부"를 액세스 수정 자로 제공하여 동일한 모듈 / 어셈블리의 모든 코드에 대한 액세스 권한을 부여합니다. 이것은 친구와 같은 것을 필요로하지 않습니다. Java에서 키워드 "protected"는 동일한 패키지에서 비슷한 WRT 액세스를합니다.
sellibitze

2
@ sellibitze, 나는 질문에서 내부의 차이점을 언급합니다. Interanl의 문제점은 어셈블리의 모든 클래스가 내부 멤버에 액세스 할 수 있다는 것입니다. 이러한 클래스 중 하나라도 액세스 할 필요가 없으므로 캡슐화가 중단됩니다.
Ash

답변:


67

프로그래밍에 친구를 둔다는 것은 "더러운"것으로 간주되며 남용하기 쉽습니다. 클래스 간의 관계를 깨고 OO 언어의 일부 기본 속성을 약화시킵니다.

그것은 훌륭한 기능이며 C ++에서 여러 번 사용해 보았습니다. C #에서도 사용하고 싶습니다. 그러나 C #의 "순수한"OOness (C ++의 의사 OOness와 비교) 때문에 내기 MS는 Java에 친구 키워드가 없기 때문에 C #도 농담해서는 안된다고 결정했습니다.

심각한 메모 : 내부는 친구만큼 좋지 않지만 일을 끝내줍니다. DLL을 통하지 않고 타사 개발자에게 코드를 배포하는 경우는 드 rare니다. 귀하와 귀하의 팀이 내부 수업과 그 사용에 대해 알고있는 한 괜찮습니다.

편집 friend 키워드가 OOP를 어떻게 손상시키는 지 설명하겠습니다.

개인 및 보호 변수 및 방법은 아마도 OOP에서 가장 중요한 부분 중 하나 일 것입니다. 객체 만이 사용할 수있는 데이터 나 로직을 보유 할 수 있다는 아이디어를 통해 환경과 독립적으로 기능 구현을 작성할 수 있으며 환경에서 처리하기에 적합하지 않은 상태 정보를 변경할 수는 없습니다. 친구를 사용하면 두 클래스의 구현을 함께 결합 할 수 있습니다. 인터페이스를 결합하면 훨씬 나쁩니다.


167
C # 'internal'은 실제로 C ++ 'friend'보다 훨씬 많은 캡슐화를 손상시킵니다. "우정"을 통해 소유자는 원하는 사람에게 명시 적으로 권한을 부여합니다. "내부"는 개방형 개념입니다.
Nemanja Trifunovic

21
Anders는 그가 포함하지 않은 기능에 대해 칭찬 받아야합니다.
Mehrdad Afshari

21
C #의 내부가 캡슐화를 해치는 것에 동의하지 않습니다. 내 의견으로는 반대입니다. 이를 사용하여 어셈블리 내에 캡슐화 된 에코 시스템을 만들 수 있습니다. 다수의 객체는이 생태계 내에서 나머지 애플리케이션에 노출되지 않는 내부 유형을 공유 할 수 있습니다. "친구"어셈블리 트릭을 사용하여 이러한 개체에 대한 단위 테스트 어셈블리를 만듭니다.
Quibblesome

26
이것은 말이되지 않습니다. friend임의의 클래스 또는 함수가 개인 멤버에 액세스하도록 허용하지 않습니다. 친구로 표시된 특정 기능이나 클래스가 그렇게 할 수 있습니다. 개인 멤버를 소유 한 클래스는 여전히 100 %에 대한 액세스를 제어합니다. 공개 멤버 메소드를 주장 할 수도 있습니다. 둘 다 클래스에 구체적으로 나열된 메소드에 클래스의 개인 멤버에게 액세스 권한이 부여되도록합니다.
jalf

8
나는 정반대로 생각합니다. 어셈블리에 50 개의 클래스가있는 경우 해당 클래스 중 3 개가 일부 유틸리티 클래스를 사용하는 경우 오늘 유일한 옵션은 해당 유틸리티 클래스를 "내부"로 표시하는 것입니다. 이렇게하면 3 개의 클래스뿐만 아니라 어셈블리의 나머지 클래스에도 사용할 수 있습니다. 원하는 세 가지 클래스 만 유틸리티 클래스에 액세스 할 수 있도록 세밀한 액세스를 제공하는 것입니다. 실제로 캡슐화를 향상시키기 때문에 객체 지향성을 향상시킵니다.
zumalifeguard

104

부수적으로. 친구 사용은 캡슐화를 위반하는 것이 아니라 반대로 캡슐화를 위반하는 것입니다. 접근 자 + 뮤 테이터, 연산자 오버로드, 퍼블릭 상속, 다운 캐스팅 등과 같이 종종 잘못 사용되지만 키워드에 나쁜 목적이 없거나 더 나쁜 것은 아닙니다.

참조 콘라드 루돌프메시지를 다른 스레드에서, 또는 당신이 선호하는 경우 참조 관련 항목 은 C ++ FAQ에 있습니다.


... 그리고 FQA의 해당 항목 : yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee

28
+ + " 친구 사용은 캡슐화를 위반하는 것이 아니라 반대로 시행하는 것 "입니다
Nawaz

2
의미 론적 의견의 문제 문제의 정확한 상황과 관련이있을 수 있습니다. 클래스 a를 만들면 friend멤버 중 하나만 설정하도록하더라도 모든 비공개 멤버에게 액세스 할 수 있습니다. 필드를 필요로하지 않는 다른 클래스에서 필드를 사용할 수있게 만드는 것은 "캡슐화 위반"으로 간주된다는 강력한 주장이 있습니다. C ++에는 필요한 곳 ​​(오버로드 연산자)이 있지만 신중하게 고려해야 할 캡슐화 상충 관계가 있습니다. C # 디자이너는 트레이드 오프가 그만한 가치가 없다고 생각한 것 같습니다.
GrandOpener

3
캡슐화는 불변을 적용하는 것입니다. 클래스를 다른 클래스의 친구로 정의 할 때 두 클래스는 첫 번째 클래스의 불변량 관리와 관련하여 강력하게 연결되어 있다고 명시 적으로 말합니다. 클래스를 친구로 만드는 것이 모든 속성을 직접 (공개 필드로) 또는 세터를 통해 노출시키는 것보다 낫습니다.
Luc Hermitte

1
바로 그거죠. 친구를 사용하고 싶지만 할 수없는 경우에는 내부 또는 공개를 사용해야합니다. 특히 팀 환경에서.
Hatchling

50

정보를 위해 .NET에서 관련이 있지만 똑같지는 않은 또 다른 것은입니다 [InternalsVisibleTo]. 이는 어셈블리가 유형 / 멤버에 대한 "내부"액세스 권한을 가진 다른 어셈블리 (예 : 단위 테스트 어셈블리)를 지정할 수있게합니다. 원래 어셈블리.


3
좋은 힌트는 아마 친구를 찾는 사람들이 더 내부적 인 '지식'을 가질 수있는 단위 테스트 방법을 찾고 싶어하기 때문입니다.
SwissCoder

14

C #에서 인터페이스를 사용하여 C ++에서 "friend"와 동일한 종류의 작업을 수행 할 수 있어야합니다. 두 클래스간에 전달되는 멤버를 명시 적으로 정의해야합니다. 이는 추가 작업이지만 코드를 이해하기 쉽게 만들 수도 있습니다.

누군가 인터페이스를 사용하여 시뮬레이션 할 수없는 "친구"를 합리적으로 사용하는 예가 있다면 공유하십시오! C ++과 C #의 차이점을 더 잘 이해하고 싶습니다.


좋은 지적. 당신이 이전 SO 질문에서 볼 수있는 기회가 있었나요 (C #으로 그것을 해결하기 위해 일산화탄소에 의해 물어 내가 최선의 방법에 관심이있을 것? 위에 링크 된?
애쉬

C #에서 어떻게 해야하는지 정확히 모르겠지만 Jon Skeet의 일산화 질소 질문에 대한 올바른 방향으로 대답합니다. C #은 중첩 클래스를 허용합니다. 그래도 실제로 코드를 작성하고 솔루션을 컴파일하려고하지 않았습니다. :)
Parappa

나는 중재자 패턴이 친구가 좋은 곳의 좋은 예라고 생각합니다. 중재자를 갖도록 양식을 리팩터링합니다. 내 양식에서 중재자의 메서드와 같은 "이벤트"를 호출 할 수 있기를 원하지만 메서드와 같은 "이벤트"를 호출하는 다른 클래스는 실제로 원하지 않습니다. "친구"기능을 사용하면 어셈블리의 다른 모든 항목을 열지 않고도 메서드에 액세스 할 수 있습니다.
Vaccano

3
'Friend'를 대체 하는 인터페이스 접근 방식 문제 는 객체가 인터페이스를 노출하는 것입니다. 즉, 누구나 해당 인터페이스로 객체를 캐스팅하고 메소드에 액세스 할 수 있습니다! 내부, 보호 또는 개인으로 인터페이스를 범위 지정해도 작동하는 것처럼 작동하지 않으므로 문제의 방법을 범위 지정할 수 있습니다! 쇼핑몰에서 엄마와 아이를 생각하십시오. 대중은 세상의 모든 사람입니다. 내부는 쇼핑몰에있는 사람들입니다. 개인은 단지 어머니 또는 자녀입니다. '친구'는 어머니와 딸 사이의 것입니다.
Mark A. Donohoe

1
여기에 하나가 있습니다 : stackoverflow.com/questions/5921612/… C ++ 배경에서 왔을 때 친구가 없으면 거의 매일 미치게됩니다. C #을 사용한 몇 년 동안 나는 친구가 없기 때문에 개인적으로 안전하게 사용할 수있는 수백 가지의 메소드를 공개했습니다. 개인적으로 나는 친구가 가장 중요한 .NET에 추가되어야 일이 생각
kaalus

14

으로 friend는 C ++ 디자이너는 개인 * 회원에 노출 누구를 정밀하게 제어 할 수 있습니다. 그러나 그는 개인 회원 모두를 공개해야했습니다.

internalC # 디자이너를 사용하면 노출중인 개인 구성원 집합을 정확하게 제어 할 수 있습니다. 분명히, 그는 단 하나의 개인 회원을 노출시킬 수 있습니다. 그러나 어셈블리의 모든 클래스에 노출됩니다.

일반적으로 설계자는 소수의 개인 메소드 만 선택된 소수의 다른 클래스에 노출하려고합니다. 예를 들어, 클래스 팩토리 패턴에서 클래스 C1은 클래스 팩토리 CF1에 의해서만 인스턴스화되는 것이 바람직 할 수 있습니다. 따라서 클래스 C1에는 보호 생성자와 친구 클래스 팩토리 CF1이있을 수 있습니다.

보시다시피, 캡슐화를 위반할 수있는 2 차원이 있습니다. friend한 차원을 따라 위반하고 다른 차원을 따라 위반합니다 internal. 캡슐화 개념에서 어느 것이 더 심각한 위반입니까? 말하기 어렵다. 그러나 모두가 좋을 것이다 friendinternal사용할 수 있습니다. 또한이 두 키워드를 추가하면 세 번째 유형의 키워드가되며 이는 멤버별로 사용되며 ( internal) 대상 클래스를 지정합니다 ( friend).

* 간결하게하기 위해 "개인 및 / 또는 보호"대신 "개인"을 사용합니다.

-닉


13

실제로 C #은 특별한 단어없이 순수한 OOP 방식으로 동일한 동작을 얻을 수있는 가능성을 제공합니다. 전용 인터페이스입니다.

질문까지 친구의 C #은 무엇입니까? 이 기사와 중복되는 것으로 표시되어 있으며 실제로 좋은 실현을 제안하는 사람은 없습니다. 두 질문에 대한 답변을 여기에 표시하겠습니다.

주요 아이디어는 여기에서 가져 왔습니다. 개인 인터페이스 란 무엇입니까?

다른 클래스의 인스턴스를 관리하고 특별한 메소드를 호출 할 수있는 클래스가 필요하다고 가정 해 봅시다. 우리는이 메소드를 다른 클래스에 호출 할 가능성을 원하지 않습니다. 이것은 친구 C ++ 키워드가 C ++ 세계에서하는 것과 정확히 동일합니다.

실제로는 일부 컨트롤러가 현재 상태 객체를 업데이트하고 필요한 경우 다른 상태 객체로 전환하는 Full State Machine 패턴이 좋은 예라고 생각합니다.

당신은 할 수 있습니다 :

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

마지막으로 클래스 컨트롤러 throw 상속을 테스트하는 방법의 예 : ControllerTest.cs

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

모든 경우를 다루고 내 답변이 도움이 되었기를 바랍니다.


더 많은 투표가 필요한 멋진 답변입니다.
KthProg

@max_cn 이것은 정말 좋은 솔루션이지만 단위 테스트를 위해 모의 프레임 워크로 작업하는 방법을 알 수 없습니다. 어떤 제안?
Steve Land

조금 혼란 스러워요. 첫 번째 예제 (개인 인터페이스)에서 외부 클래스가 Update를 호출 할 수 있도록하려면 Controller가 어떻게 수정되도록해야합니까?
AnthonyMonterrosa

@Steve Land, ControllerTest.cs와 같이 상속을 사용할 수 있습니다. 파생 된 테스트 클래스에 필요한 공용 메소드를 추가하면 기본 클래스에있는 모든 보호 된 직원이 액세스 할 수 있습니다.
max_cn

@AnthonyMonterrosa, 우리는 모든 외부 리소스에서 업데이트를 숨기고 있습니다. 왜 누군가와 공유하고 싶습니까?)? 물론 비공개 멤버에 액세스하기 위해 공개 메소드를 추가 할 수 있지만 다른 클래스의 백도어와 같습니다. 어쨌든 테스트 목적으로 필요한 경우 상속을 사용하여 ControllerTest 클래스와 같은 것으로 테스트 사례를 만듭니다.
max_cn

9

친구는 단위 테스트를 작성할 때 매우 유용합니다.

클래스 선언을 약간 오염시키는 비용이 들지만, 클래스의 내부 상태에 대해 실제로 어떤 테스트가 신경 쓰일 지에 대해 컴파일러에서 상기시켜줍니다.

내가 찾은 매우 유용하고 깨끗한 관용구는 팩토리 클래스가있을 때 보호 된 생성자가있는 항목을 친구로 만드는 것입니다. 보다 구체적으로 말하면, 보고서 작성기 개체에 대해 일치하는 렌더링 개체를 만들어 지정된 환경으로 렌더링하는 단일 팩토리가있을 때였습니다. 이 경우 보고서 작성기 클래스 (그림 블록, 레이아웃 밴드, 페이지 머리글 등)와 일치하는 렌더링 개체 간의 관계에 대한 단일 지식이 있습니다.


나는 "친구"가 팩토리 클래스에 유용하다는 의견을하려고했지만, 누군가 이미 그것을 만들었 기 때문에 기쁘다.
Edward Robertson

9

C #에 결정적인 파괴가없는 것과 같은 이유로 "friend"키워드가 없습니다. 새로운 방식이 다른 사람의 기존 방식보다 우월한 것처럼 컨벤션을 변경하면 사람들이 똑똑해집니다. 그것은 자부심에 관한 것입니다.

"친구 클래스가 나쁘다"고 말하는 것은 "goto를 사용하지 마십시오"또는 "Linux가 Windows보다 낫다"와 같은 다른 규정되지 않은 문장만큼이나 근시안적입니다.

프록시 클래스와 결합 된 "friend"키워드 는 클래스의 특정 부분 만 다른 특정 클래스 에만 노출시키는 좋은 방법 입니다. 프록시 클래스는 다른 모든 클래스에 대한 신뢰할 수있는 장벽 역할을 할 수 있습니다. "공개"는 그러한 타겟팅을 허용하지 않으며, "보호"를 사용하여 실질적으로 개념적 "is"관계가없는 경우 상속 효과를 얻는 것은 어색합니다.


가비지 수집보다 속도가 느리기 때문에 C #에 결정적 파괴가 없습니다. 결정적 파괴는 생성 된 모든 객체에 시간이 걸리는 반면 가비지 수집은 현재 존재하는 객체에 대해서만 시간이 걸립니다. 대부분의 경우이 속도가 더 빠르며 시스템에 수명이 짧은 객체가 많을수록 가비지 수집이 상대적으로 빨라집니다.
Edward Robertson

1
@Edward Robertson 소멸자가 정의되지 않으면 결정 론적 파괴는 시간이 걸리지 않습니다. 그러나이 경우 가비지 수집은 훨씬 더 나쁩니다. 왜냐하면 종료 기가 느리고 결정적이지 않은 종료자를 사용해야하기 때문입니다.
kaalus

1
... 그리고 메모리 만이 유일한 자원이 아닙니다. 사람들은 종종 그것이 사실 인 것처럼 행동합니다.
cubuspl42

8

C # 키워드 "internal"을 사용 하여 C ++ "friend"에 가까이 갈 수 있습니다 .


3
가까이 있지만, 그다지 없습니다. 어셈블리 / 클래스를 어떻게 구성하는지에 달려 있지만 친구를 사용하여 액세스 권한이 부여 된 클래스 수를 최소화하는 것이 더 우아하다고 생각합니다. 유틸리티 / 헬퍼 어셈블리의 예제에서 모든 클래스가 내부 멤버에 액세스 할 수있는 것은 아닙니다.
Ash

3
@Ash : 익숙해 져야하지만 일단 어셈블리를 응용 프로그램의 기본 구성 요소로보고 나면 internal캡슐화와 유틸리티 간의 탁월한 절충안을 제공 할 수 있습니다. 그래도 Java의 기본 액세스가 더 좋습니다.
Konrad Rudolph

7

실제로 C #에서는 문제가되지 않습니다. IL의 근본적인 한계입니다. C #은 확인할 수있는 다른 .Net 언어와 마찬가지로 이에 제한됩니다. 이 제한에는 C ++ / CLI ( 스펙 섹션 20.5 ).

나는 넬슨이 왜 이것이 나쁜지에 대해 잘 설명했다고 생각합니다.


7
-1. friend나쁜 것이 아닙니다. 반대로, 그것보다 훨씬 낫다 internal. 넬슨의 설명은 나쁘고 틀 렸습니다.
Nawaz

4

이 제한에 대한 변명을 그만두십시오. 친구는 나쁘지만 내부는 좋습니까? 그들은 같은 것입니다, 오직 그 친구 만이 당신이 접근 할 수있는 사람과 그렇지 않은 사람을보다 정확하게 통제 할 수 있습니다.

이것은 캡슐화 패러다임을 강화하는 것입니까? 접근 자 메서드를 작성해야합니다. 이제 무엇을해야합니까? B 클래스의 메소드를 제외한 모든 사람이이 메소드를 호출하지 못하게하려면 어떻게해야합니까? "친구"가 없어서 제어 할 수 없기 때문에 할 수 없습니다.

완벽한 프로그래밍 언어는 없습니다. C #은 내가 본 최고의 언어 중 하나이지만 누락 된 기능에 대해 바보 같은 변명을하는 것은 누구에게도 도움이되지 않습니다. C ++에서는 쉬운 이벤트 / 위임 시스템, 리플렉션 (+ 자동 de / serialization) 및 foreach가 누락되었지만 C #에서는 연산자 오버로드 (예, 필요하지 않다는 것을 계속 알려줍니다), 기본 매개 변수, const 우회 할 수없는 다중 상속 (예, 필요하지 않고 인터페이스가 충분히 대체되었다는 것을 계속 말하십시오) 및 메모리에서 인스턴스를 삭제하기로 결정하는 기능 (아니요, 그렇지 않으면 엄청나게 나쁘지 않습니다. 땜장이)


C #에서는 대부분의 연산자를 오버로드 할 수 있습니다. 할당 연산자에 과부하를 걸 수는 없지만 단락을 유지하면서 ||/ &&를 무시할 수 있습니다 . 기본 매개 변수는 C # 4에 추가되었습니다.
CodesInChaos

2

.Net 3부터 InternalsVisibleToAttribute가 있지만 단위 테스트가 끝난 후 어셈블리를 테스트하기 위해 추가하기 만 한 것 같습니다. 그것을 사용해야 할 다른 많은 이유를 볼 수 없습니다.

어셈블리 수준에서 작동하지만 내부가 그렇지 않은 작업을 수행합니다. 즉, 어셈블리를 배포하려고하지만 분산되지 않은 다른 어셈블리가 권한을 갖기를 원하는 위치입니다.

아주 잘 맞아 그들은 누군가 보호 된 어셈블리와 함께 친구를 만드는 것을 피하기 위해 친구 어셈블리를 강력하게 키워야합니다.


2

나는 "친구"키워드에 대한 많은 현명한 의견을 읽었으며 그것이 유용한 것에 동의하지만 "내부"키워드가 덜 유용하다고 생각하며 순수한 OO 프로그래밍에는 여전히 좋지 않습니다.

우리가 가진 것? ( "친구"에 대해서도 말하고 "내부"에 대해서도 말하고 있습니다)

  • "friend"를 사용하면 코드가 덜 순수 해 지나요?
  • 예;

  • "friend"를 사용하지 않으면 코드가 향상됩니까?

  • 아니요, 우리는 여전히 클래스간에 사적인 관계를 만들어야하며, 아름다운 캡슐화를 깨뜨릴 때만 할 수 있으므로 좋지 않습니다. "친구"를 사용하는 것보다 더 악한 것을 말할 수 있습니다.

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

나는 "어떻게"질문에만 대답 할 것입니다.

여기에는 많은 답변이 있지만 그 기능을 달성하기 위해 일종의 "디자인 패턴"을 제안하고 싶습니다. 다음과 같은 간단한 언어 메커니즘을 사용합니다.

  • 인터페이스
  • 중첩 클래스

예를 들어, 학생과 대학의 두 가지 주요 수업이 있습니다. 학생에게는 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를 컴파일하는 IL을 빌드합니다. 즉, C # 제네릭이 C ++ 제네릭과 근본적으로 다른 동일한 이유입니다.


컴파일러는 적어도 어셈블리 내의 클래스간에 합리적으로 쉽게 지원할 수 있다고 생각합니다. 대상 클래스에서 친구 클래스에 대한 액세스 검사를 편안하게하는 문제입니다. 외부 어셈블리의 클래스 간 친구는 아마도 CLR에 대한 근본적인 변화가 필요할 수 있습니다.
Ash

1

비공개로 유지하고 리플렉션을 사용하여 함수를 호출 할 수 있습니다. 테스트 프레임 워크는 개인 함수를 테스트하도록 요청하면이 작업을 수행 할 수 있습니다


Silverlight 응용 프로그램에서 작업하지 않는 한
kaalus

1

나는 친구를 정기적으로 사용했지만 OOP 위반이나 디자인 결함의 징후라고 생각하지 않습니다. 가장 적은 양의 코드로 올바른 목적을 달성하기위한 가장 효율적인 수단 인 곳이 몇 군데 있습니다.

구체적인 예는 다른 소프트웨어에 대한 통신 인터페이스를 제공하는 인터페이스 어셈블리를 생성 할 때입니다. 일반적으로 프로토콜 및 피어 특성의 복잡성을 처리하고 클라이언트 앱과 어셈블리간에 메시지 및 알림을 전달하는 비교적 간단한 연결 / 읽기 / 쓰기 / 전달 / 연결 끊기 모델을 제공하는 몇 가지 무거운 클래스가 있습니다. 이러한 메시지 / 알림은 클래스로 래핑해야합니다. 속성은 일반적으로 프로토콜 소프트웨어가 생성자이므로 프로토콜 소프트웨어로 조작해야하지만 많은 것들이 외부 세계에 대해 읽기 전용으로 남아 있어야합니다.

protocol / "creator"클래스가 생성 된 모든 클래스에 대해 친밀한 액세스 권한을 갖는 것은 OOP 위반임을 선언하는 것은 어리석은 일입니다. 제작자 클래스는 모든 데이터 비트를 조금씩 바꿔야했습니다. 내가 가장 중요한 것은 "OOP for OOP Sake"모델이 일반적으로 이끄는 모든 BS 여분의 코드 라인을 최소화하는 것입니다. 여분의 스파게티는 더 많은 버그를 만듭니다.

사람들은 속성, 속성 및 메서드 수준에서 내부 키워드를 적용 할 수 있다는 것을 알고 있습니까? 최상위 클래스 선언만을위한 것이 아닙니다 (대부분의 예제는 그것을 보여줍니다).

friend 키워드를 사용하는 C ++ 클래스가 있고이를 C # 클래스에서 에뮬레이션하려면 다음을 수행하십시오. 1. C # 클래스를 public으로 선언하십시오. 2. C ++로 보호되어 친구가 액세스 할 수있는 모든 속성 / 속성 / 방법을 선언하십시오. C #의 internal 3. 모든 내부 속성 및 속성에 대한 공개 액세스를위한 읽기 전용 속성 만들기

나는 친구와 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클래스 의 구체적인 인스턴스를 얻을 수 없기 때문에 클래스에 액세스 할 수 있습니다 . 전용 생성자가 있고 팩토리 New()메소드는 인터페이스를 리턴합니다.

자세한 내용은 인터페이스코딩 할 때 무료로 친구 및 내부 인터페이스 멤버 기사 를 참조하십시오.


슬프게도 우정은 가능한 어떤 방법으로도 시뮬레이션 할 수 없습니다. 이 질문보기 : stackoverflow.com/questions/5921612/…
kaalus

1

일부는 친구를 사용하여 상황을 통제 할 수 없다고 제안했습니다. 나는 동의하지만 그것의 유용성은 줄어들지 않습니다. 친구가 반드시 모든 반원을 공개하는 것 이상으로 OO 패러다임을 다치게 할 것이라고 확신하지 않습니다. 확실히이 언어를 사용하면 모든 멤버를 공개 할 수 있지만 이러한 유형의 디자인 패턴을 피하는 훈련 된 프로그래머입니다. 마찬가지로, 훈련 된 프로그래머는 이해가되는 특정한 경우에 친구를 사용할 수 있습니다. 어떤 경우에는 내부 노출이 너무 많이 느껴집니다. 왜 클래스 나 메소드를 어셈블리의 모든 것에 노출 시키는가?

내 자신의 기본 페이지를 상속하고 차례로 System.Web.UI.Page를 상속하는 ASP.NET 페이지가 있습니다. 이 페이지에는 응용 프로그램에 대한 최종 사용자 오류보고를 보호되는 방법으로 처리하는 코드가 있습니다.

ReportError("Uh Oh!");

이제 페이지에 포함 된 사용자 정의 컨트롤이 있습니다. 사용자 정의 컨트롤이 페이지에서 오류보고 방법을 호출 할 수 있기를 바랍니다.

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

ReportError 메서드가 보호되어 있으면 그렇게 할 수 없습니다. 내부적으로 만들 수는 있지만 어셈블리의 모든 코드에 노출됩니다. 방금 현재 페이지의 일부인 자식 요소 (자식 컨트롤 포함)에 노출시키고 싶습니다. 보다 구체적으로, 기본 제어 클래스가 정확히 동일한 오류보고 메소드를 정의하고 기본 페이지에서 메소드를 호출하기를 원합니다.

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 ++로 작업하고 있고 친구 키워드를 사용하여 자신을 찾으면 클래스에 다른 클래스의 개인 멤버에 액세스 해야하는 이유가 무엇입니까?


나는 동의한다. 나는 친구 키워드가 필요하지 않습니다. 일반적으로 복잡한 유지 관리 문제를 해결하는 데 사용됩니다.
Edouard A.

7
운영자 과부하, 누구 ??
우리는 모두 모니카

3
연산자 오버로드가 심각하게 좋은 사례를 몇 번이나 발견 했습니까?
bashmohandes

8
여러분의 '디자인 문제'의견을 완전히 해제하는 매우 간단한 사례를 알려 드리겠습니다. 부모-자식. 부모는 자녀를 소유합니다. 부모의 '자녀'컬렉션에있는 자녀는 해당 부모가 소유합니다. Child에 대한 Parent 속성의 세터는 다른 사람 만 설정할 수 없습니다. 자녀가 부모의 자녀 모음에 추가 될 때만 할당해야합니다. t 공개되면 누구나 변경할 수 있습니다. 내부 : 해당 어셈블리의 모든 사람. 은밀한? 아무도. 세터를 부모와 친구로 만들면 그러한 경우가 사라지고 수업은 분리되지만 밀접하게 관련되어 있습니다. 후자 !!
Mark A. Donohoe

2
대부분의 기본 관리자 / 관리 패턴과 같은 많은 경우가 있습니다. SO에 대한 또 다른 질문이 있으며 아무도 친구없이 그것을하는 방법을 찾지 못했습니다. 사람들이 왜 한 클래스가 "항상 충분할 것"이라고 생각하는지 확실하지 않습니다. 때때로 하나 이상의 수업이 함께 일해야합니다.
kaalus

0

Bsd

친구들은 순수한 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). 모든 클래스에서 호출 할 수 있습니다 .
Jesse McGrew

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.