Por que o C # não fornece a palavra-chave 'friend' no estilo C ++? [fechadas]


208

A palavra-chave friend C ++ permite que class Aa designe class Bcomo amiga. Isso permite Class Bacessar os private/ protectedmembros de class A.

Eu nunca li nada sobre por que isso foi deixado de fora do C # (e do VB.NET). A maioria das respostas para essa pergunta anterior do StackOverflow parece estar dizendo que é uma parte útil do C ++ e há boas razões para usá-lo. Na minha experiência, eu teria que concordar.

Outra pergunta me parece realmente perguntar como fazer algo semelhante a friendum aplicativo C #. Embora as respostas geralmente giram em torno de classes aninhadas, não parece tão elegante quanto usar a friendpalavra - chave.

O livro Design Patterns original o utiliza regularmente em todos os seus exemplos.

Então, em resumo, por que está friendfaltando no C # e qual é a maneira (ou melhor) de "prática recomendada" de simulá-lo no C #?

(A propósito, a internalpalavra-chave não é a mesma coisa, ela permite que todas as classes de toda a montagem acessem internalmembros, enquanto friendpermite que você conceda a uma certa classe acesso completo a exatamente uma outra classe)


5
Eu me sinto protegida interna é um bom compromisso ..
Gulzar Nazim

3
Não é muito confuso, é? Como eu disse acima, o livro do GOF o usa com frequência em exemplos. Para mim, não é mais confuso do que interno.
Ash

7
Certamente posso ver cenários em que eles podem ser muito úteis, como já mencionado. Mas você realmente quer que seus amigos acessem suas partes privadas?
danswain 23/10/2009

2
É fácil responder: o C # oferece "interno" como modificador de acesso que concede acesso a todo o código no mesmo módulo / conjunto. Isso elimina a necessidade de algo como amigo. Em Java, a palavra-chave "protected" comporta-se da mesma forma com acesso wrt do mesmo pacote.
sellibitze

2
@ellibitze, menciono as diferenças com interno na questão. O problema com o Interanl é que todas as classes na montagem podem acessar os membros internos. Isso quebra o encapsulamento, pois muitas dessas classes podem não precisar de acesso.
Ash

Respostas:


67

Ter amigos na programação é mais ou menos considerado "sujo" e fácil de abusar. Ele quebra os relacionamentos entre classes e mina alguns atributos fundamentais de uma linguagem OO.

Dito isto, é um recurso interessante e eu já o usei várias vezes em C ++; e gostaria de usá-lo também em c #. Mas aposto que por causa da OOness "pura" do C # (em comparação com a pseudo OOness do C ++), a Microsoft decidiu que, porque Java não tinha a palavra-chave amiga, o C # também não deveria (apenas brincando;))

Em uma observação séria: interno não é tão bom quanto amigo, mas faz o trabalho. Lembre-se de que é raro você distribuir seu código para desenvolvedores de terceiros, não através de uma DLL; contanto que você e sua equipe saibam sobre as classes internas e o uso delas, você ficará bem.

EDIT Deixe-me esclarecer como a palavra-chave friend mina OOP.

Variáveis ​​e métodos privados e protegidos são talvez uma das partes mais importantes da OOP. A idéia de que os objetos podem conter dados ou lógica que somente eles podem usar permite que você escreva sua implementação de funcionalidade independentemente do seu ambiente - e que seu ambiente não pode alterar as informações de estado que não são adequadas para lidar. Ao usar o friend, você está acoplando as implementações de duas classes - o que é muito pior do que se você apenas acoplou a interface.


167
O C # 'interno' está prejudicando o encapsulamento muito mais do que o C ++ 'amigo'. Com "amizade", o proprietário dá explicitamente a permissão a quem ele quiser. "Interno" é um conceito aberto.
Nemanja Trifunovic

21
Anders deve ser elogiado pelos traços que deixou de fora, e não pelos que incluiu.
Mehrdad Afshari

