Os recursos de um objeto devem ser identificados exclusivamente pelas interfaces que ele implementa?

36

O isoperador de C # e o instanceofoperador de Java permitem ramificar na interface (ou, mais a sério, em sua classe base) que uma instância de objeto implementou.

É apropriado usar esse recurso para ramificação de alto nível com base nos recursos que uma interface fornece?

Ou uma classe base deve fornecer variáveis ​​booleanas para fornecer uma interface para descrever os recursos que um objeto possui?

Exemplo:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

vs.

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Existem algumas armadilhas no uso da implementação de interface para identificação de capacidade?


Este exemplo foi apenas isso, um exemplo. Estou pensando em uma aplicação mais geral.

TheCatWhisperer
fonte
10
Veja seus dois exemplos de código. Qual é mais legível?
Robert Harvey
39
Apenas minha opinião, mas eu iria com a primeira apenas porque garante que você possa transmitir com segurança para o tipo apropriado. O segundo pressupõe que CanResetPasswordisso só será verdade quando algo for implementado IResetsPassword. Agora você está fazendo uma propriedade determinar algo que o sistema de tipos deve controlar (e finalmente determinará quando você pré-forma a conversão).
Becuzz
10
@nocomprende: xkcd.com/927
Robert Harvey
14
O título e o corpo da pergunta são diferentes. Para responder ao título: Idealmente, sim. Para abordar o corpo: Se você estiver verificando o tipo e a transmissão manual, provavelmente não está utilizando o sistema de tipos corretamente. Você pode nos dizer mais especificamente como você se viu nessa situação?
MetaFight
9
Essa pergunta pode parecer simples, mas cresce bastante quando você começa a pensar sobre isso. Perguntas que me vêm à mente: Você espera adicionar novos comportamentos às contas existentes ou criar tipos de contas com comportamentos diferentes? Todos os tipos de conta têm comportamentos semelhantes ou existem grandes diferenças entre eles? O código de exemplo conhece todos os tipos de contas ou apenas a interface básica da IAccount?
Euphoric

Respostas:

7

Com a ressalva de que é melhor evitar downcasting, se possível, a primeira forma é preferível por dois motivos:

  1. você está deixando o sistema de tipos trabalhar para você. No primeiro exemplo, se os isincêndios, o downcast definitivamente funcionará. No segundo formulário, você precisa garantir manualmente que apenas os objetos que implementam IResetsPasswordretornem true para essa propriedade
  2. classe base frágil. Você precisa adicionar uma propriedade à classe / interface base para todas as interfaces que deseja adicionar. Isso é complicado e propenso a erros.

Isso não quer dizer que você não possa melhorar um pouco esses problemas. Você poderia, por exemplo, ter a classe base com um conjunto de interfaces publicadas nas quais você pode verificar a inclusão. Mas, na verdade, você está apenas implementando manualmente manualmente uma parte do sistema de tipos existente que é redundante.

Aliás, minha preferência natural é a composição sobre a herança, mas principalmente no que diz respeito ao estado. Herança de interface não é tão ruim, geralmente. Além disso, se você se encontra implementando a hierarquia de tipos de pessoas pobres, é melhor usar a que já existe, pois o compilador o ajudará.

Alex
fonte
Eu também prefiro muito a digitação composicional. +1 por me fazer ver que este exemplo em particular não está sendo aproveitado corretamente. Embora, eu diria que a digitação computacional (como herança tradicional) é mais limitada quando os recursos variam amplamente (em oposição à forma como são implementados). Daí a abordagem de interface, que resolve um problema diferente do que a digitação composicional ou a herança padrão.
TheCatWhisperer
você pode simplificar a verificação da seguinte forma: (account as IResettable)?.ResetPassword();ouvar resettable = account as IResettable; if(resettable != null) {resettable.ResetPassword()} else {....}
Berin Loritsch 11/11
89

Os recursos de um objeto devem ser identificados exclusivamente pelas interfaces que ele implementa?

Os recursos de um objeto não devem ser identificados.

O cliente que usa um objeto não deve saber nada sobre como ele funciona. O cliente deve saber apenas coisas que pode dizer ao objeto para fazer. O que o objeto faz, uma vez informado, não é o problema do cliente.

Então, ao invés de

