Programação Orientada a Objetos: getters / setters ou nomes lógicos

12

Atualmente, estou pensando em uma interface para uma classe que estou escrevendo. Esta classe contém estilos para um caractere, por exemplo, se o caractere está em negrito, itálico, sublinhado, etc. Estou discutindo há dois dias se devo usar getters / setters ou nomes lógicos para os métodos que alteram os valores para esses estilos. Embora eu prefira nomes lógicos, significa escrever código que não é tão eficiente e nem tão lógico. Deixe-me lhe dar um exemplo.

Eu tenho uma classe CharacterStylesque tem variáveis de membro bold, italic, underline(e alguns outros, mas vou deixá-los fora para mantê-lo simples). A maneira mais fácil de permitir que outras partes do programa acessem essas variáveis ​​seria escrever métodos getter / setter, para que você possa fazer styles.setBold(true)e styles.setItalic(false).

Mas eu não gosto disso. Não apenas porque muitas pessoas dizem que os getters / setters quebram o encapsulamento (é realmente tão ruim assim?), Mas principalmente porque não me parece lógico. Espero estilizar um personagem através de um método, styles.format("bold", true)ou algo assim, mas não através de todos esses métodos.

Há um problema, entretanto. Como você não pode acessar uma variável de membro do objeto pelo conteúdo de uma string em C ++, eu precisaria escrever um grande contêiner if-statement / switch para todos os estilos ou teria que armazenar os estilos em uma matriz associativa ( mapa).

Não consigo descobrir qual é o melhor caminho. Em um momento, acho que devo escrever os leitores, e no momento seguinte me inclino para o outro lado. Minha pergunta é: o que você faria? E por que você faria isso?


fonte
A classe CharacterStyles realmente faz outra coisa que não agrupar três booleanos? Como é consumido?
Mark Canlas
Sim, porque CharacterStyles deve poder herdar de outros CharacterStyles. Isso significa que, se eu tiver um CharacterStyles bolddefinido como truee as outras variáveis ​​indefinidas, os métodos getter para as outras variáveis ​​deverão retornar o valor do estilo pai (que está armazenado em outra propriedade), enquanto o getter da boldpropriedade deverá retornar true. Existem outras coisas como um nome e a maneira como ele é exibido na interface também.
Sapo
Você pode usar uma enumeração para definir as várias opções de estilo e, em seguida, usar uma instrução switch. Btw, como você está lidando indefinido?
Tyanna
@Tyanna: Na verdade, eu também pensei em usar um enum, mas isso é apenas um pequeno detalhe. Eu estava planejando apenas definir valores indefinidos como NULL. Essa não é a melhor maneira?
Frog

Respostas:

6

Sim, getters / setters quebram o encapsulamento - eles basicamente são apenas uma camada extra entre acessar diretamente o campo subjacente. Você também pode acessá-lo diretamente.

Agora, se você quiser métodos mais complexos para acessar o campo, isso é válido, mas, em vez de expor o campo, é necessário pensar em quais métodos a classe deve oferecer. ie em vez de uma classe Bank com um valor Money exposto usando uma propriedade, você precisa pensar que tipos de acesso um objeto Bank deve oferecer (adicionar dinheiro, retirar, obter saldo) e implementá-los. Uma propriedade na variável Money é apenas sintaticamente diferente de expor a variável Money diretamente.

DrDobbs tem um artigo que diz mais.

Para o seu problema, eu teria 2 métodos setStyle e clearStyle (ou o que seja) que levam uma enumeração dos estilos possíveis. Uma instrução switch dentro desses métodos aplicaria os valores relevantes às variáveis ​​de classe apropriadas. Dessa forma, você pode alterar a representação interna dos estilos para outra, se posteriormente decidir armazená-los como uma sequência de caracteres (para uso em HTML, por exemplo) - algo que exigiria a alteração de todos os usuários de sua classe também se você usasse obter / definir propriedades.

Você ainda pode ativar as strings se desejar obter valores arbitrários, ou ter uma declaração if-then grande (se houver algumas delas) ou um mapa de valores de string para ponteiros de método usando std :: mem_fun (ou std :: função ) para que "bold" seja armazenado em uma chave de mapa com o valor sts :: mem_fun em um método que defina a variável bold como true (se as strings forem iguais aos nomes das variáveis ​​de membro, você também poderá usar a macro stringing para reduzir a quantidade de código que você precisa escrever)

gbjbaanb
fonte
Excelente resposta! Especialmente a parte sobre o armazenamento de dados como HTML me pareceu um bom motivo para realmente seguir esse caminho. Então você sugere que você armazene os diferentes estilos em uma enumeração e defina estilos como styles.setStyle(BOLD, true)? Isso está correto?
Frog
sim, apenas eu teria 2 métodos - setStyle (BOLD) e clearstyle (BOLD). esqueça o segundo parâmetro, eu prefiro assim e você poderá sobrecarregar o clearStyle para não usar parâmetros para limpar todos os sinalizadores de estilo.
Gbjbaanb
Isso não funcionaria, porque alguns tipos (como tamanho da fonte) precisam de um parâmetro. Mas ainda obrigado pela resposta!
Sapo
Eu tenho tentado implementar isso, mas há um problema que eu não considerei: como você implementa styles.getStyle(BOLD)quando você não possui apenas variáveis ​​de membro do tipo booleano, mas também dos tipos inteiro e string (por exemplo styles.getStyle(FONTSIZE):). Como você não pode sobrecarregar os tipos de retorno, como você programa isso? Eu sei que você poderia usar ponteiros nulos, mas isso me parece uma péssima maneira. Você tem alguma dica de como fazer isso?
Sapo
você pode retornar uma união, uma estrutura ou, com o novo padrão, pode sobrecarregar por tipo de retorno. Dito isto, você está tentando implementar um único método para definir um estilo, onde estilo é uma bandeira, uma família de fontes e um tamanho? Talvez você esteja tentando forçar a abstração um pouco demais neste caso.
precisa
11