21
Eu discordo que o interno do C # dói a incapsulação. O oposto na minha opinião. Você pode usá-lo para criar um ecossistema encapsulado dentro de uma montagem. Vários objetos podem compartilhar tipos internos dentro desse ecossistema que não são expostos ao restante do aplicativo. Eu uso os truques de montagem "friend" para criar montagens de teste de unidade para esses objetos.
Quibblesome 23/10/09

26
Isso não faz sentido. friendnão permite que classes ou funções arbitrárias acessem membros privados. Ele permite que as funções ou classes específicas que foram marcadas como amigas o façam. A classe que possui o membro privado ainda controla o acesso a ele 100%. Você também pode argumentar que métodos de membros públicos. Ambos garantem que os métodos listados especificamente na classe tenham acesso a membros privados da classe.
jalf

8
Eu acho exatamente o oposto. Se um assembly tiver 50 classes, se 3 dessas classes usarem alguma classe de utilitário, hoje, sua única opção é marcar essa classe de utilitário como "interna". Ao fazer isso, você não apenas o disponibiliza para uso nas 3 classes, mas também para o restante das classes na montagem. E se tudo que você quisesse é fornecer acesso mais refinado, de modo que apenas as três classes em questão tivessem acesso à classe de utilitário. Na verdade, torna-o mais orientado a objetos, porque melhora o encapsulamento.
Zumalifeguard 30/10

104

Em uma nota lateral. Usar amigo não é violar o encapsulamento, mas, pelo contrário, é impor isso. Como acessadores + mutadores, sobrecarga de operadores, herança pública, downcasting etc. , muitas vezes é mal utilizada, mas isso não significa que a palavra-chave não tenha, ou pior, um propósito ruim.

Veja a mensagem de Konrad Rudolph no outro tópico, ou se você preferir, veja a entrada relevante nas Perguntas frequentes sobre C ++.


... e a entrada correspondente no FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
Josh Lee

28
+1 para " Usar amigo não é violar o encapsulamento, mas, pelo contrário, é forçá-lo "
Nawaz

2
Uma questão de opinião semântica, eu acho; e talvez relacionado à situação exata em questão. Criar uma classe frienddá acesso a TODOS os seus membros privados, mesmo que você precise apenas definir um deles. Há um forte argumento a ser feito que disponibilizar campos para outra classe que não precisa deles conta como "violação do encapsulamento". Existem lugares em C ++ onde é necessário (sobrecarregando os operadores), mas há uma troca de encapsulamento que precisa ser cuidadosamente considerada. Parece que os designers de C # sentiram que a troca não valia a pena.
GrandOpener

3
Encapsulamento é sobre impor invariantes. Quando definimos uma classe como amiga de outra, dizemos explicitamente que as duas classes estão fortemente ligadas ao gerenciamento dos invariantes da primeira. Ter a classe feita amizade ainda é melhor do que expor todos os atributos diretamente (como campo público) ou através de levantadores.
Luc Hermitte

1
Exatamente. Quando você quer usar um amigo, mas não pode, é forçado a usar o público ou o público, que são muito mais suscetíveis de serem cutucados por coisas que não deveriam. Especialmente em um ambiente de equipe.
Hatchling

50

Para informações, outra coisa relacionada, mas não exatamente a mesma no .NET [InternalsVisibleTo], que permite que um assembly designe outro assembly (como um assembly de teste de unidade) que (efetivamente) tenha acesso "interno" a tipos / membros em a montagem original.


3
boa dica, como provavelmente as pessoas que procuram um amigo desejam encontrar uma maneira de testar a unidade com a capacidade de ter um "conhecimento" mais interno.
SwissCoder

14

Você deve conseguir o mesmo tipo de coisa que "amigo" é usado no C ++ usando interfaces em C #. Requer que você defina explicitamente quais membros estão sendo transmitidos entre as duas classes, o que é um trabalho extra, mas também pode facilitar a compreensão do código.

