Fac rework la o aplicaţie şi m-am hotărât să decuplez partea logică de partea de UI. Am frunzărit prin pattern-uri şi cel mai potrivit mi se părut adapter pattern.
Practic îi dai părţii logice să lucreze cu nişte widget-uri abstracte, care sunt implementate în modulul UI.
#include <iostream>
#include <string>
typedef std::string String;
// interfata abstracta
class TextWidgetInterface {
public:
virtual void setText(const String &str) = 0; // metoda abstracta
};
// functie care "rontaie" interfete abstracte
void aduna_si_afiseaza(TextWidgetInterface *textWidget, int a, int b)
{
String str = std::to_string(a + b);
textWidget->setText(str);
}
// implementare interfata
class TextWidget : public TextWidgetInterface {
public:
void setText(const String &str) {
printf("%s\n", str.c_str());
}
};
int main()
{
TextWidget textWidget;
aduna_si_afiseaza(&textWidget, 1, 2);
}
Ce ai facut tu se numeste “Programming to an interface” sau pur si simplu decuplare.
Adapter pattern e folosit sa “adapteze” un obiect cu alt obiect total diferit. Face obiectul din urma sa se comporte ca primul obiect.
E foarte bine ce faci tu doar ca nu te incurca in denumiri si implementarea design pattern-urilor efective. Daca aplici principiile SOLID e foarte bine.
E prima dată când aud de “programming to an interface” şi nu sunt foarte sigur că înţeleg care e diferenţa. Poţi să detaliezi un pic? “Programming to an interface” îmi pare mai degrabă a variantă de implementare pentru “adapter pattern”.
Păi intr-un fel sau altul, TextWidget trebuie să fie un wrapper peste widget-ul corespunzător toolkit-ului pe care il folosesc (GTK+ Text Widget, QTextLine, FXTextField, Cocoa Text Field etc). Sau chiar sa apeleze widget-ul corespunzator din Android NDK prin JNI.
Am găsit un pattern pentru abstractizare şi mai interesant (mersi @anon31094663 pentru recomandarea de a citi GoF): bridge pattern. In cazul acestui pattern nu sunt obligat sa-i dau clientului să “ronţăie” clase pur abstracte (asta ma obliga sa folosesc pointeri şi imi complica viaţa dacă voiam să folosesc operatorii << şi >>).
De exemplu, definesc o interfaţă generică care acceptă string-uri, la care “conectez” o implementare oarecare (care poate sa faca orice cu stringul ala, sa-l scrie pe disc sau sa-l afiseze pe ecran sau sa-l trimita pe un socket, pe generatorul stringului nu-l intereseaza).