Programmazione orientata agli oggetti: getter / setter o nomi logici


12

Attualmente sto pensando a un'interfaccia per una classe che sto scrivendo. Questa classe contiene stili per un personaggio, ad esempio se il carattere è grassetto, corsivo, sottolineato, ecc. Ho discusso con me stesso per due giorni se dovrei usare getter / setter o nomi logici per i metodi che cambiano i valori in questi stili. Mentre tendo a preferire i nomi logici, significa scrivere codice che non sia così efficiente e non così logico. Lasciate che vi faccia un esempio.

Ho una classe CharacterStylesche ha variabili membro bold, italic, underline(e alcuni altri, ma io li lascio fuori per mantenere le cose semplici). Il modo più semplice per consentire ad altre parti del programma di accedere a queste variabili sarebbe quello di scrivere metodi getter / setter, in modo che tu possa fare styles.setBold(true)e styles.setItalic(false).

Ma questo non mi piace. Non solo perché molte persone dicono che i getter / setter rompono l'incapsulamento (è davvero così male?), Ma soprattutto perché non mi sembra logico. Mi aspetto di modellare un personaggio con un metodo styles.format("bold", true)o qualcosa del genere, ma non con tutti questi metodi.

C'è un problema però. Dal momento che non è possibile accedere a una variabile membro oggetto dal contenuto di una stringa in C ++, dovrei o scrivere un grande contenitore if-statement / switch per tutti gli stili, oppure dovrei memorizzare gli stili in un array associativo ( carta geografica).

Non riesco a capire quale sia il modo migliore. Un momento penso che dovrei scrivere i getter / setter, e il momento successivo mi sposto verso l'altro modo. La mia domanda è: cosa faresti? E perché dovresti farlo?


La classe CharacterStyles fa effettivamente qualcosa di diverso dal bundle tre booleani? Come viene consumato?
Mark Canlas,

Sì, perché CharacterStyles dovrebbe essere in grado di ereditare da altri CharacterStyles. Ciò significa che se ho un CharacterStyles con boldset to truee le altre variabili non definite, i metodi getter per le altre variabili dovrebbero restituire il valore dello stile parent (che è memorizzato in un'altra proprietà), mentre il getter per la boldproprietà dovrebbe restituire true. Ci sono altre cose come un nome e il modo in cui viene visualizzato nell'interfaccia.
Rana,

È possibile utilizzare un enum per definire le varie opzioni di stile, quindi utilizzare un'istruzione switch. A proposito, come stai gestendo indefinito?
Tyanna,

@Tyanna: In effetti, ho anche pensato di usare un enum, ma questo è solo un piccolo dettaglio. Stavo programmando di impostare valori non definiti su NULL. Non è questo il modo migliore?
Rana,

Risposte:


6

Sì, i getter / setter interrompono l'incapsulamento - in pratica sono solo un ulteriore livello tra l'accesso diretto al campo sottostante. Potresti anche accedervi direttamente.

Ora, se vuoi che metodi più complessi accedano al campo, questo è valido, ma invece di esporre il campo, devi pensare a quali metodi dovrebbe offrire la classe. vale a dire. invece di una classe Bank con un valore di Money che viene esposto usando una proprietà, è necessario pensare a quali tipi di accesso dovrebbe offrire un oggetto Bank (aggiungere denaro, prelevare, ottenere saldo) e implementare invece quelli. Una proprietà sulla variabile Money è solo sintatticamente diversa dall'esporre direttamente la variabile Money.

DrDobbs ha un articolo che dice di più.

Per il tuo problema, avrei 2 metodi setStyle e clearStyle (o qualsiasi altra cosa) che prendono in considerazione i possibili stili. Un'istruzione switch all'interno di questi metodi applicherebbe quindi i valori rilevanti alle variabili di classe appropriate. In questo modo, puoi cambiare la rappresentazione interna degli stili in qualcos'altro se in seguito decidi di memorizzarli come una stringa (ad esempio per l'uso in HTML), qualcosa che richiederebbe di cambiare anche tutti gli utenti della tua classe se lo usassi get / set properties.

Puoi ancora attivare le stringhe se vuoi prendere valori arbitrari, avere una grande istruzione if-then (se ce ne sono alcune) o una mappa di valori di stringa per puntare i metodi usando std :: mem_fun (o std :: funzione ) in modo che "grassetto" venga archiviato in una chiave della mappa con il valore che è sts :: mem_fun in un metodo che imposta la variabile in grassetto su true (se le stringhe sono uguali ai nomi delle variabili dei membri, è possibile utilizzare anche la macro stringificante per ridurre la quantità di codice che devi scrivere)


Risposta eccellente! Soprattutto la parte relativa alla memorizzazione dei dati come HTML mi è sembrata una buona ragione per andare davvero su questa strada. Quindi suggeriresti di memorizzare i diversi stili in un enum e quindi di impostare stili come styles.setStyle(BOLD, true)? È corretto?
Rana,

Sì, avrei solo 2 metodi: setStyle (BOLD) e clearstyle (BOLD). dimentica il secondo parametro, lo preferisco così e potresti quindi sovraccaricare clearStyle per non accettare parametri per cancellare tutti i flag di stile.
gbjbaanb,

Non funzionerebbe, perché alcuni tipi (come la dimensione del carattere) richiedono un parametro. Ma grazie ancora per la risposta!
Rana,

Ho cercato di implementare questo, ma c'è un problema che non ho considerato: come si implementa styles.getStyle(BOLD)quando non si hanno solo variabili membro di tipo booleano, ma anche di tipi intero e stringa (ad esempio:) styles.getStyle(FONTSIZE). Dal momento che non è possibile sovraccaricare i tipi restituiti, come si programma? So che potresti usare indicatori vuoti, ma a me sembra davvero un brutto modo. Hai qualche suggerimento su come farlo?
Rana,