Se alguém tiver um exemplo de uso razoável de "amigo" que não possa ser simulado usando interfaces, compartilhe-o! Eu gostaria de entender melhor as diferenças entre C ++ e C #.


Bom ponto. Você teve a chance de olhar para a questão SO anteriormente (perguntado por monóxido de carbono e ligada acima ?. Eu estaria interessado em como melhor resolver isso em C #?
Ash

Não sei exatamente como fazê-lo em C #, mas acho que a resposta de Jon Skeet às perguntas do monóxido aponta na direção certa. C # permite classes aninhadas. Na verdade, não tentei codificar e compilar uma solução. :)
Parappa

Eu sinto que o padrão do mediador é um bom exemplo de onde um amigo seria legal. Refatoro meus Forms para ter um mediador. Eu quero que meu formulário possa chamar os métodos do tipo "evento" no mediador, mas eu realmente não quero que outras classes chamem esses métodos do tipo "evento". O recurso "Amigo" daria ao formulário acesso aos métodos sem abri-lo para todo o resto da montagem.
Vaccano 14/12/2009

3
O problema com a abordagem da interface de substituição de 'Friend' é que um objeto exponha uma interface, isso significa que qualquer pessoa pode converter o objeto nessa interface e ter acesso aos métodos! O escopo da interface para interno, protegido ou privado também não funciona, como se isso funcionasse, você pode apenas definir o método em questão! Pense em mãe e filho em um shopping. Público é todo mundo no mundo. Interno são apenas as pessoas no shopping. Privado é apenas a mãe ou o filho. 'Amigo' é algo apenas entre mãe e filha.
MarkDonohoe

1
Aqui está um: stackoverflow.com/questions/5921612/… Como venho de C ++, a falta de amigo me deixa louco quase diariamente. Nos meus poucos anos com C #, tornei literalmente centenas de métodos públicos onde eles poderiam ser privados e mais seguros, tudo por falta de amizade. Pessoalmente acho que amigo é o importante máximo que deve ser adicionado ao .NET
kaalus

14

Com friendum designer C ++, você tem um controle preciso sobre quem os membros privados * estão expostos. Mas, ele é forçado a expor todos os membros privados.

Com internalum designer de C #, você tem um controle preciso sobre o conjunto de membros privados que está expondo. Obviamente, ele pode expor apenas um único membro privado. Mas, ele será exposto a todas as classes na montagem.

Normalmente, um designer deseja expor apenas alguns métodos particulares a outras poucas classes selecionadas. Por exemplo, em um padrão de fábrica de classe, pode ser desejável que a classe C1 seja instanciada apenas pela fábrica de classe CF1. Portanto, a classe C1 pode ter um construtor protegido e uma fábrica de classe amiga CF1.

Como você pode ver, temos duas dimensões ao longo das quais o encapsulamento pode ser violado. friendquebra ao longo de uma dimensão, internalfaz ao longo da outra. Qual é uma violação pior no conceito de encapsulamento? Difícil de dizer. Mas seria bom ter os dois friende estar internaldisponível. Além disso, uma boa adição a essas duas seria o terceiro tipo de palavra-chave, que seria usada membro a membro (como internal) e especifica a classe de destino (como friend).

* Por uma questão de brevidade, usarei "privado" em vez de "privado e / ou protegido".

- Usuario


13

De fato, o C # oferece a possibilidade de obter o mesmo comportamento de maneira pura, sem palavras especiais - são interfaces privadas.

Quanto à pergunta Qual é o equivalente em C # de amigo? foi marcado como duplicado neste artigo e ninguém propõe uma realização realmente boa - mostrarei a resposta em ambas as perguntas aqui.

A ideia principal foi extrair daqui: O que é uma interface privada?

Digamos, precisamos de alguma classe que possa gerenciar instâncias de outras classes e chamar alguns métodos especiais para elas. Não queremos dar a possibilidade de chamar esses métodos para outras classes. É exatamente a mesma coisa que a palavra-chave c ++ do amigo faz no mundo c ++.

Eu acho que um bom exemplo na prática real pode ser o padrão Full State Machine, em que algum controlador atualiza o objeto de estado atual e alterna para outro objeto de estado quando necessário.

Você poderia:

  • A maneira mais fácil e pior de tornar público o método Update () - espero que todos entendam por que é ruim.
  • A próxima maneira é marcá-lo como interno. É bom o suficiente se você colocar suas classes em outro assembly, mas mesmo assim cada classe nesse assembly poderá chamar cada método interno.
  • Use interface privada / protegida - e eu segui esse caminho.

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

Bem, e a herança?

Precisamos usar a técnica descrita em Como as implementações explícitas dos membros da interface não podem ser declaradas virtuais e marcar IState como protegido para dar a possibilidade de derivar do Controller também.

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

E, finalmente, exemplo de como testar a classe Controller throw herança: ControllerTest.cs

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

Espero cobrir todos os casos e minha resposta foi útil.


Esta é uma resposta incrível que precisa de mais votos.
KthProg

@max_cn Esta é uma solução muito boa, mas não vejo uma maneira de fazer isso funcionar com estruturas de simulação para testes de unidade. Alguma sugestão?
Steve Terra

Estou um pouco confuso. Se eu queria que uma classe externa pudesse chamar Update no seu primeiro exemplo (interface privada), como eu modificaria o Controller para permitir que isso acontecesse?
AnthonyMonterrosa 28/02

@ Steve Land, você pode usar a herança como no ControllerTest.cs foi mostrado. Adicione quaisquer métodos públicos necessários em uma classe de teste derivada e você terá acesso a toda a equipe protegida que possui na classe base.
max_cn 24/04

@AnthonyMonterrosa, estamos escondendo a Atualização de TODOS os recursos externos; por que você deseja compartilhá-la com alguém;)? Claro que você pode adicionar algum método público para acessar membros privados, mas será como um backdoor para qualquer outra classe. De qualquer forma, se você precisar para fins de teste - use herança para criar algo como a classe ControllerTest para fazer qualquer caso de teste lá.
max_cn 24/04

