Programowanie obiektowe: metody pobierające / ustawiające lub nazwy logiczne

12

Obecnie myślę o interfejsie do klasy, którą piszę. Ta klasa zawiera style dla znaku, na przykład czy znak jest pogrubiony, pochylony, podkreślony itp. Przez dwa dni debatowałem ze sobą, czy powinienem używać metod pobierających / ustawiających lub nazw logicznych dla metod, które zmieniają wartości na te style. Chociaż wolę nazwy logiczne, oznacza to pisanie kodu, który nie jest tak wydajny i nie tak logiczny. Dam ci przykład.

Mam klasy CharacterStyles, która ma zmienne składowe bold, italic, underline(i kilka innych, ale zostawię je zachować to proste). Najprostszym sposobem, aby umożliwić innym częściom programu dostęp do tych zmiennych, byłoby napisanie metod pobierających / ustawiających, abyś mógł to zrobić styles.setBold(true)i styles.setItalic(false).

Ale mi się to nie podoba. Nie tylko dlatego, że wiele osób twierdzi, że osoby pobierające / ustawiające przerywają enkapsulację (czy to naprawdę tak źle?), Ale przede wszystkim dlatego, że nie wydaje mi się to logiczne. Spodziewam się stylizować znak za pomocą jednej metody styles.format("bold", true)lub czegoś podobnego, ale nie za pomocą wszystkich tych metod.

Jest jednak jeden problem. Ponieważ nie możesz uzyskać dostępu do zmiennej elementu obiektu przez zawartość łańcucha w C ++, musiałbym albo napisać duży kontener if-instrukcja / switch dla wszystkich stylów, albo musiałbym przechowywać style w tablicy asocjacyjnej ( mapa).

Nie mogę ustalić, jaki jest najlepszy sposób. W jednej chwili myślę, że powinienem napisać getters / setters, a za chwilę pochylam się w drugą stronę. Moje pytanie brzmi: co byś zrobił? A dlaczego miałbyś to zrobić?

Żaba
źródło
Czy klasa CharacterStyles faktycznie robi coś innego niż spakowanie trzech booleanów? Jak się go konsumuje?
Mark Canlas
Tak, ponieważ CharacterStyles powinny móc dziedziczyć po innych CharacterStyles. Oznacza to, że jeśli mam CharacterStyles z boldustawionym na, truea inne zmienne są niezdefiniowane, metody pobierające dla innych zmiennych powinny zwracać wartość stylu nadrzędnego (który jest przechowywany w innej właściwości), podczas gdy moduł pobierający dla boldwłaściwości powinien zwracać true. Istnieją również inne rzeczy, takie jak nazwa i sposób wyświetlania jej w interfejsie.
Żaba
Możesz użyć wyliczenia, aby zdefiniować różne opcje stylu, a następnie użyć instrukcji switch. Przy okazji, jak sobie radzisz z niezdefiniowanym?
Tyanna
@Tyanna: Rzeczywiście, myślałem również o użyciu wyliczenia, ale to tylko drobny szczegół. Planowałem właśnie ustawić niezdefiniowane wartości na NULL. Czy to nie najlepszy sposób?
Żaba

Odpowiedzi:

6

Tak, pobierający / ustawiający przerywają enkapsulację - w zasadzie są tylko dodatkową warstwą pomiędzy bezpośrednim dostępem do podstawowego pola. Równie dobrze możesz po prostu uzyskać do niego bezpośredni dostęp.

Teraz, jeśli chcesz uzyskać bardziej złożone metody dostępu do pola, jest to poprawne, ale zamiast ujawniać pole, musisz pomyśleć, jakie metody powinna oferować klasa. to znaczy. zamiast klasy Bank z wartością Pieniądze, która jest ujawniana przy użyciu nieruchomości, musisz pomyśleć, jaki rodzaj dostępu powinien oferować obiekt Banku (dodać pieniądze, wypłacić, uzyskać równowagę) i wdrożyć je. Właściwość zmiennej Money różni się tylko składniowo od bezpośredniego wyświetlania zmiennej Money.

DrDobbs ma artykuł, który mówi więcej.

