تكرار البيانات المرصودة.
هل يتم تخزين بيانات النطاق (domain data) في فئات مسؤولة عن واجهة المستخدم الرسومية (GUI)؟
إذن فمن الجيد فصل البيانات في فئات منفصلة، مع ضمان الاتصال والمزامنة بين فئة النطاق وواجهة المستخدم الرسومية.
##مثال
class IntervalWindow {
String startText = "0";
String endText = "0";
String lengthText = "0";
void onStartChanged(String text) {
startText = text;
calculateLength();
}
void onEndChanged(String text) {
endText = text;
calculateLength();
}
void calculateLength() {
int length = Integer.parseInt(endText) - Integer.parseInt(startText);
lengthText = String.valueOf(length);
}
}interface IntervalObserver {
void update(Interval subject);
}
class IntervalWindow implements IntervalObserver {
private final Interval subject = new Interval();
String startText;
String endText;
String lengthText;
IntervalWindow() {
subject.addObserver(this);
update(subject);
}
void onStartChanged(String text) {
subject.setStart(Integer.parseInt(text));
}
void onEndChanged(String text) {
subject.setEnd(Integer.parseInt(text));
}
public void update(Interval subject) {
startText = String.valueOf(subject.getStart());
endText = String.valueOf(subject.getEnd());
lengthText = String.valueOf(subject.getLength());
}
}
class Interval {
private final List<IntervalObserver> observers = new ArrayList<>();
private int start;
private int end;
void addObserver(IntervalObserver observer) { observers.add(observer); }
void removeObserver(IntervalObserver observer) { observers.remove(observer); }
int getStart() { return start; }
int getEnd() { return end; }
int getLength() { return end - start; }
void setStart(int arg) {
start = arg;
notifyObservers();
}
void setEnd(int arg) {
end = arg;
notifyObservers();
}
private void notifyObservers() {
for (IntervalObserver observer : observers) {
observer.update(this);
}
}
}##لماذا نعيد الهيكلة
تريد الحصول على طرق عرض متعددة لواجهة المستخدم لنفس البيانات (على سبيل المثال، لديك تطبيق سطح مكتب وتطبيق للهاتف المحمول). إذا فشلت في فصل واجهة المستخدم الرسومية عن النطاق، فستواجه صعوبة بالغة في تجنب تكرار الكود والعديد من الأخطاء.
##الفوائد
-
تقوم بتقسيم المسؤولية بين فئات منطق العمل (business logic) وفئات العرض (presentation) (قارن مع مبدأ المسؤولية الفردية)، مما يجعل برنامجك أكثر وضوحاً وفهماً.
-
إذا كنت بحاجة إلى إضافة واجهة عرض جديدة، فستنشئ فئات عرض جديدة؛ ولا تحتاج إلى تعديل كود منطق العمل (قارن مع مبدأ المفتوح/المغلق).
-
الآن يمكن لأشخاص مختلفين العمل على منطق العمل وواجهات المستخدم بشكل مستقل.
##كيفية إعادة الهيكلة
-
أخفِ الوصول المباشر إلى بيانات النطاق في فئة واجهة المستخدم الرسومية (GUI class). لهذا، من الأفضل استخدام طريقة التغليف الذاتي للحقل. وبالتالي تقوم بإنشاء دوال الحصول والتعيين (getters and setters) لهذه البيانات.
-
في معالجات أحداث فئة واجهة المستخدم الرسومية، استخدم دوال التعيين لتعيين قيم الحقول الجديدة. سيسمح لك هذا بتمرير هذه القيم إلى كائن النطاق (domain object) المرتبط.
-
أنشئ فئة نطاق (domain class) وانسخ الحقول اللازمة من فئة واجهة المستخدم الرسومية إليها. أنشئ دوال الحصول والتعيين لكل هذه الحقول.
-
أنشئ نمط المراقب (Observer pattern) لهاتين الفئتين:
-
في فئة النطاق، أنشئ مصفوفة لتخزين كائنات المراقب (كائنات واجهة المستخدم الرسومية)، بالإضافة إلى دوال لتسجيلها وحذفها وإشعارها.
-
في فئة واجهة المستخدم الرسومية، أنشئ حقلاً لتخزين المراجع إلى فئة النطاق بالإضافة إلى دالة
update()، والتي ستستجيب للتغييرات في الكائن وتحدث قيم الحقول في فئة واجهة المستخدم الرسومية. لاحظ أنه يجب تعيين تحديثات القيم مباشرة في الدالة لتجنب التكرار اللانهائي (recursion). -
في منشئ فئة واجهة المستخدم الرسومية، أنشئ نسخة من فئة النطاق واحفظها في الحقل الذي أنشأته. سجل كائن واجهة المستخدم الرسومية كمراقب في كائن النطاق.
-
في دوال التعيين لحقول فئة النطاق، استدعِ دالة إشعار المراقب (بمعنى آخر، دالة التحديث في فئة واجهة المستخدم الرسومية) لتمرير القيم الجديدة إلى واجهة المستخدم الرسومية.
-
قم بتغيير دوال التعيين لحقول فئة واجهة المستخدم الرسومية بحيث تقوم بتعيين القيم الجديدة في كائن النطاق مباشرة. تأكد من عدم تعيين القيم عبر دالة تعيين فئة النطاق—وإلا فسيؤدي ذلك إلى تكرار لانهائي.
-