9

Amigo é extremamente útil ao escrever teste de unidade.

Embora isso tenha um custo de poluir um pouco a sua declaração de classe, também é um lembrete imposto pelo compilador de quais testes realmente podem se importar com o estado interno da classe.

Um idioma muito útil e limpo que encontrei é quando tenho classes de fábrica, tornando-os amigos dos itens que eles criam e que têm um construtor protegido. Mais especificamente, foi quando eu tinha uma única fábrica responsável por criar objetos de renderização correspondentes para objetos do gravador de relatórios, renderizando em um determinado ambiente. Nesse caso, você tem um único ponto de conhecimento sobre o relacionamento entre as classes de gravador de relatório (itens como blocos de figuras, faixas de layout, cabeçalhos de página etc.) e seus objetos de renderização correspondentes.


Eu ia fazer um comentário sobre "amigo" ser útil para as aulas da fábrica, mas fico feliz em ver que alguém já o fez.
Edward Robertson

9

O C # está ausente na palavra-chave "friend" pelo mesmo motivo em que está faltando destruição determinística. A mudança de convenções faz com que as pessoas se sintam inteligentes, como se seus novos caminhos fossem superiores aos antigos de outras pessoas. É tudo sobre orgulho.

Dizer que "as classes de amigos são ruins" é tão míope quanto outras declarações não qualificadas, como "não use gotos" ou "Linux é melhor que o Windows".

A palavra-chave "friend" combinada com uma classe proxy é uma ótima maneira de expor apenas certas partes de uma classe a outras classes específicas. Uma classe de proxy pode atuar como uma barreira confiável contra todas as outras classes. "public" não permite tal segmentação e o uso de "protected" para obter o efeito com herança é estranho se realmente não houver um conceito "é um" relacionamento.