W przypadku twojego problemu mam 2 metody setStyle i clearStyle (lub cokolwiek innego), które przyjmują wyliczenie możliwych stylów. Instrukcja switch wewnątrz tych metod zastosowałaby następnie odpowiednie wartości do odpowiednich zmiennych klas. W ten sposób możesz zmienić wewnętrzną reprezentację stylów na coś innego, jeśli później zdecydujesz się zapisać je jako ciąg znaków (na przykład w HTML) - coś, co wymagałoby zmiany wszystkich użytkowników twojej klasy, jeśli używałeś pobierz / ustaw właściwości.

Nadal możesz włączyć ciągi, jeśli chcesz przyjmować dowolne wartości, albo mieć dużą instrukcję if-then (jeśli jest ich kilka), albo mapę wartości ciągów do wskaźników metod przy użyciu std :: mem_fun (lub std :: funkcja ), więc „pogrubienie” byłoby przechowywane w kluczu mapy, a jego wartość to sts :: mem_fun dla metody, która ustawia zmienną pogrubioną na true (jeśli ciągi znaków są takie same jak nazwy zmiennych składowych, można również użyć stringifying makro , aby zmniejszyć ilość kodu trzeba napisać)

gbjbaanb
źródło
Doskonała odpowiedź! Zwłaszcza część dotycząca przechowywania danych w formacie HTML wydała mi się dobrym powodem, aby naprawdę pójść tą drogą. Sugerowałbyś więc przechowywanie różnych stylów w wyliczeniu, a następnie ustawienie stylów takich jak styles.setStyle(BOLD, true)? Czy to jest poprawne?
Żaba
tak, tylko miałbym 2 metody - setStyle (BOLD) i clearstyle (BOLD). zapomnij o drugim parametrze, po prostu wolę go tak, a następnie możesz przeładować clearStyle, aby nie brać parametrów, aby usunąć wszystkie flagi stylu.
gbjbaanb
To nie zadziałałoby, ponieważ niektóre typy (np. Rozmiar czcionki) wymagają parametru. Ale wciąż dzięki za odpowiedź!
Żaba
Próbowałem to zaimplementować, ale jest jeden problem, którego nie wziąłem pod uwagę: jak zaimplementować, styles.getStyle(BOLD)gdy masz nie tylko zmienne składowe typu boolean, ale także liczby całkowite i ciąg znaków (na przykład styles.getStyle(FONTSIZE):). Ponieważ nie możesz przeciążać typów zwrotów, jak to zaprogramujesz? Wiem, że możesz użyć pustych wskaźników, ale dla mnie to brzmi naprawdę źle. Czy masz jakieś wskazówki, jak to zrobić?
Żaba
możesz zwrócić związek lub strukturę, albo z nowym standardem możesz przeciążać według typu zwrotu. To powiedziawszy, czy próbujesz wdrożyć jedną metodę ustawiania stylu, gdzie styl jest flagą, rodziną czcionek i rozmiarem? Może w tym przypadku próbujesz wymusić zbytnią abstrakcję.
gbjbaanb
11

Jednym z pomysłów, których mogłeś nie wziąć pod uwagę, jest Wzór Dekoratora . Zamiast ustawiać flagi w obiekcie, a następnie stosować te flagi do wszystkiego, co piszesz, zawijasz klasę, która zajmuje się pisaniem w dekoratorach, które z kolei stosują style.

Kod wywołujący nie musi wiedzieć, ile z tych opakowań otoczył tekst, wystarczy wywołać metodę na zewnętrznym obiekcie i wywołuje stos.

Na przykład pseudokodu:

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

I tak dalej, dla każdego stylu dekoracji. W najprostszym użyciu możesz powiedzieć

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

A kod wywołujący tę metodę nie musi znać szczegółów tego, co otrzymuje. Tak długo, jak COŚ jest w stanie narysować tekst za pomocą metody WriteString.