Uma ideia que você pode não ter considerado é o Padrão Decorador . Em vez de definir sinalizadores em um objeto e aplicá-los ao que você estiver escrevendo, agrupe a classe que escreve nos Decoradores, que, por sua vez, aplicam os estilos.

O código de chamada não precisa saber quantos desses wrappers você colocou em torno do seu texto, basta chamar um método no objeto externo e chamar a pilha.

Para um exemplo de pseudocódigo:

class TextWriter : TextDrawingInterface {
    public:
        void WriteString(string x) {
            // write some text somewhere somehow
        }
}

class BoldDecorator : TextDrawingInterface {
    public:
        void WriteString(string x) {
            // bold application on
            m_textWriter.WriteString(x);
            // bold application off
        }

        ctor (TextDrawingInterface textWriter) {
            m_textWriter = textWriter;
        }

    private:
        TextWriter m_TextWriter;
}

E assim por diante, para cada estilo de decoração. Em seu uso mais simples, você pode dizer

TextDrawingInterface GetDecoratedTextWriter() {
    return new BoldDecorator(new ItalicDecorator(new TextWriter()));
}

E o código que chama esse método não precisa saber os detalhes do que está recebendo. Contanto que ALGO possa desenhar texto através de um método WriteString.

pdr
fonte
Hmmm, eu vou dar uma olhada nisso amanhã com uma mente renovada e voltarei para você então. Obrigado pela resposta.
Frog
Sou um grande fã do padrão Decorator. O fato de esse padrão se parecer com HTML (onde a operação principal é incluir a cadeia de entrada com tags) é um atestado de que essa abordagem pode satisfazer todas as necessidades do usuário da interface. Um aspecto a ter em mente é que uma interface que é boa e fácil de usar pode ter uma implementação subjacente tecnicamente complicada. Por exemplo, se você estiver realmente implementando o renderizador de texto, poderá achar que TextWriterprecisa conversar com os dois BoldDecoratore ItalicDecorator(bidirecionalmente) para concluir o trabalho.
rwong
Embora seja uma boa resposta, ela não reintroduz a pergunta do op? "aplicação arrojada" -> como isso será feito? usando setters / getters como SetBold ()? Qual é exatamente a questão da operação.
stijn
1
@stijn: Na verdade não. Existem várias maneiras de evitar essa situação. Por exemplo, você pode agrupar isso em um construtor com um método toggleStyle (argumento enum) que adiciona e remove decoradores de uma matriz, decorando apenas o item final quando você chama BuildDecoratedTextWriter. Ou você pode fazer o que rwong sugeriu e complicar a classe base. Depende da circunstância e o OP não foi suficientemente específico sobre as especificações gerais para adivinhar qual é o melhor para ele. Ele parece inteligente o suficiente para ser capaz de descobrir uma vez que ele consegue o padrão.
PDR
1
Eu sempre pensei que o Padrão Decorador implica um pedido nas decorações. Considere o código de exemplo mostrado; como se remove o ItalicDecorator? Ainda assim, acho que algo como Decorator é o caminho a percorrer. Negrito, itálico e sublinhado não são os únicos três estilos. Existem maiúsculas, semifoldas, condensadas, pretas, largas, itálico com swash, letras minúsculas, etc. Você pode precisar de uma maneira de aplicar um conjunto arbitrariamente grande de estilos a um personagem.
Barry Brown
0

Eu me inclinaria para a segunda solução. Parece mais elegante e flexível. Embora seja difícil imaginar outros tipos além de negrito, itálico e sublinhado (sublinhado?), Seria difícil adicionar novos tipos usando variáveis ​​de membro.

Eu usei essa abordagem em um dos meus projetos mais recentes. Eu tenho uma classe que pode ter vários atributos booleanos. O número e os nomes dos atributos podem mudar com o tempo. Eu os guardo em um dicionário. Se um atributo não estiver presente, presumo que seu valor seja "false". Também tenho que armazenar uma lista de nomes de atributos disponíveis, mas isso é outra história.

Andrzej Bobak
fonte
1
Além desses tipos, eu adicionaria tachado, tamanho da fonte, família da fonte, sobrescrito, subscrito e talvez outros mais tarde. Mas por que você escolheu esse método em seu projeto? Você não acha que é um código sujo ter que armazenar uma lista de atributos permitidos? Estou realmente curioso para saber quais são suas respostas para essas perguntas!
Frog
Eu tive que lidar com situações em que alguns atributos poderiam ser definidos pelos clientes quando o aplicativo já estava implantado. Alguns outros atributos foram necessários para alguns clientes, mas obsoletos para os outros. Eu poderia personalizar os formulários, os conjuntos de dados armazenados e resolver outros problemas usando essa abordagem. Eu não acho que gera código sujo. Às vezes, você simplesmente não pode prever que tipo de informação será necessária ao seu cliente.
Andrzej Bobak
Mas meu cliente não precisará adicionar atributos personalizados. E nesse caso, acho que parece um pouco "hacky". Você não concorda?
Frog
Se você não enfrentar um problema de número dinâmico de atributos, não precisará de espaço flexível para armazená-los. Isso é verdade :) Eu não concordo com a outra parte. Encontrar atributos em um dicionário / hashmap / etc não é uma abordagem ruim na minha opinião.
Andrzej Bobak
0

Eu acho que você está pensando demais sobre o assunto.

styles.setBold(true) e styles.setItalic(false)estão bem

Tulains Córdova
fonte