O C # está perdendo destruição determinística porque é mais lento que a coleta de lixo. A destruição determinística leva tempo para cada objeto criado, enquanto a coleta de lixo leva apenas tempo para os objetos ativos no momento. Na maioria dos casos, isso é mais rápido e, quanto mais objetos de vida curta existirem no sistema, mais rápida é a coleta de lixo.
Edward Robertson

1
@ Edward Robertson A destruição determinística não leva tempo, a menos que haja um destruidor definido. Mas, nesse caso, a coleta de lixo é muito pior, pois é necessário usar um finalizador, que é mais lento e não determinístico.
kaalus

1
... e a memória não é o único tipo de recurso. As pessoas costumam agir como se isso fosse verdade.
cubuspl42

8

Você pode se aproximar de "amigo" do C ++ com a palavra-chave do C # "interna" .


3
Perto, mas não exatamente lá. Suponho que depende de como você estrutura suas assembléias / classes, mas seria mais elegante minimizar o número de classes com acesso usando friend. Por exemplo, em um assembly utilitário / auxiliar, nem todas as classes devem ter acesso a membros internos.
Ash

3
@Ash: Você precisa se acostumar com isso, mas depois de ver os conjuntos como o bloco de construção fundamental de seus aplicativos, internalfaz muito mais sentido e fornece uma excelente troca entre encapsulamento e utilidade. O acesso padrão do Java é ainda melhor.
precisa saber é o seguinte

7

Na verdade, isso não é um problema com o C #. É uma limitação fundamental na IL. O c # é limitado por isso, assim como qualquer outra linguagem .Net que procure ser verificável. Essa limitação também inclui classes gerenciadas definidas em C ++ / CLI ( seção Especificações 20.5 ).

Dito isto, acho que Nelson tem uma boa explicação sobre por que isso é uma coisa ruim.


7
-1. friendnão é uma coisa ruim; pelo contrário, é muito melhor que internal. E a explicação de Nelson é ruim e incorreta.
Nawaz

4

Pare de inventar desculpas para essa limitação. amigo é ruim, mas interno é bom? eles são a mesma coisa, apenas esse amigo lhe dá um controle mais preciso sobre quem tem permissão para acessar e quem não tem.

Isso é para reforçar o paradigma de encapsulamento? então você tem que escrever métodos acessadores e agora o que? como você deve impedir que todos (exceto os métodos da classe B) chamem esses métodos? você não pode, porque também não pode controlar isso, por falta de "amigo".

Nenhuma linguagem de programação é perfeita. O C # é um dos melhores idiomas que eu já vi, mas inventar desculpas tolas para os recursos ausentes não ajuda ninguém. No C ++, sinto falta do sistema fácil de delegação / evento, reflexão (+ des / serialização automática) e foreach, mas no C # sinto falta de sobrecarga do operador (sim, continue me dizendo que você não precisava), parâmetros padrão, uma const isso não pode ser contornado, herança múltipla (sim, continue me dizendo que você não precisava disso e as interfaces eram uma substituição suficiente) e a capacidade de decidir excluir uma instância da memória (não, isso não é terrivelmente ruim a menos que você seja um funileiro)


Em C #, você pode sobrecarregar a maioria dos operadores. Não é possível sobrecarregar os operadores de atribuição, mas, por outro lado, você pode substituir ||/ &&mantendo-os em curto-circuito. Os parâmetros padrão foram adicionados no C # 4.
CodesInChaos

2

Existe o InternalsVisibleToAttribute desde o .Net 3, mas suspeito que eles o tenham adicionado apenas para servir para testar assemblies após o aumento dos testes de unidade. Não vejo muitos outros motivos para usá-lo.

Funciona no nível da montagem, mas faz o trabalho onde interno não; ou seja, onde você deseja distribuir um assembly, mas deseja que outro assembly não distribuído tenha acesso privilegiado a ele.

Com toda a razão, eles exigem que a assembléia de amigos seja fortemente pressionada para evitar que alguém crie um fingido amigo ao lado de sua assembléia protegida.


2