pdr
źródło
Hmmm, przyjrzę się temu jutro ze świeżym umysłem i wtedy się odezwę. Dziękuję za odpowiedź.
Żaba
Jestem wielkim fanem wzoru dekoratora. Fakt, że ten wzorzec przypomina HTML (gdzie podstawową operacją jest umieszczenie ciągu wejściowego za pomocą znaczników) jest potwierdzeniem, że takie podejście może zaspokoić wszystkie potrzeby użytkownika interfejsu. Należy pamiętać, że interfejs, który jest dobry i łatwy w użyciu, może mieć implementację, która jest technicznie skomplikowana. Na przykład, jeśli faktycznie wdrażasz mechanizm renderowania tekstu, możesz TextWriterpotrzebować porozmawiać z nimi BoldDecoratori ItalicDecorator(dwukierunkowo), aby wykonać zadanie.
rwong,
choć jest to miła odpowiedź, czy nie przywraca ona pytania operacyjnego? „pogrubienie aplikacji” -> jak to się stanie? używasz setterów / getterów jak SetBold ()? To jest dokładnie pytanie op.
stijn
1
@stijn: Nie bardzo. Istnieje wiele sposobów uniknięcia tej sytuacji. Na przykład możesz owinąć to w programie budującym za pomocą metody toggleStyle (argument enum), która dodaje i usuwa dekoratory z tablicy, dekorując tylko końcowy element po wywołaniu BuildDecoratedTextWriter. Lub możesz zrobić zgodnie z sugestią rwong i skomplikować klasę podstawową. Zależy od okoliczności, a OP nie był wystarczająco szczegółowy na temat ogólnej specyfikacji, aby odgadnąć, która z nich jest dla niego najlepsza. Wydaje się na tyle mądry, że jest w stanie to rozgryźć, gdy tylko dostanie wzór.
pdr
1
Zawsze myślałem, że wzór dekoratora oznacza uporządkowanie dekoracji. Rozważ pokazany przykładowy kod; jak można przejść do usunięcia ItalicDecorator? Mimo to myślę, że coś w rodzaju Dekoratora jest właściwą drogą. Pogrubienie, kursywa i podkreślenie to nie jedyne trzy style. Są cienkie, półpogrubione, skondensowane, czarne, szerokie, kursywa-z-kreską, małe czapki itp. Może być potrzebny sposób na zastosowanie dowolnie dużego zestawu stylów do postaci.
Barry Brown
0

Skłoniłbym się do drugiego rozwiązania. Wygląda bardziej elegancko i elastycznie. Chociaż trudno jest wyobrazić sobie inne typy niż pogrubienie, kursywa i podkreślenie (overline?), Trudno byłoby dodać nowe typy przy użyciu zmiennych składowych.

Zastosowałem to podejście w jednym z moich najnowszych projektów. Mam klasę, która może mieć wiele atrybutów boolowskich. Liczba i nazwy atrybutów mogą się zmieniać w czasie. Przechowuję je w słowniku. Jeśli atrybut nie jest obecny, zakładam, że jego wartość to „fałsz”. Muszę też przechowywać listę dostępnych nazw atrybutów, ale to już inna historia.

Andrzej Bobak
źródło
1
Oprócz tych typów dodam przekreślenie, rozmiar czcionki, rodzinę czcionek, indeks górny, indeks dolny i być może inne później. Ale dlaczego wybrałeś tę metodę w swoim projekcie? Nie sądzisz, że to brudny kod, aby przechowywać listę dozwolonych atrybutów? Jestem naprawdę ciekawy, jakie są twoje odpowiedzi na te pytania!
Żaba
Musiałem poradzić sobie z sytuacją, w której niektóre atrybuty mogły zostać zdefiniowane przez klientów, gdy aplikacja była już wdrożona. Niektóre inne atrybuty były wymagane dla niektórych klientów, ale dla innych były nieaktualne. Mogę dostosować formularze, przechowywane zestawy danych i rozwiązać inne problemy, stosując to podejście. Nie sądzę, że generuje brudny kod. Czasami po prostu nie możesz przewidzieć, jakie informacje będą potrzebne Twojemu klientowi.
Andrzej Bobak,
Ale mój klient nie będzie musiał dodawać niestandardowych atrybutów. I w takim przypadku myślę, że wygląda to trochę „hacky”. Nie zgadzasz się
Żaba
Jeśli nie napotkasz problemu z dynamiczną liczbą atrybutów, nie potrzebujesz elastycznej przestrzeni do ich przechowywania. To prawda :) Nie zgadzam się jednak z drugą częścią. Odczytywanie atrybutów w słowniku / hashmap / etc nie jest moim zdaniem złym podejściem.
Andrzej Bobak,
0

Myślę, że przesadnie zastanawiasz się nad tym problemem.

styles.setBold(true) i styles.setItalic(false)są OK

Tulains Córdova
źródło