if (account is IResetsPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

ou

if (account.CanResetPassword)
    ((IResetsPassword)account).ResetPassword();
else
    Print("Not allowed to reset password with this account type!");

considerar

account.ResetPassword();

ou

account.ResetPassword(authority);

porque accountjá sabe se isso vai funcionar. Por que perguntar isso? Apenas diga o que deseja e deixe fazer o que for necessário.

Imagine isso feito de tal maneira que o cliente não se importe se funcionou ou não, porque isso é outro problema. O trabalho dos clientes era apenas fazer a tentativa. É feito agora e tem outras coisas para lidar. Esse estilo tem muitos nomes, mas o nome que eu mais gosto é dizer, não pergunte .

É muito tentador ao escrever para o cliente pensar que você precisa acompanhar tudo e, assim, puxar em sua direção tudo o que acha que precisa saber. Quando você faz isso, vira objetos de dentro para fora. Valorize sua ignorância. Afaste os detalhes e deixe que os objetos lidem com eles. Quanto menos você souber, melhor.

candied_orange
fonte
54
O polimorfismo só funciona quando eu não sei ou me importo exatamente com o que estou falando. Então, a maneira como lido com essa situação é evitá-la com cuidado.
candied_orange
7
Se o cliente realmente precisar saber se a tentativa foi bem-sucedida, o método poderá retornar um valor booleano. if (!account.AttemptPasswordReset()) /* attempt failed */;Dessa forma, o cliente conhece o resultado, mas evita alguns dos problemas com a lógica de decisão na pergunta. Se a tentativa for assíncrona, você passaria um retorno de chamada.
Radiodef
5
@DavidZ Nós dois falamos inglês. Então eu posso dizer para você votar este comentário duas vezes. Isso significa que você é capaz de fazer isso? Preciso saber se você pode antes que eu possa dizer para você fazer isso?
candied_orange
5
@QPaysTaxes, pelo que você sabe, um manipulador de erros foi passado accountquando foi construído. Pop-ups, registros, impressões e alertas de áudio podem estar acontecendo neste momento. O trabalho do cliente é dizer "faça agora". Não precisa fazer mais do que isso. Você não precisa fazer tudo em um só lugar.
candied_orange
6
@QPaysTaxes Poderia ser tratado em outro lugar e de várias maneiras diferentes, é supérfluo para essa conversa e apenas enlameará as águas.
Tom.Bowen89
17

fundo

A herança é uma ferramenta poderosa que serve a um propósito na programação orientada a objetos. No entanto, ele não resolve todos os problemas de maneira elegante: às vezes, outras soluções são melhores.

Se você se lembrar de suas primeiras aulas de ciência da computação (supondo que tenha um diploma em ciências da computação), lembre-se de um professor fornecendo um parágrafo que declara o que o cliente deseja que o software faça. Seu trabalho é ler o parágrafo, identificar os atores e ações e apresentar um esboço de quais são as classes e métodos. Haverá algumas pistas ruins que parecem importantes, mas não são. Há uma possibilidade muito real de interpretar mal os requisitos também.

Essa é uma habilidade importante que até os mais experientes cometem errado: identificar adequadamente os requisitos e traduzi-los para idiomas de máquina.

Sua pergunta

Com base no que você escreveu, acho que você pode estar entendendo mal o caso geral de ações opcionais que as classes podem executar. Sim, eu sei que seu código é apenas um exemplo e você está interessado no caso geral. No entanto, parece que você deseja saber como lidar com a situação em que certos subtipos de um objeto podem executar uma ação, mas outros não.

Só porque um objeto como um accounttem um tipo de conta não significa que isso se traduz em um tipo em um idioma OO. "Tipo" em uma linguagem humana nem sempre significa "classe". No contexto de uma conta, "tipo" pode se correlacionar mais estreitamente com "conjunto de permissões". Você deseja usar uma conta de usuário para executar uma ação, mas essa ação pode ou não ser capaz de ser executada por essa conta. Em vez de usar herança, eu usaria um delegado ou token de segurança.

Minha solução

Considere uma classe de conta que possui várias ações opcionais que pode executar. Em vez de definir "pode ​​executar a ação X" por herança, por que a conta não retorna um objeto delegado (redefinição de senha, remetente de formulário etc.) ou um token de acesso?

account.getPasswordResetter().doAction();
account.getFormSubmitter().doAction(view.getContents());

AccountManager.resetPassword(account, account.getAccessToken());

O benefício da última opção existe e se eu quiser usar as credenciais da minha conta para redefinir a senha de outra pessoa ?

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

O sistema não só é mais flexível, como também removi os tipos de conversão, mas o design é mais expressivo . Eu posso ler isso e entender facilmente o que está fazendo e como pode ser usado.


TL; DR: isso parece um problema XY . Geralmente, quando confrontados com duas opções abaixo do ideal, vale a pena dar um passo para trás e pensar "o que estou realmente tentando realizar aqui? Devo realmente estar pensando em como tornar a produção tipográfica menos feia ou procurar maneiras de remover completamente o typecast? "


fonte
1
+1 no cheiro do design, mesmo que você não tenha usado a frase.
Jared Smith
e nos casos em que a senha de redefinição tem argumentos completamente diferentes, dependendo do tipo? ResetPassword (pswd) vs ResetPasswordToRandom ()
TheCatWhisperer
1
@TheCatWhisperer O primeiro exemplo é "alterar senha", que é uma ação diferente. O segundo exemplo é o que descrevo nesta resposta.
Por que não account.getPasswordResetter().resetPassword();.
Ano 12/12
1
The benefit to the last option there is what if I want to use my account credentials to reset someone else's password?.. fácil. Você cria um objeto de domínio da conta para a outra conta e usa essa. As classes estáticas são problemáticas para o teste de unidade (esse método, por definição, precisa de acesso a algum armazenamento, portanto, agora você não pode testar facilmente qualquer código que toque mais nessa classe) e é melhor evitar. A opção de retornar alguma outra interface geralmente é uma boa idéia, mas realmente não lida com o problema subjacente (você acabou de mudar o problema para outra interface).
Voo
1

Bill Venner diz que sua abordagem é absolutamente correta ; pule para a seção "Quando usar instanceof". Você está fazendo downcasting para um tipo / interface específico que apenas implementa esse comportamento específico, para que você esteja absolutamente certo em ser seletivo.

No entanto, se você quiser usar outra abordagem, existem muitas maneiras de fazer a barba com um iaque.

Existe a abordagem do polimorfismo; você pode argumentar que todas as contas têm uma senha; portanto, todas elas devem pelo menos tentar redefinir uma senha. Isso significa que, na classe de conta base, devemos ter um resetPassword()método que todas as contas implementem, mas à sua maneira. A questão é como esse método deve se comportar quando a capacidade não existe.

Ele pode retornar um nulo e silenciosamente completo, redefinindo a senha ou não, talvez assumindo a responsabilidade de imprimir a mensagem internamente, se não redefinir a senha. Não é a melhor ideia.

Pode retornar um valor booleano indicando se a redefinição foi bem-sucedida. Ao ativar esse booleano, poderíamos relacionar que a redefinição de senha falhou.

Pode retornar uma String indicando o resultado da tentativa de redefinição de senha. A sequência poderia fornecer mais detalhes sobre o motivo da falha de uma redefinição e a saída.

Ele pode retornar um objeto ResetResult que transmite mais detalhes e combina todos os elementos de retorno anteriores.

Ele pode retornar um vazio e, em vez disso, gerar uma exceção se você tentar redefinir uma conta que não tenha esse recurso (não faça isso, usar o tratamento de exceções para o controle de fluxo normal é uma prática recomendada por vários motivos).

Ter um canResetPassword()método de correspondência pode não parecer a pior coisa do mundo, pois é um recurso estático embutido na classe quando foi escrito. Essa é uma pista do motivo pelo qual a abordagem do método é uma péssima idéia, pois sugere que a capacidade é dinâmica e quecanResetPassword() pode mudar, o que também cria a possibilidade adicional de mudar entre pedir permissão e fazer a ligação. Como mencionado em outro lugar, informe em vez de pedir permissão.

A composição sobre a herança pode ser uma opção: você pode ter uma passwordResetter campo (ou um getter equivalente) e classe (s) equivalente (s) que você pode procurar por nulo antes de chamá-lo. Embora atue como pedir permissão, a finalidade pode evitar qualquer natureza dinâmica inferida.

Você pode externalizar a funcionalidade em sua própria classe, o que pode levar uma conta como parâmetro e agir sobre ela (por exemplo, resetter.reset(account) ), embora isso também seja frequentemente uma prática ruim (uma analogia comum é um lojista que chega à sua carteira para receber dinheiro).

Em idiomas com o recurso, você pode usar mixins ou características, no entanto, acaba na posição em que começou, onde pode estar verificando a existência desses recursos.

Danikov
fonte
Nem todas as contas podem ter uma senha (do ponto de vista desse sistema específico). Por exemplo, a conta pode ser de terceiros ... google, facebook, ect. Para não dizer que esta não é uma ótima resposta!
TheCatWhisperer
0

Para este exemplo específico de " pode redefinir uma senha ", eu recomendo o uso de composição sobre herança (neste caso, herança de uma interface / contrato). Porque, ao fazer isso:

class Foo : IResetsPassword {
    //...
}

Você está especificando imediatamente (em tempo de compilação) que sua classe ' pode redefinir uma senha '. Mas, se no seu cenário, a presença do recurso for condicional e depender de outras coisas, você não poderá mais especificar as coisas no tempo de compilação. Então, sugiro fazer o seguinte:

class Foo {

    PasswordResetter passwordResetter;

}

Agora, em tempo de execução, você pode verificar se myFoo.passwordResetter != nullantes de executar esta operação. Se você deseja desacoplar ainda mais as coisas (e planeja adicionar muito mais recursos), você pode:

class Foo {
    //... foo stuff
}

class PasswordResetOperation {
    bool Execute(Foo foo) { ... }
}

class SendMailOperation {
    bool Execute(Foo foo) { ... }
}

//...and you follow this pattern for each new capability...

ATUALIZAR

Depois de ler outras respostas e comentários do OP, entendi que a pergunta não é sobre a solução composicional. Então, acho que a pergunta é sobre como identificar melhor as capacidades dos objetos em geral, em um cenário como o abaixo:

class BaseAccount {
    //...
}
class GuestAccount : BaseAccount {
    //...
}
class UserAccount : BaseAccount, IMyPasswordReset, IEditPosts {
    //...
}
class AdminAccount : BaseAccount, IPasswordReset, IEditPosts, ISendMail {
    //...
}

//Capabilities

interface IMyPasswordReset {
    bool ResetPassword();
}

interface IPasswordReset {
    bool ResetPassword(UserAccount userAcc);
}

interface IEditPosts {
    bool EditPost(long postId, ...);
}

interface ISendMail {
    bool SendMail(string from, string to, ...);
}

Agora, tentarei analisar todas as opções mencionadas:

Segundo exemplo do OP:

if (account.CanResetPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Digamos que este código esteja recebendo alguma classe de conta base (por exemplo: BaseAccountno meu exemplo); isso é ruim, pois está inserindo booleanos na classe base, poluindo-o com código que não faz nenhum sentido estar lá.

Primeiro exemplo do OP:

if (account is IResetsPassword)
       ((IResetsPassword)account).ResetPassword();
else
       Print("Not allowed to reset password with this account type!");

Para responder à pergunta, isso é mais apropriado do que a opção anterior, mas, dependendo da implementação, ele quebrará o princípio L de sólido, e provavelmente verificações como essa seriam espalhadas pelo código e dificultariam a manutenção.

O que outras pessoas estão dizendo

account.ResetPassword(authority);

Se esse ResetPasswordmétodo for inserido na BaseAccountclasse, também estará poluindo a classe base com código inadequado, como no segundo exemplo do OP

Resposta do boneco de neve:

AccountManager.resetPassword(otherAccount, adminAccount.getAccessToken());

Essa é uma boa solução, mas considera que os recursos são dinâmicos (e podem mudar com o tempo). No entanto, depois de ler vários comentários do OP, acho que a conversa aqui é sobre polimorfismo e classes definidas estaticamente (embora o exemplo de contas aponte intuitivamente para o cenário dinâmico). EG: neste AccountManagerexemplo, a verificação de permissão seria consultas ao DB; na pergunta do OP, as verificações são tentativas de converter os objetos.

Outra sugestão minha:

Use o padrão Method Method para ramificações de alto nível. A hierarquia de classes mencionada é mantida como é; criamos apenas manipuladores mais apropriados para os objetos, a fim de evitar projeções e propriedades / métodos inadequados que poluem a classe base.

//Template method
class BaseAccountOperation {

    BaseAccount account;

    void Execute() {

        //... some processing

        TryResetPassword();

        //... some processing

        TrySendMail();

        //... some processing
    }

    void TryResetPassword() {
        Print("Not allowed to reset password with this account type!");
    }

    void TrySendMail() {
        Print("Not allowed to reset password with this account type!");
    }
}

class UserAccountOperation : BaseAccountOperation {

    UserAccount userAccount;

    void TryResetPassword() {
        account.ResetPassword(...);
    }

}

class AdminAccountOperation : BaseAccountOperation {

    AdminAccount adminAccount;

    override void TryResetPassword() {
        account.ResetPassword(...);
    }

    void TrySendMail() {
        account.SendMail(...);
    }
}

Você pode vincular a operação à classe de conta apropriada usando um dicionário / hashtable ou executar operações em tempo de execução usando métodos de extensão, usar dynamicpalavra-chave ou, como última opção, usar apenas uma conversão para passar o objeto de conta para a operação (em neste caso, o número de lançamentos é apenas um, no início da operação).

Emerson Cardoso
fonte
1
Funcionaria apenas para sempre implementar o método "Execute ()", mas às vezes ele não faz nada? Ele retorna um bool, então acho que isso significa que foi feito ou não. Isso o devolve ao cliente: "Tentei redefinir a senha, mas não aconteceu (por qualquer motivo), então agora devo ..."
4
Ter os métodos "canX ()" e "doX ()" é um antipadrão: se uma interface expuser um método "doX ()", será melhor fazer X mesmo que fazer X signifique um não-op (por exemplo, padrão de objeto nulo ) Nunca deve ser responsabilidade dos usuários de uma interface envolver-se no acoplamento temporal (invoque o método A antes do método B), porque é muito fácil cometer erros e interromper as coisas.
1
Ter interfaces não tem influência no uso de composição sobre herança. Eles apenas representam uma funcionalidade que está disponível. O modo como essa funcionalidade entra em jogo é independente do uso da interface. O que você fez aqui é usar uma implementação ad-hoc de uma interface sem nenhum dos benefícios.
Miyamoto Akira
Interessante: a resposta mais votada diz basicamente o que meu comentário acima diz. Alguns dias você apenas tem sorte.
@nocomprende - sim, atualizei minha resposta de acordo com vários comentários. Graças para os feedbacks :)
Emerson Cardoso
0

Nenhuma das opções que você apresentou é boa OO. Se você está escrevendo declarações if em torno do tipo de um objeto, provavelmente está fazendo OO errado (existem exceções, essa não é uma). Aqui está a resposta OO simples para sua pergunta (pode não ser C # válido):

interface IAccount {
  bool CanResetPassword();

  void ResetPassword();

  // Other Account operations as needed
}

public class Resetable : IAccount {
  public bool CanResetPassword() {
    return true;
  }

  public void ResetPassword() {
    /* RESET PASSWORD */
  }
}

public class NotResetable : IAccount {
  public bool CanResetPassword() {
    return false;
  }

  public void ResetPassword() {
    Print("Not allowed to reset password with this account type!");}
  }

Modifiquei este exemplo para corresponder ao que o código original estava fazendo. Com base em alguns comentários, parece que as pessoas estão se perguntando se esse é o código específico 'certo' aqui. Esse não é o objetivo deste exemplo. Toda a sobrecarga polimórfica é essencialmente executar condicionalmente diferentes implementações de lógica com base no tipo do objeto. O que você está fazendo nos dois exemplos é tocar manualmente o que o seu idioma oferece como recurso. Em poucas palavras, você pode se livrar dos subtipos e colocar a capacidade de redefinir como uma propriedade booleana do tipo de Conta (ignorando outros recursos dos subtipos).

Sem uma visão mais ampla do design, é impossível saber se essa é uma boa solução para seu sistema específico. É simples e, se funcionar para o que você está fazendo, provavelmente nunca precisará pensar muito sobre isso novamente, a menos que alguém não verifique CanResetPassword () antes de chamar ResetPassword (). Você também pode retornar um booleano ou falhar silenciosamente (não recomendado). Realmente depende das especificidades do design.

JimmyJames
fonte
Nem todas as contas podem ser redefinidas. Além disso, uma conta pode ter mais funcionalidade do que ser redefinível?
TheCatWhisperer
2
"Nem todas as contas podem ser redefinidas." Não tenho certeza se isso foi escrito antes da edição. Segunda classe é NotResetable. "uma conta pode ter mais funcionalidade do que ser redefinível", como seria de esperar, mas isso não parece ser importante para a questão em questão.
JimmyJames
1
Esta é uma solução que segue na direção certa, eu acho, pois fornece uma boa solução dentro do OO. Provavelmente, alteraria o nome da interface para IPasswordReseter (ou algo nesse sentido), para segregar interfaces. O IAccount pode ser declarado como suportando várias outras interfaces. Isso daria a você a opção de ter classes com a implementação que é então composta e usada pela delegação nos objetos de domínio. @ JimmyJames, nitpick menor, em C #, ambas as classes precisarão especificar que implementam IAccount. Caso contrário, o código parece um pouco estranho ;-)
Miyamoto Akira
1
Além disso, verifique um comentário do Snowman em uma das outras respostas sobre CanX () e DoX (). Nem sempre um anti-padrão, embora, como ele tem ele usa (por exemplo, sobre o comportamento UI)
Miyamoto Akira
1
Esta resposta começa bem: isso é ruim OOP. Infelizmente, sua solução é tão ruim porque também subverte o sistema de tipos, mas gera uma exceção. Uma boa solução parece diferente: não possui métodos não implementados em um tipo. A estrutura .NET realmente usa sua abordagem para alguns tipos, mas isso foi inequivocamente um erro.
Konrad Rudolph
0

Responda

Minha resposta ligeiramente opinativa para sua pergunta é:

Os recursos de um objeto devem ser identificados por eles mesmos, não pela implementação de uma interface. Uma linguagem estaticamente fortemente tipada limitará essa possibilidade (por design).

Termo aditivo:

Ramificar no tipo de objeto é ruim.

Divagar

Vejo que você não adicionou a c#tag, mas está perguntando em um object-orientedcontexto geral .

O que você está descrevendo é um tecnicismo de linguagens estáticas / de tipos fortes, e não específico de "OO" em geral. Seu problema decorre, entre outros, do fato de que você não pode chamar métodos nesses idiomas sem ter um tipo suficientemente estreito no tempo de compilação (por definição de variável, parâmetro de método / definição de valor de retorno ou conversão explícita). Eu fiz as duas variantes no passado, nesses tipos de idiomas, e ambas me parecem feias hoje em dia.

Nas linguagens OO de tipo dinâmico, esse problema não ocorre devido a um conceito chamado "digitação de pato". Em resumo, isso significa que você basicamente não declara o tipo de um objeto em nenhum lugar (exceto ao criá-lo). Você envia mensagens para objetos e os objetos lidam com essas mensagens. Você não se importa se o objeto realmente possui um método com esse nome. Alguns idiomas ainda têm um method_missingmétodo genérico e abrangente, que o torna ainda mais flexível.

Portanto, nesse idioma (Ruby, por exemplo), seu código se torna:

account.reset_password

Período.

O accountvão quer redefinir a senha, lançar uma exceção "negado", ou jogar um "não sei como lidar com 'reset_password'" exceção.

Se você precisar ramificar explicitamente por qualquer motivo, ainda o faria na capacidade, não na classe:

account.reset_password  if account.can? :reset_password

(Onde can?é apenas um método que verifica se o objeto pode executar algum método, sem chamá-lo.)

Observe que, embora minha preferência pessoal esteja atualmente em idiomas de tipo dinâmico, essa é apenas uma preferência pessoal. Idiomas estaticamente tipificados também têm tudo a ver com eles, então, por favor, não tome essa divagação como uma festa contra idiomas tipicamente estatísticos ...

AnoE
fonte
Ótimo ter sua perspectiva aqui! Nós, do lado java / C #, tendemos a esquecer que existe um ramo diferente de OOP. Quando digo aos meus colegas, JS (para todos os seus problemas) é, de muitas maneiras, mais OO que C #, eles riem de mim!
TheCatWhisperer
Eu amo C # e acho que é uma boa linguagem de uso geral , mas é minha opinião pessoal de que, em termos de OO, é bastante pobre. Tanto nos recursos quanto na prática, tende a incentivar a programação procedural.
TheCatWhisperer
2
Pode-se argumentar que testar a existência de um método em uma linguagem tipada por pato é o mesmo que testar a implementação de uma interface em tipicamente estaticamente: você está executando uma verificação em tempo de execução para verificar se o objeto tem uma capacidade específica. Toda declaração de método é efetivamente sua própria interface, identificada apenas pelo nome do método, e sua existência não pode ser usada para inferir nenhuma outra informação sobre o objeto.
IMSOP
0

Parece que suas opções são derivadas de diferentes forças motivacionais. O primeiro parece ser um hack empregado devido à má modelagem de objetos. Demonstrou que suas abstrações estão erradas, pois quebram o polimorfismo. Muito provavelmente, ele quebra o princípio fechado aberto , o que resulta em um acoplamento mais apertado.

Sua segunda opção pode ser boa da perspectiva OOP, mas a conversão de tipo me confunde. Se sua conta permite que você redefina sua senha, por que você precisa de uma transmissão de texto? Portanto, supondo que sua CanResetPasswordcláusula represente algum requisito comercial fundamental que faz parte da abstração da conta, sua segunda opção é OK.

Zapadlo
fonte