Eu li muitos comentários inteligentes sobre a palavra-chave "friend" e concordo com o que é útil, mas acho que a palavra-chave "interna" é menos útil, e ambos ainda são ruins para a programação pura de OO.

O que nós temos? (dizendo sobre "amigo" e também "interno")

  • usar "friend" torna o código menos puro em relação ao oo?
  • sim;

  • não está usando "amigo" torna o código melhor?

  • não, ainda precisamos fazer alguns relacionamentos privados entre as classes, e só podemos fazê-lo se quebrarmos nosso belo encapsulamento, por isso também não é bom, posso dizer o que é ainda mais mau do que usar "friend".

Usar friend cria alguns problemas locais, não usá-lo cria problemas para usuários da biblioteca de códigos.

a solução boa comum para linguagem de programação que eu vejo assim:

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

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

O que você acha disso? Eu acho que é a solução orientada a objetos mais comum e pura. Você pode abrir o acesso de qualquer método que escolher para qualquer classe que desejar.


Eu não sou um sentinela do OOP, mas parece que resolve as queixas de todos contra amigos e internos. Mas eu sugiro usar um atributo para evitar a extensão da sintaxe do C #: [PublicFor Bar] void addBar (Bar bar) {} ... Não que eu tenha certeza de que faz sentido; Não sei como os atributos funcionam ou se eles podem mexer na acessibilidade (eles fazem muitas outras coisas "mágicas").
Grault

2

Vou responder apenas a pergunta "Como".

Existem muitas respostas aqui, no entanto, eu gostaria de propor um tipo de "padrão de design" para alcançar esse recurso. Vou usar um mecanismo de linguagem simples, que inclui:

  • Interfaces
  • Classe aninhada

Por exemplo, temos 2 classes principais: Estudante e Universidade. O aluno possui GPA que somente a universidade tem permissão para acessar. Aqui está o código:

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

Eu suspeito que tenha algo a ver com o modelo de compilação C # - construindo IL o JIT que o compila em tempo de execução. ou seja: o mesmo motivo pelo qual os genéricos de C # são fundamentalmente diferentes dos genéricos de C ++.


Eu acho que o compilador poderia suportá-lo razoavelmente facilmente, pelo menos entre as classes dentro de um assembly. Apenas uma questão de relaxar a verificação de acesso da classe de amigos na classe de destino. Talvez um amigo entre classes em assembléias externas precise de mudanças mais fundamentais no CLR.
Ash

1

você pode mantê-lo privado e usar a reflexão para chamar funções. A estrutura de teste pode fazer isso se você pedir para testar uma função privada


A menos que você esteja trabalhando em um aplicativo Silverlight.
kaalus

1

Eu costumava usar o amigo regularmente, e não acho que seja uma violação do OOP ou um sinal de qualquer falha de design. Existem vários lugares onde é o meio mais eficiente para o fim apropriado com a menor quantidade de código.

Um exemplo concreto é ao criar assemblies de interface que fornecem uma interface de comunicação para algum outro software. Geralmente, existem algumas classes pesadas que lidam com a complexidade do protocolo e das peculiaridades dos pares e fornecem um modelo de conexão / leitura / gravação / encaminhamento / desconexão relativamente simples, que envolve a transmissão de mensagens e notificações entre o aplicativo cliente e o assembly. Essas mensagens / notificações precisam ser agrupadas em classes. Os atributos geralmente precisam ser manipulados pelo software de protocolo, pois são seus criadores, mas muitas coisas precisam permanecer somente leitura para o mundo externo.

É simplesmente tolo declarar que é uma violação do OOP para a classe protocolo / "criador" ter acesso íntimo a todas as classes criadas - a classe criadora teve que mexer em todos os dados no caminho. O que eu achei mais importante é minimizar todas as linhas de código extras da BS às quais o modelo "OOP for OOP's Sake" geralmente leva. Espaguete extra apenas gera mais bugs.

As pessoas sabem que você pode aplicar a palavra-chave interna no nível de atributo, propriedade e método? Não é apenas para a declaração de classe de nível superior (embora a maioria dos exemplos pareça mostrar isso).