potresti restituire un'unione, o una struttura, o con il nuovo standard, puoi sovraccaricare in base al tipo restituito. Detto questo, stai cercando di implementare un singolo metodo per impostare uno stile, dove lo stile è una bandiera, una famiglia di caratteri e una dimensione? Forse stai cercando di forzare un po 'troppo l'astrazione in questo caso.
gbjbaanb,

11

Un'idea che potresti non aver preso in considerazione è il motivo decorativo . Invece di impostare le bandiere in un oggetto e quindi applicare quelle bandiere a qualsiasi cosa tu stia scrivendo, avvolgi la classe che esegue la scrittura in Decoratori, che a loro volta applicano gli stili.

Il codice chiamante non ha bisogno di sapere quanti di questi wrapper hai messo attorno al tuo testo, devi solo chiamare un metodo sull'oggetto esterno e chiama lo stack.

Per un esempio di pseudocodice:

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 così via, per ogni stile decorativo. Nel suo utilizzo più semplice, puoi quindi dire

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

E il codice che chiama questo metodo non ha bisogno di conoscere i dettagli di ciò che sta ricevendo. Finché è QUALCOSA che può disegnare il testo attraverso un metodo WriteString.


Hmmm, lo guarderò domani con una mente fresca e poi ti ricontatterò. Grazie per la risposta.
Rana,

Sono un grande fan del modello Decorator. Il fatto che questo modello assomigli all'HTML (dove l'operazione principale è racchiudere la stringa di input con i tag) è un'attestazione che questo approccio può soddisfare tutte le esigenze dell'utente dell'interfaccia. Una cosa da tenere a mente è che un'interfaccia che è buona e facile da usare, può avere un'implementazione sottostante tecnicamente complicata. Ad esempio, se stai effettivamente implementando te stesso il renderizzatore di testo, potresti scoprire che è TextWriternecessario parlare con entrambi BoldDecoratore ItalicDecorator(in due direzioni) per portare a termine il lavoro.
rwong

mentre questa è una bella risposta, non reintroduce la domanda dell'op? "grassetto applicaton on" -> come sarà fatto? usando setter / getter come SetBold ()? Qual è esattamente la domanda dell'op.
stijn

1
@stijn: Non proprio. Esistono diversi modi per evitare quella situazione. Ad esempio, puoi racchiuderlo in un builder con un metodo toggleStyle (argomento enum) che aggiunge e rimuove i decoratori da un array, decorando solo l'elemento finale quando chiami BuildDecoratedTextWriter. Oppure puoi fare come suggerito rwong e complicare la classe base. Dipende dalle circostanze e il PO non era abbastanza specifico sulle specifiche generali da indovinare quale fosse il migliore per lui. Sembra abbastanza intelligente da essere in grado di capirlo una volta ottenuto lo schema però.
pdr

1
Ho sempre pensato che il motivo Decoratore implichi un ordinamento per le decorazioni. Considera il codice di esempio mostrato; come si fa a rimuovere ItalicDecorator? Tuttavia, penso che qualcosa come Decorator sia la strada da percorrere. Grassetto, corsivo e sottolineato non sono gli unici tre stili. C'è sottile, semibold, condensato, nero, largo, corsivo con swash, maiuscoletto, ecc. Potresti aver bisogno di un modo per applicare un set arbitrariamente grande di stili a un personaggio.
Barry Brown,

0

Mi spingerei verso la seconda soluzione. Sembra più elegante e flessibile. Sebbene sia difficile immaginare tipi diversi da grassetto, corsivo e sottolineato (sottolineato?), Sarebbe difficile aggiungere nuovi tipi utilizzando le variabili membro.

Ho usato questo approccio in uno dei miei ultimi progetti. Ho una classe che può avere più attributi booleani. Il numero e i nomi degli attributi possono cambiare nel tempo. Li conservo in un dizionario. Se un attributo non è presente, suppongo che il suo valore sia "falso". Devo anche memorizzare un elenco di nomi di attributi disponibili, ma questa è un'altra storia.


1
A parte questi tipi, aggiungerei barrato, dimensione carattere, famiglia carattere, apice, pedice e forse altri in seguito. Ma perché hai scelto questo metodo nel tuo progetto? Non pensi che sia un codice sporco dover memorizzare un elenco di attributi consentiti? Sono davvero curioso di sapere quali sono le tue risposte a queste domande!
Rana,

Ho dovuto affrontare la situazione in cui alcuni attributi potevano essere definiti dai client quando l'applicazione era già stata distribuita. Alcuni altri attributi erano richiesti per alcuni client ma obsoleti per gli altri. Potrei personalizzare i moduli, i set di dati memorizzati e affrontare altri problemi utilizzando questo approccio. Non penso che generi codice sporco. A volte non riesci proprio a prevedere quale tipo di informazioni saranno necessarie per il tuo cliente.
Andrzej Bobak,

Ma il mio cliente non dovrà aggiungere attributi personalizzati. E in quel caso penso che appaia un po '"confuso". Non sei d'accordo?
Rana,

Se non si verificano problemi con il numero dinamico di attributi, non è necessario spazio flessibile per memorizzarli. È vero :) Non sono d'accordo con l'altra parte però. A mio avviso, definire gli attributi in un dizionario / hashmap / etc non è un approccio errato.
Andrzej Bobak

0

Penso che tu stia pensando troppo al problema.

styles.setBold(true) e va styles.setItalic(false)bene

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.