Se você possui uma classe C ++ que usa a palavra-chave friend e deseja emular isso em uma classe C #: 1. declare a classe C # public 2. declare todos os atributos / propriedades / métodos protegidos em C ++ e, portanto, acessíveis aos amigos como internal in C # 3. crie propriedades somente leitura para acesso público a todos os atributos e propriedades internos

Concordo que não é 100% o mesmo que amigo, e o teste de unidade é um exemplo muito valioso da necessidade de algo como amigo (como é o código de log do analisador de protocolo). No entanto, interno fornece a exposição para as classes que você deseja que seja exposto e [InternalVisibleTo ()] lida com o resto - parece que ele nasceu especificamente para teste de unidade.

Quanto ao amigo "ser melhor, porque você pode controlar explicitamente quais classes têm acesso" - o que diabos um bando de classes malignas suspeitas estão fazendo na mesma assembléia em primeiro lugar? Particione suas montagens!


1

A amizade pode ser simulada separando interfaces e implementações. A idéia é: " Exigir uma instância concreta, mas restringir o acesso à construção dessa instância ".

Por exemplo

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) { }
}

Apesar de ItsMeYourFriend()ser pública, apenas a Friendclasse pode acessá-la, pois ninguém mais pode obter uma instância concreta da Friendclasse. Tem um construtor privado, enquanto a fábricaNew() método retorna uma interface.

Veja meu artigo Amigos e membros da interface interna sem nenhum custo com codificação para interfaces para obter detalhes.


Infelizmente, a amizade não pode ser simulada de nenhuma maneira possível. Veja esta pergunta: stackoverflow.com/questions/5921612/…
kaalus 12/12/12

1

Alguns sugeriram que as coisas podem ficar fora de controle usando o amigo. Eu concordo, mas isso não diminui sua utilidade. Não tenho certeza de que um amigo necessariamente prejudique o paradigma OO mais do que tornar todos os membros de sua classe públicos. Certamente o idioma permitirá que você publique todos os seus membros, mas é um programador disciplinado que evita esse tipo de padrão de design. Da mesma forma, um programador disciplinado reservaria o uso de friend para casos específicos em que isso faz sentido. Sinto exposições internas demais em alguns casos. Por que expor uma classe ou método a tudo na montagem?

Eu tenho uma página ASP.NET que herda minha própria página base, que por sua vez herda System.Web.UI.Page. Nesta página, tenho um código que lida com o relatório de erros do usuário final para o aplicativo em um método protegido

ReportError("Uh Oh!");

Agora, eu tenho um controle de usuário que está contido na página. Desejo que o controle do usuário possa chamar os métodos de relatório de erros na página.

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

Não é possível fazer isso se o método ReportError estiver protegido. Eu posso torná-lo interno, mas está exposto a qualquer código no assembly. Eu só quero que ele seja exposto aos elementos da interface do usuário que fazem parte da página atual (incluindo controles filho). Mais especificamente, quero que minha classe de controle base defina exatamente os mesmos métodos de relatório de erros e chame simplesmente métodos na página base.

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

Acredito que algo como amigo poderia ser útil e implementado na linguagem sem tornar a linguagem menos "OO", talvez como atributos, para que você possa ter classes ou métodos amigos de classes ou métodos específicos, permitindo que o desenvolvedor forneça muito acesso específico. Talvez algo como ... (pseudo código)

[Friend(B)]
class A {

    AMethod() { }

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

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

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

No caso do meu exemplo anterior, talvez tenha algo como o seguinte (pode-se argumentar semântica, mas estou apenas tentando transmitir a ideia):

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

A meu ver, o conceito de amigo não tem mais riscos do que tornar públicas as coisas ou criar métodos ou propriedades públicas para acessar os membros. Se alguma coisa amiga permitir outro nível de granularidade na acessibilidade dos dados e permitir que você restrinja essa acessibilidade em vez de ampliá-la com interna ou pública.


0

Se você estiver trabalhando com C ++ e se encontrar usando a palavra-chave friend, é uma indicação muito forte de que você tem um problema de design, por que diabos uma classe precisa acessar os membros privados de outra classe?


Concordo. Eu nunca preciso da palavra-chave friend. Geralmente é usado para resolver problemas complexos de manutenção.
Edouard A.

7
Sobrecarga de operador, alguém?
Somos Todos Monica

3
Quantas vezes você encontrou um bom argumento para sobrecarregar seriamente o operador?
bashmohandes

8
Darei a você um caso muito simples que desmascara completamente seu comentário de 'problema de design'. Pai-Filho. Um pai é dono de seus filhos. Um filho na coleção 'Filhos' dos pais é de propriedade desse pai. O setter da propriedade Parent em Child não deve ser configurável por ninguém. Ele precisa ser atribuído quando e somente quando o filho é adicionado à coleção Filhos dos pais. Se é público, qualquer um pode mudar. Interno: qualquer pessoa nessa assembléia. Privado? Ninguém. Tornar o levantador um amigo dos pais elimina esses casos e ainda mantém suas classes separadas, mas fortemente relacionadas. Huzzah !!
Mark-Donohoe

2
Existem muitos casos, como o padrão básico de gerente / gerenciado. Há outra pergunta sobre SO e ninguém imaginou uma maneira de fazê-lo sem um amigo. Não sei o que faz as pessoas pensarem que uma classe "sempre será suficiente". Às vezes, mais de uma turma precisa trabalhar em conjunto.
kaalus

0

Bsd

Foi afirmado que amigos prejudicam pura OOness. O que eu concordo.

Também foi declarado que amigos ajudam o encapsulamento, o que também concordo.

Eu acho que a amizade deve ser adicionada à metodologia OO, mas não tanto quanto em C ++. Gostaria de ter alguns campos / métodos que minha classe de amigo possa acessar, mas NÃO gostaria que eles acessassem TODOS os meus campos / métodos. Como na vida real, eu permitia que meus amigos acessassem minha geladeira pessoal, mas não permitia que eles acessassem minha conta bancária.

Pode-se implementar isso da seguinte maneira

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

É claro que isso gerará um aviso do compilador e prejudicará o intellisense. Mas fará o trabalho.

Em uma nota lateral, acho que um programador confiante deve fazer a unidade de teste sem acessar os membros privados. isso está fora do escopo, mas tente ler sobre o TDD. no entanto, se você ainda deseja fazer isso (tendo c ++ como amigos), tente algo como

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

para que você escreva todo o seu código sem definir UNIT_TESTING e quando desejar fazer o teste de unidade, adicione #define UNIT_TESTING à primeira linha do arquivo (e escreva todo o código que faz o teste de unidade em #if UNIT_TESTING). Isso deve ser tratado com cuidado.

Como acho que o teste de unidade é um mau exemplo para o uso de amigos, eu daria um exemplo do por que acho que os amigos podem ser bons. Suponha que você tenha um sistema de interrupção (classe). Com o uso, o sistema de quebra fica desgastado e precisa ser renovado. Agora, você deseja que apenas um mecânico licenciado o conserte. Para tornar o exemplo menos trivial, eu diria que o mecânico usaria sua chave de fenda pessoal (particular) para consertá-lo. É por isso que a classe mecânica deve ser amiga da classe breakingSystem.


2
Esse parâmetro FriendClass não serve como controle de acesso: você pode chamar c.MyMethod((FriendClass) null, 5.5, 3)de qualquer classe.
Jesse McGrew

0

A amizade também pode ser simulada usando "agentes" - algumas classes internas. Considere o seguinte exemplo:

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

Poderia ser muito mais simples quando usado para acessar apenas membros estáticos. Os benefícios dessa implementação são que todos os tipos são declarados no escopo interno das classes de amizade e, diferentemente das interfaces, permitem que membros estáticos sejam acessados.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.